diff --git a/src/delegation/headless-executor.ts b/src/delegation/headless-executor.ts index b282936a..1a8be770 100644 --- a/src/delegation/headless-executor.ts +++ b/src/delegation/headless-executor.ts @@ -14,7 +14,7 @@ import { ui, warn, info } from '../utils/ui'; import { type ExecutionOptions, type ExecutionResult, type StreamMessage } from './executor/types'; import { StreamBuffer, formatToolVerbose } from './executor/stream-parser'; import { buildExecutionResult } from './executor/result-aggregator'; -import { getCcsDir } from '../utils/config-manager'; +import { getCcsDir, getModelDisplayName } from '../utils/config-manager'; // Re-export types for consumers export type { ExecutionOptions, ExecutionResult, StreamMessage } from './executor/types'; @@ -196,8 +196,7 @@ export class HeadlessExecutor { const streamBuffer = new StreamBuffer(); if (showProgress) { - const modelName = - profile === 'glm' ? 'GLM-4.6' : profile === 'kimi' ? 'Kimi' : profile.toUpperCase(); + const modelName = getModelDisplayName(profile); console.error(ui.info(`Delegating to ${modelName}...`)); } diff --git a/src/delegation/result-formatter.ts b/src/delegation/result-formatter.ts index 4eae4f9f..abe32638 100644 --- a/src/delegation/result-formatter.ts +++ b/src/delegation/result-formatter.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import { execSync } from 'child_process'; import * as fs from 'fs'; import { ui } from '../utils/ui'; +import { getModelDisplayName } from '../utils/config-manager'; import type { ExecutionResult, ExecutionError, PermissionDenial } from './executor/types'; // Alias for backward compatibility @@ -58,7 +59,7 @@ class ResultFormatter { let output = ''; // Header box - const modelName = this.getModelDisplayName(profile); + const modelName = getModelDisplayName(profile); const headerIcon = success ? '[i]' : '[X]'; output += ui.box(`${headerIcon} Delegated to ${modelName} (ccs:${profile})`, { borderStyle: 'round', @@ -225,7 +226,7 @@ class ResultFormatter { */ private static formatInfoTable(result: ExecutionResult): string { const { cwd, profile, duration, exitCode, sessionId, totalCost, numTurns } = result; - const modelName = this.getModelDisplayName(profile); + const modelName = getModelDisplayName(profile); const durationSec = (duration / 1000).toFixed(1); const rows: string[][] = [ @@ -253,20 +254,6 @@ class ResultFormatter { }); } - /** - * Get display name for model profile - */ - private static getModelDisplayName(profile: string): string { - const displayNames: Record = { - glm: 'GLM-4.6', - glmt: 'GLM-4.6 (Thinking)', - kimi: 'Kimi', - default: 'Claude', - }; - - return displayNames[profile] || profile.toUpperCase(); - } - /** * Truncate string to max length */ @@ -283,7 +270,7 @@ class ResultFormatter { static async formatMinimal(result: ExecutionResult): Promise { await ui.init(); const { profile, success, duration } = result; - const modelName = this.getModelDisplayName(profile); + const modelName = getModelDisplayName(profile); const icon = success ? ui.ok('') : ui.fail(''); const durationSec = (duration / 1000).toFixed(1); @@ -326,7 +313,7 @@ class ResultFormatter { await ui.init(); const { profile, duration, sessionId, totalCost, permissionDenials } = result; - const modelName = this.getModelDisplayName(profile); + const modelName = getModelDisplayName(profile); const timeoutMin = (duration / 60000).toFixed(1); let output = ''; diff --git a/src/utils/config-manager.ts b/src/utils/config-manager.ts index e1169137..adb8bf01 100644 --- a/src/utils/config-manager.ts +++ b/src/utils/config-manager.ts @@ -246,3 +246,37 @@ export function getSettingsPath(profile: string): string { return expandedPath; } + +/** + * Get display name for a profile by reading ANTHROPIC_MODEL from settings + * @param profile - Profile name (glm, glmt, kimi, custom, etc.) + * @returns Formatted display name (e.g., 'GLM-4.7', 'Kimi', 'Custom-Model') + */ +export function getModelDisplayName(profile: string): string { + if (!profile) { + return ''; + } + + const settingsPath = path.join(getCcsDir(), `${profile}.settings.json`); + + try { + if (fs.existsSync(settingsPath)) { + const content = fs.readFileSync(settingsPath, 'utf8'); + const settings = JSON.parse(content) as { env?: { ANTHROPIC_MODEL?: string } }; + const model = settings.env?.ANTHROPIC_MODEL; + + if (model) { + // Format: 'glm-4.7' -> 'GLM-4.7' (uppercase letters, preserve numbers) + return model + .split('-') + .map((part) => part.toUpperCase()) + .join('-'); + } + } + } catch { + // Fall through to default + } + + // Fallback: profile name uppercase + return profile.toUpperCase(); +} diff --git a/tests/unit/delegation/result-formatter.test.js b/tests/unit/delegation/result-formatter.test.js index 48e2c449..c791f15b 100644 --- a/tests/unit/delegation/result-formatter.test.js +++ b/tests/unit/delegation/result-formatter.test.js @@ -16,7 +16,7 @@ describe('ResultFormatter', () => { const formatted = await ResultFormatter.format(result); - assert.ok(formatted.includes('Delegated to GLM-4.6')); + assert.ok(formatted.toLowerCase().includes('delegated to glm')); assert.ok(formatted.includes('ccs:glm')); assert.ok(formatted.includes('/home/user/project')); assert.ok(formatted.includes('2.3s')); @@ -179,11 +179,13 @@ describe('ResultFormatter', () => { }; const glmFormatted = await ResultFormatter.format(glmResult); - assert.ok(glmFormatted.includes('GLM-4.6')); + // Model display reads from settings or falls back to profile uppercase + // Use case-insensitive check since format may vary (GLM, Glm-4.7, etc.) + assert.ok(glmFormatted.toLowerCase().includes('glm')); const kimiResult = { ...glmResult, profile: 'kimi' }; const kimiFormatted = await ResultFormatter.format(kimiResult); - assert.ok(kimiFormatted.includes('Kimi')); + assert.ok(kimiFormatted.toLowerCase().includes('kimi')); }); }); @@ -220,7 +222,8 @@ describe('ResultFormatter', () => { const minimal = await ResultFormatter.formatMinimal(result); assert.ok(minimal.includes('[OK]')); - assert.ok(minimal.includes('GLM-4.6')); + // Model display reads from settings or falls back to profile uppercase + assert.ok(minimal.toLowerCase().includes('glm')); assert.ok(minimal.includes('1.5s')); assert.ok(minimal.split('\n').length <= 3); });