@luchenglong/cli-bridge 0.2.9 → 0.2.11
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/PROVIDER-CONTRACT.md +56 -5
- package/docs/HANDOVER.md +109 -15
- package/dynamic/claude.host.js +362 -106
- package/dynamic/codebuddy.host.js +1426 -1378
- package/dynamic/codex.host.js +78 -103
- package/dynamic/mimo.host.js +720 -514
- package/dynamic/mimo.host.template.mjs +2 -0
- package/dynamic/opencode.host.js +740 -584
- package/dynamic/opencode.host.template.mjs +2 -0
- package/home-plugin/cli-bridge-indicator/lib/client.js +590 -600
- package/home-plugin/cli-bridge-indicator/lib/index.mjs +48 -0
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +1 -1
- package/mcp/cli-bridge-mcp.mjs +3 -3
- package/mcp/providers/codebuddy.mjs +427 -427
- package/package.json +3 -3
- package/providers/claude-endpoint.mjs +132 -0
- package/providers/claude-models.mjs +141 -101
- package/providers/claude.mjs +88 -6
- package/providers/codebuddy-catalog.mjs +367 -599
- package/providers/codebuddy-consts.mjs +72 -72
- package/providers/codebuddy-models-source.mjs +254 -0
- package/providers/codebuddy-models-tool.mjs +106 -106
- package/providers/codebuddy-schemas.mjs +46 -46
- package/providers/codebuddy.mjs +600 -578
- package/providers/codex-cli.mjs +33 -99
- package/providers/codex.mjs +45 -4
- package/providers/mimo-pure.mjs +270 -111
- package/providers/mimo.mjs +453 -517
- package/providers/opencode-exe.mjs +190 -63
- package/providers/opencode.mjs +553 -587
- package/scripts/build.mjs +261 -261
package/providers/mimo-pure.mjs
CHANGED
|
@@ -1,111 +1,270 @@
|
|
|
1
|
-
// providers/mimo-pure.mjs — mimo
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
1
|
+
// providers/mimo-pure.mjs — mimo 的纯逻辑片文件(无宿主依赖):事件解析 / 结果构造 +
|
|
2
|
+
// 「怎么问本机 mimo 要模型清单」链路。
|
|
3
|
+
//
|
|
4
|
+
// 从 providers/mimo.mjs 拆出,让两个文件都留在 600 行红线内(规则 §26)。
|
|
5
|
+
// build 会把 providers/mimo-*.mjs 全部内联进 dynamic 沙箱同一作用域,故主文件
|
|
6
|
+
// 仍可直接调用这些函数;ESM 形态走本文件的 import。
|
|
7
|
+
// 沙箱纪律:禁用 import.meta 字面量;fs/path/child_process 一律经
|
|
8
|
+
// globalThis.process.getBuiltinModule + typeof 守卫读取(沙箱可能没有 node 能力)。
|
|
9
|
+
// 注意:注释里绝不写出口令标记字面量(build 全局替换会把注释劈开)。
|
|
10
|
+
|
|
11
|
+
import { shortLabel, summarizeArgs, clampInt } from '../core/bridge-kit.mjs'
|
|
12
|
+
|
|
13
|
+
// Node 内建探测(沙箱纪律:getBuiltinModule + typeof 守卫)。
|
|
14
|
+
// 名字带 mimo 前缀:本文件会与 bridge-kit / core/scheduling.mjs【文本内联进同一个动态
|
|
15
|
+
// 沙箱脚本作用域】,撞名即「Identifier already declared」构建错误。
|
|
16
|
+
function mimoBuiltin(name) {
|
|
17
|
+
try {
|
|
18
|
+
const proc = globalThis.process
|
|
19
|
+
return (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule(name) : null
|
|
20
|
+
} catch (e) { return null }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── 可执行文件解析(env MIMO_BIN/MIMO_CLI > 已知安装位置 > PATH 名字)───────────
|
|
24
|
+
// MiMoCode CLI 官方便携安装目录:<USERPROFILE|HOME>/.mimocode/bin/mimo[.exe]。
|
|
25
|
+
// 沙箱内可能没有 fs —— 全部读取走 typeof 守卫,读不到就返回空数组(退回 PATH 查找)。
|
|
26
|
+
const MIMO_EXE_DIR = ['.mimocode', 'bin']
|
|
27
|
+
|
|
28
|
+
/** 探测本机 mimo 可执行文件候选路径(官方便携安装位置);无 fs 能力时返回空数组。 */
|
|
29
|
+
export function mimoCandidateExePaths(env) {
|
|
30
|
+
const e = env || {}
|
|
31
|
+
const out = []
|
|
32
|
+
const fs = mimoBuiltin('node:fs')
|
|
33
|
+
if (!fs || typeof fs.existsSync !== 'function') return out
|
|
34
|
+
const path = mimoBuiltin('node:path')
|
|
35
|
+
const join = (path && typeof path.join === 'function') ? ((a, b) => path.join(a, b)) : ((a, b) => a + '/' + b)
|
|
36
|
+
for (const k of ['USERPROFILE', 'HOME']) {
|
|
37
|
+
if (!e[k]) continue
|
|
38
|
+
const dir = join(join(String(e[k]), MIMO_EXE_DIR[0]), MIMO_EXE_DIR[1])
|
|
39
|
+
for (const base of ['mimo.exe', 'mimo']) {
|
|
40
|
+
const exe = join(dir, base)
|
|
41
|
+
try { if (fs.existsSync(exe) && out.indexOf(exe) < 0) out.push(exe) } catch (err) { /* 探测失败静默跳过 */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 解析本次调用使用的 exe 前缀(数组或字符串)。
|
|
49
|
+
* 优先级:binPath(显式路径)> env MIMO_BIN/MIMO_CLI > 已知安装位置 > PATH 名字。
|
|
50
|
+
*/
|
|
51
|
+
export function resolveMimoExePrefix(env, binPath) {
|
|
52
|
+
const e = env || {}
|
|
53
|
+
const bin = binPath || e.MIMO_BIN || e.MIMO_CLI || ''
|
|
54
|
+
if (bin) {
|
|
55
|
+
if (/\.(mjs|cjs|js)$/i.test(bin)) {
|
|
56
|
+
const proc = globalThis.process
|
|
57
|
+
return [(proc && proc.execPath) ? proc.execPath : 'node', bin]
|
|
58
|
+
}
|
|
59
|
+
return [String(bin)]
|
|
60
|
+
}
|
|
61
|
+
const found = mimoCandidateExePaths(e)
|
|
62
|
+
return found.length ? [found[0]] : ['mimo']
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── 模型列表(`mimo models`;设置面板下拉用)───────────────────────────────────
|
|
66
|
+
// 实测输出形如每行 `provider/model — window 1M, compacts at 900K`(分隔符是中文破折号
|
|
67
|
+
// U+2014)。模型名自身含连字符(xiaomi/mimo-v2.6-pro-ultraspeed),所以只认「行首的
|
|
68
|
+
// provider/model」,其后一律当描述文本;表头/说明/空行一律丢弃,绝不抛。
|
|
69
|
+
const MIMO_MODEL_ID_RE = /^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.@#+-]+)(?=$|[\s—–:|,])/
|
|
70
|
+
|
|
71
|
+
/** 解析 `mimo models` 文本输出为 [{id, provider, name, note}](去重、保序)。 */
|
|
72
|
+
export function parseMimoModels(text) {
|
|
73
|
+
const out = []
|
|
74
|
+
const seen = Object.create(null)
|
|
75
|
+
for (const raw of String(text || '').split(/\r?\n/)) {
|
|
76
|
+
// 去掉列表记号(• * - 1. 等)与行首空白,避免把记号当 provider 名。
|
|
77
|
+
const ln = raw.replace(/^[^A-Za-z0-9]+/, '').trim()
|
|
78
|
+
if (!ln) continue
|
|
79
|
+
const m = ln.match(MIMO_MODEL_ID_RE)
|
|
80
|
+
if (!m) continue
|
|
81
|
+
const id = m[1]
|
|
82
|
+
if (seen[id]) continue
|
|
83
|
+
seen[id] = true
|
|
84
|
+
const slash = id.indexOf('/')
|
|
85
|
+
out.push({ id: id, provider: id.slice(0, slash), name: id.slice(slash + 1), note: ln.slice(id.length).replace(/^[\s—–:|,-]+/, '').trim() })
|
|
86
|
+
}
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 诊断文本压成一行(CLI 的 stderr/stdout 常带换行与表格)。
|
|
91
|
+
function mimoOneLine(text, max) {
|
|
92
|
+
const s = String(text == null ? '' : text).replace(/\s+/g, ' ').trim()
|
|
93
|
+
const cap = max || 200
|
|
94
|
+
return s.length > cap ? s.slice(0, cap) + '…' : s
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 真正跑一次 `mimo models`,**带可操作失败原因**(契约 §3.2:失败必须 ok:false)。
|
|
99
|
+
*
|
|
100
|
+
* 为什么不沿用「失败返回空数组」:空数组区分不了「没装 CLI」「没登录 / 免费 API 已终止」
|
|
101
|
+
* 「超时」「输出格式变了」,而面板与用户下一步动作完全不同。
|
|
102
|
+
* @returns {{ok:boolean, models:Array, exe:string, error?:string}}
|
|
103
|
+
*/
|
|
104
|
+
export function fetchMimoModelsDetailed(env, timeoutMs) {
|
|
105
|
+
const prefix = resolveMimoExePrefix(env)
|
|
106
|
+
const exe = prefix.join(' ')
|
|
107
|
+
const child = mimoBuiltin('node:child_process')
|
|
108
|
+
if (!child || typeof child.spawnSync !== 'function') {
|
|
109
|
+
return { ok: false, models: [], exe: exe, error: '当前宿主没有子进程能力(spawnSync 不可用)—— 请在有 Node 子进程能力的形态(DSH 主进程 / MCP 进程)里点「拉取可用模型」' }
|
|
110
|
+
}
|
|
111
|
+
let r = null
|
|
112
|
+
try {
|
|
113
|
+
r = child.spawnSync(prefix[0], prefix.slice(1).concat(['models']), {
|
|
114
|
+
encoding: 'utf8', timeout: clampInt(timeoutMs, 20000, 2000, 60000),
|
|
115
|
+
windowsHide: true, stdio: ['ignore', 'pipe', 'pipe']
|
|
116
|
+
})
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return { ok: false, models: [], exe: exe, error: '启动 `' + exe + ' models` 失败:' + mimoOneLine(e && e.message || e) }
|
|
119
|
+
}
|
|
120
|
+
if (!r) return { ok: false, models: [], exe: exe, error: '`' + exe + ' models` 没有任何返回(spawnSync 返回 null),请确认可执行文件路径' }
|
|
121
|
+
if (r.error) {
|
|
122
|
+
const code = String(r.error.code || '')
|
|
123
|
+
if (code === 'ENOENT') {
|
|
124
|
+
return { ok: false, models: [], exe: exe, error: '未找到 mimo 可执行文件(' + exe + ')—— 请安装 MiMoCode CLI(官方便携安装位置 ~/.mimocode/bin/mimo.exe),或设置 MIMO_BIN 指向其绝对路径' }
|
|
125
|
+
}
|
|
126
|
+
if (code === 'ETIMEDOUT' || code === 'ABORT_ERR') {
|
|
127
|
+
return { ok: false, models: [], exe: exe, error: '`' + exe + ' models` 超时 —— 网络或鉴权可能卡住:请先在终端手动跑一次 `mimo models` 确认,必要时执行 `mimo auth login`' }
|
|
128
|
+
}
|
|
129
|
+
return { ok: false, models: [], exe: exe, error: '`' + exe + ' models` 启动失败(' + (code || 'unknown') + '):' + mimoOneLine(r.error.message || r.error) }
|
|
130
|
+
}
|
|
131
|
+
const stdout = String(r.stdout || '')
|
|
132
|
+
const stderr = String(r.stderr || '')
|
|
133
|
+
const models = parseMimoModels(stdout)
|
|
134
|
+
if (models.length) return { ok: true, models: models, exe: exe }
|
|
135
|
+
if (r.status !== 0) {
|
|
136
|
+
return { ok: false, models: [], exe: exe, error: '`mimo models` 退出码 ' + r.status + ':' + mimoOneLine(stderr || stdout) + ' —— 若提示未登录 / 免费 API 已终止,请先在终端执行 `mimo auth login`' }
|
|
137
|
+
}
|
|
138
|
+
return { ok: false, models: [], exe: exe, error: '`mimo models` 成功退出但没有解析到任何 provider/model 行' + (stdout ? '(输出开头:' + mimoOneLine(stdout, 160) + ')' : '(输出为空)') + ' —— 请先在终端手动跑一次 `mimo models` 确认输出格式与登录状态' }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 兼容薄封装:失败返回空数组(旧语义,供不关心原因的调用方)。 */
|
|
142
|
+
export function fetchMimoModelsSync(env, timeoutMs) {
|
|
143
|
+
const d = fetchMimoModelsDetailed(env, timeoutMs)
|
|
144
|
+
return d.ok ? d.models : []
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── 手动拉取缓存(契约 §3.2)─────────────────────────────────────────────────
|
|
148
|
+
// `meta()` 每次面板渲染都会被调用:**只读本缓存,绝不 spawn**。缓存只存在进程内存里
|
|
149
|
+
// (宿主进程重启即失效,不会把过时清单落盘成"配置");用户点按钮才刷新它。
|
|
150
|
+
const MIMO_MODEL_CACHE_TTL_MS = 6 * 60 * 60 * 1000
|
|
151
|
+
const MIMO_MODEL_CACHE = { at: 0, models: [] }
|
|
152
|
+
|
|
153
|
+
/** 读缓存(未拉过 / 已过期 → 空数组,面板据此提示「点按钮拉取」)。 */
|
|
154
|
+
export function cachedMimoModels(nowMs) {
|
|
155
|
+
const at = Number(nowMs) || Date.now()
|
|
156
|
+
if (!MIMO_MODEL_CACHE.models.length) return []
|
|
157
|
+
if (at - MIMO_MODEL_CACHE.at >= MIMO_MODEL_CACHE_TTL_MS) return []
|
|
158
|
+
return MIMO_MODEL_CACHE.models.slice()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 手动拉取成功后回填缓存;空清单绝不入缓存(否则下次渲染"有缓存但没内容")。 */
|
|
162
|
+
export function rememberMimoModels(models, nowMs) {
|
|
163
|
+
if (!Array.isArray(models) || !models.length) return false
|
|
164
|
+
MIMO_MODEL_CACHE.models = models.slice()
|
|
165
|
+
MIMO_MODEL_CACHE.at = Number(nowMs) || Date.now()
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function parseMimoJson(stdoutText) {
|
|
170
|
+
const trimmed = String(stdoutText || '').trim()
|
|
171
|
+
if (!trimmed) return null
|
|
172
|
+
const events = []
|
|
173
|
+
const lines = trimmed.split(/\r?\n/)
|
|
174
|
+
for (const raw of lines) {
|
|
175
|
+
const ln = raw.trim()
|
|
176
|
+
if (!ln.startsWith('{')) continue
|
|
177
|
+
try {
|
|
178
|
+
const obj = JSON.parse(ln)
|
|
179
|
+
if (obj && typeof obj === 'object') events.push(obj)
|
|
180
|
+
} catch (e) { /* tolerate log lines */ }
|
|
181
|
+
}
|
|
182
|
+
if (!events.length) {
|
|
183
|
+
try {
|
|
184
|
+
const obj = JSON.parse(trimmed)
|
|
185
|
+
if (obj && typeof obj === 'object') events.push(obj)
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return { events: [], text: trimmed, sessionId: null, error: null, steps: [] }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let sessionId = null
|
|
191
|
+
let text = ''
|
|
192
|
+
let errorMessage = null
|
|
193
|
+
let errorName = null
|
|
194
|
+
let numTurns = 0
|
|
195
|
+
let totalTokens = null
|
|
196
|
+
const steps = []
|
|
197
|
+
const textParts = []
|
|
198
|
+
for (const ev of events) {
|
|
199
|
+
if (ev.sessionID && typeof ev.sessionID === 'string') sessionId = ev.sessionID
|
|
200
|
+
if (ev.sessionId && typeof ev.sessionId === 'string') sessionId = ev.sessionId
|
|
201
|
+
if (ev.session && typeof ev.session === 'object' && ev.session.id) sessionId = ev.session.id
|
|
202
|
+
if (ev.type === 'session' && ev.properties && ev.properties.id) sessionId = ev.properties.id
|
|
203
|
+
const t = ev.type
|
|
204
|
+
const p = ev.properties || ev
|
|
205
|
+
if (t === 'error') {
|
|
206
|
+
const err = ev.error || p
|
|
207
|
+
errorMessage = (err.data && err.data.message) || err.message || err.name || 'error'
|
|
208
|
+
errorName = err.name || 'Error'
|
|
209
|
+
} else if (t === 'text' || t === 'message' || t === 'assistant' || t === 'output') {
|
|
210
|
+
const chunk = p.text || p.content || p.message || p.output || (typeof ev.text === 'string' ? ev.text : '')
|
|
211
|
+
if (typeof chunk === 'string' && chunk) {
|
|
212
|
+
textParts.push(chunk)
|
|
213
|
+
steps.push({ at: Date.now(), kind: 'text', label: shortLabel(chunk) })
|
|
214
|
+
}
|
|
215
|
+
} else if (t === 'step-start' || t === 'step-finish' || t === 'step_update') {
|
|
216
|
+
numTurns += 1
|
|
217
|
+
if (p.tokens && typeof p.tokens === 'object') {
|
|
218
|
+
const tt = p.tokens.total ?? p.tokens.input ?? p.tokens.output
|
|
219
|
+
if (typeof tt === 'number') totalTokens = (totalTokens || 0) + tt
|
|
220
|
+
}
|
|
221
|
+
if (t === 'step_update') {
|
|
222
|
+
const name = p.tool_name || p.tool || p.name
|
|
223
|
+
if (name) steps.push({ at: Date.now(), kind: 'tool', label: shortLabel(name + ' ' + JSON.stringify(summarizeArgs(p.arguments || p.args || p.input) || {})) })
|
|
224
|
+
}
|
|
225
|
+
} else if (t === 'tool' || t === 'tool-call' || t === 'tool_use') {
|
|
226
|
+
const name = p.tool || p.name || p.tool_name || 'tool'
|
|
227
|
+
steps.push({ at: Date.now(), kind: 'tool', label: shortLabel(name + ' ' + JSON.stringify(summarizeArgs(p.arguments || p.args || p.input) || {})) })
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (textParts.length) text = textParts.join('\n')
|
|
231
|
+
else {
|
|
232
|
+
const last = events[events.length - 1] || {}
|
|
233
|
+
const r = last.result || last
|
|
234
|
+
if (typeof r.response === 'string') text = r.response
|
|
235
|
+
else if (typeof r.text === 'string') text = r.text
|
|
236
|
+
}
|
|
237
|
+
return { events, text, sessionId, error: errorMessage, errorName, numTurns, totalTokens, steps }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function buildResult(parsed, outcome, mode, stderrText, stdoutText, backend) {
|
|
241
|
+
const MAX_TRAIL = 12
|
|
242
|
+
const exitCode = outcome ? outcome.exitCode : null
|
|
243
|
+
const errText = (parsed && parsed.error) ? String(parsed.error) : ''
|
|
244
|
+
const stderr = (stderrText ? String(stderrText).slice(-2000) : '') + (errText ? (stderrText ? ' ' : '') + errText : '')
|
|
245
|
+
const text = parsed && typeof parsed.text === 'string' ? parsed.text : ''
|
|
246
|
+
const failed = exitCode !== 0 || !!(parsed && parsed.error)
|
|
247
|
+
const be = (parsed && parsed.backend) || (outcome && outcome.backend) || backend || null
|
|
248
|
+
if (parsed) {
|
|
249
|
+
return {
|
|
250
|
+
ok: !failed && (!!text || parsed.accepted === true),
|
|
251
|
+
status: failed ? (parsed.errorName || 'ERROR') : (parsed.accepted && !text ? 'ACCEPTED' : (text ? 'SUCCESS' : 'EMPTY')),
|
|
252
|
+
response: text,
|
|
253
|
+
sessionId: parsed.sessionId || null,
|
|
254
|
+
durationSeconds: null,
|
|
255
|
+
numTurns: parsed.numTurns || null,
|
|
256
|
+
totalTokens: parsed.totalTokens || null,
|
|
257
|
+
exitCode, mode, backend: be,
|
|
258
|
+
stderr,
|
|
259
|
+
errorMessage: parsed.error || '',
|
|
260
|
+
accepted: parsed.accepted === true,
|
|
261
|
+
steps: Array.isArray(parsed.steps) ? parsed.steps.slice(-MAX_TRAIL) : []
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
ok: false, status: 'PARSE_ERROR', response: '', sessionId: null,
|
|
266
|
+
durationSeconds: null, numTurns: null, totalTokens: null,
|
|
267
|
+
exitCode, mode, backend: be, stderr, errorMessage: '',
|
|
268
|
+
rawStdout: String(stdoutText || '').slice(-2000)
|
|
269
|
+
}
|
|
270
|
+
}
|