fix(tui): reserve /runtime and block local tool-loop fallback
This commit is contained in:
@@ -113,6 +113,11 @@ describe('parseCommand', () => {
|
|||||||
expect(parseCommand('/backend ollama')).toEqual({ type: 'backend', provider: 'ollama' });
|
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', () => {
|
it('parses /transfer command', () => {
|
||||||
expect(parseCommand('/transfer telegram')).toEqual({ type: 'transfer', target: 'telegram' });
|
expect(parseCommand('/transfer telegram')).toEqual({ type: 'transfer', target: 'telegram' });
|
||||||
expect(parseCommand('/transfer')).toEqual({ type: 'transfer', target: '' });
|
expect(parseCommand('/transfer')).toEqual({ type: 'transfer', target: '' });
|
||||||
@@ -146,6 +151,7 @@ describe('getHelpText', () => {
|
|||||||
expect(help).toContain('/help');
|
expect(help).toContain('/help');
|
||||||
expect(help).toContain('/paste');
|
expect(help).toContain('/paste');
|
||||||
expect(help).toContain('/model');
|
expect(help).toContain('/model');
|
||||||
|
expect(help).toContain('/runtime');
|
||||||
expect(help).toContain('/tools');
|
expect(help).toContain('/tools');
|
||||||
expect(help).toContain('/research');
|
expect(help).toContain('/research');
|
||||||
expect(help).toContain('/council');
|
expect(help).toContain('/council');
|
||||||
@@ -215,6 +221,11 @@ describe('getCommandCompletions', () => {
|
|||||||
const completions = getCommandCompletions('/council pre');
|
const completions = getCommandCompletions('/council pre');
|
||||||
expect(completions).toEqual(['/council preflight']);
|
expect(completions).toEqual(['/council preflight']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('completes /runtime command', () => {
|
||||||
|
const completions = getCommandCompletions('/ru');
|
||||||
|
expect(completions).toContain('/runtime');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isToolInventoryQuery', () => {
|
describe('isToolInventoryQuery', () => {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type Command =
|
|||||||
| { type: 'verbose' }
|
| { type: 'verbose' }
|
||||||
| { type: 'model'; name?: string; providerModel?: string }
|
| { type: 'model'; name?: string; providerModel?: string }
|
||||||
| { type: 'backend'; provider?: string }
|
| { type: 'backend'; provider?: string }
|
||||||
|
| { type: 'runtime'; input?: string }
|
||||||
| { type: 'login'; provider?: string }
|
| { type: 'login'; provider?: string }
|
||||||
| { type: 'transfer'; target: string }
|
| { type: 'transfer'; target: string }
|
||||||
| { type: 'pair'; action?: 'generate' | 'list' | 'revoke'; args?: string }
|
| { type: 'pair'; action?: 'generate' | 'list' | 'revoke'; args?: string }
|
||||||
@@ -154,6 +155,15 @@ export function parseCommand(input: string): Command | null {
|
|||||||
return { type: 'backend', provider };
|
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
|
// Transfer
|
||||||
if (trimmed === '/transfer') {
|
if (trimmed === '/transfer') {
|
||||||
return { type: 'transfer', target: '' };
|
return { type: 'transfer', target: '' };
|
||||||
@@ -223,6 +233,7 @@ Commands:
|
|||||||
/model [name] Show or switch model tier (local, default, fast, complex)
|
/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)
|
/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)
|
/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
|
/research <task> Delegate a task to the configured research agent
|
||||||
/council <task> Run the councils pipeline for a task
|
/council <task> Run the councils pipeline for a task
|
||||||
/council preflight Check council tier routing, endpoint/auth mode, and probe latency
|
/council preflight Check council tier routing, endpoint/auth mode, and probe latency
|
||||||
@@ -261,6 +272,7 @@ export const SLASH_COMMANDS = [
|
|||||||
'/tools',
|
'/tools',
|
||||||
'/model',
|
'/model',
|
||||||
'/backend',
|
'/backend',
|
||||||
|
'/runtime',
|
||||||
'/research',
|
'/research',
|
||||||
'/council',
|
'/council',
|
||||||
'/reset',
|
'/reset',
|
||||||
@@ -293,6 +305,7 @@ export const COMMAND_TOOLTIPS: Record<string, string> = {
|
|||||||
'/tools': 'Show authoritative runtime tool list for this session',
|
'/tools': 'Show authoritative runtime tool list for this session',
|
||||||
'/model': 'Show or switch model (local, default, fast, complex)',
|
'/model': 'Show or switch model (local, default, fast, complex)',
|
||||||
'/backend': 'Show or switch local backend (ollama, llamacpp)',
|
'/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',
|
'/research': 'Delegate a task to the configured research agent',
|
||||||
'/council': 'Run the councils pipeline for a task; use "/council preflight" for route/auth checks',
|
'/council': 'Run the councils pipeline for a task; use "/council preflight" for route/auth checks',
|
||||||
'/reset': 'Clear conversation history',
|
'/reset': 'Clear conversation history',
|
||||||
|
|||||||
@@ -563,6 +563,18 @@ export function App({
|
|||||||
return;
|
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': {
|
case 'login': {
|
||||||
const provider = (command.provider ?? '').trim().toLowerCase();
|
const provider = (command.provider ?? '').trim().toLowerCase();
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
|
|||||||
@@ -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 () => {
|
it('collects multiline input from /paste and sends as one message', async () => {
|
||||||
const mockSession = {
|
const mockSession = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
|
|||||||
@@ -524,6 +524,10 @@ export class MinimalTui {
|
|||||||
await this.handleBackendCommand(command.provider);
|
await this.handleBackendCommand(command.provider);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'runtime':
|
||||||
|
this.handleRuntimeCommand(command.input);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'login':
|
case 'login':
|
||||||
await this.handleLoginCommand(command.provider);
|
await this.handleLoginCommand(command.provider);
|
||||||
break;
|
break;
|
||||||
@@ -895,6 +899,16 @@ export class MinimalTui {
|
|||||||
console.log(`${colors.gray}Switched to backend: ${provider}${colors.reset}\n`);
|
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> {
|
private async stopBackend(provider: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { exec } = await import('child_process');
|
const { exec } = await import('child_process');
|
||||||
|
|||||||
Reference in New Issue
Block a user