@shgroup/dsh-serenity-hooks 1.31.12 → 1.31.13
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/README.md
CHANGED
|
@@ -317,7 +317,7 @@ AI 一次能"记住"的内容有上限。满了不用你手动开新会话:
|
|
|
317
317
|
|
|
318
318
|
```bash
|
|
319
319
|
pnpm typecheck # 类型检查(node + 浏览器端两套)
|
|
320
|
-
pnpm test # 全量测试(当前 80 个文件 /
|
|
320
|
+
pnpm test # 全量测试(当前 80 个文件 / 1186 个用例)
|
|
321
321
|
pnpm build # 打包(lib/index.js + client.js)
|
|
322
322
|
```
|
|
323
323
|
|
|
@@ -327,6 +327,9 @@ pnpm build # 打包(lib/index.js + client.js)
|
|
|
327
327
|
⚠️ **改完 `package.json` 依赖后必须跑 `lockfile` 并提交锁文件** —— v1.31.6 漏了这一步,CI 从此一直红(红在"测试根本没跑",见 CHANGELOG v1.31.10)
|
|
328
328
|
- **宿主类型基准 = devDependencies**(v1.31.11):`tsconfig.json` / `client/tsconfig.json` 的 `paths` 指向**仓库内** `node_modules/@deepseek-ai/*`,由精确钉版的 devDependencies 提供 → **新增 paths 条目必须同时加 devDependency**(否则 tsc 静默回落 node_modules = 假绿,CI 的 typecheck 也就形同虚设)。机械闸门见 `tests/compliance.test.ts` F7;升级宿主时只改 `package.json` 一处 + 重跑 `lockfile`
|
|
329
329
|
- **升级宿主(v1.31.12)**:`dsh-develop host-upgrade <版本|dist-tag>` 全局升级 DSH CLI(包名硬编码 `@deepseek-ai/dsh`、默认官方源、`--dry-run` 预览)→ 把 `package.json` 的 peer/devDeps 基准抬到同一版本 + 重跑 `lockfile`(`compliance.test.ts` F6c/F7d 与 `host-manifest.test.ts` 会强制各声明面同步,漏一处必红)→ `restart-web` → `dashboard health` 看 `dshVersion`
|
|
330
|
+
- **诊断会话打不开(v1.31.13)**:`dsh-develop session-doctor` —— 会话日志体检(**只读**)。
|
|
331
|
+
`--probe` 走宿主**真实读取路径**判定(`msm("dsh-develop", ["session-doctor","--probe","--summary"])` 可全量跑)。
|
|
332
|
+
⚠️ 两个易误读点:① `readStoredLog` 是**存储层**读、**不走格式迁移** ⇒ 它对历史世代报 "no upgrade path" 属**正常现象**,不是损坏证据;判断"用户能不能打开"要看应用面 `open(id,'read')`。② 本工具修的两个真实缺陷见 CHANGELOG v1.31.13(`rebuild` 写的 `user/message` 缺 `id`/`role` ⇒ 会话**永久打不开**;`findSessionLog` 不认 `session.vN.jsonl.zstd` ⇒ 清理静默失效)
|
|
330
333
|
- **架构**:Cordis 原生插件,用 DSH 的正式接口注册工具和拦截点——**从不修改 DSH 本体**
|
|
331
334
|
- **代码地图**:[docs/codebase-overview-v1.22.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/docs/codebase-overview-v1.22.md)
|
|
332
335
|
- **设计决策**:见 [CHANGELOG.md](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/CHANGELOG.md) 和维护技能 `dsh-serenity-plugin-development`
|
|
@@ -385,4 +388,4 @@ pnpm build # 打包(lib/index.js + client.js)
|
|
|
385
388
|
|
|
386
389
|
MIT(见 [LICENSE](https://github.com/tellmewhattodo/dsh-serenity-plugin/blob/master/LICENSE))
|
|
387
390
|
|
|
388
|
-
> **版本**:v1.31.
|
|
391
|
+
> **版本**:v1.31.13 | **前置**:DSH 0.1.5-rc.2+ / Node ≥ 20 或 bun | **测试**:80 个文件 / 1186 个用例
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.13",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.5-rc.2(deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -7341,7 +7341,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7341
7341
|
const id = s.id ?? s.header?.id;
|
|
7342
7342
|
if (id) liveIds.add(id);
|
|
7343
7343
|
}
|
|
7344
|
-
const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./session-cleanup-
|
|
7344
|
+
const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./session-cleanup-CMT4mHWt.js");
|
|
7345
7345
|
const root = sessionsRootDir();
|
|
7346
7346
|
if (req.method === "GET") {
|
|
7347
7347
|
const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
|
|
@@ -8654,6 +8654,22 @@ function performRebuild(session, pending, meter) {
|
|
|
8654
8654
|
});
|
|
8655
8655
|
}
|
|
8656
8656
|
session.append("user/message", {
|
|
8657
|
+
/**
|
|
8658
|
+
* v1.31.13 修复(**会话打不开的真因**,S142 §26):
|
|
8659
|
+
* `user/message` 的 payload 必须是**完整 UserMessage**——宿主
|
|
8660
|
+
* `dsh-session/lib/types/index.js:229-259` `assertMessageEventShape` 对四种消息事件
|
|
8661
|
+
* (system/user/assistant/message + tool/result)强制:
|
|
8662
|
+
* `data.id` 非空字符串 → 否则 `"<subject> lacks an identified message"`
|
|
8663
|
+
* `data.role === 'user'` → 否则 `"<subject> message must have role \"user\""`
|
|
8664
|
+
* 而 `user/message` 的 message **就是 data 本身**(`type==='user/message' ? record : record.message`)。
|
|
8665
|
+
*
|
|
8666
|
+
* 旧实现只写 `{content, source}`(缺 `id` 与 `role`)→ 该事件**当场落盘成功**,
|
|
8667
|
+
* 但宿主在**下一次打开该会话**时整份日志校验失败抛 `SessionPersistenceCorruptionError`
|
|
8668
|
+
* → **会话永久打不开**(用户报的"会话老是损坏")。实证见 S142 §26.2(真实日志 seq 5066)。
|
|
8669
|
+
* 正确形状与 `seams/bootstrap.ts:306-311` 的锚定消息一致。
|
|
8670
|
+
*/
|
|
8671
|
+
id: `rebuild-anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
8672
|
+
role: "user",
|
|
8657
8673
|
content: [{
|
|
8658
8674
|
type: "text",
|
|
8659
8675
|
text: pending.anchor
|
|
@@ -6,7 +6,9 @@ import { join } from "node:path";
|
|
|
6
6
|
*
|
|
7
7
|
* 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
|
|
8
8
|
* base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
|
|
9
|
-
* <root>/--<project-slug>--/<encoded-session-id>/session.jsonl(.zstd)
|
|
9
|
+
* <root>/--<project-slug>--/<encoded-session-id>/session[.vN].jsonl(.zstd)
|
|
10
|
+
* **世代(v1.31.13 补)**:宿主自 DSH 0.1.5 起按世代写文件,且迁移**保留源文件**
|
|
11
|
+
* ⇒ 一个会话目录常见 v0 与 v3 两个文件并存。本模块用 `sessionLogArtifacts()` 统一枚举世代。
|
|
10
12
|
* DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
|
|
11
13
|
* 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
|
|
12
14
|
* 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
|
|
@@ -39,15 +41,17 @@ function collectEligibleSessions(root, cutoffMs, liveIds = /* @__PURE__ */ new S
|
|
|
39
41
|
const projectPath = join(root, project.name);
|
|
40
42
|
for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
|
|
41
43
|
if (!entry.isDirectory()) continue;
|
|
42
|
-
const
|
|
44
|
+
const dirPath = join(projectPath, entry.name);
|
|
45
|
+
const artifacts = sessionLogArtifacts(dirPath);
|
|
46
|
+
if (artifacts.length === 0) continue;
|
|
47
|
+
const log = findSessionLog(dirPath);
|
|
43
48
|
if (log === null) continue;
|
|
44
49
|
if (liveIds.has(entry.name)) continue;
|
|
45
|
-
let mtime;
|
|
46
|
-
try {
|
|
47
|
-
mtime = statSync(
|
|
48
|
-
} catch {
|
|
49
|
-
|
|
50
|
-
}
|
|
50
|
+
let mtime = Number.NEGATIVE_INFINITY;
|
|
51
|
+
for (const artifact of artifacts) try {
|
|
52
|
+
mtime = Math.max(mtime, statSync(artifact.path).mtimeMs);
|
|
53
|
+
} catch {}
|
|
54
|
+
if (!Number.isFinite(mtime)) continue;
|
|
51
55
|
if (mtime >= cutoffMs) continue;
|
|
52
56
|
out.push({
|
|
53
57
|
id: entry.name,
|
|
@@ -59,13 +63,46 @@ function collectEligibleSessions(root, cutoffMs, liveIds = /* @__PURE__ */ new S
|
|
|
59
63
|
}
|
|
60
64
|
return out;
|
|
61
65
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* 会话日志文件名识别:`session.jsonl` / `session.jsonl.zstd`(= **v0 世代**)/
|
|
68
|
+
* `session.v<N>.jsonl[.zstd]`(= 第 N 世代,DSH 0.1.5 起按世代写文件)。
|
|
69
|
+
* 出处:`@deepseek-ai/dsh-session-persistence-jsonl` 的 `parseSessionFormatLogFilename` 同款形态。
|
|
70
|
+
*/
|
|
71
|
+
const SESSION_LOG_RE = /^session(?:\.v(\d+))?\.jsonl(\.zstd)?$/;
|
|
72
|
+
/**
|
|
73
|
+
* 列出会话目录下的**全部日志世代**,按代递增排序(同代内 `.jsonl` 在前、`.zstd` 在后 = 后者更代表"当前书写格式")。
|
|
74
|
+
*
|
|
75
|
+
* 为什么需要(R↓,v1.31.13 修复):宿主自 0.1.5 起改成**按世代写文件**,且迁移**保留源文件**
|
|
76
|
+
* ⇒ 同一会话常见 `session.jsonl.zstd`(v0)与 `session.v3.jsonl.zstd`(v3)**并存**。
|
|
77
|
+
* 旧实现只认前两个名字 → 新格式会话对本模块**完全不可见**(静默 no-op:清理永远不生效、磁盘只涨)。
|
|
78
|
+
*/
|
|
79
|
+
function sessionLogArtifacts(sessionDirPath) {
|
|
80
|
+
let names;
|
|
81
|
+
try {
|
|
82
|
+
names = readdirSync(sessionDirPath);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
67
85
|
}
|
|
68
|
-
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const name of names) {
|
|
88
|
+
const m = SESSION_LOG_RE.exec(name);
|
|
89
|
+
if (m === null) continue;
|
|
90
|
+
out.push({
|
|
91
|
+
name,
|
|
92
|
+
generation: m[1] === void 0 ? 0 : Number(m[1]),
|
|
93
|
+
path: join(sessionDirPath, name)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return out.sort((a, b) => a.generation - b.generation || (a.name.endsWith(".zstd") ? 1 : 0) - (b.name.endsWith(".zstd") ? 1 : 0));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 在会话目录下找 session 日志文件;无 → null。
|
|
100
|
+
* 多世代并存时返回**最高世代**(= 宿主 `findLog` 选中的当前世代;旧行为对单文件目录完全一致)。
|
|
101
|
+
*/
|
|
102
|
+
function findSessionLog(sessionDirPath) {
|
|
103
|
+
const artifacts = sessionLogArtifacts(sessionDirPath);
|
|
104
|
+
const latest = artifacts[artifacts.length - 1];
|
|
105
|
+
return latest === void 0 ? null : latest.path;
|
|
69
106
|
}
|
|
70
107
|
/**
|
|
71
108
|
* 执行清理(用户拍板:直接物理删,不过滤不归档;dryRun 预览不删)。
|
package/lib/session-cleanup.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
|
|
5
5
|
* base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
|
|
6
|
-
* <root>/--<project-slug>--/<encoded-session-id>/session.jsonl(.zstd)
|
|
6
|
+
* <root>/--<project-slug>--/<encoded-session-id>/session[.vN].jsonl(.zstd)
|
|
7
|
+
* **世代(v1.31.13 补)**:宿主自 DSH 0.1.5 起按世代写文件,且迁移**保留源文件**
|
|
8
|
+
* ⇒ 一个会话目录常见 v0 与 v3 两个文件并存。本模块用 `sessionLogArtifacts()` 统一枚举世代。
|
|
7
9
|
* DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
|
|
8
10
|
* 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
|
|
9
11
|
* 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
|
|
@@ -35,7 +37,22 @@ export interface CandidateSession {
|
|
|
35
37
|
* root 不存在/为空 → 返回 [](非错误)。
|
|
36
38
|
*/
|
|
37
39
|
export declare function collectEligibleSessions(root: string, cutoffMs: number, liveIds?: ReadonlySet<string>): CandidateSession[];
|
|
38
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* 列出会话目录下的**全部日志世代**,按代递增排序(同代内 `.jsonl` 在前、`.zstd` 在后 = 后者更代表"当前书写格式")。
|
|
42
|
+
*
|
|
43
|
+
* 为什么需要(R↓,v1.31.13 修复):宿主自 0.1.5 起改成**按世代写文件**,且迁移**保留源文件**
|
|
44
|
+
* ⇒ 同一会话常见 `session.jsonl.zstd`(v0)与 `session.v3.jsonl.zstd`(v3)**并存**。
|
|
45
|
+
* 旧实现只认前两个名字 → 新格式会话对本模块**完全不可见**(静默 no-op:清理永远不生效、磁盘只涨)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function sessionLogArtifacts(sessionDirPath: string): Array<{
|
|
48
|
+
name: string;
|
|
49
|
+
generation: number;
|
|
50
|
+
path: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* 在会话目录下找 session 日志文件;无 → null。
|
|
54
|
+
* 多世代并存时返回**最高世代**(= 宿主 `findLog` 选中的当前世代;旧行为对单文件目录完全一致)。
|
|
55
|
+
*/
|
|
39
56
|
export declare function findSessionLog(sessionDirPath: string): string | null;
|
|
40
57
|
/** 删除执行结果 */
|
|
41
58
|
export interface CleanupResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.13",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.5-rc.2。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|