@ljwei-stak/dsh-model-router 0.19.0 → 0.20.0

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.
@@ -5,11 +5,13 @@
5
5
  * worktree for review. No package supplies an executable or shell fragment.
6
6
  */
7
7
  import { spawn } from 'node:child_process'
8
- import { appendFile, mkdir, realpath, stat } from 'node:fs/promises'
9
- import { randomUUID } from 'node:crypto'
8
+ import { appendFile, lstat, mkdir, realpath, stat } from 'node:fs/promises'
9
+ import { createHash, randomUUID } from 'node:crypto'
10
+ import { tmpdir } from 'node:os'
10
11
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
11
12
  import { runOfficialTool, officialToolExecutionCapabilities, officialToolReadiness } from './official-tool-executor.mjs'
12
13
  import { toolForProvider } from './official-tool-registry.mjs'
14
+ import { redactDiagnostic, verifiedDiagnostic } from './task-executors.mjs'
13
15
 
14
16
  const MAX_GIT_OUTPUT = 64_000_000
15
17
  const GIT_TIMEOUT_MS = 120_000
@@ -26,6 +28,61 @@ async function canonicalDirectory(path) {
26
28
  return canonical
27
29
  }
28
30
 
31
+ function pathIdentity(path) {
32
+ return process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path)
33
+ }
34
+
35
+ async function approvedSource(workspace, allowedRoot) {
36
+ const source = await canonicalDirectory(workspace)
37
+ const root = await canonicalDirectory(allowedRoot ?? source)
38
+ if (!within(root, source)) throw new Error('原工作区不在当前 Harness 会话批准的工作区范围内。')
39
+ return source
40
+ }
41
+
42
+ function sourceStorageKey(source) {
43
+ return createHash('sha256').update(pathIdentity(source)).digest('hex')
44
+ }
45
+
46
+ function requireDisjointStorage(source, storage) {
47
+ if (within(source, storage) || within(storage, source)) {
48
+ throw new Error('Host 独立工作区存储必须与原工作区互不包含。')
49
+ }
50
+ }
51
+
52
+ /** Reject junctions before creating deeper directories in Host-managed storage. */
53
+ async function storageDirectory(parent, name) {
54
+ const target = join(parent, name)
55
+ try { await mkdir(target) }
56
+ catch (error) { if (error?.code !== 'EEXIST') throw error }
57
+ const entry = await lstat(target)
58
+ const canonical = await canonicalDirectory(target)
59
+ if (!entry.isDirectory() || entry.isSymbolicLink() || pathIdentity(canonical) !== pathIdentity(target)) {
60
+ throw new Error('Host 独立工作区存储不能经过符号链接或目录联接。')
61
+ }
62
+ return canonical
63
+ }
64
+
65
+ /**
66
+ * Windows ACL grants need WRITE_OWNER as well as ordinary file modification.
67
+ * Use the user's existing private temp storage rather than changing a source
68
+ * drive's ACL. Only the individual run receives the sandbox write capability;
69
+ * the SDK's separate random private temp remains disjoint from that run.
70
+ */
71
+ async function windowsStorageParent(source, create = false) {
72
+ const temporary = await canonicalDirectory(tmpdir())
73
+ const rootPath = join(temporary, '.model-router-workspaces')
74
+ requireDisjointStorage(source, rootPath)
75
+ const root = create ? await storageDirectory(temporary, '.model-router-workspaces')
76
+ : await canonicalDirectory(rootPath)
77
+ if (pathIdentity(root) !== pathIdentity(rootPath)) throw new Error('Host 独立工作区存储路径校验失败。')
78
+ const key = sourceStorageKey(source)
79
+ const parentPath = join(root, key)
80
+ const parent = create ? await storageDirectory(root, key) : await canonicalDirectory(parentPath)
81
+ if (pathIdentity(parent) !== pathIdentity(parentPath)) throw new Error('Host 独立工作区存储路径校验失败。')
82
+ requireDisjointStorage(source, parent)
83
+ return parent
84
+ }
85
+
29
86
  /** The session owner, never model arguments, selects the filesystem scope. */
30
87
  export async function sessionWorkspace(ctx, exec) {
31
88
  const session = exec?.agent?.session
@@ -97,6 +154,13 @@ async function cleanRepoRoot(workspace, signal) {
97
154
  return repo
98
155
  }
99
156
 
157
+ /** Preview-only validation; never creates a worktree or changes ignore rules. */
158
+ export async function preflightOfficialWorkspace(workspace, mode = 'read-only', signal) {
159
+ const source = await canonicalDirectory(workspace)
160
+ if (mode === 'workspace-write') await cleanRepoRoot(source, signal)
161
+ return source
162
+ }
163
+
100
164
  /** Keep nested worktrees out of ordinary git status without changing tracked files. */
101
165
  async function excludeNestedWorktrees(workspace, signal) {
102
166
  const ignored = await git(['check-ignore', '-q', '--', '.model-router-workspaces/'], { cwd: workspace, signal })
@@ -114,25 +178,31 @@ async function excludeNestedWorktrees(workspace, signal) {
114
178
  if (!confirmed.ok) throw gitError('确认独立工作区忽略规则', confirmed)
115
179
  }
116
180
 
117
- /** Keep generated worktrees beside the project so they use the same drive. */
181
+ /** Source authorization is independent from the Host's isolated output storage. */
118
182
  async function isolatedWorktree(workspace, signal, allowedRoot) {
183
+ workspace = await approvedSource(workspace, allowedRoot)
119
184
  await cleanRepoRoot(workspace, signal)
120
185
  const base = await git(['rev-parse', 'HEAD'], { cwd: workspace, signal })
121
186
  if (!base.ok || !/^[0-9a-f]{40,64}$/i.test(base.output.toString('utf8').trim())) {
122
187
  throw gitError('记录原仓库基线', base)
123
188
  }
124
189
  const baseCommit = base.output.toString('utf8').trim()
125
- const policyRoot = await canonicalDirectory(allowedRoot ?? workspace)
126
- const sibling = join(dirname(workspace), '.model-router-workspaces', basename(workspace))
127
- const parent = within(policyRoot, sibling) ? sibling : join(workspace, '.model-router-workspaces')
128
- if (parent === join(workspace, '.model-router-workspaces')) await excludeNestedWorktrees(workspace, signal)
129
- await mkdir(parent, { recursive: true })
130
- const isolatedRoot = await canonicalDirectory(parent)
190
+ let isolatedRoot
191
+ if (process.platform === 'win32') isolatedRoot = await windowsStorageParent(workspace, true)
192
+ else {
193
+ const policyRoot = await canonicalDirectory(allowedRoot ?? workspace)
194
+ const sibling = join(dirname(workspace), '.model-router-workspaces', basename(workspace))
195
+ const parent = within(policyRoot, sibling) ? sibling : join(workspace, '.model-router-workspaces')
196
+ if (parent === join(workspace, '.model-router-workspaces')) await excludeNestedWorktrees(workspace, signal)
197
+ await mkdir(parent, { recursive: true })
198
+ isolatedRoot = await canonicalDirectory(parent)
199
+ }
131
200
  const target = join(isolatedRoot, `run-${randomUUID()}`)
132
201
  const added = await git(['worktree', 'add', '--detach', target, 'HEAD'], { cwd: workspace, signal })
133
202
  if (!added.ok) throw gitError('创建独立 Git 工作区', added)
134
203
  const runPath = await canonicalDirectory(target)
135
- if (!within(isolatedRoot, runPath) || runPath === isolatedRoot) throw new Error('独立工作区路径校验失败')
204
+ if (pathIdentity(dirname(runPath)) !== pathIdentity(isolatedRoot)
205
+ || pathIdentity(runPath) !== pathIdentity(target)) throw new Error('独立工作区路径校验失败')
136
206
  return { workspace: runPath, isolatedRoot, baseCommit, source: workspace }
137
207
  }
138
208
 
@@ -198,13 +268,11 @@ function checkedTeamCliModels(cliModels, assigned) {
198
268
 
199
269
  function teamCliModel(item, cliModels) {
200
270
  // Harness directory IDs and vendor CLI aliases are independent namespaces.
201
- // Only the two CLIs with a verified direct model argument contract receive
202
- // a planned ID. All other vendors use their configured default until the
203
- // user supplies a verified CLI mapping through that vendor's own settings.
271
+ // Every vendor uses its own configured default unless a package or tool
272
+ // explicitly binds the model name accepted by that CLI.
204
273
  if (cliModels[item.id]) return cliModels[item.id]
205
274
  if (cliModels[item.toolId]) return cliModels[item.toolId]
206
- return item.toolId === 'claude-code' || item.toolId === 'codex'
207
- ? item.recommendedModel : null
275
+ return null
208
276
  }
209
277
 
210
278
  function reportedCliModel(toolId, value) {
@@ -241,8 +309,8 @@ function packagePrompt(task, item, completed) {
241
309
  }
242
310
 
243
311
  /** Preflight every assignment before any model request or workspace write. */
244
- export function executableTeamPackages(plan, installedIds = [], mode = 'read-only') {
245
- const capabilities = new Map(officialToolExecutionCapabilities().map(item => [item.id, item]))
312
+ export function executableTeamPackages(plan, installedIds = [], mode = 'read-only', executionEnvironment = 'harness') {
313
+ const capabilities = new Map(officialToolExecutionCapabilities({ executionEnvironment }).map(item => [item.id, item]))
246
314
  const packages = Array.isArray(plan?.team?.workPackages) ? plan.team.workPackages : []
247
315
  const blocking = []
248
316
  const assigned = packages.map(item => {
@@ -266,6 +334,34 @@ async function removeWorktree(source, target) {
266
334
  try { await git(['worktree', 'remove', '--force', target], { cwd: source }) } catch { /* left for manual cleanup */ }
267
335
  }
268
336
 
337
+ function executionDiagnostic(value) {
338
+ let detail = String(value ?? '')
339
+ for (const key of ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN',
340
+ 'OPENAI_API_KEY', 'CODEX_API_KEY', 'KIMI_API_KEY', 'MOONSHOT_API_KEY', 'MINIMAX_API_KEY',
341
+ 'MIMO_API_KEY', 'XAI_API_KEY', 'ZAI_API_KEY', 'GEMINI_API_KEY', 'GOOGLE_API_KEY']) {
342
+ const secret = process.env[key]
343
+ if (typeof secret === 'string' && secret) detail = detail.split(secret).join('[redacted]')
344
+ }
345
+ return redactDiagnostic(detail, '', 2_000)
346
+ }
347
+
348
+ function executionException(error, signal) {
349
+ const detail = executionDiagnostic(error?.message ?? error)
350
+ const cancelled = signal?.aborted === true || error?.name === 'AbortError'
351
+ return { status: cancelled ? 'cancelled' : 'failed', exitCode: null, finalText: '',
352
+ error: `${cancelled ? '官方 CLI 执行已取消' : '官方 CLI 执行器异常'}${detail ? `:${detail}` : '。'}`,
353
+ detail }
354
+ }
355
+
356
+ /** The ledger accepts this bounded, scrubbed field instead of raw CLI streams. */
357
+ function withFailureDiagnostic(result) {
358
+ if (result.status === 'succeeded') return result
359
+ const diagnostic = executionDiagnostic(result.diagnostic ?? verifiedDiagnostic(result))
360
+ return { ...result, ...(result.error ? { error: executionDiagnostic(result.error) } : {}),
361
+ ...(result.reason ? { reason: executionDiagnostic(result.reason) } : {}),
362
+ ...(diagnostic ? { diagnostic } : {}) }
363
+ }
364
+
269
365
  /**
270
366
  * Copy the changes of an earlier, never integrated worktree into a fresh one.
271
367
  * Refuses when the original checkout moved since that run (the patch would be
@@ -279,11 +375,25 @@ async function seedWorktree(isolated, seedFrom, signal) {
279
375
  let prior
280
376
  try { prior = await canonicalDirectory(seedFrom.workspace) }
281
377
  catch { throw new Error('原运行的独立工作区已不存在,无法套用之前的改动;请重新执行整个团队任务。') }
282
- // Only a worktree of this repository that the plugin created (run-<uuid>) is accepted.
283
- const listed = await git(['worktree', 'list', '--porcelain'], { cwd: isolated.source, signal })
284
- const worktrees = listed.ok ? listed.output.toString('utf8').split(/\r?\n/).filter(line => line.startsWith('worktree ')).map(line => line.slice(9)) : []
378
+ // A basename is not ownership evidence: require this repository's membership
379
+ // and an exact canonical parent derived from this source, including old runs.
380
+ const listed = await git(['worktree', 'list', '--porcelain', '-z'], { cwd: isolated.source, signal })
381
+ const worktrees = listed.ok ? listed.output.toString('utf8').split('\0').filter(line => line.startsWith('worktree ')).map(line => line.slice(9)) : []
285
382
  const known = await Promise.all(worktrees.map(path => canonicalDirectory(path).catch(() => null)))
286
- if (!known.includes(prior) || !/^run-[0-9a-f-]{36}$/i.test(basename(prior)) || basename(dirname(dirname(prior))) !== '.model-router-workspaces' && basename(dirname(prior)) !== '.model-router-workspaces') {
383
+ const expectedParents = [join(isolated.source, '.model-router-workspaces'),
384
+ join(dirname(isolated.source), '.model-router-workspaces', basename(isolated.source))]
385
+ if (process.platform === 'win32') {
386
+ try { expectedParents.push(await windowsStorageParent(isolated.source)) } catch { /* a removed/redirected Host parent is not trusted */ }
387
+ }
388
+ const parents = await Promise.all(expectedParents.map(async path => {
389
+ try {
390
+ const canonical = await canonicalDirectory(path)
391
+ return pathIdentity(canonical) === pathIdentity(path) ? canonical : null
392
+ } catch { return null }
393
+ }))
394
+ if (!known.some(path => path && pathIdentity(path) === pathIdentity(prior))
395
+ || !/^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(basename(prior))
396
+ || !parents.some(path => path && pathIdentity(path) === pathIdentity(dirname(prior)))) {
287
397
  throw new Error('原独立工作区不是本仓库由插件创建的 Git 工作树,拒绝读取。')
288
398
  }
289
399
  const staged = await git(['add', '-A'], { cwd: prior, signal })
@@ -301,7 +411,9 @@ async function seedWorktree(isolated, seedFrom, signal) {
301
411
  }
302
412
 
303
413
  /** Sequential execution preserves DAG dependencies and avoids edit conflicts. */
304
- export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode = 'read-only', installedIds = [], cliModels, signal, sandbox, runtime, previous = [], onlyIds = null, seedFrom = null }) {
414
+ export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode = 'read-only', executionEnvironment = 'harness', installedIds = [], cliModels, signal, sandbox, runtime, previous = [], onlyIds = null, seedFrom = null }) {
415
+ if (!['harness', 'cli-native'].includes(executionEnvironment)
416
+ || (executionEnvironment === 'cli-native' && mode !== 'workspace-write')) throw new TypeError('invalid team CLI execution environment')
305
417
  // A retry runs only `onlyIds`; earlier results feed dependency context. An
306
418
  // editable retry needs `seedFrom` (the earlier, never integrated worktree and
307
419
  // its base commit): a fresh worktree gets that worktree's changes first, so
@@ -310,22 +422,23 @@ export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode
310
422
  if (retry && mode !== 'read-only' && !(seedFrom && typeof seedFrom.workspace === 'string' && typeof seedFrom.baseCommit === 'string')) {
311
423
  throw new TypeError('an editable team retry needs the earlier isolated worktree (seedFrom)')
312
424
  }
313
- const selected = executableTeamPackages(plan, installedIds, mode)
425
+ const selected = executableTeamPackages(plan, installedIds, mode, executionEnvironment)
314
426
  const assigned = retry ? selected.assigned.filter(item => onlyIds.includes(item.id)) : selected.assigned
315
427
  const blocking = retry ? selected.blocking.filter(item => onlyIds.includes(item.id)) : selected.blocking
316
428
  if (assigned.length === 0) return { status: 'blocked', blocking: [{ reason: '计划没有工作包。' }], results: [] }
317
429
  if (blocking.length > 0) return { status: 'blocked', blocking, results: [] }
318
430
  if (mode !== 'read-only' && mode !== 'workspace-write') throw new TypeError('team mode must be read-only or workspace-write')
319
431
  const configuredCliModels = checkedTeamCliModels(cliModels, assigned)
320
- const source = await canonicalDirectory(workspace)
321
- if (typeof sandbox?.confine !== 'function') return { status: 'blocked', blocking: [{ reason: '官方 Harness 进程沙箱不可用。' }], results: [] }
432
+ const source = await approvedSource(workspace, allowedRoot)
433
+ if (executionEnvironment === 'harness' && typeof sandbox?.confine !== 'function') return { status: 'blocked', blocking: [{ reason: '官方 Harness 进程沙箱不可用。' }], results: [] }
322
434
  // The Host passes no runtime override. Tests can inject inert CLI adapters to
323
435
  // exercise assignment, stopping, and Git integration without provider accounts.
324
436
  const readinessCheck = runtime?.readiness ?? officialToolReadiness
325
437
  const runTool = runtime?.runTool ?? runOfficialTool
326
- const readiness = await Promise.all([...new Set(assigned.map(item => item.toolId))].map(id => readinessCheck(id, source)))
327
- const unready = readiness.filter(item => !item.ready)
328
- if (unready.length > 0) return { status: 'blocked', blocking: unready.map(item => ({ toolId: item.id, reason: item.reason })), results: [] }
438
+ const readiness = await Promise.all([...new Set(assigned.map(item => item.toolId))].map(id => readinessCheck(id, source, { executionEnvironment })))
439
+ const unready = readiness.filter(item => !item.ready || !(item.readyModes ?? item.modes ?? [mode]).includes(mode))
440
+ if (unready.length > 0) return { status: 'blocked', blocking: unready.map(item => ({ toolId: item.id,
441
+ reason: item.reason ?? `官方 CLI 尚未就绪,或不支持 ${mode} 模式。` })), results: [] }
329
442
  const isolated = mode === 'workspace-write' ? await isolatedWorktree(source, signal, allowedRoot) : null
330
443
  const runWorkspace = isolated?.workspace ?? source
331
444
  let seeded = null
@@ -339,18 +452,28 @@ export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode
339
452
  const completed = []
340
453
  const context = retry ? (Array.isArray(previous) ? previous : []).filter(item => item?.id && typeof item.finalText === 'string') : []
341
454
  for (const item of assigned) {
342
- if (signal?.aborted) return { status: 'cancelled', workspace: runWorkspace, results: completed }
455
+ if (signal?.aborted) return { status: 'cancelled', workspace: runWorkspace, results: completed,
456
+ ...(isolated ? { baseCommit: isolated.baseCommit } : {}), ...(seeded ? { seeded } : {}) }
343
457
  const cliModel = teamCliModel(item, configuredCliModels)
344
- const result = await runTool({
345
- toolId: item.toolId,
346
- modelId: cliModel,
347
- task: packagePrompt(task, item, [...context, ...completed]),
348
- workspace: runWorkspace,
349
- mode,
350
- isolatedRoot: isolated?.isolatedRoot,
351
- sandbox,
352
- signal,
353
- })
458
+ let result
459
+ try {
460
+ result = await runTool({
461
+ toolId: item.toolId,
462
+ modelId: cliModel,
463
+ task: packagePrompt(task, item, [...context, ...completed]),
464
+ workspace: runWorkspace,
465
+ mode,
466
+ isolatedRoot: isolated?.isolatedRoot,
467
+ sandbox,
468
+ signal,
469
+ executionEnvironment,
470
+ })
471
+ if (!result || typeof result.status !== 'string') throw new TypeError('官方 CLI 执行器没有返回有效状态。')
472
+ } catch (error) {
473
+ // Keep earlier paid results and the isolated worktree available for the
474
+ // ledger and retry even when a later adapter rejects before returning.
475
+ result = executionException(error, signal)
476
+ }
354
477
  const reportedModel = reportedCliModel(item.toolId, result.reportedModel)
355
478
  const modelMismatch = result.status === 'succeeded' && cliModel && reportedModel
356
479
  && cliModel !== reportedModel
@@ -358,19 +481,26 @@ export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode
358
481
  completed.push({ id: item.id, name: item.name,
359
482
  ...(item.objective ? { objective: item.objective } : {}), provider: item.recommendedProvider,
360
483
  recommendedModel: item.recommendedModel, toolId: item.toolId,
361
- requestedCliModel: result.requestedModel ?? null,
484
+ requestedCliModel: Object.hasOwn(result, 'requestedModel') ? result.requestedModel : cliModel,
362
485
  actualModel: reportedModel,
486
+ ...(result.reportedModel !== undefined ? { reportedModel: result.reportedModel } : {}),
487
+ ...(result.usageIncomplete === true ? { usageIncomplete: true } : {}),
488
+ ...(typeof result.source === 'string' ? { source: result.source } : {}),
489
+ ...(typeof result.sandboxEnforcement === 'string' ? { sandboxEnforcement: result.sandboxEnforcement } : {}),
490
+ executionEnvironment,
363
491
  modelNotice: modelMismatch
364
492
  ? `官方 CLI 回报模型 ${reportedModel},与本包指定的 ${cliModel} 不一致;后续工作包已停止。`
365
493
  : result.modelNotice ?? (!cliModel
366
494
  ? 'Harness 推荐模型 ID 未经此厂商 CLI 验证;本包使用厂商 CLI 已配置的默认模型。'
367
495
  : reportedModel ? null : '该 CLI 未返回可核验的实际模型 ID;请以厂商运行记录核对。'),
368
496
  status: packageStatus, finalText: result.finalText ?? '',
497
+ exitCode: result.exitCode ?? null,
369
498
  ...(result.usage ? { usage: result.usage } : {}),
370
499
  ...(Number.isFinite(result.reportedCostUsd) ? { reportedCostUsd: result.reportedCostUsd } : {}),
371
- error: modelMismatch ? '官方 CLI 实际模型与指定模型不一致。' : result.error ?? result.reason ?? null,
372
- outputTail: result.status === 'succeeded' ? null : result.stderrTail ?? result.stdoutTail ?? null })
373
- if (packageStatus !== 'succeeded') return { status: 'incomplete', workspace: runWorkspace, results: completed,
500
+ error: modelMismatch ? '官方 CLI 实际模型与指定模型不一致。'
501
+ : (result.error || result.reason) ? executionDiagnostic(result.error || result.reason) : null,
502
+ outputTail: result.status === 'succeeded' ? null : executionDiagnostic(verifiedDiagnostic(result)) || null })
503
+ if (packageStatus !== 'succeeded') return { status: packageStatus === 'cancelled' ? 'cancelled' : 'incomplete', workspace: runWorkspace, results: completed,
374
504
  ...(isolated ? { baseCommit: isolated.baseCommit } : {}), ...(seeded ? { seeded } : {}) }
375
505
  }
376
506
  if (!isolated) return { status: 'cli-completed', workspace: runWorkspace, results: completed,
@@ -390,14 +520,24 @@ export async function runOfficialTeam({ plan, task, workspace, allowedRoot, mode
390
520
  }
391
521
 
392
522
  /** One editable CLI task also uses the same isolated workspace and merge gate. */
393
- export async function runOfficialTask({ toolId, task, workspace, allowedRoot, modelId, mode = 'read-only', signal, sandbox }) {
394
- const source = await canonicalDirectory(workspace)
523
+ export async function runOfficialTask({ toolId, task, workspace, allowedRoot, modelId, mode = 'read-only', signal, sandbox, runtime }) {
524
+ const source = await approvedSource(workspace, allowedRoot)
395
525
  if (typeof sandbox?.confine !== 'function') return { toolId, status: 'unsupported', reason: '官方 Harness 进程沙箱不可用。' }
396
- const readiness = await officialToolReadiness(toolId, source)
397
- if (!readiness.ready) return { toolId, status: 'unsupported', reason: readiness.reason }
526
+ // The Host supplies no runtime override; offline tests use inert adapters.
527
+ const readiness = await (runtime?.readiness ?? officialToolReadiness)(toolId, source)
528
+ const readyModes = readiness.readyModes ?? readiness.modes ?? []
529
+ if (!readiness.ready || !readyModes.includes(mode)) return { toolId, status: 'unsupported',
530
+ reason: readiness.reason ?? readiness.modeReasons?.[mode] ?? `官方 CLI 尚未就绪,或不支持 ${mode} 模式。` }
398
531
  const isolated = mode === 'workspace-write' ? await isolatedWorktree(source, signal, allowedRoot) : null
399
- const result = await runOfficialTool({ toolId, task, modelId, workspace: isolated?.workspace ?? source,
400
- mode, isolatedRoot: isolated?.isolatedRoot, signal, sandbox })
532
+ let result
533
+ try {
534
+ result = await (runtime?.runTool ?? runOfficialTool)({ toolId, task, modelId, workspace: isolated?.workspace ?? source,
535
+ mode, isolatedRoot: isolated?.isolatedRoot, signal, sandbox })
536
+ if (!result || typeof result.status !== 'string') throw new TypeError('官方 CLI 执行器没有返回有效状态。')
537
+ } catch (error) {
538
+ result = executionException(error, signal)
539
+ }
540
+ result = withFailureDiagnostic(result)
401
541
  if (!isolated || result.status !== 'succeeded') return { ...result, ...(isolated ? { isolatedWorkspace: isolated.workspace, baseCommit: isolated.baseCommit } : {}) }
402
542
  try {
403
543
  const integration = await integrateWorktree(source, isolated.workspace, isolated.baseCommit, signal)