@luchenglong/cli-bridge 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,6 @@
6
6
  // - mimocode:直接 spawn mimo CLI(MIMO_BIN/MIMO_CLI > ~/.mimocode/bin/mimo[.exe] >
7
7
  // 'mimo';.mjs/.cjs/.js 目标经 process.execPath 包装,供测试 fixture 使用)。
8
8
  // 实时折叠走 bridge-kit 行流 + engine.foldEvent(旧 foldStep 语义)。
9
- // - mimo-desktop:委托 provider.execute(provider.delegates('mimo-desktop')=true);
10
- // 非 spawn 走本地 HTTP。begin/end 由本模块做(MCP 无 kit runner)。
11
9
  // - cwd 兜底 = env MIMO_MCP_CWD || MIMO_BRIDGE_CWD || process.cwd()(旧 CWD_FALLBACK;
12
10
  // 旧仓曾硬编码机器绝对路径,新实现按 agy 定案改 process.cwd() 兜底)。
13
11
  // - 无 ctx.emit:状态走快照文件 {homeDir}/cli-bridge-mcp-mimo.json(keystone
@@ -16,12 +14,17 @@
16
14
  // 工具结果)。旧 mcp-live.json 兼容读归 keystone,本面不写旧文件。
17
15
  // - 限流(isLimited)触发即落 fallbackResult(旧 handleTool verbatim:run 内部各退出
18
16
  // 路径均已 engine.end,此处仅改返回值对象,绝不二次 engine.end —— 防 runs 虚标/计数下溢)。
17
+ //
18
+ // 2026-10 去 desktop 化:MiMo 免费 API 服务已被官方终止 + 本机 0 凭据 → 只走
19
+ // `mimo run`(MiMoCode CLI)。desktop 本地 HTTP 后端(BACKEND_DESKTOP / execute 分支 /
20
+ // preflight)全部移除;模型由用户在 CLI Bridge 设置里手动指定(defaultModel),
21
+ // 登录由用户在 CLI 侧 `mimo auth login` 自理。
19
22
 
20
23
  import { spawn as nodeSpawn } from 'node:child_process'
21
24
  import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs'
22
25
  import { dirname, join as joinPath, resolve as resolvePath } from 'node:path'
23
26
 
24
- import defineProvider from '../../providers/mimo.mjs'
27
+ import defineProvider, { resolveMimoExePrefix } from '../../providers/mimo.mjs'
25
28
  import {
26
29
  createStatusEngine, createLineStream, buildMcpBridgePayload,
27
30
  fallbackResult, shortLabel, renderStatus
@@ -30,7 +33,6 @@ import { packageVersion } from '../version.mjs'
30
33
 
31
34
  export const meta = { id: 'mimo', label: 'MIMO', version: packageVersion() }
32
35
 
33
- const BACKEND_DESKTOP = 'mimo-desktop'
34
36
  const SETTINGS_FILE_NAME = 'cli-bridge-settings.json'
35
37
 
36
38
  // 统一设置文件分区读(与 preset 同规则;MCP 子进程无 ctx.get,只读 env/文件)。
@@ -82,44 +84,28 @@ function publishSnapshot(snapshot) {
82
84
  const engine = createStatusEngine({ provider, publish: publishSnapshot })
83
85
  provider.engine = engine
84
86
 
85
- // ── exe / cwd 解析(旧 resolveMimoExe + CWD_FALLBACK verbatim 语义)──────────────
87
+ // ── exe / cwd 解析(与 descriptor resolveMimoExePrefix 单一真源同优先级)──────────
88
+ // MIMO_BIN/MIMO_CLI > 已知安装位置 ~/.mimocode/bin/mimo[.exe] > 'mimo';
89
+ // .mjs/.cjs/.js 目标经 process.execPath 包装(供测试 fixture 使用)。
86
90
  function exePrefix() {
87
- const env = process.env
88
- const candidates = []
89
- if (env.MIMO_BIN) candidates.push(env.MIMO_BIN)
90
- if (env.MIMO_CLI) candidates.push(env.MIMO_CLI)
91
- if (env.USERPROFILE) candidates.push(env.USERPROFILE + '\\.mimocode\\bin\\mimo.exe')
92
- if (env.HOME) {
93
- candidates.push(env.HOME + '\\.mimocode\\bin\\mimo.exe')
94
- candidates.push(env.HOME + '/.mimocode/bin/mimo')
95
- }
96
- for (const exe of candidates) {
97
- try { if (exe && existsSync(exe)) return /\.(mjs|cjs|js)$/i.test(exe) ? [process.execPath, exe] : [exe] } catch (e) { }
98
- }
99
- const bin = env.MIMO_BIN || env.MIMO_CLI || ''
100
- if (bin) return /\.(mjs|cjs|js)$/i.test(bin) ? [process.execPath, bin] : [bin]
101
- return ['mimo']
91
+ return resolveMimoExePrefix(process.env)
102
92
  }
103
93
  function resolveCwd(a) {
104
94
  if (a && a.cwd) return resolvePath(String(a.cwd))
105
95
  return process.env.MIMO_MCP_CWD || process.env.MIMO_BRIDGE_CWD || process.cwd()
106
96
  }
107
- function preferredBackendFromEnv() {
108
- const env = process.env
109
- return env.MIMO_BRIDGE_BACKEND || env.MIMO_BACKEND || (readPartition(env).partition || {}).preferredBackend || null
110
- }
111
97
 
112
98
  // ── spawn(旧 runMimo verbatim 语义;实时折叠走 bridge-kit 行流)────────────────
113
99
  function runMimo(args) {
114
100
  return new Promise((resolve) => {
115
101
  const cwd = resolveCwd(args)
116
- const built = provider.buildArgv(exePrefix(), args, { planActive: false, defaultMode: 'auto', backend: 'mimocode' })
102
+ const built = provider.buildArgv(exePrefix(), args, { planActive: false, defaultMode: 'auto' })
117
103
  engine.begin(cwd)
118
104
  let child
119
105
  try {
120
106
  child = nodeSpawn(built.argv[0], built.argv.slice(1), { cwd, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })
121
107
  } catch (e) {
122
- const res = { ok: false, status: 'MIMO_UNAVAILABLE', response: '', sessionId: null, exitCode: null, mode: built.mode, backend: 'mimocode', stderr: 'mimo spawn failed: ' + String(e && e.message || e), errorMessage: String(e && e.message || e) }
108
+ const res = { ok: false, status: 'MIMO_UNAVAILABLE', response: '', sessionId: null, exitCode: null, mode: built.mode, backend: null, stderr: 'mimo spawn failed: ' + String(e && e.message || e), errorMessage: String(e && e.message || e) }
123
109
  engine.end(res, cwd)
124
110
  resolve(res)
125
111
  return
@@ -142,7 +128,7 @@ function runMimo(args) {
142
128
  child.on('error', (e) => {
143
129
  clearTimeout(timer)
144
130
  stream.flush()
145
- const res = { ok: false, status: 'MIMO_UNAVAILABLE', response: '', sessionId: null, exitCode: null, mode: built.mode, backend: 'mimocode', stderr: String(e && e.message || e), errorMessage: String(e && e.message || e) }
131
+ const res = { ok: false, status: 'MIMO_UNAVAILABLE', response: '', sessionId: null, exitCode: null, mode: built.mode, backend: null, stderr: String(e && e.message || e), errorMessage: String(e && e.message || e) }
146
132
  engine.end(res, cwd)
147
133
  resolve(res)
148
134
  })
@@ -150,7 +136,7 @@ function runMimo(args) {
150
136
  clearTimeout(timer)
151
137
  stream.flush()
152
138
  const outcome = { exitCode: killed ? 124 : (code == null ? -1 : code) }
153
- let res = provider.buildResult(provider.parseJson(out), outcome, built.mode, err, out, 'mimocode')
139
+ let res = provider.buildResult(provider.parseJson(out), outcome, built.mode, err, out)
154
140
  if (killed && !res.ok) {
155
141
  res.status = 'HUNG_TIMEOUT'
156
142
  res.stderr = (res.stderr ? res.stderr + ' ' : '') + '[killed by DSH timeout guard after ' + built.timeoutSec + 's; if this was a long-running script (build/test) raise timeoutSec]'
@@ -164,17 +150,16 @@ function runMimo(args) {
164
150
  // ── 文本渲染(旧 renderResultText verbatim;契约 §4:错误也是文本)───────────────
165
151
  function textResult(res) {
166
152
  if (!res) return '(no result)'
167
- const b = res.backend ? ' backend=' + res.backend : ''
168
153
  if (res.fallback) {
169
- return 'status=FALLBACK_TO_DSH fallback=true' + b + '\n' + (res.reason ? 'reason=' + res.reason + '\n' : '') +
154
+ return 'status=FALLBACK_TO_DSH fallback=true\n' + (res.reason ? 'reason=' + res.reason + '\n' : '') +
170
155
  'The user/host chose DSH-local. Finish with native tools and NEVER call mimo again for this task.'
171
156
  }
172
- if (res.ok === false && res.error) return 'mimo_settings/error' + b + ': ' + res.error
157
+ if (res.ok === false && res.error) return 'mimo_settings/error: ' + res.error
173
158
  if (!res.ok) {
174
159
  const hint = provider.failureHint ? provider.failureHint(res) : ''
175
- return 'mimo failed status=' + (res.status || 'ERROR') + b + '\n' + shortLabel(res.stderr || res.errorMessage || '') + (hint ? '\n' + hint : '')
160
+ return 'mimo failed status=' + (res.status || 'ERROR') + '\n' + shortLabel(res.stderr || res.errorMessage || '') + (hint ? '\n' + hint : '')
176
161
  }
177
- return 'mimo ok status=' + res.status + b + (res.sessionId ? ' sessionId=' + res.sessionId : '') + '\n\n' + String(res.response || '')
162
+ return 'mimo ok status=' + res.status + (res.sessionId ? ' sessionId=' + res.sessionId : '') + '\n\n' + String(res.response || '')
178
163
  }
179
164
 
180
165
  // ── 工具面(旧 MCP TOOLS verbatim:含 sessionId 关键注释与 mimo_settings)────────
@@ -197,7 +182,6 @@ export function tools(env) {
197
182
  export async function call(name, args, io) {
198
183
  if (io) lastIo = io
199
184
  const a = args || {}
200
- const runtimeBackend = provider.resolveBackend(a, null, preferredBackendFromEnv())
201
185
 
202
186
  if (name === 'mimo_settings') {
203
187
  const v = await provider.extraTools[0].logic(a, null)
@@ -206,8 +190,6 @@ export async function call(name, args, io) {
206
190
 
207
191
  if (name === 'mimo_status') {
208
192
  const snap = await provider.statusToolLogic(a)
209
- snap.defaultBackend = runtimeBackend
210
- snap.backend = runtimeBackend
211
193
  return renderStatus(snap, provider)[0].text
212
194
  }
213
195
 
@@ -221,41 +203,18 @@ export async function call(name, args, io) {
221
203
  cwd: a.cwd,
222
204
  timeoutSec: a.timeoutSec,
223
205
  title: a.title,
224
- backend: a.backend,
225
206
  background: false, // MCP 恒同步阻塞(无宿主 jobs)
226
- // 【关键】desktop 后端的 sessionId 必须转发。旧 handleTool 此处曾是白名单转发,
227
- // 漏了 sessionId(只在 mimo_continue 分支补)→ mimo_run 传 sessionId 被静默丢弃
228
- // → 永远 newDesktopSessionId 造新 id → 因 Desktop API 不能创建会话,mimo_run 在
229
- // desktop 后端下完全不可用。
230
207
  sessionId: a.sessionId
231
208
  }
232
209
  if (name === 'mimo_continue') {
233
210
  if (a.sessionId) mapped.sessionId = a.sessionId
234
211
  else mapped.continueLatest = true
235
212
  }
236
- const backend = provider.resolveBackend(mapped, null, preferredBackendFromEnv())
237
- let res
238
- if (backend === BACKEND_DESKTOP) {
239
- const pf = provider.preflight({ backend })
240
- if (pf && pf.abort) {
241
- res = pf.abort
242
- engine.begin(resolveCwd(mapped))
243
- engine.end(res, resolveCwd(mapped))
244
- } else {
245
- const cwd = resolveCwd(mapped)
246
- const built = provider.buildArgv(exePrefix(), mapped, { planActive: false, defaultMode: 'auto', backend })
247
- engine.begin(cwd)
248
- res = await provider.execute({ args: mapped, cwd, built, backend })
249
- engine.end(res, cwd)
250
- }
251
- } else {
252
- res = await runMimo(mapped)
253
- }
213
+ const res = await runMimo(mapped)
254
214
  if (!res.ok && provider.isLimited(res)) {
255
- // runMimo / desktop 分支所有退出路径均已 engine.end;此处仅改返回值对象,
215
+ // runMimo 所有退出路径均已 engine.end;此处仅改返回值对象,
256
216
  // 绝不二次 engine.end(防 runs 虚标与 running 计数下溢)。
257
- res = fallbackResult(res, res.mode, provider)
258
- res.backend = backend
217
+ return textResult(fallbackResult(res, res.mode, provider))
259
218
  }
260
219
  return textResult(res)
261
220
  }
@@ -278,14 +237,24 @@ export function check(env) {
278
237
  if (!names.includes(provider.tools[k].name)) throw new Error('mimo mcp: tools surface missing descriptor name ' + provider.tools[k].name)
279
238
  }
280
239
  // mimocode argv 契约(旧 core verbatim)。
281
- const b = provider.buildArgv(['mimo'], { prompt: 'x' }, { planActive: false, backend: 'mimocode' })
240
+ const b = provider.buildArgv(['mimo'], { prompt: 'x' }, { planActive: false })
282
241
  for (const f of ['run', '--format', 'json', '--dangerously-skip-permissions', '--agent', 'build']) {
283
242
  if (!b.argv.includes(f)) throw new Error('mimo mcp: argv contract drift: ' + f)
284
243
  }
285
- // mimo-desktop 空壳(非 spawn 路径,交 execute)。
286
- const d = provider.buildArgv(['mimo'], { prompt: 'x' }, { planActive: false, backend: BACKEND_DESKTOP })
287
- if (d.argv.length !== 0) throw new Error('mimo mcp: desktop buildArgv must be empty shell')
288
- // delegates 规则:只有 desktop 委托。
289
- if (provider.delegates(BACKEND_DESKTOP) !== true || provider.delegates('mimocode') !== false) throw new Error('mimo mcp: delegates contract drift')
244
+ // 模型语义(去 desktop 化后):设置 defaultModel 留空 = 不传 --model;
245
+ // args.model 或 settings.defaultModel 有值才落地 --model(provider/model 语义)。
246
+ if (b.argv.includes('--model')) throw new Error('mimo mcp: --model must be absent when no default model is set')
247
+ const withArg = provider.buildArgv(['mimo'], { prompt: 'x', model: 'xiaomi/mimo-v2.6-flash' }, { planActive: false })
248
+ const mi = withArg.argv.indexOf('--model')
249
+ if (mi < 0 || withArg.argv[mi + 1] !== 'xiaomi/mimo-v2.6-flash') throw new Error('mimo mcp: --model must carry the provider/model argument')
250
+ // 设置分区契约:defaultModel 单一键,默认空串(不写死模型)。
251
+ const keys = provider.settings.keys
252
+ if (keys.length !== 1 || keys[0] !== 'defaultModel') throw new Error('mimo mcp: settings.keys must be [defaultModel]')
253
+ if (provider.settings.defaults().defaultModel !== '') throw new Error('mimo mcp: defaultModel default must be empty (mimo CLI self-decides)')
254
+ if (provider.settings.normalize({ defaultModel: ' xiaomi/mimo-v2.6-pro ', junk: 1 }).defaultModel !== 'xiaomi/mimo-v2.6-pro') {
255
+ throw new Error('mimo mcp: settings.normalize must trim defaultModel and drop unknown keys')
256
+ }
257
+ // 单后端:无 backend 概念残留(旧 mimo-desktop 已取消)。
258
+ if (provider.resolveBackend({ backend: 'mimo-desktop' }, null, null) != null) throw new Error('mimo mcp: backend must stay null after de-desktop-ification')
290
259
  return true
291
260
  }
@@ -14,13 +14,40 @@ import { resolve as resolvePath } from 'node:path'
14
14
  import defineProvider, { resolveExePrefix } from '../../providers/opencode.mjs'
15
15
  import { createStatusEngine, createLineStream, buildMcpBridgePayload, isLimited, renderStatus } from '../../core/bridge-kit.mjs'
16
16
  import { packageVersion } from '../version.mjs'
17
+ import { readFileSync } from 'node:fs'
18
+ import { dirname } from 'node:path'
17
19
 
18
20
  export const meta = { id: 'opencode', label: 'OPENCODE', version: packageVersion() }
19
21
 
22
+ // ── 统一设置文件分区读(MCP 子进程无 ctx.get,只读 env/文件)───────────────────
23
+ // 与 mimo/agy 面同规则。注入 descriptor 的 readSettings,使设置面板里指定的
24
+ // binPath/defaultModel 在 MCP 形态同样生效(不只 preset 形态)—— 三个形态行为一致。
25
+ const SETTINGS_FILE_NAME = 'cli-bridge-settings.json'
26
+ function resolveSettingsFile(env) {
27
+ try {
28
+ if (env.CLI_BRIDGE_SETTINGS_FILE) return env.CLI_BRIDGE_SETTINGS_FILE
29
+ if (env.DSH_HOME) return resolvePath(env.DSH_HOME, SETTINGS_FILE_NAME)
30
+ if (process.platform === 'win32' && env.APPDATA) return resolvePath(env.APPDATA, 'DSH Desktop', 'dsh-home', SETTINGS_FILE_NAME)
31
+ if (env.HOME) return resolvePath(env.HOME, '.dsh', SETTINGS_FILE_NAME)
32
+ } catch (e) { }
33
+ return null
34
+ }
35
+ function readPartition(env) {
36
+ const p = resolveSettingsFile(env)
37
+ if (!p) return { partition: null, path: null, home: null }
38
+ try {
39
+ const all = JSON.parse(readFileSync(p, 'utf8'))
40
+ return { partition: (all && typeof all === 'object' && all.opencode) ? all.opencode : null, path: p, home: dirname(p) }
41
+ } catch (e) {
42
+ return { partition: null, path: p, home: dirname(p) }
43
+ }
44
+ }
45
+
20
46
  const provider = defineProvider({
21
47
  env: process.env,
22
48
  process,
23
- nodeChild: { spawn: nodeSpawn }
49
+ nodeChild: { spawn: nodeSpawn },
50
+ readSettings: () => readPartition(process.env)
24
51
  })
25
52
 
26
53
  let lastIo = null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luchenglong/cli-bridge",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "CLI Bridge for DeepSeek Harness (DSH): one package bridging four local coding-CLI agents — agy (Google Antigravity CLI), Tencent CodeBuddy Code, Xiaomi MiMo (MiMoCode / MiMo Desktop), and OpenCode. Each provider keeps its own agent preset (cordis-agy / codebuddy-first / cordis-mimo / opencode-first), tools (agy_run / codebuddy_run / mimo_run / opencode_run + continue + status), rate-limit/network fallback dialog, and zero-dependency MCP server, unified under a single home-level status light, one settings file, and one bundle patch. Merges agy-first-bridge, codebuddy-first-bridge and mimo-first-bridge.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -90,3 +90,5 @@
90
90
  }
91
91
  }
92
92
 
93
+
94
+
@@ -178,6 +178,18 @@ export function apply(ctx, config) {
178
178
  }
179
179
  for (const tool of [runTool, continueTool, statusTool]) ctx.tools.register(tool)
180
180
 
181
+ // 附加工具(codebuddy_models):描述符按宿主能力条件注册(preset 形态有 process
182
+ // 与 subprocess → 恒有);dynamic 沙箱形态省略。注册逻辑与 opencode/agy 同构。
183
+ for (const et of provider.extraTools || []) {
184
+ ctx.effect(() => ctx.tools.register({
185
+ name: et.name,
186
+ description: et.description,
187
+ parameters: et.inputSchema,
188
+ output: { schema: OUTPUT_SCHEMA, render: et.render || ((args, value) => renderResult(value, provider)) },
189
+ execute: (args, exec) => et.logic(args, exec)
190
+ }))
191
+ }
192
+
181
193
  // 提示词策略段(旧 preset verbatim:name 'codebuddy:policy',order 5,
182
194
  // POLICY_TEXT 全文在 providers/codebuddy.mjs —— 提示词连续性红线)。
183
195
  ctx.effect(() => ctx.systemPrompt.section({
@@ -12,7 +12,7 @@
12
12
  import fs from 'node:fs'
13
13
  import path from 'node:path'
14
14
 
15
- import defineProvider from '../../providers/mimo.mjs'
15
+ import defineProvider, { resolveMimoExePrefix } from '../../providers/mimo.mjs'
16
16
  import { renderResult, renderStatus } from '../../core/bridge-kit.mjs'
17
17
  import { isBridgeEnabled } from '../../core/scheduling.mjs'
18
18
  import { PROVIDER_IDS } from '../../providers/registry.mjs'
@@ -22,7 +22,6 @@ export const inject = ['tools', 'subprocess', 'systemPrompt', 'timer']
22
22
 
23
23
  const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
24
24
  const SETTINGS_FILE_NAME = 'cli-bridge-settings.json'
25
- const BACKEND_DESKTOP = 'mimo-desktop'
26
25
 
27
26
  // 统一设置文件路径解析(与 home-plugin/lib/settings.mjs 同规则,本模块不 import keystone,
28
27
  // 只复刻只读逻辑):CLI_BRIDGE_SETTINGS_FILE > DSH_HOME/<file>。解析不到 → null(回退默认)。
@@ -44,20 +43,10 @@ function readPartition(env) {
44
43
  }
45
44
  }
46
45
 
47
- // 旧 resolveMimoExe 候选链 verbatim(MIMO_BIN > MIMO_CLI > ~/.mimocode/bin/mimo[.exe])。
48
- function mimoExeFromEnvCandidates(env) {
49
- const candidates = []
50
- if (env.MIMO_BIN) candidates.push(env.MIMO_BIN)
51
- if (env.MIMO_CLI) candidates.push(env.MIMO_CLI)
52
- if (env.USERPROFILE) candidates.push(env.USERPROFILE + '\\.mimocode\\bin\\mimo.exe')
53
- if (env.HOME) {
54
- candidates.push(env.HOME + '\\.mimocode\\bin\\mimo.exe')
55
- candidates.push(env.HOME + '/.mimocode/bin/mimo')
56
- }
57
- for (const exe of candidates) {
58
- try { if (exe && fs.existsSync(exe)) return exe } catch (e) { }
59
- }
60
- return null
46
+ // 设置分区读取(descriptor 的 host.getSettings('mimo') 契约:直接返回分区对象
47
+ // { defaultModel } 或空对象)。每次调用都重新读文件 —— 面板改默认模型后无需重启即生效。
48
+ function settingsPartition(env) {
49
+ return readPartition(env).partition || {}
61
50
  }
62
51
 
63
52
  export function apply(ctx, config) {
@@ -70,13 +59,6 @@ export function apply(ctx, config) {
70
59
  const env = process.env
71
60
  const cfg = config || {}
72
61
 
73
- // 预选链(旧 loadRuntime 语义):config.backend > env > 统一设置文件。工具参数级在
74
- // descriptor.resolveBackend 内处理(arg 优先),这里给 runner 的是「非 arg」的最高优先源。
75
- function preferredBackend() {
76
- const b = cfg.backend || env.MIMO_BRIDGE_BACKEND || env.MIMO_BACKEND || (readPartition(env).partition || {}).preferredBackend
77
- return b || null
78
- }
79
-
80
62
  const provider = defineProvider({
81
63
  process,
82
64
  env,
@@ -87,11 +69,9 @@ export function apply(ctx, config) {
87
69
  })
88
70
 
89
71
  // 状态快照 → 家级灯(keystone 听 'mimo/status',事件是 app 级广播)。
90
- // 附带 defaultBackend/backend 便利字段(旧 mimo/status payload,契约容忍额外键)。
91
72
  function publish(snapshot) {
92
73
  try {
93
- const snap = Object.assign({ defaultBackend: preferredBackend() || provider.defaultBackend, backend: preferredBackend() || provider.defaultBackend }, snapshot)
94
- ctx.emit('mimo/status', { snapshot: snap })
74
+ ctx.emit('mimo/status', { snapshot })
95
75
  } catch (e) { /* ignore */ }
96
76
  }
97
77
 
@@ -103,15 +83,17 @@ export function apply(ctx, config) {
103
83
  return process.cwd()
104
84
  }
105
85
 
106
- // exe:mimo-desktop 走本地 HTTP 非 spawn → 返回占位 'mimo'(永不 throw,让 kit 落到
107
- // delegates→execute);mimocode 走 MIMO_BIN/MIMO_CLI > PATH 候选 > subprocess 查找(
108
- // 找不到 throw → kit 发 MIMO_UNAVAILABLE + 回退弹窗)。
86
+ // exe 解析(与 descriptor 单一真源 resolveMimoExePrefix 同优先级):
87
+ // MIMO_BIN > MIMO_CLI > 已知安装位置 ~/.mimocode/bin/mimo[.exe] > PATH 查找 mimo。
88
+ // .mjs/.cjs/.js 目标经 process.execPath 包装(供测试 fixture 使用)。
89
+ // 找不到 → throw → kit 发 MIMO_UNAVAILABLE + 回退弹窗。
109
90
  async function resolveExe(backend, signal) {
110
- if (backend === BACKEND_DESKTOP) return 'mimo'
111
- const fromCandidates = mimoExeFromEnvCandidates(env)
112
- if (fromCandidates) {
113
- if (/\.(mjs|cjs|js)$/i.test(fromCandidates)) return [process.execPath, fromCandidates]
114
- return fromCandidates
91
+ const prefix = resolveMimoExePrefix(env)
92
+ if (prefix.length && prefix[0]) {
93
+ const head = String(prefix[0])
94
+ if (prefix.length > 1) return prefix
95
+ if (/\.(mjs|cjs|js)$/i.test(head)) return [process.execPath, head]
96
+ return head
115
97
  }
116
98
  return await subprocess.resolveExecutable('mimo', undefined, signal)
117
99
  }
@@ -133,18 +115,16 @@ export function apply(ctx, config) {
133
115
  resolveExe,
134
116
  getCwdFallback,
135
117
  planActiveFor,
136
- getPreferredBackend: preferredBackend,
137
- getSettings: () => (readPartition(env).partition || {}),
118
+ getSettings: () => settingsPartition(env),
138
119
  publish
139
120
  })
140
121
 
141
122
  // 宣告 mimo 优先模式:家级灯据此决定常驻显示(presetActive)。挂载即宣告 +
142
- // 每 30s 心跳续租(TTL 75s,契约 §2)。旧 payload 带 backend,新契约要 sessionId;都带。
123
+ // 每 30s 心跳续租(TTL 75s,契约 §2)。payload 只带 active/sessionId。
143
124
  function announceMode() {
144
125
  try {
145
126
  const sid = (ctx.session && ctx.session.id) || ctx.sessionId || null
146
- const bk = preferredBackend() || provider.defaultBackend
147
- const payload = { active: true, backend: bk }
127
+ const payload = { active: true }
148
128
  if (sid) payload.sessionId = sid
149
129
  ctx.emit('mimo/mode', payload)
150
130
  } catch (e) { /* ignore */ }
@@ -194,13 +174,21 @@ export function apply(ctx, config) {
194
174
  }))
195
175
  }
196
176
 
197
- // 旧 policy 文案 verbatim(契约 §8:提示词连续性,不许顺手改)。
177
+ // 提示词连续性(契约 §8):决策表 / 上下文协议 / 回退协议三段保留旧语义;后端段与
178
+ // 模型段随「去 desktop 化 + 模型手动指定」重写(免费 API 已终止 + mimo auth login)。
198
179
  const policyText = [
199
- 'mimo-first execution policy (Xiaomi MiMo: MiMoCode CLI or MiMo Desktop client).',
180
+ 'mimo-first execution policy (Xiaomi MiMo: MiMoCode CLI).',
181
+ '',
182
+ 'Local `mimo` CLI (MiMoCode) handles coding tasks under full DSH control (--dangerously-skip-permissions, never prompts).',
200
183
  '',
201
- 'Local backends (selectable in settings: backend = mimocode | mimo-desktop):',
202
- '- mimocode (default): local `mimo` CLI (MiMoCode). Full DSH control (--dangerously-skip-permissions, never prompts).',
203
- '- mimo-desktop: Xiaomi MiMo Desktop client local API (running Desktop app; uses its login/model/session UI).',
184
+ 'Model:',
185
+ '- The model is chosen in the CLI Bridge settings panel (provider/model form, e.g. xiaomi/mimo-v2.6-flash) — this bridge does not pick one.',
186
+ '- Leave the setting empty to pass no --model, letting the mimo CLI use its own default. A per-call `model` argument overrides the setting for that call only.',
187
+ '- List available models with `mimo models` on this machine.',
188
+ '',
189
+ 'Credentials:',
190
+ "- Xiaomi MiMo's free API service has ENDED. The user must run `mimo auth login` (or configure a third-party API) on this machine before dispatch works.",
191
+ '- If a run fails with sign-in / free-API-ended errors, tell the user to log in. Do not retry in a loop.',
204
192
  '',
205
193
  'By default, dispatch runs in the background and returns a jobId immediately; collect via job_output and watch live progress via mimo_status.',
206
194
  '',
@@ -210,12 +198,6 @@ export function apply(ctx, config) {
210
198
  '- Follow-ups on the same topic -> mimo_continue (with latest=true or sessionId; context is preserved).',
211
199
  '- Final build/test verification of mimo output -> Native tools.',
212
200
  '- Plan / read-only analysis -> mimo_run with mode=plan.',
213
- '- Desktop-native work (Desktop session UI, shared Desktop login/model) -> prefer backend=mimo-desktop (or preselect it in settings).',
214
- '',
215
- 'Backend preselection (settings):',
216
- '- Default backend is configured OUT-OF-BAND (plugin config.backend / env MIMO_BRIDGE_BACKEND / settings file).',
217
- '- Per-call `backend` argument overrides the default only when necessary.',
218
- '- Do NOT invent a backend every call — use the preselected default.',
219
201
  '',
220
202
  'Context protocol (one topic = one mimo session):',
221
203
  '- First dispatch on a topic: compact CONTEXT preamble (point to paths, do not paste file contents).',