Show native Agent configuration file checks

Report recognized and absent configuration alternatives for selected native directories. Verified desktop security tests, frontend tests, both frontend builds, and git diff --check.
This commit is contained in:
Ryder Sun committed 2026-11-07 23:57:09 +08:00
1 parent fc041c6887
commit e031f0945d
6 files changed
+49 -8

No files matched your search

+20 -1
View File
@@ -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 || '' } : {}) }
})
@@ -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)
@@ -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' } })
+11 -7
View File
@@ -4,6 +4,8 @@
<p v-if="directory !== null">{{ directory || t('nativeConfig.automatic') }}</p>
<p v-if="['zcode', 'qwen', 'opencodereview'].includes(kind)">{{ t(`nativeConfig.expected.${kind}`) }}</p>
<p v-if="directoryMissing" class="form-error" role="alert">{{ t('nativeConfig.directoryMissing') }} {{ t(`nativeConfig.expected.${kind}`) }}</p>
<p v-if="directory && !directoryMissing && configurationFiles?.present?.length">{{ t('nativeConfig.foundFiles') }}: {{ configurationFiles.present.join(', ') }}</p>
<p v-if="directory && configurationFiles?.missing?.length">{{ t('nativeConfig.otherFilesMissing') }}: {{ configurationFiles.missing.join(', ') }}</p>
<p v-if="kind === 'hermes' && directory !== null">{{ t('nativeConfig.configuredProvider') }}: {{ provider || t('nativeConfig.providerUnknown') }}</p>
<p v-if="['codex', 'zcode'].includes(kind) && directory !== null">{{ t('nativeConfig.userConfiguredProvider') }}: {{ provider || t('nativeConfig.providerUnknown') }}</p>
<p v-if="kind === 'qwen' && directory !== null">{{ t('nativeConfig.userAuthType') }}: {{ authType || t('nativeConfig.providerUnknown') }}</p>
@@ -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) {
+2
View File
@@ -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.',
+2
View File
@@ -6,6 +6,8 @@ export default {
'nativeConfig.providerUnknown': '未指定或无法读取',
'nativeConfig.automatic': '未手动指定目录;将自动发现 CLI 的原生配置。',
'nativeConfig.directoryMissing': '所选目录不可用,或其中已没有可识别的配置文件。',
'nativeConfig.foundFiles': '已找到',
'nativeConfig.otherFilesMissing': '未找到的其他配置(可能不需要)',
'nativeConfig.choose': '选择目录',
'nativeConfig.clear': '恢复自动发现',
'nativeConfig.invalid': '所选目录没有找到此 Agent 的原生配置文件。',