@zhushanwen/zcode-subagent-cli 0.2.1 → 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,7 +22,6 @@ 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;
@@ -31,6 +30,9 @@ var ZCODE_APPSERVER_HARVEST_GRACE_MS = 1e3;
31
30
  function isFailedTerminalStatus(status) {
32
31
  return status === "failed" || status === "error";
33
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;
34
36
 
35
37
  // src/db-path.ts
36
38
  import * as os from "os";
@@ -278,9 +280,9 @@ async function readZcodeSessionView(dbPath, sessionId) {
278
280
  }
279
281
 
280
282
  // src/zcode-engine.ts
281
- import * as fs5 from "fs";
283
+ import * as fs6 from "fs";
282
284
  import * as path4 from "path";
283
- import { getLogger as getLogger3 } from "@zhushanwen/subagent-engine-sdk";
285
+ import { getLogger as getLogger4 } from "@zhushanwen/subagent-engine-sdk";
284
286
  import {
285
287
  buildEngineChildEnv as buildEngineChildEnv2,
286
288
  buildSchemaEmulationSegment,
@@ -289,6 +291,7 @@ import {
289
291
  synthesizeTimeoutOutcome,
290
292
  engineTimeoutDetail,
291
293
  resolvePoolDir,
294
+ SHARED_POOL_KEY,
292
295
  spawnEngineChild as spawnEngineChild2
293
296
  } from "@zhushanwen/subagent-engine-sdk";
294
297
 
@@ -779,7 +782,7 @@ var AppServerConnection = class {
779
782
  }
780
783
  const timeoutMs = opts?.timeoutMs ?? this.requestTimeoutMs;
781
784
  const id = ++this.reqSeq;
782
- return new Promise((resolve, reject) => {
785
+ return new Promise((resolve2, reject) => {
783
786
  const timer = setTimeout(() => {
784
787
  this.pending.delete(id);
785
788
  reject(new Error(`\u8BF7\u6C42 ${method} \u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`));
@@ -787,7 +790,7 @@ var AppServerConnection = class {
787
790
  this.pending.set(id, {
788
791
  // 泛型 T 是调用方对线上 unknown 结果的视图声明,连接层不做运行时校验
789
792
  // (协议层保持透明,结构校验归会话层/消费方)。
790
- resolve,
793
+ resolve: resolve2,
791
794
  reject,
792
795
  timer,
793
796
  method
@@ -1138,6 +1141,46 @@ function extractReadUsage(readResult) {
1138
1141
  }
1139
1142
  return isRecord3(readResult.usage) ? readResult.usage : void 0;
1140
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
+ }
1141
1184
  var TurnTimeoutError = class extends Error {
1142
1185
  kind;
1143
1186
  elapsed;
@@ -1298,6 +1341,17 @@ var SessionChannel = class {
1298
1341
  );
1299
1342
  }
1300
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
+ }
1301
1355
  /**
1302
1356
  * session/close(A.2 ⑦,D4 用后即毁——回收引擎驻留内存,SQLite 持久化保留)。
1303
1357
  * best-effort:失败只 warn 不抛(会话已终态,close 失败只泄漏一个驻留会话,
@@ -1644,8 +1698,166 @@ var SessionChannel = class {
1644
1698
  }
1645
1699
  };
1646
1700
 
1647
- // 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";
1648
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");
1649
1861
  var PROBE_VERSION_TIMEOUT_MS = 15e3;
1650
1862
  var COMMON_THOUGHT_LEVELS = ["low", "high", "max"];
1651
1863
  var ZcodeEngine = class {
@@ -1672,8 +1884,11 @@ var ZcodeEngine = class {
1672
1884
  schemaEnforcement: "emulated",
1673
1885
  // send-while-running 恒 -32010 硬错误(旧实测)——app-server 常驻化不改变此判据
1674
1886
  steer: "unsupported",
1675
- // 无同进程 idle 复用(D4:每任务自包含 create→run→close)
1676
- 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",
1677
1892
  // 无 --append-system-prompt flag(实测拒收)——persona 只能拼进 prompt
1678
1893
  personaInjection: "prompt",
1679
1894
  // app-server 推送流实时流出(session/event payload.delta → text_delta)
@@ -1717,7 +1932,7 @@ var ZcodeEngine = class {
1717
1932
  }
1718
1933
  /** check 1:二进制存在性(isFile 才算——同名目录不是可执行入口)。 */
1719
1934
  probeBinaryCheck(cliPath) {
1720
- const binaryOk = fs5.existsSync(cliPath) && fs5.statSync(cliPath).isFile();
1935
+ const binaryOk = fs6.existsSync(cliPath) && fs6.statSync(cliPath).isFile();
1721
1936
  return {
1722
1937
  name: "binary",
1723
1938
  ok: binaryOk,
@@ -1755,8 +1970,8 @@ var ZcodeEngine = class {
1755
1970
  /**
1756
1971
  * 常驻路径主编排:模型解析(v2 单源校验)→ 惰性连接 + runTurn(事件时序前移:
1757
1972
  * text_delta 流式、终态后 message_end/turn_end)→ schema 仿真重试 → outcome/handle。
1758
- * poolKey 固定 'shared'(共享宿主 HOME,无池),onPoolResolved 在 prepare 期、
1759
- * onHandleReady 在 create 应答后(§3.4 不变量 3)。
1973
+ * onHandleReady 在 create 应答后(§3.4 不变量 3;原 onPoolResolved prepare 期
1974
+ * 声明已随 [池抽象降级 2026-09-13] poolKey 协议面退役删除)。
1760
1975
  */
1761
1976
  async runViaAppServer(task, ctx) {
1762
1977
  const startedAt = Date.now();
@@ -1766,10 +1981,10 @@ var ZcodeEngine = class {
1766
1981
  const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
1767
1982
  this.warnIgnoredCtxModel(task, ctx, modelRef);
1768
1983
  this.warnThoughtLevelUncommon(task, ctx);
1769
- ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
1770
1984
  const cwd = task.cwd ?? process.cwd();
1771
1985
  const schema = isPlainObject(task.schema) ? task.schema : void 0;
1772
- 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);
1773
1988
  const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
1774
1989
  const final = await this.runAppServerAttemptsWithRetry(task, ctx, modelRef, cwd, basePrompt, schema, usageAcc);
1775
1990
  const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
@@ -1777,7 +1992,6 @@ var ZcodeEngine = class {
1777
1992
  }
1778
1993
  /** pre-aborted 短路收口:合成中止 outcome + 'shared' 锚定 handle。 */
1779
1994
  abortedAppServerRun(task, ctx, startedAt) {
1780
- ctx.onPoolResolved?.(ZCODE_SHARED_POOL_KEY);
1781
1995
  const outcome = this.finalizeOutcome(
1782
1996
  task,
1783
1997
  ctx,
@@ -1811,11 +2025,11 @@ var ZcodeEngine = class {
1811
2025
  if (final.kind === "run-failed" && final.transient !== void 0 && ctx.signal?.aborted !== true && !this.disposed) {
1812
2026
  const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
1813
2027
  if (budget.state === "depleted") {
1814
- logger3.warn(
2028
+ logger4.warn(
1815
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`
1816
2030
  );
1817
2031
  } else {
1818
- logger3.warn(
2032
+ logger4.warn(
1819
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")
1820
2034
  );
1821
2035
  const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
@@ -1851,7 +2065,6 @@ var ZcodeEngine = class {
1851
2065
  dbPath: zcodeSessionDbPath(this.deps.engineDataDir()),
1852
2066
  ...outcome.sessionId !== void 0 ? { sessionId: outcome.sessionId } : {}
1853
2067
  },
1854
- poolKey: ZCODE_SHARED_POOL_KEY,
1855
2068
  ...this.probeCache?.engineVersion !== void 0 && this.probeCache.engineVersion !== "" ? { engineVersion: this.probeCache.engineVersion } : {},
1856
2069
  adapterVersion: ZCODE_ADAPTER_VERSION
1857
2070
  }
@@ -1871,8 +2084,8 @@ var ZcodeEngine = class {
1871
2084
  const createParams = buildAppServerCreateParams(task, providerId, modelId, cwd);
1872
2085
  let currentSessionId;
1873
2086
  let signalSessionCreated;
1874
- const sessionCreated = new Promise((resolve) => {
1875
- signalSessionCreated = resolve;
2087
+ const sessionCreated = new Promise((resolve2) => {
2088
+ signalSessionCreated = resolve2;
1876
2089
  });
1877
2090
  const turn = rt.channel.runTurn(createParams, prompt, {
1878
2091
  // 事件时序前移:payload.delta → text_delta 实时流出(stream 粒度,D5);
@@ -1890,8 +2103,7 @@ var ZcodeEngine = class {
1890
2103
  rt.activeSessions.add(sessionId);
1891
2104
  signalSessionCreated?.();
1892
2105
  ctx.onHandleReady?.({
1893
- sessionRef: { dbPath: zcodeSessionDbPath(this.deps.engineDataDir()), sessionId },
1894
- poolKey: ZCODE_SHARED_POOL_KEY
2106
+ sessionRef: { dbPath: zcodeSessionDbPath(this.deps.engineDataDir()), sessionId }
1895
2107
  });
1896
2108
  },
1897
2109
  // 显式总上界传参(D2/U4:缺省缺席——channel 侧 resolveTurnTimerMs 走 env→默认)
@@ -1939,14 +2151,14 @@ var ZcodeEngine = class {
1939
2151
  */
1940
2152
  async awaitConnFinalized(rt) {
1941
2153
  if (!rt.conn.alive) return;
1942
- await new Promise((resolve) => {
2154
+ await new Promise((resolve2) => {
1943
2155
  const timer = setTimeout(() => finish(), ZCODE_APPSERVER_HARVEST_GRACE_MS);
1944
2156
  if (typeof timer.unref === "function") timer.unref();
1945
2157
  const off = rt.conn.onClose(() => finish());
1946
2158
  function finish() {
1947
2159
  clearTimeout(timer);
1948
2160
  off();
1949
- resolve();
2161
+ resolve2();
1950
2162
  }
1951
2163
  });
1952
2164
  }
@@ -1980,7 +2192,7 @@ var ZcodeEngine = class {
1980
2192
  delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false)
1981
2193
  ]);
1982
2194
  if (settled) return "settled-in-grace";
1983
- logger3.warn(
2195
+ logger4.warn(
1984
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`
1985
2197
  );
1986
2198
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
@@ -2005,19 +2217,19 @@ var ZcodeEngine = class {
2005
2217
  } catch (err) {
2006
2218
  if (entry.escalateOn === "stop-outcome") {
2007
2219
  if (isAppServerRpcError(err)) {
2008
- logger3.debug(
2220
+ logger4.debug(
2009
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`
2010
2222
  );
2011
2223
  return "stop-rejected";
2012
2224
  }
2013
- logger3.warn(
2225
+ logger4.warn(
2014
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`
2015
2227
  );
2016
2228
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
2017
2229
  await this.awaitConnFinalized(rt);
2018
2230
  return "stop-unreachable-killed";
2019
2231
  }
2020
- logger3.debug(
2232
+ logger4.debug(
2021
2233
  `[zcode-engine] session/stop \u5931\u8D25\uFF08${errMessage3(err)}\uFF09\u2014\u2014grace \u540E\u8D70 killChain \u515C\u5E95`
2022
2234
  );
2023
2235
  }
@@ -2046,7 +2258,7 @@ var ZcodeEngine = class {
2046
2258
  const sessionDbPath = zcodeSessionDbPath(engineDataDir);
2047
2259
  env.ZCODE_SESSION_DB_PATH = sessionDbPath;
2048
2260
  delete env.ZCODE_SESSION_DB;
2049
- fs5.mkdirSync(path4.dirname(sessionDbPath), { recursive: true });
2261
+ fs6.mkdirSync(path4.dirname(sessionDbPath), { recursive: true });
2050
2262
  const conn = new AppServerConnection({
2051
2263
  cliPath,
2052
2264
  dataDir: engineDataDir,
@@ -2064,6 +2276,11 @@ var ZcodeEngine = class {
2064
2276
  activeSessions: /* @__PURE__ */ new Set()
2065
2277
  };
2066
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();
2067
2284
  return rt;
2068
2285
  }
2069
2286
  /**
@@ -2081,10 +2298,10 @@ var ZcodeEngine = class {
2081
2298
  * 先到时 onClose 收割先行,dispose 收割幂等 no-op(零回归)。
2082
2299
  */
2083
2300
  async shutdownRuntimeAndDisposeChannel(rt) {
2084
- const harvested = new Promise((resolve) => {
2301
+ const harvested = new Promise((resolve2) => {
2085
2302
  const off = rt.conn.onClose(() => {
2086
2303
  off();
2087
- resolve();
2304
+ resolve2();
2088
2305
  });
2089
2306
  });
2090
2307
  await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
@@ -2111,17 +2328,6 @@ var ZcodeEngine = class {
2111
2328
  }
2112
2329
  await this.shutdownRuntimeAndDisposeChannel(rt);
2113
2330
  }
2114
- /**
2115
- * [u7a D5] 引擎在途只读快照:activeSessions 非空才算在途——poolKey 'shared' 的
2116
- * app-server **空闲常驻进程恒活,禁止按进程存在判定**(进程在 ≠ 在途,否则推迟
2117
- * 判定恒真、30min 上限从兜底变常态路径,设计 §3.3 D5 显式排除)。计数权威 =
2118
- * activeSessions(attempt 入 create 应答后 add / finally settle 后 delete,与
2119
- * dispose 的 close 帧目标集同一状态源,无双记账)。运行时未初始化(从未 run /
2120
- * 已 dispose)→ 恒 0。
2121
- */
2122
- inFlightSnapshot() {
2123
- return { inFlight: this.appserverRuntime?.activeSessions.size ?? 0 };
2124
- }
2125
2331
  /** 终态合成(extension-conventions 函数 80 行上限,从 run 提取):aborted / run-failed / parsed 三分支。 */
2126
2332
  finalizeOutcome(task, ctx, final, usageAcc, startedAt) {
2127
2333
  const outcome = {
@@ -2210,8 +2416,12 @@ var ZcodeEngine = class {
2210
2416
  * sessionId 缺失(解析失败的 run 无法定位 session)跳过①级;②级依赖
2211
2417
  * handle.journalPath(宿主 run 后回填)。dbPath:新 handle 恒为隔离库绝对路径
2212
2418
  * (zcodeSessionDbPath(engineDataDir),tier1 白名单集合见方法体——宿主路径仅
2213
- * 「共享 HOME 时代」存量兼容);旧 records(池时代)的相对路径仍按 poolKey
2214
- * 锚定解析(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
+ * 行为安全)。
2215
2425
  */
2216
2426
  async read(handle) {
2217
2427
  if (handle.data.engineId !== ZCODE_ENGINE_ID) {
@@ -2225,21 +2435,28 @@ var ZcodeEngine = class {
2225
2435
  if (zcodeDbPathAllowlist(this.deps.engineDataDir()).includes(dbPathRaw)) {
2226
2436
  dbPath = dbPathRaw;
2227
2437
  } else {
2228
- 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", {
2229
2439
  dbPath: dbPathRaw
2230
2440
  });
2231
2441
  }
2232
2442
  } else {
2233
- dbPath = path4.join(
2234
- resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey),
2235
- dbPathRaw
2443
+ const poolDir = path4.resolve(
2444
+ resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, SHARED_POOL_KEY)
2236
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
+ }
2237
2454
  }
2238
2455
  if (dbPath !== void 0) {
2239
2456
  try {
2240
2457
  return await readZcodeSessionView(dbPath, sessionId);
2241
2458
  } catch (err) {
2242
- 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", {
2243
2460
  dbPath,
2244
2461
  sessionId,
2245
2462
  reason: toErrorMessage(err)
@@ -2265,7 +2482,7 @@ var ZcodeEngine = class {
2265
2482
  const thoughtLevel = task.thinkingLevel?.trim();
2266
2483
  if (thoughtLevel === void 0 || thoughtLevel === "") return;
2267
2484
  if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
2268
- logger3.warn(
2485
+ logger4.warn(
2269
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`,
2270
2487
  { taskId: ctx.taskId }
2271
2488
  );
@@ -2283,11 +2500,40 @@ var ZcodeEngine = class {
2283
2500
  if (ctx.ctxModel === void 0) return;
2284
2501
  const requested = task.model?.trim();
2285
2502
  if (requested !== void 0 && requested !== "") return;
2286
- logger3.warn(
2503
+ logger4.warn(
2287
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}`,
2288
2505
  { taskId: ctx.taskId }
2289
2506
  );
2290
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
+ }
2291
2537
  /**
2292
2538
  * persona 拼接后的完整 prompt(personaInjection: 'prompt'——zcode 无 flag 通道):
2293
2539
  * appendSystemPrompt 段在前(人设/约束语境——D6 合流后 persona≡skillPath+
@@ -2301,6 +2547,38 @@ var ZcodeEngine = class {
2301
2547
  return segments.join("\n\n");
2302
2548
  }
2303
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
+ }
2304
2582
  var ZCODE_TURN_RETRY_MIN_BUDGET_MS = 3e5;
2305
2583
  function resolveTransientRetryBudget(totalBudgetMs, consumedMs) {
2306
2584
  if (totalBudgetMs === void 0) return { state: "unbounded" };
@@ -2318,8 +2596,8 @@ function errMessage3(err) {
2318
2596
  return toErrorMessage(err);
2319
2597
  }
2320
2598
  function delayResolved(ms, value) {
2321
- return new Promise((resolve) => {
2322
- const t = setTimeout(() => resolve(value), ms);
2599
+ return new Promise((resolve2) => {
2600
+ const t = setTimeout(() => resolve2(value), ms);
2323
2601
  if (typeof t.unref === "function") t.unref();
2324
2602
  });
2325
2603
  }
@@ -2474,7 +2752,7 @@ function isHostTimeoutAbort(ctx) {
2474
2752
  var PROBE_STDOUT_CAP_CHARS = 4096;
2475
2753
  async function defaultProbeVersion(cliPath, dataDir) {
2476
2754
  try {
2477
- return await new Promise((resolve, reject) => {
2755
+ return await new Promise((resolve2, reject) => {
2478
2756
  const child = spawnEngineChild2({
2479
2757
  command: "node",
2480
2758
  args: [cliPath, "--version"],
@@ -2491,11 +2769,11 @@ async function defaultProbeVersion(cliPath, dataDir) {
2491
2769
  child.once("error", reject);
2492
2770
  child.once("close", (code) => {
2493
2771
  if (code !== 0) reject(new Error(`zcode --version exited with code ${code}`));
2494
- else resolve(out.trim().split("\n")[0]?.trim() || void 0);
2772
+ else resolve2(out.trim().split("\n")[0]?.trim() || void 0);
2495
2773
  });
2496
2774
  });
2497
2775
  } catch (err) {
2498
- logger3.debug(
2776
+ logger4.debug(
2499
2777
  `[zcode-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
2500
2778
  );
2501
2779
  return void 0;
@@ -2643,18 +2921,17 @@ var EngineProtocolServer = class {
2643
2921
  } } : void 0;
2644
2922
  const runCtx = {
2645
2923
  taskId: runId,
2646
- poolKey: ctx.poolKey,
2647
2924
  signal: controller.signal,
2648
2925
  onEvent: (event) => this.emitEvent(runId, event),
2649
2926
  ...ctxModel !== void 0 ? { ctxModel } : {},
2650
2927
  ...stream !== void 0 ? { stream } : {},
2651
2928
  ...ctx.schemaEnv !== void 0 ? { schemaEnv: ctx.schemaEnv } : {},
2652
2929
  ...ctx.engineFallback !== void 0 ? { engineFallback: ctx.engineFallback } : {},
2653
- onPoolResolved: (poolKey) => {
2654
- void this.reverseRequestInternal("host/poolResolved", { runId, poolKey });
2655
- },
2930
+ // [U6 / §3.2.6 要点 3] resume 锚点透传(宿主 → 引擎的续聊通道:zcode 锚
2931
+ // sessionRef {sessionId, dbPath},引擎侧 resume 读 + 新 session 注入消费)。
2932
+ ...params.resume !== void 0 ? { resume: params.resume } : {},
2656
2933
  onHandleReady: (partial) => {
2657
- void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef, poolKey: partial.poolKey });
2934
+ void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef });
2658
2935
  }
2659
2936
  };
2660
2937
  try {
@@ -2700,14 +2977,14 @@ var EngineProtocolServer = class {
2700
2977
  }
2701
2978
  reverseRequestInternal(method, params) {
2702
2979
  const id = `rev-${++this.revSeq}`;
2703
- return new Promise((resolve, reject) => {
2980
+ return new Promise((resolve2, reject) => {
2704
2981
  const timer = setTimeout(() => {
2705
2982
  this.reversePending.delete(id);
2706
2983
  this.reverseClock?.settled(id);
2707
2984
  reject(new Error(`reverse request ${method} (${id}) timed out after ${this.reverseTimeoutMs}ms`));
2708
2985
  }, this.reverseTimeoutMs);
2709
2986
  if (typeof timer.unref === "function") timer.unref();
2710
- this.reversePending.set(id, { resolve, reject, timer });
2987
+ this.reversePending.set(id, { resolve: resolve2, reject, timer });
2711
2988
  this.reverseClock?.started(id);
2712
2989
  this.write({ id, method, params });
2713
2990
  });