@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.
- package/dist/{chunk-36VY2TNC.js → chunk-DMHXNYG2.js} +395 -91
- package/dist/index.cjs +416 -113
- package/dist/index.d.cts +27 -29
- package/dist/index.d.ts +27 -29
- package/dist/index.js +1 -1
- package/dist/main.cjs +419 -116
- package/dist/main.js +1 -1
- package/package.json +3 -3
|
@@ -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
|
|
283
|
+
import * as fs6 from "fs";
|
|
279
284
|
import * as path4 from "path";
|
|
280
|
-
import { getLogger as
|
|
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((
|
|
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: {
|
|
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
|
-
|
|
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/
|
|
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
|
-
//
|
|
1636
|
-
|
|
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 =
|
|
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
|
-
*
|
|
1719
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
1835
|
-
signalSessionCreated =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
2301
|
+
const harvested = new Promise((resolve2) => {
|
|
2040
2302
|
const off = rt.conn.onClose(() => {
|
|
2041
2303
|
off();
|
|
2042
|
-
|
|
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
|
|
2180
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
2200
|
-
resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
2288
|
-
const t = setTimeout(() =>
|
|
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((
|
|
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
|
|
2772
|
+
else resolve2(out.trim().split("\n")[0]?.trim() || void 0);
|
|
2464
2773
|
});
|
|
2465
2774
|
});
|
|
2466
2775
|
} catch (err) {
|
|
2467
|
-
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
|
|
2624
|
-
|
|
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
|
|
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((
|
|
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
|
});
|