diff --git a/desktop/src/shell/main-ipc.cjs b/desktop/src/shell/main-ipc.cjs index 6189510..269537f 100644 --- a/desktop/src/shell/main-ipc.cjs +++ b/desktop/src/shell/main-ipc.cjs @@ -439,10 +439,29 @@ function registerDesktopIpc(options) { throw new Error('LOCAL_NATIVE_CONFIG_INVALID') } let directoryMissing = false + let configurationFiles = null if (directory) { - try { validateNativeConfigDirectory(args[0], directory) } catch { directoryMissing = true } + try { + validateNativeConfigDirectory(args[0], directory) + const spec = NATIVE_CONFIG_DIRECTORIES[args[0]] + const entries = [...spec.files.map(name => [name, false]), ...(spec.directories || []).map(name => [name, true])] + const present = [] + const missing = [] + for (const [name, isDirectory] of entries) { + let exists = false + try { + const stat = fs.statSync(path.join(directory, name)) + exists = isDirectory ? stat.isDirectory() : stat.isFile() + } catch { /* The entry is absent or unreadable. */ } + if (exists) present.push(isDirectory ? `${name}/` : name) + else missing.push(isDirectory ? `${name}/` : name) + } + configurationFiles = { present, missing } + if (!present.length) directoryMissing = true + } catch { directoryMissing = true } } return { directory, ...(directoryMissing ? { directoryMissing: true } : {}), + ...(configurationFiles ? { configurationFiles } : {}), ...(summary && Object.hasOwn(summary, 'provider') ? { provider: summary.provider || '' } : {}), ...(summary && Object.hasOwn(summary, 'authType') ? { authType: summary.authType || '' } : {}) } }) diff --git a/desktop/test/security/main-security.test.cjs b/desktop/test/security/main-security.test.cjs index 8063373..1399725 100644 --- a/desktop/test/security/main-security.test.cjs +++ b/desktop/test/security/main-security.test.cjs @@ -529,6 +529,7 @@ test('native Agent configuration selection is validated and used for execution', assert.equal((await get(harness.event(), 'hermes')).directory, chosen.directory) const summary = await get(harness.event(), 'hermes') assert.equal(summary.provider, 'custom:work') + assert.deepEqual(summary.configurationFiles, { present: ['config.yaml'], missing: ['auth.json', '.env'] }) assert.doesNotMatch(JSON.stringify(summary), /synthetic-secret/) await harness.installerInstances[0].input.detectAgents() assert.equal(detectedEnvironment.HERMES_HOME, chosen.directory) diff --git a/frontend/src/__tests__/meldwork/AgentNativeConfig.spec.js b/frontend/src/__tests__/meldwork/AgentNativeConfig.spec.js index 16b52af..0bd89ca 100644 --- a/frontend/src/__tests__/meldwork/AgentNativeConfig.spec.js +++ b/frontend/src/__tests__/meldwork/AgentNativeConfig.spec.js @@ -33,6 +33,19 @@ it('warns when a previously selected native directory is no longer valid', async wrapper.unmount() }) +it('shows found and absent native configuration names without treating alternatives as errors', async () => { + window.meldworkDesktop = { localAgentNativeConfig: { get: vi.fn().mockResolvedValue({ + directory: '/native/hermes', configurationFiles: { present: ['config.yaml'], missing: ['auth.json', '.env'] }, + }) } } + const wrapper = mount(AgentNativeConfig, { props: { kind: 'hermes' } }) + await flushPromises() + expect(wrapper.text()).toContain('config.yaml') + expect(wrapper.text()).toContain('auth.json') + expect(wrapper.text()).toContain(t('nativeConfig.otherFilesMissing')) + expect(wrapper.find('[role="alert"]').exists()).toBe(false) + wrapper.unmount() +}) + it('explains the Zcode home directory used by the selected configuration', async () => { window.meldworkDesktop = { localAgentNativeConfig: { get: vi.fn().mockResolvedValue({ directory: '' }) } } const wrapper = mount(AgentNativeConfig, { props: { kind: 'zcode' } }) diff --git a/frontend/src/components/AgentNativeConfig.vue b/frontend/src/components/AgentNativeConfig.vue index 9ab7e1a..e571486 100644 --- a/frontend/src/components/AgentNativeConfig.vue +++ b/frontend/src/components/AgentNativeConfig.vue @@ -4,6 +4,8 @@
{{ directory || t('nativeConfig.automatic') }}
{{ t(`nativeConfig.expected.${kind}`) }}
{{ t('nativeConfig.directoryMissing') }} {{ t(`nativeConfig.expected.${kind}`) }}
+{{ t('nativeConfig.foundFiles') }}: {{ configurationFiles.present.join(', ') }}
+{{ t('nativeConfig.otherFilesMissing') }}: {{ configurationFiles.missing.join(', ') }}
{{ t('nativeConfig.configuredProvider') }}: {{ provider || t('nativeConfig.providerUnknown') }}
{{ t('nativeConfig.userConfiguredProvider') }}: {{ provider || t('nativeConfig.providerUnknown') }}
{{ t('nativeConfig.userAuthType') }}: {{ authType || t('nativeConfig.providerUnknown') }}
@@ -22,6 +24,7 @@ const props = defineProps({ kind: { type: String, required: true } }) const api = desktopApi().localAgentNativeConfig const directory = ref(null) const directoryMissing = ref(false) +const configurationFiles = ref(null) const provider = ref('') const authType = ref('') const busy = ref(true) @@ -32,6 +35,7 @@ async function load() { const result = await api.get(props.kind) directory.value = result.directory directoryMissing.value = result.directoryMissing === true + configurationFiles.value = result.configurationFiles || null provider.value = result.provider || '' authType.value = result.authType || '' } @@ -46,15 +50,15 @@ async function update(action) { if (!result.canceled) { directory.value = result.directory directoryMissing.value = false + configurationFiles.value = null provider.value = '' authType.value = '' - if (['hermes', 'codex', 'qwen', 'zcode'].includes(props.kind)) { - const details = await api.get(props.kind) - if (details.directory === result.directory) { - provider.value = details.provider || '' - authType.value = details.authType || '' - directoryMissing.value = details.directoryMissing === true - } + const details = await api.get(props.kind) + if (details.directory === result.directory) { + provider.value = details.provider || '' + authType.value = details.authType || '' + directoryMissing.value = details.directoryMissing === true + configurationFiles.value = details.configurationFiles || null } } } catch (caught) { diff --git a/frontend/src/locales/en.js b/frontend/src/locales/en.js index e4ae9e8..c8d7d82 100644 --- a/frontend/src/locales/en.js +++ b/frontend/src/locales/en.js @@ -6,6 +6,8 @@ export default { 'nativeConfig.providerUnknown': 'Not specified or unavailable', 'nativeConfig.automatic': 'No directory selected; the CLI native configuration will be discovered automatically.', 'nativeConfig.directoryMissing': 'The selected directory is unavailable or no longer contains a recognized configuration.', + 'nativeConfig.foundFiles': 'Found', + 'nativeConfig.otherFilesMissing': 'Other configurations not found (may not be needed)', 'nativeConfig.choose': 'Choose directory', 'nativeConfig.clear': 'Restore automatic discovery', 'nativeConfig.invalid': 'The selected directory does not contain this Agent’s native configuration.', diff --git a/frontend/src/locales/zh.js b/frontend/src/locales/zh.js index 7c1679f..28ad89b 100644 --- a/frontend/src/locales/zh.js +++ b/frontend/src/locales/zh.js @@ -6,6 +6,8 @@ export default { 'nativeConfig.providerUnknown': '未指定或无法读取', 'nativeConfig.automatic': '未手动指定目录;将自动发现 CLI 的原生配置。', 'nativeConfig.directoryMissing': '所选目录不可用,或其中已没有可识别的配置文件。', + 'nativeConfig.foundFiles': '已找到', + 'nativeConfig.otherFilesMissing': '未找到的其他配置(可能不需要)', 'nativeConfig.choose': '选择目录', 'nativeConfig.clear': '恢复自动发现', 'nativeConfig.invalid': '所选目录没有找到此 Agent 的原生配置文件。',