fix(tui): add /queue command support across tui and routing

This commit is contained in:
William Valentin
2026-02-16 12:19:21 -08:00
parent d9f7807ab2
commit fd7ad7bfb0
6 changed files with 382 additions and 0 deletions
+73
View File
@@ -210,6 +210,79 @@ describe('daemon command fast-path integration', () => {
expect(session.setConfig).toHaveBeenCalledWith('modelTier', 'fast');
});
it('handles queue command via fast-path and persists queue override', async () => {
const processSpy = vi.spyOn(AgentOrchestrator.prototype, 'process');
const session = {
id: 'telegram:user-queue',
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 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,
},
server: {
queue: {
mode: 'collect',
cap: 50,
overflow: 'drop_old',
debounce_ms: 0,
summarize_overflow: true,
},
},
compaction: { enabled: false },
models: { default: { provider: 'anthropic', model: 'claude' } },
} as unknown as MessageRouterDeps['config'],
commandRegistry,
});
const reply = vi.fn(async (_message: OutboundMessage) => {});
await router.handler({
id: 'q1',
channel: 'telegram',
senderId: 'user-queue',
text: '/queue set mode followup',
timestamp: Date.now(),
metadata: { isCommand: true, command: 'queue', commandArgs: 'set mode followup' },
} as MessageRouterInput, reply);
expect(processSpy).not.toHaveBeenCalled();
expect(session.setConfig).toHaveBeenCalledWith('queue.mode', 'followup');
});
it('uses intent match to override agent target', async () => {
const session = {
id: 'telegram:user-2',
+81
View File
@@ -616,6 +616,87 @@ export function createMessageRouter(deps: {
return `Elevated mode: on until ${new Date(untilMs).toISOString()}`;
},
getQueue: () => {
const mode = session.getConfig('queue.mode') ?? deps.config.server.queue.mode;
const cap = session.getConfig('queue.cap') ?? String(deps.config.server.queue.cap);
const overflow = session.getConfig('queue.overflow') ?? deps.config.server.queue.overflow;
const debounceMs = session.getConfig('queue.debounce_ms') ?? String(deps.config.server.queue.debounce_ms);
const summarizeOverflow = session.getConfig('queue.summarize_overflow') ?? String(deps.config.server.queue.summarize_overflow);
const source = session.getConfig('queue.mode')
|| session.getConfig('queue.cap')
|| session.getConfig('queue.overflow')
|| session.getConfig('queue.debounce_ms')
|| session.getConfig('queue.summarize_overflow')
? 'session override'
: 'default config';
return [
'**Queue policy**',
`mode: ${mode}`,
`cap: ${cap}`,
`overflow: ${overflow}`,
`debounce_ms: ${debounceMs}`,
`summarize_overflow: ${summarizeOverflow}`,
`source: ${source}`,
].join('\n');
},
setQueue: (input: string) => {
const [rawKey, ...rest] = input.trim().split(/\s+/);
const value = rest.join(' ').trim();
if (!rawKey || !value) {
return 'Usage: /queue <mode|cap|overflow|debounce_ms|summarize_overflow> <value>';
}
const key = rawKey.toLowerCase();
if (key === 'mode') {
if (!['collect', 'followup', 'steer', 'steer_backlog', 'interrupt'].includes(value)) {
return 'Invalid mode. Use one of: collect, followup, steer, steer_backlog, interrupt';
}
session.setConfig('queue.mode', value);
return `Set queue.mode=${value} for this session`;
}
if (key === 'cap') {
const cap = Number.parseInt(value, 10);
if (!Number.isFinite(cap) || cap < 1 || cap > 1000) {
return 'Invalid cap. Use an integer between 1 and 1000';
}
session.setConfig('queue.cap', String(cap));
return `Set queue.cap=${cap} for this session`;
}
if (key === 'overflow') {
if (value !== 'drop_old' && value !== 'drop_new') {
return 'Invalid overflow. Use drop_old or drop_new';
}
session.setConfig('queue.overflow', value);
return `Set queue.overflow=${value} for this session`;
}
if (key === 'debounce_ms') {
const debounceMs = Number.parseInt(value, 10);
if (!Number.isFinite(debounceMs) || debounceMs < 0 || debounceMs > 60_000) {
return 'Invalid debounce_ms. Use an integer between 0 and 60000';
}
session.setConfig('queue.debounce_ms', String(debounceMs));
return `Set queue.debounce_ms=${debounceMs} for this session`;
}
if (key === 'summarize_overflow') {
const normalized = value.toLowerCase();
if (normalized !== 'true' && normalized !== 'false') {
return 'Invalid summarize_overflow. Use true or false';
}
session.setConfig('queue.summarize_overflow', normalized);
return `Set queue.summarize_overflow=${normalized} for this session`;
}
return 'Unknown queue key. Use one of: mode, cap, overflow, debounce_ms, summarize_overflow';
},
resetQueue: () => {
session.deleteConfig('queue.mode');
session.deleteConfig('queue.cap');
session.deleteConfig('queue.overflow');
session.deleteConfig('queue.debounce_ms');
session.deleteConfig('queue.summarize_overflow');
return 'Reset session queue overrides.';
},
},
});