@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.
- package/CHANGELOG.md +73 -0
- package/docs/HANDOVER.md +201 -51
- package/dynamic/agy.host.js +1 -1
- package/dynamic/codebuddy.host.js +1332 -925
- package/dynamic/codebuddy.host.template.mjs +1 -0
- package/dynamic/mimo.host.js +314 -548
- package/dynamic/mimo.host.template.mjs +43 -30
- package/dynamic/opencode.host.js +32 -22
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +11 -14
- package/mcp/cli-bridge-mcp.mjs +1 -1
- package/mcp/providers/codebuddy.mjs +427 -398
- package/mcp/providers/mimo.mjs +37 -68
- package/mcp/providers/opencode.mjs +28 -1
- package/package.json +3 -1
- package/preset/codebuddy-first/cli-bridge.mjs +12 -0
- package/preset/mimo-first/cli-bridge.mjs +32 -50
- package/providers/codebuddy-catalog.mjs +605 -0
- package/providers/codebuddy-models-tool.mjs +106 -0
- package/providers/codebuddy.mjs +636 -926
- package/providers/mimo.mjs +271 -517
- package/providers/opencode.mjs +31 -21
- package/scripts/build.mjs +262 -235
package/mcp/providers/mimo.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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'
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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') +
|
|
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 +
|
|
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
|
|
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
|
|
215
|
+
// runMimo 所有退出路径均已 engine.end;此处仅改返回值对象,
|
|
256
216
|
// 绝不二次 engine.end(防 runs 虚标与 running 计数下溢)。
|
|
257
|
-
|
|
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
|
|
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
|
-
//
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
|
|
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.
|
|
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
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
|
107
|
-
//
|
|
108
|
-
//
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
177
|
+
// 提示词连续性(契约 §8):决策表 / 上下文协议 / 回退协议三段保留旧语义;后端段与
|
|
178
|
+
// 模型段随「去 desktop 化 + 模型手动指定」重写(免费 API 已终止 + mimo auth login)。
|
|
198
179
|
const policyText = [
|
|
199
|
-
'mimo-first execution policy (Xiaomi MiMo: MiMoCode CLI
|
|
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
|
-
'
|
|
202
|
-
'-
|
|
203
|
-
'-
|
|
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).',
|