@zhushanwen/zcode-subagent-cli 0.2.0 → 0.3.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.
@@ -22,12 +22,17 @@ function parseZcodeTurnTimeoutEnv(raw) {
22
22
  if (!Number.isFinite(parsed)) return { state: "invalid" };
23
23
  return { state: "valid", ms: parsed };
24
24
  }
25
- var ZCODE_SHARED_POOL_KEY = "shared";
26
25
  var ZCODE_APPSERVER_STOP_TIMEOUT_MS = 3e3;
27
26
  var ZCODE_APPSERVER_ABORT_GRACE_MS = 3e3;
28
27
  var ZCODE_APPSERVER_ERR_MODEL_CONFIG_MISSING = -32603;
29
28
  var ZCODE_APPSERVER_ERR_BUSY_SESSION = -32010;
30
29
  var ZCODE_APPSERVER_HARVEST_GRACE_MS = 1e3;
30
+ function isFailedTerminalStatus(status) {
31
+ return status === "failed" || status === "error";
32
+ }
33
+ var ZCODE_RESUME_HISTORY_TOKEN_BUDGET = 24e3;
34
+ var ZCODE_RESUME_CHARS_PER_TOKEN = 4;
35
+ var ZCODE_SESSION_SWEEP_DEFER_MS = 50;
31
36
 
32
37
  // src/db-path.ts
33
38
  import * as os from "os";
@@ -275,9 +280,9 @@ async function readZcodeSessionView(dbPath, sessionId) {
275
280
  }
276
281
 
277
282
  // src/zcode-engine.ts
278
- import * as fs5 from "fs";
283
+ import * as fs6 from "fs";
279
284
  import * as path4 from "path";
280
- import { getLogger as getLogger3 } from "@zhushanwen/subagent-engine-sdk";
285
+ import { getLogger as getLogger4 } from "@zhushanwen/subagent-engine-sdk";
281
286
  import {
282
287
  buildEngineChildEnv as buildEngineChildEnv2,
283
288
  buildSchemaEmulationSegment,
@@ -286,6 +291,7 @@ import {
286
291
  synthesizeTimeoutOutcome,
287
292
  engineTimeoutDetail,
288
293
  resolvePoolDir,
294
+ SHARED_POOL_KEY,
289
295
  spawnEngineChild as spawnEngineChild2
290
296
  } from "@zhushanwen/subagent-engine-sdk";
291
297
 
@@ -776,7 +782,7 @@ var AppServerConnection = class {
776
782
  }
777
783
  const timeoutMs = opts?.timeoutMs ?? this.requestTimeoutMs;
778
784
  const id = ++this.reqSeq;
779
- return new Promise((resolve, reject) => {
785
+ return new Promise((resolve2, reject) => {
780
786
  const timer = setTimeout(() => {
781
787
  this.pending.delete(id);
782
788
  reject(new Error(`\u8BF7\u6C42 ${method} \u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`));
@@ -784,7 +790,7 @@ var AppServerConnection = class {
784
790
  this.pending.set(id, {
785
791
  // 泛型 T 是调用方对线上 unknown 结果的视图声明,连接层不做运行时校验
786
792
  // (协议层保持透明,结构校验归会话层/消费方)。
787
- resolve,
793
+ resolve: resolve2,
788
794
  reject,
789
795
  timer,
790
796
  method
@@ -1135,6 +1141,46 @@ function extractReadUsage(readResult) {
1135
1141
  }
1136
1142
  return isRecord3(readResult.usage) ? readResult.usage : void 0;
1137
1143
  }
1144
+ function extractResumeHistory(resumeResult) {
1145
+ if (!isRecord3(resumeResult) || !Array.isArray(resumeResult.messages)) return [];
1146
+ const turns = [];
1147
+ for (const m of resumeResult.messages) {
1148
+ if (!isRecord3(m)) continue;
1149
+ const role = messageRole(m);
1150
+ if (role !== "user" && role !== "assistant") continue;
1151
+ const text = partsToText(m.parts) ?? contentToText(m.content);
1152
+ if (text === void 0 || text === "") continue;
1153
+ turns.push({ role, text });
1154
+ }
1155
+ return turns;
1156
+ }
1157
+ function accumulateTokenFields(source, keys, acc) {
1158
+ for (const key of keys) {
1159
+ const raw = source[key];
1160
+ const v = typeof raw === "number" ? raw : Number(raw);
1161
+ if (Number.isFinite(v) && v > 0) {
1162
+ acc.total += v;
1163
+ acc.has = true;
1164
+ }
1165
+ }
1166
+ }
1167
+ function accumulateStepFinishTokens(parts, acc) {
1168
+ for (const p of parts) {
1169
+ if (!isRecord3(p) || p.type !== "step-finish" || !isRecord3(p.tokens)) continue;
1170
+ const t = p.tokens;
1171
+ accumulateTokenFields(t, ["input", "output"], acc);
1172
+ if (isRecord3(t.cache)) accumulateTokenFields(t.cache, ["read", "write"], acc);
1173
+ }
1174
+ }
1175
+ function extractResumeTotalTokens(resumeResult) {
1176
+ if (!isRecord3(resumeResult) || !Array.isArray(resumeResult.messages)) return void 0;
1177
+ const acc = { total: 0, has: false };
1178
+ for (const m of resumeResult.messages) {
1179
+ if (!isRecord3(m) || messageRole(m) !== "assistant" || !Array.isArray(m.parts)) continue;
1180
+ accumulateStepFinishTokens(m.parts, acc);
1181
+ }
1182
+ return acc.has ? acc.total : void 0;
1183
+ }
1138
1184
  var TurnTimeoutError = class extends Error {
1139
1185
  kind;
1140
1186
  elapsed;
@@ -1295,6 +1341,17 @@ var SessionChannel = class {
1295
1341
  );
1296
1342
  }
1297
1343
  }
1344
+ /**
1345
+ * session/resume 读通道([U6 / §3.2.6 要点 3,P-1 探针实证]):应答自带完整
1346
+ * 双向 messages 历史(user/assistant 全量、tokens、parts)——zcode 续聊选型的
1347
+ * 历史来源(读通道成立;原地 resume 续写被 -32031 卡死,写通道不可用)。错误
1348
+ * 原样上抛(调用方决定降级形态——引擎侧 warn 后无历史前缀开新会话)。
1349
+ * 经 conn.request 惰性启动连接(resume 是 run 的**首个**请求——调用时点连接
1350
+ * 可能尚未启动,与 readBestEffort 的「已死跳过」判据不同场景,不做 alive 守卫)。
1351
+ */
1352
+ async resumeSession(sessionId) {
1353
+ return this.conn.request("session/resume", { sessionId }, { timeoutMs: ZCODE_APPSERVER_TURN_READ_TIMEOUT_MS });
1354
+ }
1298
1355
  /**
1299
1356
  * session/close(A.2 ⑦,D4 用后即毁——回收引擎驻留内存,SQLite 持久化保留)。
1300
1357
  * best-effort:失败只 warn 不抛(会话已终态,close 失败只泄漏一个驻留会话,
@@ -1371,7 +1428,13 @@ var SessionChannel = class {
1371
1428
  });
1372
1429
  const turn = {
1373
1430
  sessionId,
1374
- callbacks: { onTextDelta: opts.onTextDelta, onThinkingDelta: opts.onThinkingDelta },
1431
+ callbacks: {
1432
+ onTextDelta: opts.onTextDelta,
1433
+ onThinkingDelta: opts.onThinkingDelta,
1434
+ // [PR3] tool 执行期活性回调(SessionTurnCallbacks.onActivity 注释——宿主侧
1435
+ // 无进展守护的工具执行期刷新面)
1436
+ onActivity: opts.onActivity
1437
+ },
1375
1438
  settled: false,
1376
1439
  deltas: [],
1377
1440
  finalText: void 0,
@@ -1487,7 +1550,9 @@ var SessionChannel = class {
1487
1550
  if (payload === void 0) return;
1488
1551
  this.refreshIdle(turn);
1489
1552
  if (this.applyFinalFrame(turn, payload)) return;
1490
- this.applyStreamDelta(turn, payload);
1553
+ if (!this.applyStreamDelta(turn, payload) && !turn.settled) {
1554
+ turn.callbacks.onActivity?.();
1555
+ }
1491
1556
  }
1492
1557
  /**
1493
1558
  * 收尾帧判定与吸收(A.2 ⑤ 两种形态:payload.response / stopReason+content 变体)。
@@ -1511,9 +1576,12 @@ var SessionChannel = class {
1511
1576
  /** 增量 delta 分发:非空 delta 入账 + 回调;终态后迟到丢弃(不变量 2)。
1512
1577
  * kind 区分两通道(F2):reasoning_delta 只走 thinking 回调(不入 deltas 账本
1513
1578
  * ——deltas 是 response 兜底聚合,推理文本不是答案正文);text_delta / kind
1514
- * 缺席走 text 路径。 */
1579
+ * 缺席走 text 路径。
1580
+ * 返回值([PR3]):帧被 delta 路径消费(thinking 或 text,回调若注册则已触发)
1581
+ * → true;无 delta / 空 delta / 终态后迟到丢弃 → false(handleSessionEvent 据
1582
+ * 此分流 onActivity——delta 帧已是宿主可见进展,不双发活性信号)。 */
1515
1583
  applyStreamDelta(turn, payload) {
1516
- if (typeof payload.delta !== "string" || payload.delta === "") return;
1584
+ if (typeof payload.delta !== "string" || payload.delta === "") return false;
1517
1585
  if (turn.settled) {
1518
1586
  logger2.warn(
1519
1587
  `\u7EC8\u6001\u540E\u8FDF\u5230\u7684 delta \u4E22\u5F03\uFF08\u4F1A\u8BDD ${turn.sessionId}\uFF0C\u4E0D\u53D8\u91CF 2\uFF1Aresolve \u540E\u4E0D\u518D\u53D1\u4E8B\u4EF6\uFF09: ${payload.delta.slice(
@@ -1521,14 +1589,15 @@ var SessionChannel = class {
1521
1589
  DELTA_LOG_CHARS
1522
1590
  )}`
1523
1591
  );
1524
- return;
1592
+ return false;
1525
1593
  }
1526
1594
  if (payload.kind === "reasoning_delta") {
1527
1595
  turn.callbacks.onThinkingDelta?.(payload.delta);
1528
- return;
1596
+ return true;
1529
1597
  }
1530
1598
  turn.deltas.push(payload.delta);
1531
1599
  turn.callbacks.onTextDelta?.(payload.delta);
1600
+ return true;
1532
1601
  }
1533
1602
  handleTelemetry(params) {
1534
1603
  if (!isRecord3(params)) return;
@@ -1548,13 +1617,38 @@ var SessionChannel = class {
1548
1617
  turn.lastTerminalStatus = status;
1549
1618
  this.recordTerminalError(turn, params);
1550
1619
  if (turn.settled) {
1551
- logger2.warn(
1552
- `\u6743\u5A01\u7EC8\u6001\u665A\u4E8E\u843D\u5B9A\u7ED3\u679C\u5230\u8FBE\uFF08\u4F1A\u8BDD ${turn.sessionId}\uFF0C\u5DF2\u843D\u5B9A source=${turn.terminal?.source}\uFF09\uFF1Aturn.terminal status="${status}" \u4EC5\u8BB0\u5F55\u4E0D\u6539\u5199\uFF08P0-1 D5\u2460\uFF09`
1553
- );
1620
+ this.logLateTerminal(turn, status);
1554
1621
  return;
1555
1622
  }
1556
1623
  turn.settle({ status, source: "turn.terminal" });
1557
1624
  }
1625
+ /**
1626
+ * 已落定 turn 的迟到权威终态分级日志(P-Z2 后续:常态迟到降噪)。真机实证该
1627
+ * 形态的唯一可达路径是 success 终态常态迟到(final-frame 先落定 + 权威
1628
+ * turn.terminal 迟到)——即每个成功任务必经;原无条件 warn 经 SDK cli-entry
1629
+ * 的 stderr 兜底 → runtime [rpc:stderr] 全量 console.error 链路,每个成功任务
1630
+ * 产一条零异常区分度的 ERROR 级日志。按权威 status 分级(落定语义不动——
1631
+ * lastTerminalStatus/lastTerminalError 的入账在上游无条件路径完成):
1632
+ * - "success":常态迟到,零日志;
1633
+ * - "interrupted":用户中断形态,debug(stderr 兜底对 debug no-op——对齐
1634
+ * SDK logger CONSOLE_SINK 语义,host/log 反向请求侧仍可观测);
1635
+ * - 失败类(isFailedTerminalStatus,与引擎失败分流同口径)与 unknown/未识别
1636
+ * status:保守 warn(原样 status 已写入文案)——假成功识破的防御面,理论
1637
+ * 不可达但保留,宁可误报不静默。
1638
+ */
1639
+ logLateTerminal(turn, status) {
1640
+ if (status === "success") return;
1641
+ const message = `\u6743\u5A01\u7EC8\u6001\u665A\u4E8E\u843D\u5B9A\u7ED3\u679C\u5230\u8FBE\uFF08\u4F1A\u8BDD ${turn.sessionId}\uFF0C\u5DF2\u843D\u5B9A source=${turn.terminal?.source}\uFF09\uFF1Aturn.terminal status="${status}" \u4EC5\u8BB0\u5F55\u4E0D\u6539\u5199\uFF08P0-1 D5\u2460\uFF09`;
1642
+ if (isFailedTerminalStatus(status)) {
1643
+ logger2.warn(message);
1644
+ return;
1645
+ }
1646
+ if (status === "interrupted") {
1647
+ logger2.debug(message);
1648
+ return;
1649
+ }
1650
+ logger2.warn(message);
1651
+ }
1558
1652
  /** terminal 帧专属错误信息入账(⛔P-Z2 实证:真实 failed 终态的 errorCode/errorMessage
1559
1653
  * 只在 terminal 帧携带,read/delta 拿不到——先到/迟到都记录,消费层合成失败
1560
1654
  * 文案时优先采信)。二者均缺失时不挂 lastTerminalError 字段。 */
@@ -1604,8 +1698,166 @@ var SessionChannel = class {
1604
1698
  }
1605
1699
  };
1606
1700
 
1607
- // src/zcode-engine.ts
1701
+ // src/session-db-maintenance.ts
1702
+ import * as fs5 from "fs";
1703
+ import { getLogger as getLogger3 } from "@zhushanwen/subagent-engine-sdk";
1608
1704
  var logger3 = getLogger3("subagents");
1705
+ var ZCODE_SESSION_TTL_MS = 2592e6;
1706
+ var ZCODE_SESSION_SWEEP_INTERVAL_MS = 864e5;
1707
+ var SWEEP_BUSY_TIMEOUT_MS = 5e3;
1708
+ var SESSION_CHILD_TABLES = [
1709
+ "message",
1710
+ "todo",
1711
+ "session_entry",
1712
+ "session_input",
1713
+ "session_target",
1714
+ "model_usage",
1715
+ "turn_usage",
1716
+ "tool_usage"
1717
+ ];
1718
+ function loadDatabaseSyncCtor() {
1719
+ const getBuiltin = process.getBuiltinModule;
1720
+ if (typeof getBuiltin !== "function") return void 0;
1721
+ const mod = getBuiltin("node:sqlite");
1722
+ return typeof mod?.DatabaseSync === "function" ? mod.DatabaseSync : void 0;
1723
+ }
1724
+ function sessionLastActivity(row) {
1725
+ for (const v of [row.time_updated, row.time_created]) {
1726
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1727
+ }
1728
+ return null;
1729
+ }
1730
+ function collectExpiredSessions(db, cutoff, keep) {
1731
+ const rows = db.prepare("SELECT id, time_updated, time_created FROM session").all();
1732
+ const expired = [];
1733
+ let keptActive = 0;
1734
+ for (const row of rows) {
1735
+ if (typeof row.id !== "string" || row.id === "") continue;
1736
+ const last = sessionLastActivity(row);
1737
+ if (last === null || last >= cutoff) continue;
1738
+ if (keep.has(row.id)) {
1739
+ keptActive++;
1740
+ continue;
1741
+ }
1742
+ expired.push(row.id);
1743
+ }
1744
+ return { expired, keptActive };
1745
+ }
1746
+ function ensureForeignKeysOn(db) {
1747
+ db.exec("PRAGMA foreign_keys = ON");
1748
+ const fkOn = db.prepare("PRAGMA foreign_keys").get();
1749
+ if (fkOn?.foreign_keys !== 1) {
1750
+ throw new Error("PRAGMA foreign_keys=ON \u672A\u751F\u6548\u2014\u2014\u4E2D\u6B62\u5220\u9664\uFF08\u9632\u5B64\u513F\u884C\uFF09\uFF0C\u672C\u8F6E sweep \u653E\u5F03");
1751
+ }
1752
+ }
1753
+ function listPresentTables(db) {
1754
+ return new Set(
1755
+ db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((r) => typeof r.name === "string" ? r.name : "")
1756
+ );
1757
+ }
1758
+ var DELETE_BATCH_SIZE = 500;
1759
+ function chunkIds(ids, size) {
1760
+ const batches = [];
1761
+ for (let i = 0; i < ids.length; i += size) batches.push(ids.slice(i, i + size));
1762
+ return batches;
1763
+ }
1764
+ function deleteExpiredSessionsInTx(db, expired, present) {
1765
+ db.exec("BEGIN");
1766
+ try {
1767
+ let swept = 0;
1768
+ for (const batch of chunkIds(expired, DELETE_BATCH_SIZE)) {
1769
+ const ph = batch.map(() => "?").join(", ");
1770
+ for (const table of SESSION_CHILD_TABLES) {
1771
+ if (present.has(table)) db.prepare(`DELETE FROM ${table} WHERE session_id IN (${ph})`).run(...batch);
1772
+ }
1773
+ if (present.has("session_task_link")) {
1774
+ db.prepare(`DELETE FROM session_task_link WHERE child_session_id IN (${ph})`).run(...batch);
1775
+ db.prepare(`UPDATE session_task_link SET parent_session_id = NULL WHERE parent_session_id IN (${ph})`).run(...batch);
1776
+ }
1777
+ if (present.has("workflow_run")) {
1778
+ db.prepare(`UPDATE workflow_run SET parent_session_id = NULL WHERE parent_session_id IN (${ph})`).run(...batch);
1779
+ }
1780
+ if (present.has("workflow_activity")) {
1781
+ db.prepare(`UPDATE workflow_activity SET child_session_id = NULL WHERE child_session_id IN (${ph})`).run(...batch);
1782
+ }
1783
+ if (present.has("input_history")) {
1784
+ db.prepare(`DELETE FROM input_history WHERE session_id IN (${ph})`).run(...batch);
1785
+ }
1786
+ const res = db.prepare(`DELETE FROM session WHERE id IN (${ph})`).run(...batch);
1787
+ swept += res.changes ?? batch.length;
1788
+ }
1789
+ db.exec("COMMIT");
1790
+ return swept;
1791
+ } catch (err) {
1792
+ try {
1793
+ db.exec("ROLLBACK");
1794
+ } catch (rollbackErr) {
1795
+ void rollbackErr;
1796
+ }
1797
+ throw err;
1798
+ }
1799
+ }
1800
+ function sweepExpiredZcodeSessions(dbPath, opts = {}) {
1801
+ const noOp = { swept: 0, keptActive: 0 };
1802
+ if (!fs5.existsSync(dbPath)) return noOp;
1803
+ const Ctor = loadDatabaseSyncCtor();
1804
+ if (Ctor === void 0) {
1805
+ logger3.warn("[zcode-ttl] node:sqlite \u4E0D\u53EF\u7528\uFF08\u9700 Node \u226522.3 \u7684 process.getBuiltinModule\uFF09\u2014\u2014\u672C\u8F6E sweep \u8DF3\u8FC7");
1806
+ return noOp;
1807
+ }
1808
+ const now = opts.nowMs ?? Date.now();
1809
+ const ttl = opts.ttlMs ?? ZCODE_SESSION_TTL_MS;
1810
+ const cutoff = now - ttl;
1811
+ const keep = opts.keepSessionIds ?? /* @__PURE__ */ new Set();
1812
+ let db;
1813
+ try {
1814
+ db = new Ctor(dbPath);
1815
+ db.exec(`PRAGMA busy_timeout = ${SWEEP_BUSY_TIMEOUT_MS}`);
1816
+ const { expired, keptActive } = collectExpiredSessions(db, cutoff, keep);
1817
+ if (expired.length === 0) {
1818
+ db.close();
1819
+ return { swept: 0, keptActive };
1820
+ }
1821
+ ensureForeignKeysOn(db);
1822
+ const present = listPresentTables(db);
1823
+ const swept = deleteExpiredSessionsInTx(db, expired, present);
1824
+ logger3.debug(`[zcode-ttl] \u9694\u79BB\u5E93\u8D85\u7A97\u6761\u76EE\u5DF2\u6E05\uFF1A${swept} \u6761\uFF08TTL ${ttl}ms\uFF0C\u8C41\u514D\u6D3B\u8DC3 ${keptActive}\uFF09`, { dbPath });
1825
+ try {
1826
+ db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
1827
+ } catch (err) {
1828
+ void err;
1829
+ }
1830
+ return { swept, keptActive };
1831
+ } catch (err) {
1832
+ logger3.warn(
1833
+ `[zcode-ttl] sweep \u5931\u8D25\uFF08\u8F85\u52A9\u8D44\u6E90\u9762\uFF0C\u4E0D\u5F71\u54CD run \u4E3B\u94FE\u8DEF\uFF1B\u4E0B\u4E2A\u8282\u6D41\u7A97\u91CD\u8BD5\uFF09: ${err instanceof Error ? err.message : String(err)}`,
1834
+ { dbPath }
1835
+ );
1836
+ return noOp;
1837
+ } finally {
1838
+ try {
1839
+ db?.close();
1840
+ } catch (err) {
1841
+ void err;
1842
+ }
1843
+ }
1844
+ }
1845
+ var sweepThrottle = /* @__PURE__ */ new Map();
1846
+ function maybeSweepExpiredZcodeSessions(dbPath, opts = {}) {
1847
+ const now = Date.now();
1848
+ const last = sweepThrottle.get(dbPath);
1849
+ if (!opts.force && last !== void 0 && now - last < ZCODE_SESSION_SWEEP_INTERVAL_MS) {
1850
+ return { swept: 0, keptActive: 0 };
1851
+ }
1852
+ const result = sweepExpiredZcodeSessions(dbPath, {
1853
+ ...opts.keepSessionIds !== void 0 ? { keepSessionIds: opts.keepSessionIds } : {}
1854
+ });
1855
+ sweepThrottle.set(dbPath, now);
1856
+ return result;
1857
+ }
1858
+
1859
+ // src/zcode-engine.ts
1860
+ var logger4 = getLogger4("subagents");
1609
1861
  var PROBE_VERSION_TIMEOUT_MS = 15e3;
1610
1862
  var COMMON_THOUGHT_LEVELS = ["low", "high", "max"];
1611
1863
  var ZcodeEngine = class {
@@ -1632,8 +1884,11 @@ var ZcodeEngine = class {
1632
1884
  schemaEnforcement: "emulated",
1633
1885
  // send-while-running 恒 -32010 硬错误(旧实测)——app-server 常驻化不改变此判据
1634
1886
  steer: "unsupported",
1635
- // 无同进程 idle 复用(D4:每任务自包含 create→run→close)
1636
- conversation: "unsupported",
1887
+ // [U6 / §3.2.6 要点 4] "cold" = 冷恢复会话:session/resume 读通道取结构化
1888
+ // 历史 + session/create 新会话注入首轮(无热 steering——interrupt 维持
1889
+ // kill-only 如实声明)。P-1 探针:原地 resume 续写被 -32031 卡死,热会话
1890
+ // 通道不可用,声明不越级。
1891
+ conversation: "cold",
1637
1892
  // 无 --append-system-prompt flag(实测拒收)——persona 只能拼进 prompt
1638
1893
  personaInjection: "prompt",
1639
1894
  // app-server 推送流实时流出(session/event payload.delta → text_delta)
@@ -1677,7 +1932,7 @@ var ZcodeEngine = class {
1677
1932
  }
1678
1933
  /** check 1:二进制存在性(isFile 才算——同名目录不是可执行入口)。 */
1679
1934
  probeBinaryCheck(cliPath) {
1680
- const binaryOk = fs5.existsSync(cliPath) && fs5.statSync(cliPath).isFile();
1935
+ const binaryOk = fs6.existsSync(cliPath) && fs6.statSync(cliPath).isFile();
1681
1936
  return {
1682
1937
  name: "binary",
1683
1938
  ok: binaryOk,
@@ -1715,8 +1970,8 @@ var ZcodeEngine = class {
1715
1970
  /**
1716
1971
  * 常驻路径主编排:模型解析(v2 单源校验)→ 惰性连接 + runTurn(事件时序前移:
1717
1972
  * text_delta 流式、终态后 message_end/turn_end)→ schema 仿真重试 → outcome/handle。
1718
- * poolKey 固定 'shared'(共享宿主 HOME,无池),onPoolResolved 在 prepare 期、
1719
- * onHandleReady 在 create 应答后(§3.4 不变量 3)。
1973
+ * onHandleReady 在 create 应答后(§3.4 不变量 3;原 onPoolResolved prepare 期
1974
+ * 声明已随 [池抽象降级 2026-09-13] poolKey 协议面退役删除)。
1720
1975
  */
1721
1976
  async runViaAppServer(task, ctx) {
1722
1977
  const startedAt = Date.now();
@@ -1726,10 +1981,10 @@ var ZcodeEngine = class {
1726
1981
  const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
1727
1982
  this.warnIgnoredCtxModel(task, ctx, modelRef);
1728
1983
  this.warnThoughtLevelUncommon(task, ctx);
1729
- ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
1730
1984
  const cwd = task.cwd ?? process.cwd();
1731
1985
  const schema = isPlainObject(task.schema) ? task.schema : void 0;
1732
- const basePrompt = this.buildPrompt(task, schema);
1986
+ const resumePrefix = await this.buildResumeHistoryPrefix(ctx);
1987
+ const basePrompt = resumePrefix === void 0 ? this.buildPrompt(task, schema) : resumePrefix + this.buildPrompt(task, schema);
1733
1988
  const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
1734
1989
  const final = await this.runAppServerAttemptsWithRetry(task, ctx, modelRef, cwd, basePrompt, schema, usageAcc);
1735
1990
  const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
@@ -1737,7 +1992,6 @@ var ZcodeEngine = class {
1737
1992
  }
1738
1993
  /** pre-aborted 短路收口:合成中止 outcome + 'shared' 锚定 handle。 */
1739
1994
  abortedAppServerRun(task, ctx, startedAt) {
1740
- ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
1741
1995
  const outcome = this.finalizeOutcome(
1742
1996
  task,
1743
1997
  ctx,
@@ -1771,11 +2025,11 @@ var ZcodeEngine = class {
1771
2025
  if (final.kind === "run-failed" && final.transient !== void 0 && ctx.signal?.aborted !== true && !this.disposed) {
1772
2026
  const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
1773
2027
  if (budget.state === "depleted") {
1774
- logger3.warn(
2028
+ logger4.warn(
1775
2029
  `[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`
1776
2030
  );
1777
2031
  } else {
1778
- logger3.warn(
2032
+ logger4.warn(
1779
2033
  `[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")
1780
2034
  );
1781
2035
  const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
@@ -1811,7 +2065,6 @@ var ZcodeEngine = class {
1811
2065
  dbPath: zcodeSessionDbPath(this.deps.engineDataDir()),
1812
2066
  ...outcome.sessionId !== void 0 ? { sessionId: outcome.sessionId } : {}
1813
2067
  },
1814
- poolKey: ZCODE_SHARED_POOL_KEY,
1815
2068
  ...this.probeCache?.engineVersion !== void 0 && this.probeCache.engineVersion !== "" ? { engineVersion: this.probeCache.engineVersion } : {},
1816
2069
  adapterVersion: ZCODE_ADAPTER_VERSION
1817
2070
  }
@@ -1831,8 +2084,8 @@ var ZcodeEngine = class {
1831
2084
  const createParams = buildAppServerCreateParams(task, providerId, modelId, cwd);
1832
2085
  let currentSessionId;
1833
2086
  let signalSessionCreated;
1834
- const sessionCreated = new Promise((resolve) => {
1835
- signalSessionCreated = resolve;
2087
+ const sessionCreated = new Promise((resolve2) => {
2088
+ signalSessionCreated = resolve2;
1836
2089
  });
1837
2090
  const turn = rt.channel.runTurn(createParams, prompt, {
1838
2091
  // 事件时序前移:payload.delta → text_delta 实时流出(stream 粒度,D5);
@@ -1840,13 +2093,17 @@ var ZcodeEngine = class {
1840
2093
  // 不变量 3a 的拼接比对不含 reasoning)
1841
2094
  onTextDelta: (delta) => ctx.onEvent?.({ type: "text_delta", delta }),
1842
2095
  onThinkingDelta: (delta) => ctx.onEvent?.({ type: "thinking_delta", delta }),
2096
+ // [PR3] 工具执行期活性:非终态非增量的 session/event 帧(tool.updated progress
2097
+ // 等,真机探针实证每 ~1s 一帧)→ activity 事件(零载荷纯活性信号——宿主无进展
2098
+ // 守护对任何事件类型刷新;reducer no-op、不落 journal)。zcode 引擎
2099
+ // conversation:"unsupported",run 域是唯一发射面(无 chat 域接线)。
2100
+ onActivity: () => ctx.onEvent?.({ type: "activity" }),
1843
2101
  onSessionCreated: (sessionId) => {
1844
2102
  currentSessionId = sessionId;
1845
2103
  rt.activeSessions.add(sessionId);
1846
2104
  signalSessionCreated?.();
1847
2105
  ctx.onHandleReady?.({
1848
- sessionRef: { dbPath: zcodeSessionDbPath(this.deps.engineDataDir()), sessionId },
1849
- poolKey: ZCODE_SHARED_POOL_KEY
2106
+ sessionRef: { dbPath: zcodeSessionDbPath(this.deps.engineDataDir()), sessionId }
1850
2107
  });
1851
2108
  },
1852
2109
  // 显式总上界传参(D2/U4:缺省缺席——channel 侧 resolveTurnTimerMs 走 env→默认)
@@ -1894,14 +2151,14 @@ var ZcodeEngine = class {
1894
2151
  */
1895
2152
  async awaitConnFinalized(rt) {
1896
2153
  if (!rt.conn.alive) return;
1897
- await new Promise((resolve) => {
2154
+ await new Promise((resolve2) => {
1898
2155
  const timer = setTimeout(() => finish(), ZCODE_APPSERVER_HARVEST_GRACE_MS);
1899
2156
  if (typeof timer.unref === "function") timer.unref();
1900
2157
  const off = rt.conn.onClose(() => finish());
1901
2158
  function finish() {
1902
2159
  clearTimeout(timer);
1903
2160
  off();
1904
- resolve();
2161
+ resolve2();
1905
2162
  }
1906
2163
  });
1907
2164
  }
@@ -1935,7 +2192,7 @@ var ZcodeEngine = class {
1935
2192
  delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false)
1936
2193
  ]);
1937
2194
  if (settled) return "settled-in-grace";
1938
- logger3.warn(
2195
+ logger4.warn(
1939
2196
  `[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`
1940
2197
  );
1941
2198
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
@@ -1960,19 +2217,19 @@ var ZcodeEngine = class {
1960
2217
  } catch (err) {
1961
2218
  if (entry.escalateOn === "stop-outcome") {
1962
2219
  if (isAppServerRpcError(err)) {
1963
- logger3.debug(
2220
+ logger4.debug(
1964
2221
  `[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`
1965
2222
  );
1966
2223
  return "stop-rejected";
1967
2224
  }
1968
- logger3.warn(
2225
+ logger4.warn(
1969
2226
  `[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`
1970
2227
  );
1971
2228
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
1972
2229
  await this.awaitConnFinalized(rt);
1973
2230
  return "stop-unreachable-killed";
1974
2231
  }
1975
- logger3.debug(
2232
+ logger4.debug(
1976
2233
  `[zcode-engine] session/stop \u5931\u8D25\uFF08${errMessage3(err)}\uFF09\u2014\u2014grace \u540E\u8D70 killChain \u515C\u5E95`
1977
2234
  );
1978
2235
  }
@@ -2001,7 +2258,7 @@ var ZcodeEngine = class {
2001
2258
  const sessionDbPath = zcodeSessionDbPath(engineDataDir);
2002
2259
  env.ZCODE_SESSION_DB_PATH = sessionDbPath;
2003
2260
  delete env.ZCODE_SESSION_DB;
2004
- fs5.mkdirSync(path4.dirname(sessionDbPath), { recursive: true });
2261
+ fs6.mkdirSync(path4.dirname(sessionDbPath), { recursive: true });
2005
2262
  const conn = new AppServerConnection({
2006
2263
  cliPath,
2007
2264
  dataDir: engineDataDir,
@@ -2019,6 +2276,11 @@ var ZcodeEngine = class {
2019
2276
  activeSessions: /* @__PURE__ */ new Set()
2020
2277
  };
2021
2278
  this.appserverRuntime = rt;
2279
+ const sweepDbPath = sessionDbPath;
2280
+ const sweepTimer = setTimeout(() => {
2281
+ maybeSweepExpiredZcodeSessions(sweepDbPath, { keepSessionIds: this.appserverRuntime?.activeSessions });
2282
+ }, ZCODE_SESSION_SWEEP_DEFER_MS);
2283
+ if (typeof sweepTimer.unref === "function") sweepTimer.unref();
2022
2284
  return rt;
2023
2285
  }
2024
2286
  /**
@@ -2036,10 +2298,10 @@ var ZcodeEngine = class {
2036
2298
  * 先到时 onClose 收割先行,dispose 收割幂等 no-op(零回归)。
2037
2299
  */
2038
2300
  async shutdownRuntimeAndDisposeChannel(rt) {
2039
- const harvested = new Promise((resolve) => {
2301
+ const harvested = new Promise((resolve2) => {
2040
2302
  const off = rt.conn.onClose(() => {
2041
2303
  off();
2042
- resolve();
2304
+ resolve2();
2043
2305
  });
2044
2306
  });
2045
2307
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
@@ -2066,17 +2328,6 @@ var ZcodeEngine = class {
2066
2328
  }
2067
2329
  await this.shutdownRuntimeAndDisposeChannel(rt);
2068
2330
  }
2069
- /**
2070
- * [u7a D5] 引擎在途只读快照:activeSessions 非空才算在途——poolKey 'shared' 的
2071
- * app-server **空闲常驻进程恒活,禁止按进程存在判定**(进程在 ≠ 在途,否则推迟
2072
- * 判定恒真、30min 上限从兜底变常态路径,设计 §3.3 D5 显式排除)。计数权威 =
2073
- * activeSessions(attempt 入 create 应答后 add / finally settle 后 delete,与
2074
- * dispose 的 close 帧目标集同一状态源,无双记账)。运行时未初始化(从未 run /
2075
- * 已 dispose)→ 恒 0。
2076
- */
2077
- inFlightSnapshot() {
2078
- return { inFlight: this.appserverRuntime?.activeSessions.size ?? 0 };
2079
- }
2080
2331
  /** 终态合成(extension-conventions 函数 80 行上限,从 run 提取):aborted / run-failed / parsed 三分支。 */
2081
2332
  finalizeOutcome(task, ctx, final, usageAcc, startedAt) {
2082
2333
  const outcome = {
@@ -2145,17 +2396,6 @@ var ZcodeEngine = class {
2145
2396
  }
2146
2397
  for (const ev of synthesizeCoarseEvents(payload.response, payload.usage)) emit(ev);
2147
2398
  }
2148
- /**
2149
- * D1 可选面:zcode 首期不支持 conversation(capabilities 声明)——同步拒绝、
2150
- * 不创建进程,文案给可操作建议(A11)。
2151
- */
2152
- async interact(_handle, _action) {
2153
- return {
2154
- ok: false,
2155
- code: "engine_capability_unsupported",
2156
- message: "zcode \u5F15\u64CE\u4E0D\u652F\u6301 conversation \u4EA4\u4E92\u63A7\u5236\u9762\uFF08capabilities.conversation = 'unsupported'\uFF0C\u6BCF\u4EFB\u52A1\u81EA\u5305\u542B\u4F1A\u8BDD\uFF0C\u65E0\u540C\u8FDB\u7A0B idle \u590D\u7528\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u6539\u7528\u5355\u6B21 subagent \u8C03\u7528\u91CD\u65B0\u6D3E\u53D1\u4EFB\u52A1\uFF0C\u6216\u4F7F\u7528 engine: 'pi'\uFF08chatMode idle \u590D\u7528\uFF0C\u652F\u6301 message/close/cancel\uFF09\u3002"
2157
- };
2158
- }
2159
2399
  /** [U7] 模型可发现性:v2 桌面登录态聚合(带凭据 provider × models),失败安全返回清单本身可能为空。 */
2160
2400
  listModels() {
2161
2401
  return listZcodeModels(this.deps.sources);
@@ -2176,8 +2416,12 @@ var ZcodeEngine = class {
2176
2416
  * sessionId 缺失(解析失败的 run 无法定位 session)跳过①级;②级依赖
2177
2417
  * handle.journalPath(宿主 run 后回填)。dbPath:新 handle 恒为隔离库绝对路径
2178
2418
  * (zcodeSessionDbPath(engineDataDir),tier1 白名单集合见方法体——宿主路径仅
2179
- * 「共享 HOME 时代」存量兼容);旧 records(池时代)的相对路径仍按 poolKey
2180
- * 锚定解析(read 兼容旧数据,池目录不存在时自然落②级 journal 降级)。
2419
+ * 「共享 HOME 时代」存量兼容);旧 records(池时代)的相对路径仍按池目录锚定
2420
+ * 解析(read 兼容旧数据,池目录不存在时自然落②级 journal 降级)。[池抽象降级
2421
+ * 2026-09-13] 锚定 key 用常量 SHARED_POOL_KEY 承载——协议面 poolKey 已删,池
2422
+ * 时代真实池 key 不再随 handle 传输;若旧 record 的池目录恰为 shared 布局则①级
2423
+ * 仍可达,其余池 key 的旧 record 直接走②级 journal 降级(30 天 TTL 同尺度衰减,
2424
+ * 行为安全)。
2181
2425
  */
2182
2426
  async read(handle) {
2183
2427
  if (handle.data.engineId !== ZCODE_ENGINE_ID) {
@@ -2191,21 +2435,28 @@ var ZcodeEngine = class {
2191
2435
  if (zcodeDbPathAllowlist(this.deps.engineDataDir()).includes(dbPathRaw)) {
2192
2436
  dbPath = dbPathRaw;
2193
2437
  } else {
2194
- logger3.warn("[zcode-engine] record dbPath \u975E\u767D\u540D\u5355 db \u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u62D2\u7EDD \u2460\u7EA7\u8BFB\u53D6\u964D journal", {
2438
+ logger4.warn("[zcode-engine] record dbPath \u975E\u767D\u540D\u5355 db \u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u62D2\u7EDD \u2460\u7EA7\u8BFB\u53D6\u964D journal", {
2195
2439
  dbPath: dbPathRaw
2196
2440
  });
2197
2441
  }
2198
2442
  } else {
2199
- dbPath = path4.join(
2200
- resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey),
2201
- dbPathRaw
2443
+ const poolDir = path4.resolve(
2444
+ resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, SHARED_POOL_KEY)
2202
2445
  );
2446
+ const resolved = path4.resolve(poolDir, dbPathRaw);
2447
+ if (resolved.startsWith(poolDir + path4.sep)) {
2448
+ dbPath = resolved;
2449
+ } else {
2450
+ logger4.warn("[zcode-engine] record dbPath \u76F8\u5BF9\u8DEF\u5F84\u9003\u9038\u6C60\u76EE\u5F55\uFF0C\u62D2\u7EDD \u2460\u7EA7\u8BFB\u53D6\u964D journal", {
2451
+ dbPath: dbPathRaw
2452
+ });
2453
+ }
2203
2454
  }
2204
2455
  if (dbPath !== void 0) {
2205
2456
  try {
2206
2457
  return await readZcodeSessionView(dbPath, sessionId);
2207
2458
  } catch (err) {
2208
- logger3.warn("[zcode-engine] native session read failed, degrade to journal replay", {
2459
+ logger4.warn("[zcode-engine] native session read failed, degrade to journal replay", {
2209
2460
  dbPath,
2210
2461
  sessionId,
2211
2462
  reason: toErrorMessage(err)
@@ -2231,7 +2482,7 @@ var ZcodeEngine = class {
2231
2482
  const thoughtLevel = task.thinkingLevel?.trim();
2232
2483
  if (thoughtLevel === void 0 || thoughtLevel === "") return;
2233
2484
  if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
2234
- logger3.warn(
2485
+ logger4.warn(
2235
2486
  `[zcode-engine] thinkingLevel=${thoughtLevel} \u5DF2\u900F\u4F20\u4E3A thoughtLevel\uFF08\u975E\u5E38\u89C1\u6863\u4F4D\uFF09\uFF1A\u82E5\u76EE\u6807\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u6863\u4F4D\u5C06\u88AB\u5FFD\u7565/\u56DE\u843D\u5230\u6A21\u578B\u7F3A\u7701\u63A8\u7406\u6863\u4F4D\uFF08\u5E38\u89C1\u6863\u4F4D\uFF1A${COMMON_THOUGHT_LEVELS.join("/")}\uFF09\uFF1B\u6863\u4F4D\u662F\u5426\u751F\u6548\u4EE5\u6A21\u578B\u5B9E\u9645\u884C\u4E3A\u4E3A\u51C6`,
2236
2487
  { taskId: ctx.taskId }
2237
2488
  );
@@ -2249,11 +2500,40 @@ var ZcodeEngine = class {
2249
2500
  if (ctx.ctxModel === void 0) return;
2250
2501
  const requested = task.model?.trim();
2251
2502
  if (requested !== void 0 && requested !== "") return;
2252
- logger3.warn(
2503
+ logger4.warn(
2253
2504
  `[zcode-engine] ctx.ctxModel\uFF08${ctx.ctxModel.id}\uFF09\u88AB\u5FFD\u7565\u2014\u2014ctxModel \u662F pi \u94FE\u8DEF\u515C\u5E95\uFF0Czcode \u4E0D\u6D88\u8D39\uFF1Btask.model \u672A\u663E\u5F0F\u6307\u5B9A\uFF0C\u5B9E\u9645\u4F7F\u7528\u5F15\u64CE\u7F3A\u7701\u6A21\u578B ${modelRef}`,
2254
2505
  { taskId: ctx.taskId }
2255
2506
  );
2256
2507
  }
2508
+ /**
2509
+ * [U6 / §3.2.6 要点 3] resume 锚 → 历史前缀(interact-resume 的读半段)。
2510
+ *
2511
+ * 无锚 / 非 zcode 锚形态 → undefined(行为不变)。读通道失败(条目被 TTL 清 /
2512
+ * 会话失效 / 控制面错误)= 宿主侧锚判据通过后、引擎派发前的窄竞态窗——降级为
2513
+ * **无历史前缀的新会话**(warn 留痕;宿主 reopen 摘要承担主降级面),不炸轮:
2514
+ * 续聊消息本身仍可送达(模型缺历史上下文,比整轮失败可用性高,与 reopen 降级
2515
+ * 的「带摘要重开」语义同族)。
2516
+ */
2517
+ async buildResumeHistoryPrefix(ctx) {
2518
+ const anchor = zcodeResumeAnchorOf(ctx, this.deps.engineDataDir());
2519
+ if (anchor === void 0) return void 0;
2520
+ const rt = this.ensureAppServerRuntime();
2521
+ let history;
2522
+ let tokens;
2523
+ try {
2524
+ const result = await rt.channel.resumeSession(anchor.sessionId);
2525
+ history = extractResumeHistory(result);
2526
+ tokens = extractResumeTotalTokens(result);
2527
+ } catch (err) {
2528
+ logger4.warn(
2529
+ `[zcode-engine] session/resume \u8BFB\u5386\u53F2\u5931\u8D25\uFF08\u951A ${anchor.sessionId}\uFF09\u2014\u2014\u964D\u7EA7\u4E3A\u65E0\u5386\u53F2\u524D\u7F00\u7684\u65B0\u4F1A\u8BDD: ${errMessage3(err)}`,
2530
+ { taskId: ctx.taskId }
2531
+ );
2532
+ return void 0;
2533
+ }
2534
+ if (history.length === 0) return void 0;
2535
+ return buildResumeInjectionSegment(history, anchor.sessionId, tokens);
2536
+ }
2257
2537
  /**
2258
2538
  * persona 拼接后的完整 prompt(personaInjection: 'prompt'——zcode 无 flag 通道):
2259
2539
  * appendSystemPrompt 段在前(人设/约束语境——D6 合流后 persona≡skillPath+
@@ -2267,6 +2547,38 @@ var ZcodeEngine = class {
2267
2547
  return segments.join("\n\n");
2268
2548
  }
2269
2549
  };
2550
+ function zcodeResumeAnchorOf(ctx, engineDataDir) {
2551
+ const ref = ctx.resume?.resume?.sessionRef;
2552
+ if (ref === void 0) return void 0;
2553
+ const sessionId = ref["sessionId"];
2554
+ const dbPath = ref["dbPath"];
2555
+ if (typeof sessionId !== "string" || sessionId === "") return void 0;
2556
+ if (typeof dbPath !== "string" || dbPath === "") return void 0;
2557
+ if (!zcodeDbPathAllowlist(engineDataDir).includes(dbPath)) return void 0;
2558
+ return { sessionId, dbPath };
2559
+ }
2560
+ function buildResumeInjectionSegment(history, priorSessionId, totalTokens, tokenBudget = ZCODE_RESUME_HISTORY_TOKEN_BUDGET) {
2561
+ const budgetTokens = tokenBudget;
2562
+ const charBudget = budgetTokens * ZCODE_RESUME_CHARS_PER_TOKEN;
2563
+ const lines = history.map((t) => `${t.role}: ${t.text}`);
2564
+ let kept = lines;
2565
+ let omitted = 0;
2566
+ while (kept.length > 1 && kept.reduce((n, l) => n + l.length + 1, 0) > charBudget) {
2567
+ kept = kept.slice(1);
2568
+ omitted++;
2569
+ }
2570
+ const omissionNote = omitted > 0 ? `[... ${omitted} older turn(s) omitted to fit the ${tokenBudget}-token history budget ...]
2571
+ ` : "";
2572
+ const usageNote = totalTokens !== void 0 ? `
2573
+ (prior session retained ~${totalTokens} tokens of history)` : "";
2574
+ return `[Continued conversation] You are continuing an existing subagent conversation on a NEW session (prior session id: ${priorSessionId}). The new session has no memory of its own \u2014 the full prior conversation history recovered from the session store follows. Continue seamlessly from it; the user message after this block is the next turn of the SAME conversation.
2575
+
2576
+ <conversation_history>
2577
+ ${omissionNote}${kept.join("\n")}
2578
+ </conversation_history>${usageNote}
2579
+
2580
+ `;
2581
+ }
2270
2582
  var ZCODE_TURN_RETRY_MIN_BUDGET_MS = 3e5;
2271
2583
  function resolveTransientRetryBudget(totalBudgetMs, consumedMs) {
2272
2584
  if (totalBudgetMs === void 0) return { state: "unbounded" };
@@ -2284,8 +2596,8 @@ function errMessage3(err) {
2284
2596
  return toErrorMessage(err);
2285
2597
  }
2286
2598
  function delayResolved(ms, value) {
2287
- return new Promise((resolve) => {
2288
- const t = setTimeout(() => resolve(value), ms);
2599
+ return new Promise((resolve2) => {
2600
+ const t = setTimeout(() => resolve2(value), ms);
2289
2601
  if (typeof t.unref === "function") t.unref();
2290
2602
  });
2291
2603
  }
@@ -2355,9 +2667,6 @@ function authoritativeTerminalStatus(r) {
2355
2667
  if (r.lastTerminalStatus !== void 0) return r.lastTerminalStatus;
2356
2668
  return r.terminal.source === "turn.terminal" ? r.terminal.status : void 0;
2357
2669
  }
2358
- function isFailedTerminalStatus(status) {
2359
- return status === "failed" || status === "error";
2360
- }
2361
2670
  function parsedAppServerAttempt(task, r) {
2362
2671
  if (isFailedTerminalStatus(authoritativeTerminalStatus(r))) {
2363
2672
  return failedTerminalAppServerAttempt(r);
@@ -2443,7 +2752,7 @@ function isHostTimeoutAbort(ctx) {
2443
2752
  var PROBE_STDOUT_CAP_CHARS = 4096;
2444
2753
  async function defaultProbeVersion(cliPath, dataDir) {
2445
2754
  try {
2446
- return await new Promise((resolve, reject) => {
2755
+ return await new Promise((resolve2, reject) => {
2447
2756
  const child = spawnEngineChild2({
2448
2757
  command: "node",
2449
2758
  args: [cliPath, "--version"],
@@ -2460,11 +2769,11 @@ async function defaultProbeVersion(cliPath, dataDir) {
2460
2769
  child.once("error", reject);
2461
2770
  child.once("close", (code) => {
2462
2771
  if (code !== 0) reject(new Error(`zcode --version exited with code ${code}`));
2463
- else resolve(out.trim().split("\n")[0]?.trim() || void 0);
2772
+ else resolve2(out.trim().split("\n")[0]?.trim() || void 0);
2464
2773
  });
2465
2774
  });
2466
2775
  } catch (err) {
2467
- logger3.debug(
2776
+ logger4.debug(
2468
2777
  `[zcode-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
2469
2778
  );
2470
2779
  return void 0;
@@ -2542,7 +2851,7 @@ var EngineProtocolServer = class {
2542
2851
  }
2543
2852
  }
2544
2853
  }
2545
- /** 10 正向方法分发(表驱动:method → 处理器;未知方法 → engine_protocol_unknown_method)。 */
2854
+ /** 9 正向方法分发(表驱动:method → 处理器;未知方法 → engine_protocol_unknown_method)。 */
2546
2855
  async dispatch(id, method, params) {
2547
2856
  const handler = this.methodHandlers[method];
2548
2857
  if (handler === void 0) {
@@ -2555,7 +2864,7 @@ var EngineProtocolServer = class {
2555
2864
  return handler(params);
2556
2865
  }
2557
2866
  /**
2558
- * 10 正向方法 → 处理器映射(每个处理器消费原始 params 并自行收敛类型——
2867
+ * 9 正向方法 → 处理器映射(每个处理器消费原始 params 并自行收敛类型——
2559
2868
  * 与原 switch case 表达式一一对应)。
2560
2869
  */
2561
2870
  methodHandlers = {
@@ -2563,7 +2872,6 @@ var EngineProtocolServer = class {
2563
2872
  probe: (p) => this.engine.probe(probeParamsOf(p)),
2564
2873
  run: (p) => this.run(p),
2565
2874
  cancel: (p) => this.cancel(p),
2566
- interact: (p) => this.interact(p),
2567
2875
  read: (p) => this.read(p),
2568
2876
  listModels: () => ({ models: this.engine.listModels?.() ?? null }),
2569
2877
  validateModel: (p) => this.validateModel(p),
@@ -2613,18 +2921,17 @@ var EngineProtocolServer = class {
2613
2921
  } } : void 0;
2614
2922
  const runCtx = {
2615
2923
  taskId: runId,
2616
- poolKey: ctx.poolKey,
2617
2924
  signal: controller.signal,
2618
2925
  onEvent: (event) => this.emitEvent(runId, event),
2619
2926
  ...ctxModel !== void 0 ? { ctxModel } : {},
2620
2927
  ...stream !== void 0 ? { stream } : {},
2621
2928
  ...ctx.schemaEnv !== void 0 ? { schemaEnv: ctx.schemaEnv } : {},
2622
2929
  ...ctx.engineFallback !== void 0 ? { engineFallback: ctx.engineFallback } : {},
2623
- onPoolResolved: (poolKey) => {
2624
- void this.reverseRequestInternal("host/poolResolved", { runId, poolKey });
2625
- },
2930
+ // [U6 / §3.2.6 要点 3] resume 锚点透传(宿主 → 引擎的续聊通道:zcode 锚
2931
+ // sessionRef {sessionId, dbPath},引擎侧 resume 读 + 新 session 注入消费)。
2932
+ ...params.resume !== void 0 ? { resume: params.resume } : {},
2626
2933
  onHandleReady: (partial) => {
2627
- void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef, poolKey: partial.poolKey });
2934
+ void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef });
2628
2935
  }
2629
2936
  };
2630
2937
  try {
@@ -2639,9 +2946,6 @@ var EngineProtocolServer = class {
2639
2946
  if (active !== void 0) active.controller.abort(new Error(`cancelled by host: ${params.reason}`));
2640
2947
  return { ok: true };
2641
2948
  }
2642
- async interact(params) {
2643
- return this.engine.interact({ data: params.handle }, params.action);
2644
- }
2645
2949
  read(params) {
2646
2950
  return this.engine.read({ data: params.handle });
2647
2951
  }
@@ -2673,14 +2977,14 @@ var EngineProtocolServer = class {
2673
2977
  }
2674
2978
  reverseRequestInternal(method, params) {
2675
2979
  const id = `rev-${++this.revSeq}`;
2676
- return new Promise((resolve, reject) => {
2980
+ return new Promise((resolve2, reject) => {
2677
2981
  const timer = setTimeout(() => {
2678
2982
  this.reversePending.delete(id);
2679
2983
  this.reverseClock?.settled(id);
2680
2984
  reject(new Error(`reverse request ${method} (${id}) timed out after ${this.reverseTimeoutMs}ms`));
2681
2985
  }, this.reverseTimeoutMs);
2682
2986
  if (typeof timer.unref === "function") timer.unref();
2683
- this.reversePending.set(id, { resolve, reject, timer });
2987
+ this.reversePending.set(id, { resolve: resolve2, reject, timer });
2684
2988
  this.reverseClock?.started(id);
2685
2989
  this.write({ id, method, params });
2686
2990
  });