@shgroup/dsh-serenity-hooks 1.95.0 → 1.96.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/dsh.plugin.json +1 -1
- package/lib/cro/turns.d.ts +37 -7
- package/lib/index.js +80 -15
- package/lib/trajectory/interrupted.d.ts +39 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/cro/turns.d.ts
CHANGED
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
*
|
|
13
13
|
* | 事件 | 动作 | 出处 |
|
|
14
14
|
* |---|---|---|
|
|
15
|
-
* | `agent/status` = `running` |
|
|
16
|
-
* | `agent/
|
|
15
|
+
* | `agent/status` = `running` | **置位**「在跑」 | 宿主 `AgentLoop.setPhase`:`emit("agent/status", { status })`,**只在状态真的变化时发** |
|
|
16
|
+
* | `agent/status` = `idle` | **清位** | 同上;`kick()` 的 `finally` 一定 `setPhase({kind:'idle'})` |
|
|
17
17
|
* | `agent/disposed` | **清该载体的项** | `host/contract.ts` |
|
|
18
18
|
* | `session/disposed` | **清该载体的项** | `host/contract.ts` |
|
|
19
19
|
*
|
|
20
|
+
* 🔴 **`agent/turn-stopping` 刻意不订阅** —— 它是「轮末暂停点」不是「轮结束」,见下方 D244 段。
|
|
21
|
+
*
|
|
20
22
|
* ## 🔴🔴 D243(2026-10-24):**⛔ 不许用 `agent/created` 标记「在跑」**
|
|
21
23
|
*
|
|
22
24
|
* **修前**:`agent/created` 也 `markTurnRunning(...)`。**为什么这是错的** —— 该事件是
|
|
@@ -37,18 +39,46 @@
|
|
|
37
39
|
* **代价与兜底**:删掉 `agent/created` 后,本表在"宿主重启后、某会话真正开跑之前"为空
|
|
38
40
|
* ⇒ 落到文件尾那条**便宜**的误判方向(报"没跑"),不会被拿来藏会话。
|
|
39
41
|
*
|
|
42
|
+
* ## 🔴🔴 D244(2026-10-24):**⛔ `agent/turn-stopping` 不得清表**
|
|
43
|
+
*
|
|
44
|
+
* **它看起来最像"收轮",其实不是。** 宿主 `AgentLoop.turn()` 原文:
|
|
45
|
+
*
|
|
46
|
+
* ```js
|
|
47
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
48
|
+
* await this.dispatch.serial("agent/turn-stopping", { turn, signal }) // 暂停点
|
|
49
|
+
* signal.throwIfAborted()
|
|
50
|
+
* }
|
|
51
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) break // 只有 nextStep 空才真结束
|
|
52
|
+
* target = "next-step" // 否则同轮继续
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* ⇒ 任何监听者在 `turn-stopping` 回调里 `steer`(往 `next-step` 塞消息)⇒ **同一轮继续跑**,
|
|
56
|
+
* 而 `turn/start` **不重发**、`agent/status` 也**不重发**(状态没变,还是 running)。
|
|
57
|
+
*
|
|
58
|
+
* 🔴 **实测代价**(真机 2026-10-24 18:1x):`trajectory/rebuild.ts` 的 auto-continue 正是
|
|
59
|
+
* 在 `turn-stopping` 里 `agent.steer(...)` ⇒ 一条轮**连跑 9 分钟**
|
|
60
|
+
* (`turn/start(32)` 18:12:00 → `turn/end(32)` 18:21:27),而本表**整段时间都是空的**
|
|
61
|
+
* ⇒ `interrupted` 的排除失效 ⇒ `steer` 被塞进**正在跑的那一轮**(对它是误导)。
|
|
62
|
+
* ⚠️ D243 之前这一格被 `agent/created` 的假置位**掩盖**;D243 删掉它 ⇒ 洞才暴露。
|
|
63
|
+
* ⇒ **D243 的方向是对的,它只是揭开了一个更老的语义误解**。
|
|
64
|
+
*
|
|
65
|
+
* ⇒ **清理只认 `agent/status = idle`**(`kick()` 的 `finally` 一定 `setPhase({kind:'idle'})`)。
|
|
66
|
+
* ⚠️ **已知窄代价**:`runMaintenance` 期间 `status` 也是 `idle` ⇒ 压缩等内部工作时不置位。
|
|
67
|
+
* 判可接受:那时没有 CRO 该救的轮;方向落到那条**便宜**的误判(报"没跑")。
|
|
68
|
+
* ⛔ **不要把 `clearTurnRunning` 加回 `turn-stopping`** —— 等于重新打开那个 9 分钟的洞。
|
|
69
|
+
*
|
|
40
70
|
* ## 🔴 清理设计(**这是本模块最容易被做坏的地方**)
|
|
41
71
|
*
|
|
42
72
|
* ⚠️ 本容器栽过 **5 次**「只增不清」的病(wake-registry / keeper 三表 / pendingRebuilds …),
|
|
43
73
|
* 而 `agent/disposed` 在 `HOST_EVENTS` 里的 impact 原文**恰好就是**
|
|
44
74
|
* 「*per-会话内存态不清理(**长跑泄漏**)*」⇒ **本模块属于该已登记病族,必须在设计期就带清理**。
|
|
45
75
|
*
|
|
46
|
-
*
|
|
47
|
-
* 1. **正常路径**:`
|
|
76
|
+
* **三层清理**(D244 起第 1 层已从 `turn-stopping` 换成 `status=idle`):
|
|
77
|
+
* 1. **正常路径**:`agent/status = idle` ⇒ 清除该项;
|
|
48
78
|
* 2. **载体销毁**:`agent/disposed` / `session/disposed` ⇒ 清除该项;
|
|
49
79
|
* 3. 🔴 **异常路径(前两层都漏掉的兜底)**:读取时按 **TTL** 过滤——
|
|
50
|
-
* 超过 `TURN_TTL_MS`
|
|
51
|
-
* 触发场景:进程被 kill / 卡死 /
|
|
80
|
+
* 超过 `TURN_TTL_MS` 未刷新的项**视为不在跑**(并顺手清除)。
|
|
81
|
+
* 触发场景:进程被 kill / 卡死 / 事件丢失(这些情况下 `idle` 永远不会来)。
|
|
52
82
|
*
|
|
53
83
|
* ## TTL 取值的**方向性判据**(R↓,别用"感觉合适")
|
|
54
84
|
*
|
|
@@ -58,7 +88,7 @@
|
|
|
58
88
|
* 「漏排是硬故障、多排是常态成本」)。
|
|
59
89
|
*
|
|
60
90
|
* ⇒ **不确定时宁可报"没跑"**(选便宜的那种错)⇒ **TTL 不宜过长**。
|
|
61
|
-
* 取 **30 分钟**:单轮静默超过 30 分钟仍无 `
|
|
91
|
+
* 取 **30 分钟**:单轮静默超过 30 分钟仍无 `idle` 的情形罕见;
|
|
62
92
|
* 而真发生了,代价只是一次可能多余的唤起(5min tick 限制频率,且 CRO 程序自己还能看
|
|
63
93
|
* `pendingWakes` / `lastWakeAt` 二次判)。
|
|
64
94
|
*
|
package/lib/index.js
CHANGED
|
@@ -8442,11 +8442,13 @@ function registerDisposer(ctx, label, cleanup) {
|
|
|
8442
8442
|
*
|
|
8443
8443
|
* | 事件 | 动作 | 出处 |
|
|
8444
8444
|
* |---|---|---|
|
|
8445
|
-
* | `agent/status` = `running` |
|
|
8446
|
-
* | `agent/
|
|
8445
|
+
* | `agent/status` = `running` | **置位**「在跑」 | 宿主 `AgentLoop.setPhase`:`emit("agent/status", { status })`,**只在状态真的变化时发** |
|
|
8446
|
+
* | `agent/status` = `idle` | **清位** | 同上;`kick()` 的 `finally` 一定 `setPhase({kind:'idle'})` |
|
|
8447
8447
|
* | `agent/disposed` | **清该载体的项** | `host/contract.ts` |
|
|
8448
8448
|
* | `session/disposed` | **清该载体的项** | `host/contract.ts` |
|
|
8449
8449
|
*
|
|
8450
|
+
* 🔴 **`agent/turn-stopping` 刻意不订阅** —— 它是「轮末暂停点」不是「轮结束」,见下方 D244 段。
|
|
8451
|
+
*
|
|
8450
8452
|
* ## 🔴🔴 D243(2026-10-24):**⛔ 不许用 `agent/created` 标记「在跑」**
|
|
8451
8453
|
*
|
|
8452
8454
|
* **修前**:`agent/created` 也 `markTurnRunning(...)`。**为什么这是错的** —— 该事件是
|
|
@@ -8467,18 +8469,46 @@ function registerDisposer(ctx, label, cleanup) {
|
|
|
8467
8469
|
* **代价与兜底**:删掉 `agent/created` 后,本表在"宿主重启后、某会话真正开跑之前"为空
|
|
8468
8470
|
* ⇒ 落到文件尾那条**便宜**的误判方向(报"没跑"),不会被拿来藏会话。
|
|
8469
8471
|
*
|
|
8472
|
+
* ## 🔴🔴 D244(2026-10-24):**⛔ `agent/turn-stopping` 不得清表**
|
|
8473
|
+
*
|
|
8474
|
+
* **它看起来最像"收轮",其实不是。** 宿主 `AgentLoop.turn()` 原文:
|
|
8475
|
+
*
|
|
8476
|
+
* ```js
|
|
8477
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
8478
|
+
* await this.dispatch.serial("agent/turn-stopping", { turn, signal }) // 暂停点
|
|
8479
|
+
* signal.throwIfAborted()
|
|
8480
|
+
* }
|
|
8481
|
+
* if (turnEnds && this.inbox.nextStep.length === 0) break // 只有 nextStep 空才真结束
|
|
8482
|
+
* target = "next-step" // 否则同轮继续
|
|
8483
|
+
* ```
|
|
8484
|
+
*
|
|
8485
|
+
* ⇒ 任何监听者在 `turn-stopping` 回调里 `steer`(往 `next-step` 塞消息)⇒ **同一轮继续跑**,
|
|
8486
|
+
* 而 `turn/start` **不重发**、`agent/status` 也**不重发**(状态没变,还是 running)。
|
|
8487
|
+
*
|
|
8488
|
+
* 🔴 **实测代价**(真机 2026-10-24 18:1x):`trajectory/rebuild.ts` 的 auto-continue 正是
|
|
8489
|
+
* 在 `turn-stopping` 里 `agent.steer(...)` ⇒ 一条轮**连跑 9 分钟**
|
|
8490
|
+
* (`turn/start(32)` 18:12:00 → `turn/end(32)` 18:21:27),而本表**整段时间都是空的**
|
|
8491
|
+
* ⇒ `interrupted` 的排除失效 ⇒ `steer` 被塞进**正在跑的那一轮**(对它是误导)。
|
|
8492
|
+
* ⚠️ D243 之前这一格被 `agent/created` 的假置位**掩盖**;D243 删掉它 ⇒ 洞才暴露。
|
|
8493
|
+
* ⇒ **D243 的方向是对的,它只是揭开了一个更老的语义误解**。
|
|
8494
|
+
*
|
|
8495
|
+
* ⇒ **清理只认 `agent/status = idle`**(`kick()` 的 `finally` 一定 `setPhase({kind:'idle'})`)。
|
|
8496
|
+
* ⚠️ **已知窄代价**:`runMaintenance` 期间 `status` 也是 `idle` ⇒ 压缩等内部工作时不置位。
|
|
8497
|
+
* 判可接受:那时没有 CRO 该救的轮;方向落到那条**便宜**的误判(报"没跑")。
|
|
8498
|
+
* ⛔ **不要把 `clearTurnRunning` 加回 `turn-stopping`** —— 等于重新打开那个 9 分钟的洞。
|
|
8499
|
+
*
|
|
8470
8500
|
* ## 🔴 清理设计(**这是本模块最容易被做坏的地方**)
|
|
8471
8501
|
*
|
|
8472
8502
|
* ⚠️ 本容器栽过 **5 次**「只增不清」的病(wake-registry / keeper 三表 / pendingRebuilds …),
|
|
8473
8503
|
* 而 `agent/disposed` 在 `HOST_EVENTS` 里的 impact 原文**恰好就是**
|
|
8474
8504
|
* 「*per-会话内存态不清理(**长跑泄漏**)*」⇒ **本模块属于该已登记病族,必须在设计期就带清理**。
|
|
8475
8505
|
*
|
|
8476
|
-
*
|
|
8477
|
-
* 1. **正常路径**:`
|
|
8506
|
+
* **三层清理**(D244 起第 1 层已从 `turn-stopping` 换成 `status=idle`):
|
|
8507
|
+
* 1. **正常路径**:`agent/status = idle` ⇒ 清除该项;
|
|
8478
8508
|
* 2. **载体销毁**:`agent/disposed` / `session/disposed` ⇒ 清除该项;
|
|
8479
8509
|
* 3. 🔴 **异常路径(前两层都漏掉的兜底)**:读取时按 **TTL** 过滤——
|
|
8480
|
-
* 超过 `TURN_TTL_MS`
|
|
8481
|
-
* 触发场景:进程被 kill / 卡死 /
|
|
8510
|
+
* 超过 `TURN_TTL_MS` 未刷新的项**视为不在跑**(并顺手清除)。
|
|
8511
|
+
* 触发场景:进程被 kill / 卡死 / 事件丢失(这些情况下 `idle` 永远不会来)。
|
|
8482
8512
|
*
|
|
8483
8513
|
* ## TTL 取值的**方向性判据**(R↓,别用"感觉合适")
|
|
8484
8514
|
*
|
|
@@ -8488,7 +8518,7 @@ function registerDisposer(ctx, label, cleanup) {
|
|
|
8488
8518
|
* 「漏排是硬故障、多排是常态成本」)。
|
|
8489
8519
|
*
|
|
8490
8520
|
* ⇒ **不确定时宁可报"没跑"**(选便宜的那种错)⇒ **TTL 不宜过长**。
|
|
8491
|
-
* 取 **30 分钟**:单轮静默超过 30 分钟仍无 `
|
|
8521
|
+
* 取 **30 分钟**:单轮静默超过 30 分钟仍无 `idle` 的情形罕见;
|
|
8492
8522
|
* 而真发生了,代价只是一次可能多余的唤起(5min tick 限制频率,且 CRO 程序自己还能看
|
|
8493
8523
|
* `pendingWakes` / `lastWakeAt` 二次判)。
|
|
8494
8524
|
*
|
|
@@ -8604,10 +8634,6 @@ function registerCroTurnTracking(ctx) {
|
|
|
8604
8634
|
if (p.status === "running") markTurnRunning(id, Date.now());
|
|
8605
8635
|
else if (p.status === "idle") clearTurnRunning(id);
|
|
8606
8636
|
});
|
|
8607
|
-
on("agent/turn-stopping", (payload) => {
|
|
8608
|
-
const id = sessionIdOf$2(payload?.agent);
|
|
8609
|
-
if (id) clearTurnRunning(id);
|
|
8610
|
-
});
|
|
8611
8637
|
on("agent/disposed", (payload) => {
|
|
8612
8638
|
const id = sessionIdOf$2(payload?.agent);
|
|
8613
8639
|
if (id) clearTurnRunning(id);
|
|
@@ -9143,6 +9169,33 @@ function readTailEvents(logPath, want) {
|
|
|
9143
9169
|
*/
|
|
9144
9170
|
const UNFINISHED_KINDS = /* @__PURE__ */ new Set(["interrupted", "aborted"]);
|
|
9145
9171
|
/**
|
|
9172
|
+
* 尾部窗口里**最后一条 `turn/start` 的轮号**(D244 输出面,给调用方做 A108 的现场判断)。
|
|
9173
|
+
*
|
|
9174
|
+
* ⚠️ 与 {@link detectUnclosedTurn} 不同:**它不判"是否未收尾"**,只**如实报告窗口里有什么**。
|
|
9175
|
+
* 分开的理由:前者是判据(会 deciding 命中),后者是读数(零判据风险)——
|
|
9176
|
+
* 把读数塞进判据函数,调用方就没法在"判据因为窗口截断而沉默"时看出**为什么**沉默。
|
|
9177
|
+
*
|
|
9178
|
+
* @returns 轮号;窗口内没有 `turn/start` 或轮号缺失 ⇒ `null`
|
|
9179
|
+
*/
|
|
9180
|
+
function lastTurnStartTurnOf(events) {
|
|
9181
|
+
for (let i = events.length - 1; i >= 0; i--) if (events[i]?.type === "turn/start") {
|
|
9182
|
+
const t = events[i]?.data?.turn;
|
|
9183
|
+
return typeof t === "number" ? t : null;
|
|
9184
|
+
}
|
|
9185
|
+
return null;
|
|
9186
|
+
}
|
|
9187
|
+
/**
|
|
9188
|
+
* 尾部窗口里**最后一条 `turn/end` 的 `reason.kind`**(同上:读数,不是判据)。
|
|
9189
|
+
* @returns kind;窗口内没有 `turn/end`(形态 B 的典型)或 kind 缺失 ⇒ `null`
|
|
9190
|
+
*/
|
|
9191
|
+
function lastTurnEndKindOf(events) {
|
|
9192
|
+
for (let i = events.length - 1; i >= 0; i--) if (events[i]?.type === "turn/end") {
|
|
9193
|
+
const k = events[i]?.data?.reason?.kind;
|
|
9194
|
+
return typeof k === "string" ? k : null;
|
|
9195
|
+
}
|
|
9196
|
+
return null;
|
|
9197
|
+
}
|
|
9198
|
+
/**
|
|
9146
9199
|
* 命中判定(**形态 A:靠 `turn/end.reason.kind`**)。
|
|
9147
9200
|
*
|
|
9148
9201
|
* ⚠️ **本函数刻意不判「开了没收」** —— 那种形态与「此刻正在跑」在日志读数上**不可分**,
|
|
@@ -9312,7 +9365,9 @@ function scanInterruptedSessions(projectDir, options = {}) {
|
|
|
9312
9365
|
* 实测(本机 10 个窗口内会话):**4 条**只能由形态 B 抓到(末条 `tool/call` / `step/start`),
|
|
9313
9366
|
* 修前全落到 `skipped`。
|
|
9314
9367
|
*/
|
|
9315
|
-
const
|
|
9368
|
+
const formA = detectInterruptedEnd(events);
|
|
9369
|
+
const formB = formA === null ? detectUnclosedTurn(events) : null;
|
|
9370
|
+
const hit = formA ?? formB;
|
|
9316
9371
|
if (!hit) {
|
|
9317
9372
|
result.skipped++;
|
|
9318
9373
|
continue;
|
|
@@ -9324,7 +9379,10 @@ function scanInterruptedSessions(projectDir, options = {}) {
|
|
|
9324
9379
|
turn: hit.turn,
|
|
9325
9380
|
lastEventAt: typeof tailTime === "number" ? tailTime : null,
|
|
9326
9381
|
mtimeMs,
|
|
9327
|
-
pendingTools: hit.pendingTools
|
|
9382
|
+
pendingTools: hit.pendingTools,
|
|
9383
|
+
form: formA !== null ? "interrupted-end" : "unclosed",
|
|
9384
|
+
lastTurnEndKind: lastTurnEndKindOf(events),
|
|
9385
|
+
lastTurnStartTurn: lastTurnStartTurnOf(events)
|
|
9328
9386
|
});
|
|
9329
9387
|
} catch (err) {
|
|
9330
9388
|
result.errors.push({
|
|
@@ -11088,13 +11146,20 @@ function renderInterruptedScan(input) {
|
|
|
11088
11146
|
for (const s of scan.sessions) {
|
|
11089
11147
|
const at = s.lastEventAt !== null ? new Date(s.lastEventAt).toISOString() : "(时间未知)";
|
|
11090
11148
|
lines.push(` · ${s.sessionId}`);
|
|
11149
|
+
const formText = s.form === "unclosed" ? "未收尾(窗口内无 turn/end)" : "有收尾(reason.kind 命中)";
|
|
11091
11150
|
lines.push(` 轮次 turn=${s.turn ?? "?"} · 末事件 ${at}`);
|
|
11151
|
+
lines.push(` 形态 ${s.form} = ${formText} · 末条 turn/end.kind=${s.lastTurnEndKind ?? "null"} · 窗口内最后 turn/start=${s.lastTurnStartTurn ?? "null"}`);
|
|
11092
11152
|
if (s.pendingTools.length > 0) lines.push(` ⚠️ 有调用无结果的工具(副作用未知): ${s.pendingTools.join(", ")}`);
|
|
11093
11153
|
}
|
|
11094
11154
|
lines.push("");
|
|
11095
|
-
lines.push("
|
|
11155
|
+
lines.push("判据(两种形态,任一命中即收):");
|
|
11156
|
+
lines.push(" A `interrupted-end`: 末条 `turn/end.reason.kind` ∈ {interrupted, aborted},且其后没有新的 turn/start。");
|
|
11096
11157
|
lines.push(" interrupted = 进程崩溃后被 resume 补收尾 / aborted = 被正常终止(重启、销毁、用户中断)。");
|
|
11097
|
-
lines.push("
|
|
11158
|
+
lines.push(" B `unclosed`: 窗口内最后一条 `turn/start` 之后没有 `turn/end`(进程在轮内任意位置死掉,宿主来不及写收尾)。");
|
|
11159
|
+
lines.push("🔴 **怎么用「形态」判该不该续**(调用方口径,ACC 不代判):");
|
|
11160
|
+
lines.push(" · 投递方式 `cold-resume` ⇒ 该会话**未被载入本进程**(死着)⇒ 真需要接走;");
|
|
11161
|
+
lines.push(" · 投递方式 `live+steer` ⇒ 它在进程里 ⇒ ⚠️ **「载入」≠「空闲」**:");
|
|
11162
|
+
lines.push(" `form=unclosed` 且 `窗口内最后 turn/start` == 命中 turn ⇒ 可能**那一轮正在跑** ⇒ 先核再注入。");
|
|
11098
11163
|
lines.push("🔴 本动作**只读** —— 是否续跑、续哪条、几点续,由调用方(你的认知轨迹)决定。");
|
|
11099
11164
|
lines.push("⚠️ 未完成的工具调用「副作用未知」≠「没执行」:它们可能已写文件 / 已发消息。");
|
|
11100
11165
|
lines.push(" 续跑前应先核对副作用,而不是把那一轮整段重放。");
|
|
@@ -82,6 +82,30 @@ export interface InterruptedSession {
|
|
|
82
82
|
mtimeMs: number;
|
|
83
83
|
/** 该轮里"有 call 无 result"的工具名(**尽力而为**,可能因尾部窗口截断而不全) */
|
|
84
84
|
pendingTools: string[];
|
|
85
|
+
/**
|
|
86
|
+
* 🔴 **命中形态**(D244 输出面,guard 2026-10-24 要的判别式 Y 的一半)。
|
|
87
|
+
*
|
|
88
|
+
* | 值 | 含义 | 末条 `turn/end` |
|
|
89
|
+
* |---|---|---|
|
|
90
|
+
* | `interrupted-end` | 形态 A:日志**有** `turn/end`,其 `reason.kind ∈ {interrupted, aborted}` | 存在,见 {@link lastTurnEndKind} |
|
|
91
|
+
* | `unclosed` | 形态 B:**开了没收** —— 窗口内最后一条 `turn/start` 之后没有 `turn/end` | **可能不存在** |
|
|
92
|
+
*
|
|
93
|
+
* 🔴 **为什么必须暴露它**:调用方(如 guard 的守护程序)要能自己判「该不该注入」,
|
|
94
|
+
* 而这两种形态的**可用判据完全不同** —— 形态 B 连"末条 turn/end 的 kind"都不存在。
|
|
95
|
+
* 把决定权留在 ACC 里 = 让调用方没法自己躲开误注入(D237 同族:读数要能改变下一步)。
|
|
96
|
+
*/
|
|
97
|
+
form: 'interrupted-end' | 'unclosed';
|
|
98
|
+
/**
|
|
99
|
+
* 形态 A:末条 `turn/end` 的 `reason.kind`;形态 B:**恒为 `null`**(那时没有末条 turn/end)。
|
|
100
|
+
* ⚠️ `null` 在这里**承载语义**("该格不存在"),不是"没读到"(D233 同族)——
|
|
101
|
+
* 两种成因在读数上不同:形态 B 的 `form` 会是 `unclosed`。
|
|
102
|
+
*/
|
|
103
|
+
lastTurnEndKind: string | null;
|
|
104
|
+
/**
|
|
105
|
+
* 日志尾部窗口里**最后一条 `turn/start` 的轮号**;窗口内没有则 `null`。
|
|
106
|
+
* 与 {@link turn} 对照可看出「窗口是否把这一轮的起点挤出去了」(A108 的现场判据)。
|
|
107
|
+
*/
|
|
108
|
+
lastTurnStartTurn: number | null;
|
|
85
109
|
}
|
|
86
110
|
/** 扫描结果 */
|
|
87
111
|
export interface ScanResult {
|
|
@@ -99,6 +123,21 @@ export interface ScanResult {
|
|
|
99
123
|
/** 扫描耗时(ms) */
|
|
100
124
|
elapsedMs: number;
|
|
101
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* 尾部窗口里**最后一条 `turn/start` 的轮号**(D244 输出面,给调用方做 A108 的现场判断)。
|
|
128
|
+
*
|
|
129
|
+
* ⚠️ 与 {@link detectUnclosedTurn} 不同:**它不判"是否未收尾"**,只**如实报告窗口里有什么**。
|
|
130
|
+
* 分开的理由:前者是判据(会 deciding 命中),后者是读数(零判据风险)——
|
|
131
|
+
* 把读数塞进判据函数,调用方就没法在"判据因为窗口截断而沉默"时看出**为什么**沉默。
|
|
132
|
+
*
|
|
133
|
+
* @returns 轮号;窗口内没有 `turn/start` 或轮号缺失 ⇒ `null`
|
|
134
|
+
*/
|
|
135
|
+
export declare function lastTurnStartTurnOf(events: SessionEvent[]): number | null;
|
|
136
|
+
/**
|
|
137
|
+
* 尾部窗口里**最后一条 `turn/end` 的 `reason.kind`**(同上:读数,不是判据)。
|
|
138
|
+
* @returns kind;窗口内没有 `turn/end`(形态 B 的典型)或 kind 缺失 ⇒ `null`
|
|
139
|
+
*/
|
|
140
|
+
export declare function lastTurnEndKindOf(events: SessionEvent[]): string | null;
|
|
102
141
|
/**
|
|
103
142
|
* 命中判定(**形态 A:靠 `turn/end.reason.kind`**)。
|
|
104
143
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|