@zhushanwen/zcode-subagent-cli 0.4.4 → 0.6.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.
package/dist/index.cjs CHANGED
@@ -61,16 +61,6 @@ var ZCODE_APPSERVER_STDERR_TAIL_CHARS = 400;
61
61
  var ZCODE_APPSERVER_STDERR_TAIL_BUFFER_CHARS = 2048;
62
62
  var ZCODE_APPSERVER_TURN_READ_TIMEOUT_MS = 5e3;
63
63
  var ZCODE_APPSERVER_TURN_CLOSE_TIMEOUT_MS = 1500;
64
- var ZCODE_TURN_IDLE_TIMEOUT_ENV = "TAIJI_ZCODE_TURN_IDLE_TIMEOUT_MS";
65
- var ZCODE_TURN_MAX_TIMEOUT_ENV = "TAIJI_ZCODE_TURN_MAX_TIMEOUT_MS";
66
- var ZCODE_TURN_IDLE_TIMEOUT_MS = 18e5;
67
- var ZCODE_TURN_MAX_TIMEOUT_MS = 36e5;
68
- function parseZcodeTurnTimeoutEnv(raw) {
69
- if (raw === void 0 || raw === "") return { state: "unset" };
70
- const parsed = Number(raw);
71
- if (!Number.isFinite(parsed)) return { state: "invalid" };
72
- return { state: "valid", ms: parsed };
73
- }
74
64
  var ZCODE_APPSERVER_STOP_TIMEOUT_MS = 3e3;
75
65
  var ZCODE_APPSERVER_ABORT_GRACE_MS = 3e3;
76
66
  var ZCODE_APPSERVER_STOP_TIMEOUT_ENV = "TAIJI_ZCODE_TEST_STOP_TIMEOUT_MS";
@@ -400,6 +390,7 @@ const os = require('node:os');
400
390
 
401
391
  const CLI_PATH = process.env.ZCODE_ENG_CLI_PATH;
402
392
  const V2_PATH = process.env.ZCODE_ENG_V2_CONFIG || path.join(os.homedir(), '.zcode', 'v2', 'config.json');
393
+ const PROVIDER_CONFIG_PATH = process.env.ZCODE_ENG_PROVIDER_CONFIG || path.join(os.homedir(), '.zcode', 'v2', 'provider_config.json');
403
394
  const CONFIG_PATH = path.join(os.homedir(), '.zcode', 'cli', 'config.json');
404
395
 
405
396
  // \u5408\u5E76\u5F62\u6001\uFF1A\u771F\u5B9E cli config \u539F\u6837\uFF08model/plugins/mcp/subagents \u7B49 worker \u7EE7\u627F\u9762\u4E0D\u53D8\uFF09
@@ -408,6 +399,10 @@ const CONFIG_PATH = path.join(os.homedir(), '.zcode', 'cli', 'config.json');
408
399
  // \u914D\u7F6E\uFF082026-08-25 \u4E8B\u6545\uFF1A\u6B8B\u7559\u65E7 key \u538B\u8FC7\u65B0\u51ED\u636E\u81F4 turn 0 401\uFF09\uFF0C\u6545\u4E0D\u53D6 real \u4F18\u5148\u3002
409
400
  // model.main \u7F3A\u5931\u65F6\u53EA\u4ECE v2 \u900F\u4F20\uFF08v2 \u4E5F\u6CA1\u6709\u5219\u4E0D\u4F2A\u9020\u2014\u2014CLI \u7F3A\u7701\u89E3\u6790\u81EA\u4F1A\u843D\u5230\u6CE8\u518C\u8868
410
401
  // \u53EF\u7528\u6A21\u578B\uFF1Bplan \u5BB6\u65CF id \u5DF2\u4E0D\u53EF\u7528\uFF0C\u4E0D\u518D\u515C\u5E95\u4F2A\u9020\uFF09\u3002
402
+ // [D3 \u987A\u5E26\u53D1\u73B0 6] provider_config.json\uFF08\u5F15\u64CE\u4FA7\u6A21\u578B\u6821\u9A8C\u6E90\uFF09\u8865\u6CE8\u5165\uFF1A\u4EC5\u5B58\u5728\u4E8E
403
+ // providerRules \u7684\u4E2A\u4EBA provider\uFF08v2 \u5B57\u5178\u672A\u7269\u5316\uFF09\u4E0D\u5728\u6CE8\u5165\u96C6 \u2192 app-server \u6CE8\u518C\u8868
404
+ // \u7F3A\u6761\u76EE\uFF0C\u663E\u5F0F\u6A21\u578B\u9996\u6D3E\u5373\u62A5\u300CProvider Registry \u4E2D\u4E0D\u5B58\u5728 Model\u300D\u3002\u4E3A\u5E26 apiKey+baseUrl
405
+ // \u4E14 v2/real \u5747\u7F3A\u5931\u7684 rule \u5408\u6210 cli-config \u6761\u76EE\uFF08\u5F62\u72B6\u7167\u6284\u73B0\u7F51\u4E2A\u4EBA\u6761\u76EE\uFF09\u3002
411
406
  // \u8FD4\u56DE null = \u65E0\u53EF\u6CE8\u5165\u51ED\u636E\uFF08v2 \u65E0 provider \u6761\u76EE\uFF09\u2014\u2014\u4E0D patch\uFF0C\u8BA9\u539F\u751F\u62A5\u9519\u900F\u51FA\u3002
412
407
  // \u8BFB\u6E90\u7ECF readOrig \u95F4\u63A5\u53D6\uFF1Apatch \u524D\u9996\u8C03 = fs.readFileSync \u672C\u4F53\uFF1Bpatch \u540E = \u539F\u51FD\u6570
413
408
  // \uFF08\u9632 patch \u540E\u7684\u9012\u5F52\u81EA\u8C03\u7528\uFF09\u3002
@@ -425,6 +420,7 @@ function injectedConfigText() {
425
420
  }
426
421
  return null;
427
422
  }
423
+ const realProviders = real.provider && typeof real.provider === 'object' ? real.provider : {};
428
424
  const v2Providers = v2.provider && typeof v2.provider === 'object' ? v2.provider : {};
429
425
  const hasKey = (entry) => entry && typeof entry === 'object'
430
426
  && entry.options && typeof entry.options.apiKey === 'string' && entry.options.apiKey !== '';
@@ -432,9 +428,60 @@ function injectedConfigText() {
432
428
  for (const [id, entry] of Object.entries(v2Providers)) {
433
429
  if (hasKey(entry)) injectable[id] = entry;
434
430
  }
431
+ // provider_config.json \u8865\u6CE8\u5165\uFF08\u8BFB\u53D6\u5931\u8D25\u9759\u9ED8\u964D\u7EA7\u2014\u2014\u6821\u9A8C\u6E90\u7F3A\u5E2D\u65F6\u5F15\u64CE\u4FA7\u6821\u9A8C\u540C\u6837\u4E0D\u53EF
432
+ // \u7528\uFF0C\u6CE8\u5165\u9762\u7EF4\u6301 v2-only \u73B0\u72B6\uFF1BproviderRules \u7F3A\u5931\u4E0D\u6784\u6210\u51ED\u636E\u9519\u8BEF\uFF09\u3002
433
+ try {
434
+ const pc = JSON.parse(readOrig(PROVIDER_CONFIG_PATH, 'utf8'));
435
+ const pcCfg = pc && typeof pc.config === 'object' && pc.config ? pc.config : {};
436
+ const pcr = typeof pcCfg.providerConfigRules === 'object' && pcCfg.providerConfigRules ? pcCfg.providerConfigRules : {};
437
+ const rules = Array.isArray(pcr.providerRules) ? pcr.providerRules : [];
438
+ const mcr = typeof pcCfg.modelConfigRules === 'object' && pcCfg.modelConfigRules ? pcCfg.modelConfigRules : {};
439
+ const modelRules = Array.isArray(mcr.providerModelRules) ? mcr.providerModelRules : [];
440
+ const modelMeta = {};
441
+ for (const mr of modelRules) {
442
+ if (!mr || typeof mr !== 'object' || typeof mr.providerId !== 'string' || typeof mr.modelId !== 'string') continue;
443
+ (modelMeta[mr.providerId] = modelMeta[mr.providerId] || []).push(mr);
444
+ }
445
+ for (const rule of rules) {
446
+ if (!rule || typeof rule !== 'object') continue;
447
+ const id = typeof rule.providerId === 'string' ? rule.providerId : '';
448
+ const cfg = rule.config && typeof rule.config === 'object' ? rule.config : {};
449
+ const access = cfg.access && typeof cfg.access === 'object' ? cfg.access : {};
450
+ const api = cfg.api && typeof cfg.api === 'object' ? cfg.api : {};
451
+ // v2/real \u5DF2\u6709 \u2192 \u4E0D\u91CD\u590D\u5408\u6210\uFF08v2 \u6761\u76EE\u66F4\u5168\uFF0C\u6574\u6761\u4F18\u5148\u8BED\u4E49\u4E0D\u53D7\u5F71\u54CD\uFF09
452
+ if (!id || injectable[id] || realProviders[id]) continue;
453
+ // hasKey \u7EAA\u5F8B\u540C\u6B3E\uFF1A\u65E0 apiKey \u4E0D\u5408\u6210\uFF1B\u65E0 baseUrl\uFF08endpoint \u7531\u6A21\u677F\u63D0\u4F9B\u7684\u5F62\u6001\uFF09
454
+ // \u4E0D\u5408\u6210\u2014\u2014\u6B8B\u7F3A\u6761\u76EE\u6BD4\u4E0D\u6CE8\u5165\u66F4\u7CDF\uFF0C\u539F\u751F\u300C\u4E0D\u5B58\u5728 Model\u300D\u62A5\u9519\u7167\u65E7\u900F\u51FA
455
+ if (typeof access.apiKey !== 'string' || access.apiKey === '') continue;
456
+ if (typeof api.baseUrl !== 'string' || api.baseUrl === '') continue;
457
+ const order = Array.isArray(cfg.modelOrder) ? cfg.modelOrder
458
+ : (Array.isArray(cfg.personalModelIds) ? cfg.personalModelIds : []);
459
+ const models = {};
460
+ for (const mr of modelMeta[id] || []) {
461
+ const mrCfg = mr.config && typeof mr.config === 'object' ? mr.config : {};
462
+ if (mrCfg.enabled === false) continue; // \u7981\u7528\u6A21\u578B\u4E0D\u8FDB\u6CE8\u518C\u8868\uFF08GUI \u8BED\u4E49\u4E00\u81F4\uFF09
463
+ const props = mrCfg.properties && typeof mrCfg.properties === 'object' ? mrCfg.properties : {};
464
+ models[mr.modelId] = typeof props.contextWindow === 'number'
465
+ ? { limit: { context: props.contextWindow } }
466
+ : {};
467
+ }
468
+ for (const mid of order) {
469
+ if (typeof mid === 'string' && mid !== '' && !(mid in models)) models[mid] = {};
470
+ }
471
+ injectable[id] = {
472
+ name: typeof rule.providerName === 'string' ? rule.providerName : id,
473
+ // provider_config.json \u4E0D\u643A\u5E26\u534F\u8BAE\u5F62\u6001\u2014\u2014standard-personal \u7EC4\u4EE5 openai-compatible
474
+ // \u4E3A\u591A\u6570\u5148\u4F8B\uFF1B\u9519\u914D\u65F6\u8BF7\u6C42\u671F\u534F\u8BAE\u9519\u8BEF\u53EF\u89C1\uFF08\u4E0D\u963B\u585E\u6CE8\u518C\u8868\u88C5\u8F7D\uFF09\uFF0C\u771F\u673A\u6F02\u79FB\u4FE1\u53F7\u660E\u786E
475
+ kind: 'openai-compatible',
476
+ options: { apiKey: access.apiKey, baseURL: api.baseUrl },
477
+ source: 'custom',
478
+ models: models,
479
+ };
480
+ }
481
+ } catch { /* provider_config \u7F3A\u5931/\u635F\u574F \u2192 \u8865\u6CE8\u5165\u9762\u9759\u9ED8\u964D\u7EA7\uFF08v2 \u6CE8\u5165\u4E0D\u53D7\u5F71\u54CD\uFF09 */ }
435
482
  if (Object.keys(injectable).length === 0) return null;
436
483
  const merged = { ...real };
437
- merged.provider = { ...(real.provider && typeof real.provider === 'object' ? real.provider : {}), ...injectable };
484
+ merged.provider = { ...realProviders, ...injectable };
438
485
  if ((!merged.model || typeof merged.model !== 'object' || !merged.model.main)
439
486
  && v2.model && typeof v2.model === 'object' && v2.model.main) {
440
487
  merged.model = { ...(merged.model || {}), main: v2.model.main };
@@ -1396,66 +1443,6 @@ function extractResumeTotalTokens(resumeResult) {
1396
1443
  }
1397
1444
  return acc.has ? acc.total : void 0;
1398
1445
  }
1399
- var TurnTimeoutError = class extends Error {
1400
- kind;
1401
- elapsed;
1402
- lastEventAt;
1403
- thresholdMs;
1404
- constructor(kind, parts) {
1405
- super(
1406
- kind === "idle" ? `zcode turn idle \u5224\u6B7B\uFF1A\u8FDE\u7EED ${parts.thresholdMs}ms \u672A\u89C2\u5BDF\u5230\u672C turn \u4EFB\u4F55\u4E8B\u4EF6\uFF08session/event \u4E0E v4/telemetry/event \u5747\u9759\u9ED8\uFF09\uFF0C\u7EC8\u6001\u672A\u5230\u8FBE\u3002` + (parts.lastEventAt === void 0 ? "\u672C\u8F6E\u81EA send \u8D77\u672A\u89C2\u5BDF\u5230\u4EFB\u4F55\u4E8B\u4EF6\uFF08\u8FDB\u7A0B\u5047\u6B7B/\u534F\u8BAE\u9759\u9ED8\u5F62\u6001\uFF09\u3002" : `\u6700\u540E\u4E8B\u4EF6\u65F6\u523B ${new Date(parts.lastEventAt).toISOString()}\u3002`) + "\u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\uFF1B\u82E5\u6301\u7EED\u51FA\u73B0\uFF0C\u68C0\u67E5 ZCode \u684C\u9762\u7AEF\u6A21\u578B\u8FDE\u901A\u6027\u6216\u6539\u7528 engine: pi\u3002" : `zcode turn \u603B\u4E0A\u754C\u5224\u6B7B\uFF1A${parts.thresholdMs}ms \u5185\u672A\u89C2\u5BDF\u5230\u7EC8\u6001\uFF08turn.terminal \u4E0E\u6536\u5C3E\u5E27\u5747\u672A\u5230\u8FBE\uFF1Bidle \u5224\u5B9A\u672A\u89E6\u53D1\u2014\u2014\u4E8B\u4EF6\u6D41\u4ECD\u6D3B\u8DC3\uFF0Cchatty-wedge \u5F62\u6001\uFF09\u3002` + (parts.lastEventAt === void 0 ? "" : `\u6700\u540E\u4E8B\u4EF6\u65F6\u523B ${new Date(parts.lastEventAt).toISOString()}\u3002`) + `\u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\uFF1B\u82E5\u672C\u4EFB\u52A1\u5C5E\u5408\u6CD5\u8D85\u957F\u4EFB\u52A1\uFF08\u9884\u671F\u8D85\u8FC7 ${parts.thresholdMs}ms \u603B\u4E0A\u754C\uFF09\uFF0C\u91CD\u8DD1\u524D\u8BBE ${ZCODE_TURN_MAX_TIMEOUT_ENV} \u4E3A\u66F4\u5927\u6BEB\u79D2\u503C\uFF0C\u6216\u8BBE 0 \u5173\u95ED\u603B\u4E0A\u754C\uFF08\u5173\u95ED\u540E chatty \u5F62\u6001\u4E0D\u518D\u81EA\u52A8\u56DE\u6536\uFF0C\u9759\u9ED8 wedged \u4ECD\u7531 idle \u5C42\u515C\u5E95\u2014\u2014\u81EA\u884C\u6743\u8861\uFF09\u3002`
1407
- );
1408
- this.name = "TurnTimeoutError";
1409
- this.kind = kind;
1410
- this.elapsed = parts.elapsed;
1411
- this.lastEventAt = parts.lastEventAt;
1412
- this.thresholdMs = parts.thresholdMs;
1413
- }
1414
- };
1415
- function clearTurnTimers(turn) {
1416
- if (turn.idleTimer !== void 0) {
1417
- clearTimeout(turn.idleTimer);
1418
- turn.idleTimer = void 0;
1419
- }
1420
- if (turn.ceilingTimer !== void 0) {
1421
- clearTimeout(turn.ceilingTimer);
1422
- turn.ceilingTimer = void 0;
1423
- }
1424
- }
1425
- function armTurnTimer(onFire, ms) {
1426
- const timer = setTimeout(onFire, ms);
1427
- if (typeof timer.unref === "function") timer.unref();
1428
- return timer;
1429
- }
1430
- function resolveTurnTimerMs(parts) {
1431
- let value;
1432
- let source;
1433
- if (parts.explicit !== void 0) {
1434
- value = parts.explicit;
1435
- source = "\u663E\u5F0F\u4F20\u53C2";
1436
- } else {
1437
- const raw = process.env[parts.envName];
1438
- const parsed = parseZcodeTurnTimeoutEnv(raw);
1439
- if (parsed.state === "valid") {
1440
- value = parsed.ms;
1441
- source = `env ${parts.envName}=${raw}`;
1442
- } else {
1443
- if (parsed.state === "invalid") {
1444
- logger2.warn(
1445
- `[session-channel] ${parts.envName}="${raw}" \u975E\u6CD5\uFF08\u5E94\u4E3A\u6BEB\u79D2\u6570\u5B57\uFF09\u2014\u2014\u56DE\u843D\u9ED8\u8BA4 ${parts.fallbackMs}ms\uFF08${parts.label}\uFF09\u3002\u8BBE\u7F6E\u6B63\u6BEB\u79D2\u503C\u8986\u76D6\uFF0C\u6216 0 \u663E\u5F0F\u5173\u95ED`
1446
- );
1447
- }
1448
- value = parts.fallbackMs;
1449
- source = "\u9ED8\u8BA4\u503C";
1450
- }
1451
- }
1452
- if (value <= 0) {
1453
- logger2.warn(
1454
- `[session-channel] zcode turn ${parts.label}\u5DF2\u5173\u95ED\uFF08${source}\uFF09\u2014\u2014${parts.offConsequence}\u3002\u8BBE\u6B63\u6BEB\u79D2\u503C\uFF08env ${parts.envName} \u6216\u663E\u5F0F\u4F20\u53C2\uFF09\u6062\u590D\u56DE\u6536\u5C42`
1455
- );
1456
- }
1457
- return value;
1458
- }
1459
1446
  var SessionChannel = class {
1460
1447
  conn;
1461
1448
  /** sessionId → 在途 turn(终态落定后仍保留到 runTurn 收尾——收尾帧数据吸收窗口)。 */
@@ -1481,23 +1468,23 @@ var SessionChannel = class {
1481
1468
  ),
1482
1469
  // 连接崩溃收割:进程死亡(崩溃/我方杀链)立即 fail 全部在途 turn——
1483
1470
  // onClose 契约保证触发时连接层在途 request 已全部 reject,此处补齐
1484
- // 「无在途 request 的 turn」的收割(否则挂到 turn idle/总上界预算耗尽)
1471
+ // 「无在途 request 的 turn」的收割(否则在途 turn 失去全部终局来源)
1485
1472
  conn.onClose((reason) => this.failAllTurns(`app-server ${reason}`))
1486
1473
  ];
1487
1474
  }
1488
1475
  /**
1489
1476
  * [P0-1 U5/D7] dispose 前收割 + 退订:「退订 = 不会再有事件」在结构上蕴含「在途
1490
1477
  * turn 不应再等」——close 缺失/被吞没形态(onClose 收割主路径未触发)下,在途
1491
- * turn 于退订前经既有 failAllTurns 收敛为明确失败,不再挂满自身 idle/总上界预算
1492
- * (设计 §3.4 退化路径闭合;race 窗口在引擎 shutdownRuntimeAndDisposeChannel,
1493
- * 量级与 awaitConnFinalized 同源 ZCODE_APPSERVER_HARVEST_GRACE_MS)。幂等:与
1494
- * onClose 收割先到者赢(turn.fail 的 settled 守卫——已落定 turn 不改写),
1495
- * activeTurns 已空则 no-op。不影响连接自身生命周期(R4)。
1478
+ * turn 于退订前经既有 failAllTurns 收敛为明确失败(设计 §3.4 退化路径闭合;
1479
+ * race 窗口在引擎 shutdownRuntimeAndDisposeChannel,量级与 awaitConnFinalized
1480
+ * 同源 ZCODE_APPSERVER_HARVEST_GRACE_MS)。幂等:与 onClose 收割先到者赢
1481
+ * (turn.fail 的 settled 守卫——已落定 turn 不改写),activeTurns 已空则 no-op。
1482
+ * 不影响连接自身生命周期(R4)。
1496
1483
  */
1497
1484
  dispose() {
1498
1485
  this.disposed = true;
1499
1486
  this.failAllTurns(
1500
- "zcode session channel \u5DF2 dispose\uFF1A\u8FDE\u63A5 close \u4E8B\u4EF6\u672A\u5728\u6536\u5272\u7A97\u53E3\u5185\u5230\u8FBE\uFF0C\u5728\u9014 turn \u4E8E\u9000\u8BA2\u524D\u6536\u5272\u4E3A\u660E\u786E\u5931\u8D25\uFF08\u4E0D\u518D\u6302\u5230 turn \u81EA\u8EAB\u9884\u7B97\u8017\u5C3D\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\u3002"
1487
+ "zcode session channel \u5DF2 dispose\uFF1A\u8FDE\u63A5 close \u4E8B\u4EF6\u672A\u5728\u6536\u5272\u7A97\u53E3\u5185\u5230\u8FBE\uFF0C\u5728\u9014 turn \u4E8E\u9000\u8BA2\u524D\u6536\u5272\u4E3A\u660E\u786E\u5931\u8D25\u3002\u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\u3002"
1501
1488
  );
1502
1489
  for (const off of this.offHandlers.splice(0)) off();
1503
1490
  }
@@ -1627,7 +1614,6 @@ var SessionChannel = class {
1627
1614
  ...opened.turn.lastTerminalError !== void 0 ? { lastTerminalError: opened.turn.lastTerminalError } : {}
1628
1615
  };
1629
1616
  } finally {
1630
- opened.stopTimers();
1631
1617
  this.activeTurns.delete(sessionId);
1632
1618
  await this.closeSession(sessionId);
1633
1619
  }
@@ -1648,7 +1634,7 @@ var SessionChannel = class {
1648
1634
  onTextDelta: opts.onTextDelta,
1649
1635
  onThinkingDelta: opts.onThinkingDelta,
1650
1636
  // [PR3] tool 执行期活性回调(SessionTurnCallbacks.onActivity 注释——宿主侧
1651
- // 无进展守护的工具执行期刷新面)
1637
+ // 的工具执行期刷新面)
1652
1638
  onActivity: opts.onActivity
1653
1639
  },
1654
1640
  settled: false,
@@ -1658,83 +1644,25 @@ var SessionChannel = class {
1658
1644
  terminal: void 0,
1659
1645
  lastTerminalStatus: void 0,
1660
1646
  lastTerminalError: void 0,
1661
- idleMs: 0,
1662
- ceilingMs: 0,
1663
- startedAt: Date.now(),
1664
- lastEventAt: void 0,
1665
- idleTimer: void 0,
1666
- ceilingTimer: void 0,
1667
- // 装配占位:下方 timer 创建后重绑(settle/fail 需要清理两 timer)
1647
+ // 装配占位:下方重绑(settle/fail 闭包引用 turn 本体)
1668
1648
  settle: () => {
1669
1649
  },
1670
1650
  fail: () => {
1671
- },
1672
- fireTimeout: () => {
1673
1651
  }
1674
1652
  };
1675
- turn.idleMs = resolveTurnTimerMs({
1676
- explicit: opts.idleTimeoutMs,
1677
- envName: ZCODE_TURN_IDLE_TIMEOUT_ENV,
1678
- fallbackMs: ZCODE_TURN_IDLE_TIMEOUT_MS,
1679
- label: "idle \u4E3B\u5224\u5B9A",
1680
- offConsequence: "\u9759\u9ED8 wedged\uFF08\u65E0\u4E8B\u4EF6\uFF09\u5F62\u6001\u5C06\u65E0\u81EA\u52A8\u56DE\u6536\uFF0C\u4EFB\u52A1\u53EF\u80FD\u6302\u5230\u5BBF\u4E3B\u8FDB\u7A0B\u9000\u51FA"
1681
- });
1682
- turn.ceilingMs = resolveTurnTimerMs({
1683
- explicit: opts.turnTimeoutMs,
1684
- envName: ZCODE_TURN_MAX_TIMEOUT_ENV,
1685
- fallbackMs: ZCODE_TURN_MAX_TIMEOUT_MS,
1686
- label: "\u603B\u4E0A\u754C",
1687
- offConsequence: "chatty-wedge\uFF08\u6709\u4E8B\u4EF6\u65E0\u7EC8\u6001\uFF09\u5F62\u6001\u5C06\u65E0\u81EA\u52A8\u56DE\u6536\uFF0C\u4EC5\u5269 idle \u9759\u9ED8\u5224\u5B9A\u515C\u5E95"
1688
- });
1689
- const fireTimeout = (kind) => {
1690
- if (turn.settled) return;
1691
- turn.settled = true;
1692
- clearTurnTimers(turn);
1693
- this.activeTurns.delete(sessionId);
1694
- rejectDone(
1695
- new TurnTimeoutError(kind, {
1696
- thresholdMs: kind === "idle" ? turn.idleMs : turn.ceilingMs,
1697
- elapsed: Date.now() - turn.startedAt,
1698
- lastEventAt: turn.lastEventAt
1699
- })
1700
- );
1701
- };
1702
- turn.fireTimeout = fireTimeout;
1703
- if (turn.idleMs > 0) {
1704
- turn.idleTimer = armTurnTimer(() => turn.fireTimeout("idle"), turn.idleMs);
1705
- }
1706
- if (turn.ceilingMs > 0) {
1707
- turn.ceilingTimer = armTurnTimer(
1708
- () => turn.fireTimeout("ceiling"),
1709
- turn.ceilingMs
1710
- );
1711
- }
1712
1653
  turn.settle = (t) => {
1713
1654
  if (turn.settled) return;
1714
1655
  turn.settled = true;
1715
1656
  turn.terminal = t;
1716
- clearTurnTimers(turn);
1717
1657
  resolveDone(t);
1718
1658
  };
1719
1659
  turn.fail = (err) => {
1720
1660
  if (turn.settled) return;
1721
1661
  turn.settled = true;
1722
- clearTurnTimers(turn);
1723
1662
  rejectDone(err);
1724
1663
  };
1725
1664
  this.activeTurns.set(sessionId, turn);
1726
- return { turn, done, stopTimers: () => clearTurnTimers(turn) };
1727
- }
1728
- /**
1729
- * idle 主判定的事件刷新(P0-1 D1):本 turn 任何事件到达即重置 idle 倒数——
1730
- * 活跃事件流零误杀的结构保证。总上界不受影响(固定倒数)。已落定 turn 无
1731
- * timer 可刷新(只剩 lastEventAt 记账)。
1732
- */
1733
- refreshIdle(turn) {
1734
- turn.lastEventAt = Date.now();
1735
- if (turn.settled || turn.idleTimer === void 0) return;
1736
- clearTimeout(turn.idleTimer);
1737
- turn.idleTimer = armTurnTimer(() => turn.fireTimeout("idle"), turn.idleMs);
1665
+ return { turn, done };
1738
1666
  }
1739
1667
  lookupTurn(sessionId) {
1740
1668
  if (sessionId !== void 0) return this.activeTurns.get(sessionId);
@@ -1764,7 +1692,6 @@ var SessionChannel = class {
1764
1692
  if (turn === void 0) return;
1765
1693
  const payload = isRecord3(params) && isRecord3(params.payload) ? params.payload : void 0;
1766
1694
  if (payload === void 0) return;
1767
- this.refreshIdle(turn);
1768
1695
  if (this.applyFinalFrame(turn, payload)) return;
1769
1696
  if (!this.applyStreamDelta(turn, payload) && !turn.settled) {
1770
1697
  turn.callbacks.onActivity?.();
@@ -1881,7 +1808,6 @@ var SessionChannel = class {
1881
1808
  applyStreamChunkTelemetry(params) {
1882
1809
  const turn = this.lookupTurn(extractPushSessionId(params));
1883
1810
  if (turn === void 0 || turn.settled) return;
1884
- this.refreshIdle(turn);
1885
1811
  for (const key of ["chunk", "text", "content"]) {
1886
1812
  const v = params[key];
1887
1813
  if (typeof v === "string" && v !== "") {
@@ -2122,7 +2048,10 @@ var ZcodeEngine = class {
2122
2048
  // --mode build/edit/plan/yolo 原生权限档位
2123
2049
  permissionMode: "native",
2124
2050
  // [D3-④] 无 turn_end 语义,轮数上限不可兑现(预检 gate 据此同步拒绝)
2125
- maxTurns: false
2051
+ maxTurns: false,
2052
+ // [subagent-model-switch] zcode 引擎不在模型切换设计范围(协议面引擎中立预留,
2053
+ // 后续另立项接入)——setModel 通路未接通,unsupported 如实声明
2054
+ setModel: "unsupported"
2126
2055
  };
2127
2056
  }
2128
2057
  /** 探针(D7):二进制存在 + 版本解析(zcode 无公开契约,版本漂移的入口信号)。 */
@@ -2237,30 +2166,19 @@ var ZcodeEngine = class {
2237
2166
  * 只在最终轮终态后合成(不变量 2/5)。
2238
2167
  */
2239
2168
  async runAppServerAttemptsWithRetry(task, ctx, modelRef, cwd, basePrompt, schema, usageAcc) {
2240
- const attemptStartedAt = Date.now();
2241
2169
  let final = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt);
2242
2170
  accumulateUsage(usageAcc, final);
2243
2171
  if (final.kind === "run-failed" && final.transient !== void 0 && ctx.signal?.aborted !== true && !this.disposed) {
2244
- const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
2245
- if (budget.state === "depleted") {
2246
- logger4.warn(
2247
- `[zcode-engine] \u672B\u6B21 attempt \u77AC\u65F6\u5931\u8D25\uFF08${final.transient}\uFF09\u2014\u2014\u663E\u5F0F\u603B\u4E0A\u754C\u9884\u7B97\u5269\u4F59\u4E0D\u8DB3 ${ZCODE_TURN_RETRY_MIN_BUDGET_MS}ms\uFF0C\u4E0D\u91CD\u8BD5\u76F4\u63A5\u7EC8\u6001\u5316\uFF08\u9884\u7B97\u7EE7\u627F\uFF1A\u91CD\u8BD5\u4E0D\u91CD\u7F6E\u603B\u9884\u7B97\uFF09`
2248
- );
2249
- } else {
2250
- logger4.warn(
2251
- `[zcode-engine] \u672B\u6B21 attempt \u77AC\u65F6\u5931\u8D25\uFF08${final.transient}\uFF09\u2014\u2014\u6B62\u635F\u94FE\u5DF2\u7EC8\u5C40\uFF0C\u65B0\u4F1A\u8BDD\u81EA\u52A8\u91CD\u8BD5\u4E00\u6B21` + (budget.state === "inherit" ? `\uFF08\u9884\u7B97\u7EE7\u627F\uFF1A\u91CD\u8BD5\u8F6E\u603B\u4E0A\u754C=\u5269\u4F59 ${budget.remainingMs}ms\uFF0C\u4E0D\u91CD\u7F6E\u603B\u9884\u7B97\uFF09` : "\uFF08\u65E0\u663E\u5F0F\u603B\u4E0A\u754C\u9884\u7B97\uFF0C\u91CD\u8BD5\u8F6E\u8D70 env/\u9ED8\u8BA4\u4E0A\u754C\uFF09")
2252
- );
2253
- const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
2254
- // 预算继承传递点(D2 内部传参面):显式预算 → 剩余值;无显式预算 → 缺省
2255
- // (channel 侧走同一 env/默认,与首轮行为一致)
2256
- ...budget.state === "inherit" ? { turnTimeoutMs: budget.remainingMs } : {},
2257
- // 重试事实进文案:「已自动重试一次」句仅对真实发生的重试生效(未重试形态
2258
- // 不含——与行为一致,§5.2 F-1/F-4)
2259
- retried: true
2260
- });
2261
- accumulateUsage(usageAcc, retry);
2262
- final = retry;
2263
- }
2172
+ logger4.warn(
2173
+ `[zcode-engine] \u672B\u6B21 attempt \u77AC\u65F6\u5931\u8D25\uFF08${final.transient}\uFF09\u2014\u2014\u6B62\u635F\u94FE\u5DF2\u7EC8\u5C40\uFF0C\u65B0\u4F1A\u8BDD\u81EA\u52A8\u91CD\u8BD5\u4E00\u6B21`
2174
+ );
2175
+ const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
2176
+ // 重试事实进文案:「已自动重试一次」句仅对真实发生的重试生效(未重试形态
2177
+ // 不含——与行为一致,§5.2 F-1/F-4)
2178
+ retried: true
2179
+ });
2180
+ accumulateUsage(usageAcc, retry);
2181
+ final = retry;
2264
2182
  }
2265
2183
  if (final.kind === "parsed" && final.schemaResult !== void 0 && !final.schemaResult.ok && schema !== void 0) {
2266
2184
  const retryPrompt = appendSchemaRetryDirective(basePrompt, final.schemaResult.error);
@@ -2292,9 +2210,7 @@ var ZcodeEngine = class {
2292
2210
  * 单轮常驻执行:runTurn 组合面 + D3 abort 链 + 事件前移(text_delta 实时流出;
2293
2211
  * 终态数据经 read 兜底收口后才 resolve——不变量 1/2)。
2294
2212
  *
2295
- * @param opts turnTimeoutMs:显式总上界传参面(D2 内部传参点)——U4/D6 预算继承
2296
- * 向重试轮传剩余值;缺省不传(channel 走 env→默认,首轮行为)。
2297
- * retried:瞬时重试轮标记——失败文案补「已自动重试一次」句(F-1/F-4)。
2213
+ * @param opts retried:瞬时重试轮标记——失败文案补「已自动重试一次」句(F-1/F-4)。
2298
2214
  */
2299
2215
  async attemptAppServerTurn(task, ctx, modelRef, cwd, prompt, opts = {}) {
2300
2216
  const rt = this.ensureAppServerRuntime();
@@ -2324,12 +2240,10 @@ var ZcodeEngine = class {
2324
2240
  ctx.onHandleReady?.({
2325
2241
  sessionRef: { dbPath: zcodeSessionDbPath(this.deps.engineDataDir()), sessionId }
2326
2242
  });
2327
- },
2328
- // 显式总上界传参(D2/U4:缺省缺席——channel 侧 resolveTurnTimerMs 走 env→默认)
2329
- ...opts.turnTimeoutMs !== void 0 ? { turnTimeoutMs: opts.turnTimeoutMs } : {}
2243
+ }
2330
2244
  });
2331
2245
  const onAbort = () => {
2332
- void this.appServerAbortChain(rt, turn, () => currentSessionId, sessionCreated, { escalateOn: "turn-settled" });
2246
+ void this.appServerAbortChain(rt, turn, () => currentSessionId, sessionCreated);
2333
2247
  };
2334
2248
  if (ctx.signal !== void 0) {
2335
2249
  if (ctx.signal.aborted) onAbort();
@@ -2355,32 +2269,16 @@ var ZcodeEngine = class {
2355
2269
  /**
2356
2270
  * [attemptAppServerTurn 拆分] 失败分流(catch 半边):
2357
2271
  * - signal 已 aborted → 中止终态收口;
2358
- * - [P0-1 U2] 超时入口(D3 v1.1):turn 已被 channel 判死 reject——升级判据不能
2359
- * 再挂在 turn 落定上(race 恒真,killChain 结构性不可达的 v1 击穿点),改以
2360
- * stop 应答三态裁决。await 链终局(非 fire-and-forget):outcome 止损文案与
2361
- * 重试时序(D6,u-z4)都依赖链终局信号——止损完成前不合成终态。timeout 类
2362
- * (idle/ceiling 都算)是 D6 明文的可重试形态——结构化标记
2363
- * (TurnTimeoutError 类型化判据,不经字符串匹配,D4 同精神);
2364
- * - [P0-1 U4] 连接崩溃收割形态(failAllTurns 的错误,D6 第二可重试形态)判据:
2272
+ * - [P0-1 U4] 连接崩溃收割形态(failAllTurns 的错误,D6 可重试形态)判据:
2365
2273
  * 非 RPC error(服务端无明确应答——有应答即精确错误归类,非瞬时崩溃面)且
2366
2274
  * conn 不存活。时序可靠性:catch 时刻紧随 onClose 收割,连接重建仅由
2367
2275
  * conn.request 惰性触发——本链路中 runTurn finally 的 closeSession 对死连接
2368
- * 短路(channel 侧 !alive 守卫)、stop 只属 abort/超时入口(前者已被
2369
- * signal.aborted 短路、后者走上一分支)——此刻无 request 可重建,判据可靠;
2276
+ * 短路(channel 侧 !alive 守卫)、stop 只属 abort 链(已被 signal.aborted
2277
+ * 短路)——此刻无 request 可重建,判据可靠;
2370
2278
  * - 其余 → 精确错误终态。
2371
2279
  */
2372
2280
  async classifyAppServerTurnFailure(err, ctx, args) {
2373
2281
  if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
2374
- if (err instanceof TurnTimeoutError) {
2375
- const stopPath = await this.appServerAbortChain(
2376
- args.rt,
2377
- args.turn,
2378
- args.currentSessionId,
2379
- args.sessionCreated,
2380
- { escalateOn: "stop-outcome" }
2381
- );
2382
- return timeoutAppServerAttempt(err, args.currentSessionId(), stopPath, { retried: args.retried });
2383
- }
2384
2282
  if (!isAppServerRpcError(err) && !args.rt.conn.alive) {
2385
2283
  return failedAppServerAttempt(err, args.currentSessionId(), { retried: args.retried, transient: "conn-closed" });
2386
2284
  }
@@ -2391,8 +2289,8 @@ var ZcodeEngine = class {
2391
2289
  * shutdown resolve 于 `exit` 事件,而 finalize 挂 `close`(stdio 排空)——两者之间的
2392
2290
  * 事件窗口内 conn.child 仍非 null,紧接的下一任务 request 会复用垂死进程(写入成功
2393
2291
  * 但必败,走崩溃路径)而非触发重建。与 shutdownRuntimeAndDisposeChannel 的
2394
- * HARVEST_GRACE 同款 race 形态(close 永不到达不挂死)。超时入口的 await 链终局
2395
- * 语义(D6 重试时序依据)因此是「进程收割确认完成」而非「SIGTERM 已发出」。
2292
+ * HARVEST_GRACE 同款 race 形态(close 永不到达不挂死)。abort 链的 await 终局
2293
+ * 语义因此是「进程收割确认完成」而非「SIGTERM 已发出」。
2396
2294
  */
2397
2295
  async awaitConnFinalized(rt) {
2398
2296
  if (!rt.conn.alive) return;
@@ -2408,25 +2306,14 @@ var ZcodeEngine = class {
2408
2306
  });
2409
2307
  }
2410
2308
  /**
2411
- * D3 abort 链执行体(双入口分岔,P0-1 U2 参数化,D3 v1.1):
2412
- * - **用户取消入口**(`escalateOn: "turn-settled"`,现状语义零改动;fire-and-forget——
2413
- * 与 turn promise 并行推进):stop 帧(超时 ZCODE_APPSERVER_STOP_TIMEOUT_MS)→
2414
- * grace 窗口内 turn 落定即止(不杀共享进程)→ 超窗 killChain(conn.shutdown 全序:
2415
- * SIGTERM→grace→SIGKILL)。turn 的最终落定由 attempt 主路径 await 收口,本链不
2416
- * 直接产出终态。abort 与 create 竞态(signal 先到、session 未建立):等会话建立
2417
- * (带上限)再发 stop——否则 stop 永远发不出,直接连坐杀共享进程。
2418
- * - **超时入口**(`escalateOn: "stop-outcome"`,channel 判死后由 catch await 链终局):
2419
- * turn 已 reject,对它 race 恒真不可用(v1 击穿点)——升级判据改挂在 **stop 应答
2420
- * 三态**:①成功应答 → 服务端接受停 turn,止损确认,链终止;②协议性 error 应答
2421
- * (有 error 帧即控制面活的证据,多因 runTurn finally 的 closeSession 先行关会话,
2422
- * 健康形态竞态)→ 链终止**不升级**(止损由 close 回收 + 服务端自治承担;把
2423
- * 「stop 报错」一律升级会误杀健康共享进程并连坐并发任务);③超时/写入失败/进程
2424
- * 死等连接级失败(控制面死)→ killChain 升级。判据实现依据:error 应答帧 reject
2425
- * 携带 number code(isAppServerRpcError);连接级失败是无 code 的新 Error
2426
- * (connection.request 三态 reject 形态)。返回值即止损路径(超时入口的 outcome
2427
- * 文案素材——D3 强制可观测面)。
2309
+ * D3 abort 链执行体(用户取消入口,fire-and-forget——与 turn promise 并行推进):
2310
+ * stop 帧(超时 ZCODE_APPSERVER_STOP_TIMEOUT_MS)→ grace 窗口内 turn 落定即止
2311
+ * (不杀共享进程)→ 超窗 killChain(conn.shutdown 全序:SIGTERM→grace→SIGKILL)。
2312
+ * turn 的最终落定由 attempt 主路径 await 收口,本链不直接产出终态。abort 与 create
2313
+ * 竞态(signal 先到、session 未建立):等会话建立(带上限)再发 stop——否则 stop
2314
+ * 永远发不出,直接连坐杀共享进程。
2428
2315
  */
2429
- async appServerAbortChain(rt, turn, getSessionId, sessionCreated, entry) {
2316
+ async appServerAbortChain(rt, turn, getSessionId, sessionCreated) {
2430
2317
  const graceRaceThenKill = async () => {
2431
2318
  const settled = await Promise.race([
2432
2319
  turn.then(
@@ -2435,49 +2322,31 @@ var ZcodeEngine = class {
2435
2322
  ),
2436
2323
  delayResolved(abortGraceMs(), false)
2437
2324
  ]);
2438
- if (settled) return "settled-in-grace";
2325
+ if (settled) return;
2439
2326
  logger4.warn(
2440
2327
  `[zcode-engine] abort grace \u7A97\u53E3\u5185\u672A\u89C1\u7EC8\u6001\u2014\u2014killChain \u6536\u5272\u5171\u4EAB\u8FDB\u7A0B\uFF08\u63A5\u53D7\u8FDE\u5750\uFF0C\u5728\u9014\u4EFB\u52A1\u8D70\u5D29\u6E83\u8DEF\u5F84\uFF09`
2441
2328
  );
2442
2329
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
2443
2330
  await this.awaitConnFinalized(rt);
2444
- return "escalated-kill";
2445
2331
  };
2446
2332
  let sessionId = getSessionId();
2447
2333
  if (sessionId === void 0) {
2448
2334
  await Promise.race([sessionCreated, delayResolved(stopTimeoutMs(), void 0)]);
2449
2335
  sessionId = getSessionId();
2450
2336
  if (sessionId === void 0) {
2451
- if (entry.escalateOn === "stop-outcome") return "no-session";
2452
2337
  return graceRaceThenKill();
2453
2338
  }
2454
2339
  }
2455
2340
  if (!rt.conn.alive) {
2456
- if (entry.escalateOn === "stop-outcome") return "stop-unreachable-killed";
2457
2341
  return graceRaceThenKill();
2458
2342
  }
2459
2343
  try {
2460
2344
  await rt.conn.request("session/stop", { sessionId }, { timeoutMs: stopTimeoutMs() });
2461
2345
  } catch (err) {
2462
- if (entry.escalateOn === "stop-outcome") {
2463
- if (isAppServerRpcError(err)) {
2464
- logger4.debug(
2465
- `[zcode-engine] session/stop \u62A5\u534F\u8BAE\u6027 error\uFF08${errMessage3(err)}\uFF09\u2014\u2014\u4F1A\u8BDD\u5DF2\u56DE\u6536\uFF0C\u63A7\u5236\u9762\u5B58\u6D3B\uFF0C\u4E0D\u5347\u7EA7\u6740\u94FE`
2466
- );
2467
- return "stop-rejected";
2468
- }
2469
- logger4.warn(
2470
- `[zcode-engine] session/stop \u65E0\u5E94\u7B54\uFF08${errMessage3(err)}\uFF09\u2014\u2014\u5347\u7EA7 killChain \u6536\u5272\u5171\u4EAB\u8FDB\u7A0B\uFF08\u8D85\u65F6\u5165\u53E3\uFF0C\u63A5\u53D7\u8FDE\u5750\uFF09`
2471
- );
2472
- await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
2473
- await this.awaitConnFinalized(rt);
2474
- return "stop-unreachable-killed";
2475
- }
2476
2346
  logger4.debug(
2477
2347
  `[zcode-engine] session/stop \u5931\u8D25\uFF08${errMessage3(err)}\uFF09\u2014\u2014grace \u540E\u8D70 killChain \u515C\u5E95`
2478
2348
  );
2479
2349
  }
2480
- if (entry.escalateOn === "stop-outcome") return "stop-acked";
2481
2350
  return graceRaceThenKill();
2482
2351
  }
2483
2352
  // ── 常驻运行时管理(D1/D6)──────────────────────
@@ -2499,6 +2368,7 @@ var ZcodeEngine = class {
2499
2368
  const env = buildAppServerEnv(this.deps.processEnv ?? process.env);
2500
2369
  env.ZCODE_ENG_CLI_PATH = cliPath;
2501
2370
  env.ZCODE_ENG_V2_CONFIG = this.deps.sources?.v2ConfigPath ?? defaultV2ConfigPath();
2371
+ env.ZCODE_ENG_PROVIDER_CONFIG = this.deps.sources?.personalProviderConfigPath ?? defaultPersonalProviderConfigPath();
2502
2372
  const sessionDbPath = zcodeSessionDbPath(engineDataDir);
2503
2373
  env.ZCODE_SESSION_DB_PATH = sessionDbPath;
2504
2374
  delete env.ZCODE_SESSION_DB;
@@ -2531,15 +2401,15 @@ var ZcodeEngine = class {
2531
2401
  * [R5 修复 R4 既有竞态] shutdown → 等崩溃收割实际发生 → channel 退订。killChain 在
2532
2402
  * `exit` 事件 resolve,而连接 finalize(onClose → channel 的 failAllTurns)挂
2533
2403
  * `close` 事件——两者之间有一个事件循环窗口:shutdown resolve 后立即退订,在途
2534
- * turn 会错过收割、挂到 turnTimeoutMs(300s)。退订前等 onClose 触发(本方法先于
2404
+ * turn 会错过收割而失去终局来源。退订前等 onClose 触发(本方法先于
2535
2405
  * shutdown 订阅;channel 的订阅在构造期更早——其 failAllTurns 先于本 promise
2536
2406
  * resolve 执行);ZCODE_APPSERVER_HARVEST_GRACE_MS 兜底防 `close` 永不到达时挂死。
2537
2407
  * [P0-1 U5/D7] grace race 输掉(close 迟到/永不到达——stdio 被孙进程持有排空不
2538
2408
  * 尽等病态形态)时,channel.dispose() 内置的 dispose 收割(failAllTurns 先于退订,
2539
- * SessionChannel.dispose)兜底在途 turn——退化终点从「挂满 turn 自身 idle/总上界
2540
- * 预算」收敛为「grace 窗口内明确失败」(设计 §3.4 退化路径闭合);race 窗口与
2541
- * awaitConnFinalized 同源同量级(ZCODE_APPSERVER_HARVEST_GRACE_MS)。正常 close
2542
- * 先到时 onClose 收割先行,dispose 收割幂等 no-op(零回归)。
2409
+ * SessionChannel.dispose)兜底在途 turn——在途 turn 收敛为「grace 窗口内明确失败」
2410
+ * (设计 §3.4 退化路径闭合);race 窗口与 awaitConnFinalized 同源同量级
2411
+ * (ZCODE_APPSERVER_HARVEST_GRACE_MS)。正常 close 先到时 onClose 收割先行,
2412
+ * dispose 收割幂等 no-op(零回归)。
2543
2413
  */
2544
2414
  async shutdownRuntimeAndDisposeChannel(rt) {
2545
2415
  const harvested = new Promise((resolve2) => {
@@ -2844,16 +2714,6 @@ ${omissionNote}${kept.join("\n")}
2844
2714
  function buildResumeUnavailableNoticeSegment() {
2845
2715
  return "[\u4F1A\u8BDD\u5EF6\u7EED\u63D0\u793A] \u672C\u6D88\u606F\u662F\u540C\u4E00\u4EFB\u52A1\u7684\u5EF6\u7EED\u4F1A\u8BDD\uFF0C\u4F46\u4E0A\u4E00\u4F1A\u8BDD\u7684\u5386\u53F2\u8BB0\u5F55\u4E0D\u53EF\u6062\u590D\uFF08\u539F\u59CB\u4F1A\u8BDD\u5DF2\u5931\u6548\uFF09\u3002\n\u6CA1\u6709\u66F4\u65E9\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u53EF\u5F15\u7528\u2014\u2014\u8BF7\u57FA\u4E8E\u4E0B\u65B9\u6700\u65B0\u6D88\u606F\u72EC\u7ACB\u5224\u65AD\u5E76\u7EE7\u7EED\u63A8\u8FDB\u4EFB\u52A1\u3002\n\n";
2846
2716
  }
2847
- var ZCODE_TURN_RETRY_MIN_BUDGET_MS = 3e5;
2848
- function resolveTransientRetryBudget(totalBudgetMs, consumedMs) {
2849
- if (totalBudgetMs === void 0) return { state: "unbounded" };
2850
- const remainingMs = totalBudgetMs - consumedMs;
2851
- return remainingMs >= ZCODE_TURN_RETRY_MIN_BUDGET_MS ? { state: "inherit", remainingMs } : { state: "depleted" };
2852
- }
2853
- function explicitTurnBudgetMs() {
2854
- const parsed = parseZcodeTurnTimeoutEnv(process.env[ZCODE_TURN_MAX_TIMEOUT_ENV]);
2855
- return parsed.state === "valid" && parsed.ms > 0 ? parsed.ms : void 0;
2856
- }
2857
2717
  function isPlainObject(v) {
2858
2718
  return typeof v === "object" && v !== null && !Array.isArray(v);
2859
2719
  }
@@ -2999,8 +2859,7 @@ function failedAppServerAttempt(err, currentSessionId, opts = {}) {
2999
2859
  kind: "run-failed",
3000
2860
  output: syntheticAppServerOutput(null),
3001
2861
  message: buildAppServerRunFailedMessage(err, currentSessionId, opts.retried === true),
3002
- // [P0-1 U4/D6] 瞬时失败形态标记(重试判定判据——timeout 形态走
3003
- // timeoutAppServerAttempt,此处只承载 conn-closed)
2862
+ // [P0-1 U4/D6] 瞬时失败形态标记(重试判定判据,conn-closed 连接崩溃形态)
3004
2863
  ...opts.transient !== void 0 ? { transient: opts.transient } : {},
3005
2864
  // 错误规格表 -32004 行「按任务失败上报(含会话 id)」:create 成功后运行中失败
3006
2865
  // (-32004/-32010 等)时留痕会话 id——经 applyRunFailedOutcome 落 outcome.sessionId
@@ -3008,38 +2867,6 @@ function failedAppServerAttempt(err, currentSessionId, opts = {}) {
3008
2867
  ...currentSessionId !== void 0 ? { sessionId: currentSessionId } : {}
3009
2868
  };
3010
2869
  }
3011
- function timeoutAppServerAttempt(err, currentSessionId, stopPath, opts = {}) {
3012
- return {
3013
- kind: "run-failed",
3014
- output: syntheticAppServerOutput(null),
3015
- message: buildAppServerTimeoutMessage(err, currentSessionId, stopPath, opts.retried === true),
3016
- transient: "timeout",
3017
- ...currentSessionId !== void 0 ? { sessionId: currentSessionId } : {}
3018
- };
3019
- }
3020
- function stopPathText(stopPath) {
3021
- switch (stopPath) {
3022
- case "stop-acked":
3023
- return "session/stop \u5DF2\u9001\u8FBE\uFF08\u670D\u52A1\u7AEF\u63A5\u53D7\u505C turn\uFF09";
3024
- case "stop-rejected":
3025
- return "session/stop \u62A5\u534F\u8BAE\u6027 error\uFF08\u4F1A\u8BDD\u5DF2\u88AB\u56DE\u6536\uFF0C\u63A7\u5236\u9762\u5B58\u6D3B\u2014\u2014\u6B62\u635F\u7531\u4F1A\u8BDD\u56DE\u6536\u627F\u62C5\uFF0C\u4E0D\u5347\u7EA7\u6740\u94FE\uFF09";
3026
- case "stop-unreachable-killed":
3027
- return `session/stop \u65E0\u5E94\u7B54\u5DF2\u5347\u7EA7\u6740\u94FE\uFF08SIGTERM\u2192${ZCODE_KILL_GRACE_MS}ms\u2192SIGKILL \u6536\u5272\u5171\u4EAB\u8FDB\u7A0B\uFF09`;
3028
- case "no-session":
3029
- return "\u4F1A\u8BDD\u672A\u5EFA\u7ACB\uFF08\u4EFB\u52A1\u672A\u5F00\u59CB\u6267\u884C\uFF0C\u65E0\u5728\u9014\u6D88\u8017\uFF09";
3030
- default:
3031
- return "grace \u7A97\u53E3\u5185\u7EC8\u6001\u843D\u5B9A\u6216\u5DF2\u8D70\u6740\u94FE";
3032
- }
3033
- }
3034
- function buildAppServerTimeoutMessage(err, sessionId, stopPath, retried = false) {
3035
- const sid = sessionId !== void 0 ? `\uFF08\u4F1A\u8BDD ${sessionId}\uFF09` : "";
3036
- const lastEventText = err.lastEventAt !== void 0 ? `\uFF0C\u6700\u540E\u4E8B\u4EF6 ${new Date(err.lastEventAt).toISOString()}` : "\uFF0C\u6574\u8F6E\u672A\u89C2\u5BDF\u5230\u4EFB\u4F55\u4E8B\u4EF6\uFF08\u8FDB\u7A0B\u5047\u6B7B/\u534F\u8BAE\u9759\u9ED8\u5F62\u6001\uFF09";
3037
- const head = err.kind === "idle" ? `engine_timeout: zcode turn \u8FDE\u7EED\u9759\u9ED8 ${err.thresholdMs}ms\uFF08idle \u5224\u5B9A${lastEventText}\uFF0C\u603B\u8017\u65F6 ${err.elapsed}ms\uFF09${sid}\u3002` : `engine_timeout: zcode turn \u603B\u4E0A\u754C ${err.thresholdMs}ms \u5185\u672A\u89C2\u5BDF\u5230\u7EC8\u6001\uFF08chatty-wedge \u5224\u5B9A\u2014\u2014\u4E8B\u4EF6\u6D41\u4ECD\u6D3B\u8DC3\u800C\u7EC8\u6001\u672A\u5230\u8FBE\uFF0C\u603B\u8017\u65F6 ${err.elapsed}ms\uFF09${sid}\u3002`;
3038
- const selfHelp = err.kind === "ceiling" ? `\u82E5\u672C\u4EFB\u52A1\u5C5E\u5408\u6CD5\u8D85\u957F\u4EFB\u52A1\uFF08\u9884\u671F\u8D85\u8FC7 ${err.thresholdMs}ms\uFF09\uFF0C\u91CD\u8DD1\u524D\u8BBE ${ZCODE_TURN_MAX_TIMEOUT_ENV} \u4E3A\u66F4\u5927\u6BEB\u79D2\u503C\u6216 0 \u5173\u95ED\u603B\u4E0A\u754C\uFF08\u5173\u95ED\u540E chatty \u5F62\u6001\u4E0D\u518D\u81EA\u52A8\u56DE\u6536\uFF0C\u9759\u9ED8 wedged \u4ECD\u7531 idle \u5C42\u515C\u5E95\u2014\u2014\u81EA\u884C\u6743\u8861\uFF09\u3002` : "";
3039
- const rerunGuide = retried ? `\u{1F449} \u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\uFF08\u77AC\u65F6\u6545\u969C\u5DF2\u81EA\u52A8\u91CD\u8BD5\u4E00\u6B21\u4ECD\u8D85\u65F6\uFF1B\u91CD\u8BD5\u5728\u6B62\u635F\u94FE\u7EC8\u5C40\u540E\u542F\u52A8\uFF0C\u65E0\u65B0\u65E7\u4EFB\u52A1\u53CC\u8DD1\u7A97\uFF09\uFF1B` : `\u{1F449} \u6062\u590D\u6307\u5F15\uFF1A\u76F4\u63A5\u91CD\u8DD1\u672C\u4EFB\u52A1\uFF1B`;
3040
- return `${head}\u6B62\u635F\u8DEF\u5F84\uFF1A${stopPathText(stopPath)}\u3002
3041
- ${rerunGuide}${selfHelp}\u82E5\u6301\u7EED\u51FA\u73B0\uFF0C\u68C0\u67E5 ZCode \u684C\u9762\u7AEF\u6A21\u578B\u8FDE\u901A\u6027\u6216\u6539\u7528 engine: pi\u3002`;
3042
- }
3043
2870
  function isHostTimeoutAbort(ctx) {
3044
2871
  return ctx.signal?.aborted === true && ctx.signal.reason === import_subagent_engine_sdk11.HOST_TIMEOUT_ABORT_REASON;
3045
2872
  }