diff --git a/ui-tui/src/__tests__/createSlashHandler.test.ts b/ui-tui/src/__tests__/createSlashHandler.test.ts index 9180651f51..b47efb3d52 100644 --- a/ui-tui/src/__tests__/createSlashHandler.test.ts +++ b/ui-tui/src/__tests__/createSlashHandler.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createSlashHandler } from '../app/createSlashHandler.js' +import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js' import { getOverlayState, resetOverlayState } from '../app/overlayStore.js' import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js' @@ -55,7 +56,7 @@ describe('createSlashHandler', () => { expect( createSlashHandler(ctx)( - '/model anthropic/claude-sonnet-4.6 --provider openrouter --tui-session' + `/model anthropic/claude-sonnet-4.6 --provider openrouter ${TUI_SESSION_MODEL_FLAG}` ) ).toBe(true) expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', { diff --git a/ui-tui/src/app/slash/commands/session.ts b/ui-tui/src/app/slash/commands/session.ts index a6408ec1f1..a31a4cbe43 100644 --- a/ui-tui/src/app/slash/commands/session.ts +++ b/ui-tui/src/app/slash/commands/session.ts @@ -10,6 +10,7 @@ import type { VoiceToggleResponse } from '../../../gatewayTypes.js' import { fmtK } from '../../../lib/text.js' +import { TUI_SESSION_MODEL_FLAG } from '../../../domain/slash.js' import type { PanelSection } from '../../../types.js' import { patchOverlayState } from '../../overlayStore.js' import { patchUiState } from '../../uiStore.js' @@ -17,8 +18,8 @@ import type { SlashCommand } from '../types.js' const GLOBAL_MODEL_FLAG_RE = /(?:^|\s)--global(?:\s|$)/ -/** Stripped before `config.set`; TUI model picker uses this for session-scoped switches. */ -const TUI_SESSION_MODEL_RE = /(?:^|\s)--tui-session(?:\s|$)/ +const TUI_SESSION_MODEL_RE = new RegExp(`(?:^|\\s)${TUI_SESSION_MODEL_FLAG}(?:\\s|$)`) +const TUI_SESSION_STRIP_RE = new RegExp(`\\s*${TUI_SESSION_MODEL_FLAG}\\b\\s*`, 'g') const persistedModelArg = (arg: string) => { const trimmed = arg.trim() @@ -26,6 +27,9 @@ const persistedModelArg = (arg: string) => { return !trimmed || GLOBAL_MODEL_FLAG_RE.test(trimmed) ? trimmed : `${trimmed} --global` } +const stripTuiSessionFlag = (trimmed: string) => + trimmed.replace(TUI_SESSION_STRIP_RE, ' ').replace(/\s+/g, ' ').trim() + const modelValueForConfigSet = (arg: string) => { const trimmed = arg.trim() @@ -34,7 +38,7 @@ const modelValueForConfigSet = (arg: string) => { } if (TUI_SESSION_MODEL_RE.test(trimmed)) { - return trimmed.replace(/\s*--tui-session\b\s*/g, ' ').replace(/\s+/g, ' ').trim() + return stripTuiSessionFlag(trimmed) } return persistedModelArg(trimmed) diff --git a/ui-tui/src/components/modelPicker.tsx b/ui-tui/src/components/modelPicker.tsx index ec9acaa779..b5882a1352 100644 --- a/ui-tui/src/components/modelPicker.tsx +++ b/ui-tui/src/components/modelPicker.tsx @@ -2,6 +2,7 @@ import { Box, Text, useInput, useStdout } from '@hermes/ink' import { useEffect, useMemo, useState } from 'react' import { providerDisplayNames } from '../domain/providers.js' +import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js' import type { GatewayClient } from '../gatewayClient.js' import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.js' import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js' @@ -112,7 +113,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke if (provider && model) { onSelect( - `${model} --provider ${provider.slug}${persistGlobal ? ' --global' : ' --tui-session'}` + `${model} --provider ${provider.slug}${persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}` ) } else { setStage('provider') @@ -140,7 +141,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke } } else if (provider && models[offset + n - 1]) { onSelect( - `${models[offset + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ' --tui-session'}` + `${models[offset + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}` ) } } diff --git a/ui-tui/src/domain/slash.ts b/ui-tui/src/domain/slash.ts index 1fc8082ba5..8090f6046f 100644 --- a/ui-tui/src/domain/slash.ts +++ b/ui-tui/src/domain/slash.ts @@ -1,3 +1,6 @@ +/** Appended to `/model` args from the TUI picker for session scope; stripped in `session` slash before `config.set`. */ +export const TUI_SESSION_MODEL_FLAG = '--tui-session' + export const looksLikeSlashCommand = (text: string) => /^\/[^\s/]*(?:\s|$)/.test(text) export const parseSlashCommand = (cmd: string) => {