From 7f9e95d86c9a6fb27730301b6a2c9f686cc35e91 Mon Sep 17 00:00:00 2001 From: Ryder Sun Date: Tue, 20 Oct 2026 14:31:41 +0800 Subject: [PATCH] fix: honor MiMo native provider configuration when checking readiness Recognize the selected custom Provider instead of treating official-account whoami output as proof that a configured native model cannot run. Keep native model and credential selection inside MiMo and isolate auth probes in tests. Checks on the combined working tree: focused context/preferences/MiMo/preload 47/47 passed; readiness/WorkBuddy/catalog 71/71 passed. Real MiMo ACP first call, native Session continuation, and production Electron group invocation passed without forcing a model. git diff --cached --check passed. --- desktop/src/agents/cli/mimo-native-config.cjs | 23 +++++++++++++++ desktop/src/agents/local-agent-readiness.cjs | 4 +++ .../agents/cli/mimo-native-config.test.cjs | 29 +++++++++++++++++++ .../agents/local-agent-readiness.test.cjs | 17 ++++++++--- 4 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 desktop/src/agents/cli/mimo-native-config.cjs create mode 100644 desktop/test/agents/cli/mimo-native-config.test.cjs diff --git a/desktop/src/agents/cli/mimo-native-config.cjs b/desktop/src/agents/cli/mimo-native-config.cjs new file mode 100644 index 0000000..1a12562 --- /dev/null +++ b/desktop/src/agents/cli/mimo-native-config.cjs @@ -0,0 +1,23 @@ +const fs = require('node:fs') +const path = require('node:path') +const os = require('node:os') + +function mimoConfiguredProvider(options = {}) { + const env = options.env || process.env + const root = env.XDG_CONFIG_HOME || path.join(options.home || os.homedir(), '.config') + try { + const file = path.join(root, 'mimocode', 'mimocode.json') + if (fs.statSync(file).size > 2 * 1024 * 1024) return false + const config = JSON.parse(fs.readFileSync(file, 'utf8')) + if (typeof config.model !== 'string' || !config.model.includes('/')) return false + const provider = config.provider?.[config.model.split('/')[0]] + let key = provider?.options?.apiKey + if (typeof key !== 'string' || !key.trim()) return false + const reference = key.match(/^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/) + if (reference) key = env[reference[1]] + else if (/^\{.*\}$/.test(key)) return false + return typeof key === 'string' && Boolean(key.trim()) + } catch { return false } +} + +module.exports = { mimoConfiguredProvider } diff --git a/desktop/src/agents/local-agent-readiness.cjs b/desktop/src/agents/local-agent-readiness.cjs index 02997f7..202d1b5 100644 --- a/desktop/src/agents/local-agent-readiness.cjs +++ b/desktop/src/agents/local-agent-readiness.cjs @@ -7,6 +7,7 @@ const { promisify } = require('node:util') const { prepareCommand, searchPath } = require('./cli/cli-adapters.cjs') const { isOpenClawSecretReference } = require('./cli/openclaw-runtime.cjs') const { NETWORK_ENV_KEYS, networkEnvironment } = require('./cli/cli-network-environment.cjs') +const { mimoConfiguredProvider } = require('./cli/mimo-native-config.cjs') const execFileAsync = promisify(execFile) @@ -746,6 +747,9 @@ async function queryMimoAuthState(options = {}) { async function resolveNativeCredentialState(kind, options = {}) { const current = nativeCredentialState(kind, options) if (kind === 'mimo') { + if (current.state === 'ready' || mimoConfiguredProvider(options)) { + return { state: 'ready', source: 'native-credential' } + } return await queryMimoAuthState(options) || current } if (kind === 'openclaw') { diff --git a/desktop/test/agents/cli/mimo-native-config.test.cjs b/desktop/test/agents/cli/mimo-native-config.test.cjs new file mode 100644 index 0000000..480ceba --- /dev/null +++ b/desktop/test/agents/cli/mimo-native-config.test.cjs @@ -0,0 +1,29 @@ +const test = require('node:test') +const assert = require('node:assert/strict') +const fs = require('node:fs') +const os = require('node:os') +const path = require('node:path') +const { resolveNativeCredentialState } = require('../../../src/agents/local-agent-readiness.cjs') + +test('MiMo selected native provider remains usable without signing in to the built-in account', async t => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-mimo-selected-')) + t.after(() => fs.rmSync(home, { recursive: true, force: true })) + const root = path.join(home, '.config', 'mimocode') + fs.mkdirSync(root, { recursive: true }) + const config = { model: 'private/text', provider: { private: { options: { apiKey: 'synthetic-key' } } } } + const file = path.join(root, 'mimocode.json') + fs.writeFileSync(file, JSON.stringify(config)) + const probe = async () => ({ stdout: 'Not logged in. Run `mimo auth login` to log in.' }) + const options = { home, env: {}, executable: '/tmp/mimo', execFileFn: probe } + const result = await resolveNativeCredentialState('mimo', options) + assert.deepEqual(result, { state: 'ready', source: 'native-credential' }) + assert.equal(JSON.stringify(result).includes('synthetic-key'), false) + config.model = 'unconfigured/text' + fs.writeFileSync(file, JSON.stringify(config)) + assert.equal((await resolveNativeCredentialState('mimo', options)).state, 'missing') + config.model = 'private/text' + config.provider.private.options.apiKey = '{env:PRIVATE_MODEL_KEY}' + fs.writeFileSync(file, JSON.stringify(config)) + assert.equal((await resolveNativeCredentialState('mimo', options)).state, 'missing') + assert.equal((await resolveNativeCredentialState('mimo', { ...options, env: { PRIVATE_MODEL_KEY: 'native-env-value' } })).state, 'ready') +}) diff --git a/desktop/test/agents/local-agent-readiness.test.cjs b/desktop/test/agents/local-agent-readiness.test.cjs index 65b1f59..a6d6139 100644 --- a/desktop/test/agents/local-agent-readiness.test.cjs +++ b/desktop/test/agents/local-agent-readiness.test.cjs @@ -771,13 +771,16 @@ test('OpenClaw model status overrides a stale gateway token without exposing aut } }) -test('MiMo readiness uses the installed CLI identity without forwarding private values', async () => { +test('MiMo readiness uses the installed CLI identity without forwarding private values', async (t) => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-mimo-auth-')) + t.after(() => fs.rmSync(home, { recursive: true, force: true })) const calls = [] for (const stdout of [ '\u001b[32mProvider: MiMo\u001b[0m\nUser ID: user-123', 'Provider: MiMo\nType: OAuth', ]) { const result = await resolveNativeCredentialState('mimo', { + home, env: { PATH: '/usr/bin', MELDWORK_PRIVATE_VALUE: 'desktop-private-value' }, executable: '/tmp/mimo', execFileFn: async (command, args, options) => { @@ -795,8 +798,11 @@ test('MiMo readiness uses the installed CLI identity without forwarding private assert.equal(calls.every(call => call.env.MELDWORK_PRIVATE_VALUE === undefined), true) }) -test('MiMo readiness recognizes ANSI-colored logged-out output even when the CLI exits zero', async () => { +test('MiMo readiness recognizes ANSI-colored logged-out output even when the CLI exits zero', async (t) => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-mimo-auth-')) + t.after(() => fs.rmSync(home, { recursive: true, force: true })) const result = await resolveNativeCredentialState('mimo', { + home, env: {}, executable: '/tmp/mimo', execFileFn: async () => ({ @@ -807,8 +813,10 @@ test('MiMo readiness recognizes ANSI-colored logged-out output even when the CLI assert.deepEqual(result, { state: 'missing', source: 'native-auth-status' }) }) -test('MiMo readiness remains unknown for missing or malformed identity output', async () => { - assert.deepEqual(await resolveNativeCredentialState('mimo', {}), { +test('MiMo readiness remains unknown for missing or malformed identity output', async (t) => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-mimo-auth-')) + t.after(() => fs.rmSync(home, { recursive: true, force: true })) + assert.deepEqual(await resolveNativeCredentialState('mimo', { home, env: {} }), { state: 'unknown', source: 'unverified', }) @@ -820,6 +828,7 @@ test('MiMo readiness remains unknown for missing or malformed identity output', 'Authentication status unavailable', ]) { const result = await resolveNativeCredentialState('mimo', { + home, env: {}, executable: '/tmp/mimo', execFileFn: async () => ({ stdout }),