fix(tui): reserve /runtime and block local tool-loop fallback

This commit is contained in:
William Valentin
2026-02-24 10:41:33 -08:00
parent 2e192ef407
commit c44bc387b7
5 changed files with 73 additions and 0 deletions
+11
View File
@@ -113,6 +113,11 @@ describe('parseCommand', () => {
expect(parseCommand('/backend ollama')).toEqual({ type: 'backend', provider: 'ollama' });
});
it('parses /runtime command', () => {
expect(parseCommand('/runtime')).toEqual({ type: 'runtime' });
expect(parseCommand('/runtime status')).toEqual({ type: 'runtime', input: 'status' });
});
it('parses /transfer command', () => {
expect(parseCommand('/transfer telegram')).toEqual({ type: 'transfer', target: 'telegram' });
expect(parseCommand('/transfer')).toEqual({ type: 'transfer', target: '' });
@@ -146,6 +151,7 @@ describe('getHelpText', () => {
expect(help).toContain('/help');
expect(help).toContain('/paste');
expect(help).toContain('/model');
expect(help).toContain('/runtime');
expect(help).toContain('/tools');
expect(help).toContain('/research');
expect(help).toContain('/council');
@@ -215,6 +221,11 @@ describe('getCommandCompletions', () => {
const completions = getCommandCompletions('/council pre');
expect(completions).toEqual(['/council preflight']);
});
it('completes /runtime command', () => {
const completions = getCommandCompletions('/ru');
expect(completions).toContain('/runtime');
});
});
describe('isToolInventoryQuery', () => {
+13
View File
@@ -14,6 +14,7 @@ export type Command =
| { type: 'verbose' }
| { type: 'model'; name?: string; providerModel?: string }
| { type: 'backend'; provider?: string }
| { type: 'runtime'; input?: string }
| { type: 'login'; provider?: string }
| { type: 'transfer'; target: string }
| { type: 'pair'; action?: 'generate' | 'list' | 'revoke'; args?: string }
@@ -154,6 +155,15 @@ export function parseCommand(input: string): Command | null {
return { type: 'backend', provider };
}
// Runtime backend mode control (daemon/channel command; reserved in TUI)
if (trimmed === '/runtime') {
return { type: 'runtime' };
}
if (trimmed.startsWith('/runtime ')) {
const input = trimmed.slice('/runtime '.length).trim();
return { type: 'runtime', input };
}
// Transfer
if (trimmed === '/transfer') {
return { type: 'transfer', target: '' };
@@ -223,6 +233,7 @@ Commands:
/model [name] Show or switch model tier (local, default, fast, complex)
/model <tier> <p/m> Change tier's provider/model (e.g. /model default anthropic/claude-sonnet-4)
/backend [provider] Show or switch local backend (ollama, llamacpp)
/runtime [args] Runtime backend mode control (daemon/channel sessions)
/research <task> Delegate a task to the configured research agent
/council <task> Run the councils pipeline for a task
/council preflight Check council tier routing, endpoint/auth mode, and probe latency
@@ -261,6 +272,7 @@ export const SLASH_COMMANDS = [
'/tools',
'/model',
'/backend',
'/runtime',
'/research',
'/council',
'/reset',
@@ -293,6 +305,7 @@ export const COMMAND_TOOLTIPS: Record<string, string> = {
'/tools': 'Show authoritative runtime tool list for this session',
'/model': 'Show or switch model (local, default, fast, complex)',
'/backend': 'Show or switch local backend (ollama, llamacpp)',
'/runtime': 'Runtime backend mode control (daemon/channel command; not local TUI backend switch)',
'/research': 'Delegate a task to the configured research agent',
'/council': 'Run the councils pipeline for a task; use "/council preflight" for route/auth checks',
'/reset': 'Clear conversation history',
+12
View File
@@ -563,6 +563,18 @@ export function App({
return;
}
case 'runtime': {
pushAssistantMessage(
'Runtime backend mode command is not available in fullscreen TUI mode.\n'
+ 'Use it in daemon/channel sessions:\n'
+ '/runtime status\n'
+ '/runtime activate pi\n'
+ '/runtime deactivate pi\n'
+ '/runtime use config',
);
return;
}
case 'login': {
const provider = (command.provider ?? '').trim().toLowerCase();
if (!provider) {
+23
View File
@@ -403,6 +403,29 @@ describe('MinimalTui backend command', () => {
}
});
it('prints guidance when /runtime is invoked in TUI mode', async () => {
const mockSession = {
id: 'test',
getHistory: () => [],
addMessage: vi.fn(),
clear: vi.fn(),
replaceHistory: vi.fn(),
};
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
try {
const tui = new MinimalTui({
session: asSession(mockSession),
modelClient: asModelClient({}),
systemPrompt: 'test',
});
await minimalTuiPrivates(tui).handleCommand({ type: 'runtime', input: 'status' });
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Runtime backend mode command is not available in this TUI mode.'));
} finally {
logSpy.mockRestore();
}
});
it('collects multiline input from /paste and sends as one message', async () => {
const mockSession = {
id: 'test',
+14
View File
@@ -524,6 +524,10 @@ export class MinimalTui {
await this.handleBackendCommand(command.provider);
break;
case 'runtime':
this.handleRuntimeCommand(command.input);
break;
case 'login':
await this.handleLoginCommand(command.provider);
break;
@@ -895,6 +899,16 @@ export class MinimalTui {
console.log(`${colors.gray}Switched to backend: ${provider}${colors.reset}\n`);
}
private handleRuntimeCommand(_input?: string): void {
console.log(`${colors.gray}Runtime backend mode command is not available in this TUI mode.${colors.reset}`);
console.log(`${colors.gray}Use it in daemon/channel sessions:${colors.reset}`);
console.log(' /runtime status');
console.log(' /runtime activate pi');
console.log(' /runtime deactivate pi');
console.log(' /runtime use config');
console.log('');
}
private async stopBackend(provider: string): Promise<void> {
try {
const { exec } = await import('child_process');