@stardustlc/dsh-dream 0.5.0 → 0.5.1
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/CHANGELOG.md +9 -0
- package/README.en.md +12 -7
- package/README.md +12 -7
- package/lib/client.js +54 -6
- package/lib/journal.d.ts +15 -0
- package/lib/journal.js +32 -11
- package/lib/knowledge-store.d.ts +92 -7
- package/lib/knowledge-store.js +815 -187
- package/lib/knowledge.d.ts +26 -3
- package/lib/knowledge.js +36 -5
- package/lib/retrieval.d.ts +35 -7
- package/lib/retrieval.js +130 -18
- package/lib/tools.js +49 -7
- package/lib/web.d.ts +4 -1
- package/lib/web.js +2 -1
- package/package.json +1 -1
- package/skills/dream-protocol/SKILL.md +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1(2026-09-29)
|
|
4
|
+
|
|
5
|
+
- **修复两个已发布缺陷**(0.5.0 起):① 日记文件开头有 UTF-8 BOM 时整份日记被读空;② 心境聚合用普通对象做映射,`__proto__` 键被吞、`constructor` 等键会把计数变成字符串(`"function Object()…1"`)。现在 BOM 会被剥离,聚合用无原型对象后返回普通副本,并新增 `skippedLines` 让坏行可诊断。
|
|
6
|
+
- **检索策略修正(R1/R3/R4/R5)**:`dream_context` 默认不注入候选(`candidate-hold`)与无证据条目(`no-evidence`);`applicability.versions` 只在包名被识别时参与判定,版本不满足记 `version-mismatch`;预算改为按排名整条装入、遇第一条放不下即停(`budget-stop`,`rankInversionCount` 恒 0),条件与例外永不截断;`RetrievedLesson` 增加 `revision`(`dream_review` 的 `expectedRevision` 因此可达),`dream_learn.evidence` 改为可选。
|
|
7
|
+
- **证据纪律(R2′)**:任何 `verification:"read"` 的证据让经验初始即为 `usable`;其中用户明确纠正记 `review{decision:accepted, actor:user}`,其余 read 记 `unreviewed`;只有 `claimed` 证据或无证据才停在 `candidate`。SKILL.md 同时写明「只有真的读到来源才能标 read,自我判断一律 claimed」,防止模型自称 read 自我提权。
|
|
8
|
+
- **存储可诊断与前向兼容(R6/R7)**:`stats()` 拆分 `orphanEvents` / `firstReplayedEventId` / `unsupportedVersions`(与 `badLines` 分开)并给出 `replayMode`;`index.json` 增加 checkpoint(偏移 + 前缀哈希 + 末事件 id,校验不符回退全量回放),截断不再静默丢实体;`validateLesson(value, options?)` 支持 `accept`/`migrate` 与 `LESSON_MIGRATIONS` 占位;事件增加 `requestHash`(同幂等键不同内容 → `duplicate` 错误而非静默返回旧快照);新增只读 `diagnose()`;文件锁增加 `bootId` 与心跳,超限给可操作错误而不静默抢占。
|
|
9
|
+
- **面板**:经验卡显示适用条件与例外;`truncated` 或存在坏行时显示提示行并注明 `events.jsonl` 未被改动。
|
|
10
|
+
- **验收**:L1 七场景断言(S1/S5 命中、S2/S3/S4/S6/S7 零注入、`rankInversionCount=0`)**9/9**;证据纪律 5/5;旧行为基线 4/4(旧日记与既有六工具输出只增不减、只读零写入);全量 **204 项 / 203 通过 / 1 默认跳过**(并发压测需 `DSH_DREAM_STRESS=1`,8 进程 × 8 次实测不丢);`tsc`、`node --check lib/client.js`、官方加载器回归通过。
|
|
11
|
+
- **已知限制(未达标,下一批修)**:读/写路径在知识库增长时仍是 O(历史)——50k 条证据时单次读操作数百毫秒、逐条追加呈二次增长。checkpoint 已能避免"截断丢实体",但尚未把读路径变成增量。本版**不承诺大知识库下的性能**;下一批计划 `evidenceKeys` 常数时间去重与索引增量更新。
|
|
3
12
|
## 0.5.0(2026-09-29)
|
|
4
13
|
|
|
5
14
|
- **从「反思」升级为「有来源的经验」**:新增 `dream_learn`(提交候选经验 + 证据,校验 / 脱敏 / 去重 / 按项目或全局范围保存)、`dream_context`(任务前取回少量适用经验,只读,默认 5 条 / 3000 字符,硬上限 20 条 / 20000 字符)、`dream_review`(采纳 / 驳回 / 标记过期 / 标记冲突 / 补充证据,强制 `expectedRevision`)。六个既有工具行为不变,输出字段只增不减。
|
package/README.en.md
CHANGED
|
@@ -8,13 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
- **Fixes a Desktop boot failure**: the front-end module id now matches the scoped package name (it used to register as `dsh-dream`, so the host retried the bundle and threw `duplicate factory registration`, ending in `1 entry did not activate`). No journal or config migration.
|
|
10
10
|
|
|
11
|
+
## 0.5.1 update (2026-09-29)
|
|
12
|
+
|
|
13
|
+
- **Fixes**: a leading UTF-8 BOM used to hide the whole journal; mood keys such as `__proto__`/`constructor` corrupted counts. **Retrieval discipline**: candidates and evidence-free lessons are not injected by default, version checks need package context, and the budget packs by rank. **Evidence discipline**: lessons backed by read evidence start usable; model-claimed only stays candidate.
|
|
14
|
+
- 204 tests (203 passing, 1 opt-in concurrency stress test skipped); L1 acceptance 9/9. Known limit: reads/writes still grow with history on very large libraries.
|
|
15
|
+
|
|
11
16
|
## 0.5.0 update (2026-09-29)
|
|
12
17
|
|
|
13
18
|
- **Three new tools**: `dream_learn` (submit an evidence-backed candidate lesson), `dream_context` (retrieve a few applicable lessons before a task; read-only; 5 items / 3000 characters by default) and `dream_review` (accept / reject / mark stale or disputed / attach evidence; every mutation requires a revision).
|
|
14
19
|
- **New data directory `<journalDir>/knowledge/`**: `events.jsonl` is the authoritative append-only log, `evidence.jsonl` appends evidence, and `index.json` is a disposable derived index. The old `dreams.jsonl` stays read-compatible and is never rewritten.
|
|
15
20
|
- **Masking and scope guarantees**: every new write path runs through `mask.ts` when `maskSecrets` is on; evidence stores only masked one-line summaries plus locators — never hidden reasoning, system prompts, tool arguments or result bodies. Lessons are scoped to a project or global; with an unknown project `dream_context` returns global lessons only and never scans across projects.
|
|
16
21
|
- **Compatibility**: the old journal and the six existing tools (dream_digest / dream_save / dream_journal / dream_recall / dream_bridge / dream_health) keep their behaviour and output fields only grow. No migration is required and no old data is rewritten.
|
|
17
|
-
- **New read-only "Lessons" block** in the panel: state (candidate / usable / disputed / stale / rejected), applicability, scope, evidence summary and last-validated time. Candidates and disputed lessons are visually distinct, and an empty knowledge store has its own empty state. The panel still has no write actions.
|
|
22
|
+
- **New read-only "Lessons" block** in the panel: state (candidate / usable / disputed / stale / rejected), applicability, scope, applicability details (package / versions / platform) and exceptions (only when non-empty), evidence summary and last-validated time. Candidates and disputed lessons are visually distinct, and an empty knowledge store has its own empty state. When the event log is truncated or has bad lines the block shows a notice and says events.jsonl was left untouched. The panel still has no write actions.
|
|
18
23
|
|
|
19
24
|
## 0.4.0 update (2026-09-29)
|
|
20
25
|
|
|
@@ -26,7 +31,7 @@ Humans consolidate memories by replaying the day during sleep — dsh-dream give
|
|
|
26
31
|
|
|
27
32
|
## Compatibility
|
|
28
33
|
|
|
29
|
-
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-29.
|
|
34
|
+
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-29. The 18-plugin joint load was re-run before the 0.5.0 release: contracts `ok`, **99 tools / 35 skills**, no failing plugins (`suite-0.2.6-compat.json`), with this plugin passing 204/169 tests in that run (the 0.4.1 baseline of 112 also passed). The plugin registers 9 tools (the 6 original tools plus M1's dream_learn / dream_context / dream_review) and the `dream-protocol` skill, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round; the panel is reachable on loopback only.
|
|
30
35
|
|
|
31
36
|
Reads v0/v1/v2/v3/v4 plaintext JSONL and multi-frame zstd, legacy packed chunks and embedded streams. Only the newest canonical file is selected per session. PTC child calls retain tool names without double counting; system prompts, reasoning, tool arguments and result bodies are excluded.
|
|
32
37
|
|
|
@@ -55,7 +60,7 @@ Once installed, DSH's **Settings →「梦境日记」(Dream journal)** grows a
|
|
|
55
60
|
- **Data file**: the panel renders the journal itself — ~/.dsh/.dsh-dream/dreams.jsonl by default ($DSH_HOME/.dsh-dream/dreams.jsonl; an explicit journalDir wins). The storage format is unchanged: one JSON object per line, safe to remove or read with dream_journal.
|
|
56
61
|
- **Privacy**: the panel performs no second-pass masking — masking only happens when a dream is written (maskSecrets), and the panel shows exactly what is already in the file. It does not upload, export or make any network request. The privacy mode at the top blurs reflections and lessons in one click, but it is a display-layer effect only (the DOM still holds plaintext), not encryption. Review the journal before sharing your screen or the JSONL.
|
|
57
62
|
- **Before the first dream**: the panel shows an empty state pointing you to ask the agent to「做个梦」; a single dream renders as one card, with neither the mood strip nor the lessons board left empty and no misleading charts.
|
|
58
|
-
- **Lessons block (M1)**: a read-only "Lessons" block shows each technical lesson's state (candidate / usable / disputed / stale / rejected), applicability, scope, evidence summary and last-validated time. Candidates and disputed lessons get distinct colours and borders, and the empty state is separate from the dream empty state. Data comes from the read-only GET /_dsh/dsh-dream/knowledge route (loopback only; non-GET → 405, non-local Host → 403). There are no accept / edit / delete buttons — use `dream_review` to review.
|
|
63
|
+
- **Lessons block (M1)**: a read-only "Lessons" block shows each technical lesson's state (candidate / usable / disputed / stale / rejected), applicability, scope, applicability details and exceptions (exceptions render only when non-empty), evidence summary and last-validated time. Candidates and disputed lessons get distinct colours and borders, and the empty state is separate from the dream empty state. When the event log is truncated or has bad lines, a notice appears saying events.jsonl was left untouched. Data comes from the read-only GET /_dsh/dsh-dream/knowledge route (loopback only; non-GET → 405, non-local Host → 403). There are no accept / edit / delete buttons — use `dream_review` to review.
|
|
59
64
|
- **Roadmap**: record bridge status via a sidecar (which lessons were written into which AGENTS.md, and when). Not in this release — v1 reads no AGENTS.md and adds no state file.
|
|
60
65
|
|
|
61
66
|
## Tools
|
|
@@ -68,8 +73,8 @@ Once installed, DSH's **Settings →「梦境日记」(Dream journal)** grows a
|
|
|
68
73
|
| `dream_recall` | Keyword search across the journal | `query` required |
|
|
69
74
|
| `dream_bridge` | Merge top lessons into AGENTS.md (idempotent marker block) — dreams become long-term memory | `path` required |
|
|
70
75
|
| `dream_health` | Self-check: sessions dir / dream count / config summary | — |
|
|
71
|
-
| `dream_learn` | Submit a candidate technical lesson with evidence (validate / mask / dedupe / scope; evidence-free input stays a candidate) | `kind`, `title`, `action`, `when
|
|
72
|
-
| `dream_context` | Retrieve a few applicable lessons before a task (read-only) | `query` required; `projectId` / `workspaceRoot` / `limit` / `maxChars` / `packageVersion` optional |
|
|
76
|
+
| `dream_learn` | Submit a candidate technical lesson with evidence (validate / mask / dedupe / scope; evidence-free input stays a candidate) | `kind`, `title`, `action`, `when` required; `evidence` optional (defaults to `[]`) |
|
|
77
|
+
| `dream_context` | Retrieve a few applicable lessons before a task (read-only; candidates are held out by default) | `query` required; `projectId` / `workspaceRoot` / `limit` / `maxChars` / `packageName` / `packageVersion` / `includeCandidates` optional |
|
|
73
78
|
| `dream_review` | Review and transition state: accept / reject / mark stale / mark disputed / attach evidence | `action`, `lessonId`, `expectedRevision` required |
|
|
74
79
|
|
|
75
80
|
`dream_bridge` merges the most frequent dream lessons into a target `AGENTS.md` behind idempotent marker blocks; `dream_journal` also reports mood distribution and top lessons.
|
|
@@ -80,8 +85,8 @@ The bundled `dream-protocol` skill teaches the agent when and how to dream (and
|
|
|
80
85
|
|
|
81
86
|
The dream journal holds subjective reflections; technical lessons that should be reused go to `<journalDir>/knowledge/`:
|
|
82
87
|
|
|
83
|
-
- **Candidate → evidence → review**: `dream_learn` stores "when / action / exceptions / scope" together with evidence as a candidate. `verification: read` means the original record was actually read; `claimed` only means the model asserts it (it does not count toward independent support). Use `dream_review` to accept, reject, mark stale or disputed, or attach new evidence — every mutation carries `expectedRevision` and stale writes are rejected.
|
|
84
|
-
- **Budgeted retrieval**: `dream_context` returns at most 5 items / 3000 characters by default (hard caps 20 items / 20000 characters).
|
|
88
|
+
- **Candidate → evidence → review**: `dream_learn` stores "when / action / exceptions / scope" together with evidence as a candidate. `verification: read` means the original record was actually read; `claimed` only means the model asserts it (it does not count toward independent support). Use `dream_review` to accept, reject, mark stale or disputed, or attach new evidence — every mutation carries `expectedRevision` and stale writes are rejected. When the evidence is `kind: "user-correction"` with `verification: "read"`, the lesson starts as `usable` and `review` records `actor: "user"` / `decision: "accepted"` — a user decision outranks model inference; all other evidence starts as `candidate`, and claimed evidence neither counts toward independent support nor overrides a user correction.
|
|
89
|
+
- **Budgeted retrieval**: `dream_context` returns at most 5 items / 3000 characters by default (hard caps 20 items / 20000 characters). Items are packed whole by rank and packing stops at the first item that does not fit (`skipped: budget-stop`); `title / when / action / exceptions` are never truncated, and no half item is ever returned. Candidates and evidence-free non-usable items are held out of task advice by default (`includeCandidates: false`, reported as `candidate-hold` / `no-evidence`); pass `includeCandidates: true` only when explicitly reviewing candidates, and note that candidates rank after usable lessons and must not be treated as verified. Rejected, stale and unresolved disputed lessons are not injected; `applicability.versions` only counts when the package is identified (explicit `packageName` or a package name in the query).
|
|
85
90
|
- **Frequency is not reliability**: resubmitting the same evidence is idempotent; never grind counts by calling `dream_save` / `dream_learn` repeatedly. Repeated occurrences within one session count as a single independent source.
|
|
86
91
|
- **Recoverable storage**: `events.jsonl` is the authoritative append-only event log, `evidence.jsonl` appends evidence and `index.json` is a disposable derived index. Rebuilding the index deletes no source files. When the directory does not exist, the read route returns an empty result and never creates anything.
|
|
87
92
|
- **Removing it**: delete `<journalDir>/knowledge/` to clear lesson memory. The old `dreams.jsonl` journal is unaffected, and older plugin versions can still read it.
|
package/README.md
CHANGED
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
- **修复 Desktop 启动失败**:前端模块注册 ID 现与 scoped 包名一致(此前注册成 `dsh-dream`,宿主会重试加载并抛 `duplicate factory registration`,导致 `1 entry did not activate`)。日记数据与配置不变,无迁移。
|
|
8
8
|
|
|
9
|
+
## 0.5.1 更新(2026-09-29)
|
|
10
|
+
|
|
11
|
+
- **修复**:日记 BOM 导致读空、心境键(`__proto__`/`constructor`)污染计数;**检索纪律**:候选与无证据经验默认不注入、版本判定需要包名上下文、预算按排名整条装入;**证据纪律**:读到来源(read)即为可用,模型自我判断(claimed)停在候选。
|
|
12
|
+
- 全量 **204 项 / 203 通过 / 1 默认跳过**;L1 七场景断言 9/9。已知限制:知识库很大时读/写仍随历史线性增长,本版不承诺大库性能。
|
|
13
|
+
|
|
9
14
|
## 0.5.0 更新(2026-09-29)
|
|
10
15
|
|
|
11
16
|
- **新增三个工具**:`dream_learn`(提交带证据的候选经验)、`dream_context`(任务前按项目 / 版本取回少量适用经验,只读,默认最多 5 条、3000 字符)、`dream_review`(采纳 / 驳回 / 标记过期或冲突 / 补充证据,强制携带 revision)。
|
|
12
17
|
- **新数据目录 `<journalDir>/knowledge/`**:`events.jsonl` 是权威追加日志,`evidence.jsonl` 追加证据,`index.json` 是可丢弃重建的派生索引。旧日记 `dreams.jsonl` 仍只读兼容、永不重写。
|
|
13
18
|
- **脱敏与范围承诺**:所有新写盘路径在 `maskSecrets` 开启时先过 `mask.ts`;证据只保存已脱敏摘要和定位信息,不保存隐藏推理、系统提示、工具参数 / 结果正文。经验按项目或全局范围保存,未知当前项目时 `dream_context` 只返回全局经验,绝不跨项目扫描。
|
|
14
19
|
- **兼容性**:旧日记与六个既有工具(dream_digest / dream_save / dream_journal / dream_recall / dream_bridge / dream_health)行为不变、输出字段只增不减;升级不需要迁移,也不会改写旧数据。
|
|
15
|
-
- **只读面板新增「经验」区块**:展示状态(候选 / 可用 / 有冲突 / 待复核 /
|
|
20
|
+
- **只读面板新增「经验」区块**:展示状态(候选 / 可用 / 有冲突 / 待复核 / 已驳回)、适用条件、范围、适用(package / versions / platform)与例外、证据摘要与最近核验时间;候选与有冲突有明确视觉区分,没有经验时是独立空态;知识日志被截断或有坏行时显示「events.jsonl 未改动」提示。面板仍然没有任何写操作。
|
|
16
21
|
|
|
17
22
|
## 0.4.0 更新(2026-09-29)
|
|
18
23
|
|
|
@@ -26,7 +31,7 @@
|
|
|
26
31
|
|
|
27
32
|
## 兼容性
|
|
28
33
|
|
|
29
|
-
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-29)。
|
|
34
|
+
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-29)。18 插件同载复验已在 0.5.0 发布前完成:契约 `ok`、共 **99 个工具 / 35 个技能**、无失败插件(`suite-0.2.6-compat.json`),当次本插件 169/169 项测试通过(0.4.1 基线 112 项同样全过)。本插件注册 9 个工具(6 个既有工具 + M1 的 dream_learn / dream_context / dream_review)与 `dream-protocol` 技能,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务;面板仅在本机回环地址下可达。
|
|
30
35
|
|
|
31
36
|
支持 v0/v1/v2/v3/v4 的普通 JSONL 与多帧 zstd、旧 packed-chunk 行及内嵌 stream;升级留下多代文件时只读取每个会话的最新规范文件。PTC 子工具调用保留工具名且不重复计数,不提取系统提示、推理、工具参数或结果正文。
|
|
32
37
|
|
|
@@ -62,7 +67,7 @@ dsh plugin --profile web remove @stardustlc/dsh-dream
|
|
|
62
67
|
- **数据文件**:面板读的就是梦境日记本身,默认 ~/.dsh/.dsh-dream/dreams.jsonl($DSH_HOME/.dsh-dream/dreams.jsonl;配置了 journalDir 时以配置为准)。存储格式不变,仍是每行一条 JSON 的 JSONL,可直接删除或用 dream_journal 读取。
|
|
63
68
|
- **隐私**:面板不做二次脱敏——脱敏只发生在入梦写盘时(maskSecrets),面板原样显示文件里已有的内容。它不上传、不导出、不发起任何网络请求;顶部「隐私模式」可一键模糊正文与教训,但它只是浏览器里的显示效果(DOM 中仍是明文),不是加密。共享屏幕或转发 dreams.jsonl 前请自行确认内容。
|
|
64
69
|
- **还没有梦时**:面板显示空态,提示对 agent 说「做个梦」——它会回放最近的会话、反思之后把第一条梦写进这里;只有一场梦时按单条卡片展示,心境方块与教训榜都不留空、不画失真图表。
|
|
65
|
-
- **经验区块(M1)**:面板下方的只读「经验」区块显示技术经验的状态(候选 / 可用 / 有冲突 / 待复核 /
|
|
70
|
+
- **经验区块(M1)**:面板下方的只读「经验」区块显示技术经验的状态(候选 / 可用 / 有冲突 / 待复核 / 已驳回)、适用条件、范围、适用与例外(exceptions 非空才显示)、证据摘要与最近核验时间,候选与冲突有独立配色和边框,空态与梦境空态分开。知识日志被截断(truncated)或有坏行(badLines)时显示一行提示,并注明 events.jsonl 未改动。数据来自只读路由 GET /_dsh/dsh-dream/knowledge(仅回环地址可达;非 GET → 405,非本机 Host → 403),没有采纳 / 编辑 / 删除按钮;审阅请用 dream_review。
|
|
66
71
|
- **后续计划**:用 sidecar 记录桥接状态(哪些教训、何时写进了哪个 AGENTS.md)。本轮不做——v1 不读取 AGENTS.md,也不新增任何状态文件。
|
|
67
72
|
|
|
68
73
|
## 工具一览
|
|
@@ -75,8 +80,8 @@ dsh plugin --profile web remove @stardustlc/dsh-dream
|
|
|
75
80
|
| `dream_recall` | 忆梦:关键词检索梦境 | `query` 必填 |
|
|
76
81
|
| `dream_bridge` | 渡梦:把高频教训幂等合并进 AGENTS.md,梦变成长期记忆 | `path` 必填;`maxLessons` 可选 |
|
|
77
82
|
| `dream_health` | 自检:会话目录/梦境计数/配置汇总 | 无 |
|
|
78
|
-
| `dream_learn` | 提交候选技术经验及证据(校验 / 脱敏 / 去重 / 按范围保存;无证据只能留在候选) | `kind`、`title`、`action`、`when
|
|
79
|
-
| `dream_context` |
|
|
83
|
+
| `dream_learn` | 提交候选技术经验及证据(校验 / 脱敏 / 去重 / 按范围保存;无证据只能留在候选) | `kind`、`title`、`action`、`when` 必填;`evidence` 可选(缺省 `[]`) |
|
|
84
|
+
| `dream_context` | 任务前取回少量适用经验(只读;候选默认不注入) | `query` 必填;`projectId` / `workspaceRoot` / `limit` / `maxChars` / `packageName` / `packageVersion` / `includeCandidates` 可选 |
|
|
80
85
|
| `dream_review` | 审阅与状态流转:采纳 / 驳回 / 标记过期 / 标记冲突 / 补充证据 | `action`、`lessonId`、`expectedRevision` 必填 |
|
|
81
86
|
|
|
82
87
|
### 示例
|
|
@@ -103,8 +108,8 @@ dream_review { action: "accept", lessonId: "lesson-…", expectedRevision: 1 }
|
|
|
103
108
|
|
|
104
109
|
梦境日记是主观感悟;真正要复用的技术经验走 `<journalDir>/knowledge/`:
|
|
105
110
|
|
|
106
|
-
- **候选 → 证据 → 审阅**:`dream_learn` 把「何时 / 做什么 / 例外 / 范围」和证据一起保存为 candidate;`verification: read` 表示读到原记录,`claimed` 只表示模型声称(不计入独立支持数)。用户采纳、驳回、标记过期或冲突用 `dream_review`,所有修改都要带 `expectedRevision
|
|
107
|
-
- **取回有预算**:`dream_context` 默认最多返回 5 条、3000 字符(硬上限 20 条 / 20000
|
|
111
|
+
- **候选 → 证据 → 审阅**:`dream_learn` 把「何时 / 做什么 / 例外 / 范围」和证据一起保存为 candidate;`verification: read` 表示读到原记录,`claimed` 只表示模型声称(不计入独立支持数)。用户采纳、驳回、标记过期或冲突用 `dream_review`,所有修改都要带 `expectedRevision`,过期写入会被拒绝。当证据为 `kind: "user-correction"` 且已读到原文(`verification: "read"`)时,该经验初始状态直接是 `usable`,`review` 记 `actor: "user"`、`decision: "accepted"`——用户决定优先于模型推测;其余证据一律从 candidate 起,claimed 不计独立支持、也不能覆盖用户纠正。
|
|
112
|
+
- **取回有预算**:`dream_context` 默认最多返回 5 条、3000 字符(硬上限 20 条 / 20000 字符);按排名整条装入,遇到第一条放不下就停止(`skipped: budget-stop`),`title / when / action / exceptions` 永不截断,绝不返回半条。候选与无独立证据的非 usable 条目默认不注入(`includeCandidates: false`,记 `candidate-hold` / `no-evidence`);显式 `includeCandidates: true` 可在审阅候选时看到,且候选排在可用经验之后——未经审阅不得当作已证实结论。已驳回、已过期和冲突未解决的不注入;`applicability.versions` 只在包名被识别(显式 `packageName` 或 query 中出现包名)时参与判定。
|
|
108
113
|
- **频率不是可靠性**:同一条证据重复提交是幂等的;不要在同一个会话里反复 `dream_save` / `dream_learn` 刷次数。同一个会话中重复出现的教训只算一次独立来源。
|
|
109
114
|
- **存储可恢复**:`events.jsonl` 是权威事件日志(追加写),`evidence.jsonl` 追加证据,`index.json` 是可丢弃重建的派生索引;重建索引不删除任何原文件。目录不存在时读取接口返回空结果,不会为了读而创建目录。
|
|
110
115
|
- **删除新数据**:直接删除 `<journalDir>/knowledge/` 即清空经验记忆;这不会影响 `dreams.jsonl` 里的旧日记,旧版本插件也仍能读取旧日记。
|
package/lib/client.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// 教训榜、按时间倒序的梦卡片;再只读展示 M1 的技术经验(<journalDir>/knowledge/)。
|
|
7
7
|
// 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal 与
|
|
8
8
|
// GET /_dsh/dsh-dream/knowledge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
|
|
9
|
-
// 经验区块只展示状态 / 条件 / 范围 / 证据摘要 /
|
|
9
|
+
// 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
|
|
10
|
+
// 不展示权重、向量或原始 JSON。
|
|
10
11
|
// 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
|
|
11
12
|
window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
|
|
12
13
|
var module = { exports: {} }; var exports = module.exports;
|
|
@@ -224,10 +225,30 @@ function trimmedText(value) {
|
|
|
224
225
|
return typeof value === "string" ? value.trim() : "";
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
/** applicability 数组 → 紧凑适用串(package + versions / platform);坏形状跳过,空数组给 ""。 */
|
|
229
|
+
function applicabilityTextOf(value) {
|
|
230
|
+
if (!Array.isArray(value)) return "";
|
|
231
|
+
const parts = [];
|
|
232
|
+
for (const raw of value) {
|
|
233
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
234
|
+
const pkg = trimmedText(raw.package);
|
|
235
|
+
const versions = trimmedText(raw.versions);
|
|
236
|
+
const platform = trimmedText(raw.platform);
|
|
237
|
+
const main = pkg !== "" ? (versions !== "" ? pkg + " " + versions : pkg) : versions;
|
|
238
|
+
const bits = [];
|
|
239
|
+
if (main !== "") bits.push(main);
|
|
240
|
+
if (platform !== "") bits.push("平台 " + platform);
|
|
241
|
+
if (bits.length > 0) parts.push(bits.join(" · "));
|
|
242
|
+
}
|
|
243
|
+
if (parts.length === 0) return "";
|
|
244
|
+
if (parts.length > 4) return parts.slice(0, 4).join(";") + ";等 " + String(parts.length) + " 条适用范围";
|
|
245
|
+
return parts.join(";");
|
|
246
|
+
}
|
|
247
|
+
|
|
227
248
|
/**
|
|
228
|
-
* 经验视图模型:只投影展示所需字段(title / when /
|
|
229
|
-
* evidenceSummary / lastValidatedAt),不碰权重、向量、原始 JSON。
|
|
230
|
-
* limit 只截断渲染行数,byState
|
|
249
|
+
* 经验视图模型:只投影展示所需字段(title / when / applicability / exceptions /
|
|
250
|
+
* state / scopeLabel / evidenceSummary / lastValidatedAt),不碰权重、向量、原始 JSON。
|
|
251
|
+
* limit 只截断渲染行数,byState 仍按全量统计;truncated / badLines 原样带给提示行。
|
|
231
252
|
*/
|
|
232
253
|
function normalizeKnowledge(value, now, limit) {
|
|
233
254
|
const source = value !== null && typeof value === "object" ? value : {};
|
|
@@ -249,6 +270,10 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
249
270
|
id: trimmedText(raw.id),
|
|
250
271
|
title: title !== "" ? title : action,
|
|
251
272
|
when: trimmedText(raw.when),
|
|
273
|
+
applicability: applicabilityTextOf(raw.applicability),
|
|
274
|
+
exceptions: Array.isArray(raw.exceptions)
|
|
275
|
+
? raw.exceptions.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => item.trim()).slice(0, 10)
|
|
276
|
+
: [],
|
|
252
277
|
state: state,
|
|
253
278
|
stateLabel: lessonStateLabel(state),
|
|
254
279
|
stateClass: lessonStateClass(state),
|
|
@@ -263,11 +288,13 @@ function normalizeKnowledge(value, now, limit) {
|
|
|
263
288
|
}
|
|
264
289
|
const total = typeof statsSource.lessons === "number" && statsSource.lessons >= 0 ? statsSource.lessons : rawLessons.length;
|
|
265
290
|
const evidenceTotal = typeof statsSource.evidence === "number" && statsSource.evidence >= 0 ? statsSource.evidence : 0;
|
|
291
|
+
const badLinesRaw = statsSource.badLines;
|
|
292
|
+
const badLines = typeof badLinesRaw === "number" && Number.isFinite(badLinesRaw) && badLinesRaw > 0 ? Math.floor(badLinesRaw) : 0;
|
|
266
293
|
return {
|
|
267
294
|
rows: rows,
|
|
268
295
|
total: total,
|
|
269
296
|
shown: rows.length,
|
|
270
|
-
stats: { lessons: total, evidence: evidenceTotal, byState: byState },
|
|
297
|
+
stats: { lessons: total, evidence: evidenceTotal, byState: byState, truncated: statsSource.truncated === true, badLines: badLines },
|
|
271
298
|
};
|
|
272
299
|
}
|
|
273
300
|
|
|
@@ -287,6 +314,18 @@ function knowledgeCountText(stats) {
|
|
|
287
314
|
return parts.join(" · ");
|
|
288
315
|
}
|
|
289
316
|
|
|
317
|
+
/** 截断 / 坏行提示:无问题时给 "";确保面板不静默展示不完整的经验集。 */
|
|
318
|
+
function knowledgeNoticeText(stats) {
|
|
319
|
+
const source = stats !== null && typeof stats === "object" ? stats : {};
|
|
320
|
+
const truncated = source.truncated === true;
|
|
321
|
+
const badLines = typeof source.badLines === "number" && Number.isFinite(source.badLines) && source.badLines > 0 ? Math.floor(source.badLines) : 0;
|
|
322
|
+
if (truncated !== true && badLines === 0) return "";
|
|
323
|
+
const parts = [];
|
|
324
|
+
if (truncated === true) parts.push("日志过大,仅回放最近一段事件");
|
|
325
|
+
if (badLines > 0) parts.push("有 " + String(badLines) + " 行损坏已跳过");
|
|
326
|
+
return parts.join(";") + ";events.jsonl 未改动。";
|
|
327
|
+
}
|
|
328
|
+
|
|
290
329
|
/** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
|
|
291
330
|
function knowledgeEmptyState() {
|
|
292
331
|
return {
|
|
@@ -642,6 +681,8 @@ function KnowledgeRow(props) {
|
|
|
642
681
|
]),
|
|
643
682
|
h("div", { className: "dshd-kbody", key: "body" }, [
|
|
644
683
|
h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
|
|
684
|
+
row.applicability !== "" ? h("div", { key: "applicability", title: row.applicability }, "适用:" + row.applicability) : null,
|
|
685
|
+
row.exceptions.length > 0 ? h("div", { key: "exceptions", title: row.exceptions.join(";") }, "例外:" + row.exceptions.join(";")) : null,
|
|
645
686
|
h("div", { key: "evidence" }, "证据:" + row.evidenceSummary),
|
|
646
687
|
]),
|
|
647
688
|
]);
|
|
@@ -649,6 +690,8 @@ function KnowledgeRow(props) {
|
|
|
649
690
|
|
|
650
691
|
function KnowledgeCard(props) {
|
|
651
692
|
const vm = props.vm;
|
|
693
|
+
const notice = knowledgeNoticeText(vm === null || vm === undefined ? null : vm.stats);
|
|
694
|
+
const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
|
|
652
695
|
const head = function (extra) {
|
|
653
696
|
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
654
697
|
h("h4", { key: "t" }, "经验"),
|
|
@@ -667,6 +710,7 @@ function KnowledgeCard(props) {
|
|
|
667
710
|
const empty = knowledgeEmptyState();
|
|
668
711
|
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
669
712
|
h("h4", { key: "t" }, "经验"),
|
|
713
|
+
noticeNode,
|
|
670
714
|
h("div", { className: "dshd-kempty", key: "e" }, [
|
|
671
715
|
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
672
716
|
h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
|
|
@@ -678,13 +722,14 @@ function KnowledgeCard(props) {
|
|
|
678
722
|
head(props.loading === true
|
|
679
723
|
? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
|
|
680
724
|
: h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
|
|
725
|
+
noticeNode,
|
|
681
726
|
h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
|
|
682
727
|
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
|
|
683
728
|
})),
|
|
684
729
|
vm.shown < vm.total
|
|
685
730
|
? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
|
|
686
731
|
: null,
|
|
687
|
-
h("div", { className: "dshd-note", key: "note" }, "
|
|
732
|
+
h("div", { className: "dshd-note", key: "note" }, "只读展示:候选可被检索到,但默认不注入任务建议、且排在可用经验之后;未经审阅不得当作已证实结论。采纳、驳回或标记冲突请用 dream_review。"),
|
|
688
733
|
]);
|
|
689
734
|
}
|
|
690
735
|
|
|
@@ -1118,6 +1163,7 @@ const CSS = [
|
|
|
1118
1163
|
".dshd-kempty{padding:14px 10px;text-align:center;color:var(--dsw-alias-label-tertiary,#6b7280)}",
|
|
1119
1164
|
".dshd-kempty-title{color:var(--dsw-alias-label-primary,#111);font-weight:600;font-size:13px;margin-bottom:4px}",
|
|
1120
1165
|
".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
|
|
1166
|
+
".dshd-knotice{font-size:11px;color:var(--dsw-alias-label-primary,#111);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 14%,transparent);border-radius:8px;padding:5px 8px;margin-bottom:7px}",
|
|
1121
1167
|
".dshd-privacy .dshd-kbody{filter:blur(4px)}",
|
|
1122
1168
|
".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}"
|
|
1123
1169
|
].join("");
|
|
@@ -1165,6 +1211,8 @@ exports.__internals = {
|
|
|
1165
1211
|
fetchKnowledge: fetchKnowledge,
|
|
1166
1212
|
normalizeKnowledge: normalizeKnowledge,
|
|
1167
1213
|
normalizeLessonState: normalizeLessonState,
|
|
1214
|
+
applicabilityTextOf: applicabilityTextOf,
|
|
1215
|
+
knowledgeNoticeText: knowledgeNoticeText,
|
|
1168
1216
|
lessonStateLabel: lessonStateLabel,
|
|
1169
1217
|
lessonStateClass: lessonStateClass,
|
|
1170
1218
|
knowledgeCountText: knowledgeCountText,
|
package/lib/journal.d.ts
CHANGED
|
@@ -9,6 +9,19 @@ export interface DreamEntry {
|
|
|
9
9
|
export declare function journalFile(journalDir: string): string;
|
|
10
10
|
/** 追加一条梦;返回落盘后的条目。 */
|
|
11
11
|
export declare function saveDream(journalDir: string, reflection: string, lessons: string[], mood: string): DreamEntry;
|
|
12
|
+
/** 读取结果:梦境 + 被跳过的行数(坏 JSON 或不是梦境记录)。 */
|
|
13
|
+
export interface DreamReadResult {
|
|
14
|
+
dreams: DreamEntry[];
|
|
15
|
+
skippedLines: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 倒序读取梦境并**报告**跳过了多少行。
|
|
19
|
+
*
|
|
20
|
+
* 两个健壮性要求(评审实测发现):
|
|
21
|
+
* - 文件开头的 UTF-8 BOM 必须剥离,否则第一行解析失败会让整份日记读空;
|
|
22
|
+
* - 坏行跳过要可计数,不能静默——调用方(dream_health / 只读路由)可以透出。
|
|
23
|
+
*/
|
|
24
|
+
export declare function readDreamsDetailed(journalDir: string, limit: number): DreamReadResult;
|
|
12
25
|
/** 倒序读取梦境(新梦在前);损坏行跳过。 */
|
|
13
26
|
export declare function readDreams(journalDir: string, limit: number): DreamEntry[];
|
|
14
27
|
/** 教训榜的一行:次数与最近一次出现时间(lastAt 缺失时是空串,绝不为 null)。 */
|
|
@@ -22,6 +35,8 @@ export interface DreamStats {
|
|
|
22
35
|
total: number;
|
|
23
36
|
moods: Record<string, number>;
|
|
24
37
|
topLessons: LessonStat[];
|
|
38
|
+
/** 解析时跳过的行数(坏 JSON 或不是梦境记录);0 表示全部可读。 */
|
|
39
|
+
skippedLines: number;
|
|
25
40
|
}
|
|
26
41
|
/**
|
|
27
42
|
* 统计梦境(基于全量日记)。
|
package/lib/journal.js
CHANGED
|
@@ -26,18 +26,27 @@ export function saveDream(journalDir, reflection, lessons, mood) {
|
|
|
26
26
|
}
|
|
27
27
|
/** 需要读全量日记时的上限:检索与统计共用,避免两处上限漂移。 */
|
|
28
28
|
const FULL_SCAN_LIMIT = 100000;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 倒序读取梦境并**报告**跳过了多少行。
|
|
31
|
+
*
|
|
32
|
+
* 两个健壮性要求(评审实测发现):
|
|
33
|
+
* - 文件开头的 UTF-8 BOM 必须剥离,否则第一行解析失败会让整份日记读空;
|
|
34
|
+
* - 坏行跳过要可计数,不能静默——调用方(dream_health / 只读路由)可以透出。
|
|
35
|
+
*/
|
|
36
|
+
export function readDreamsDetailed(journalDir, limit) {
|
|
31
37
|
const file = journalFile(journalDir);
|
|
32
38
|
if (!existsSync(file))
|
|
33
|
-
return [];
|
|
34
|
-
|
|
35
|
-
const
|
|
39
|
+
return { dreams: [], skippedLines: 0 };
|
|
40
|
+
// 开头 BOM 不参与解析(Windows 记事本等工具会写入)。
|
|
41
|
+
const text = readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
|
|
42
|
+
const lines = text.split('\n').filter((line) => line.trim() !== '');
|
|
43
|
+
const dreams = [];
|
|
44
|
+
let skippedLines = 0;
|
|
36
45
|
for (const line of lines) {
|
|
37
46
|
try {
|
|
38
47
|
const rec = JSON.parse(line);
|
|
39
48
|
if (typeof rec.reflection === 'string') {
|
|
40
|
-
|
|
49
|
+
dreams.push({
|
|
41
50
|
id: String(rec.id ?? ''),
|
|
42
51
|
at: String(rec.at ?? ''),
|
|
43
52
|
reflection: rec.reflection,
|
|
@@ -45,10 +54,18 @@ export function readDreams(journalDir, limit) {
|
|
|
45
54
|
mood: typeof rec.mood === 'string' ? rec.mood : '',
|
|
46
55
|
});
|
|
47
56
|
}
|
|
57
|
+
else
|
|
58
|
+
skippedLines += 1;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
skippedLines += 1;
|
|
48
62
|
}
|
|
49
|
-
catch { /* 损坏行跳过 */ }
|
|
50
63
|
}
|
|
51
|
-
return
|
|
64
|
+
return { dreams: dreams.reverse().slice(0, limit), skippedLines };
|
|
65
|
+
}
|
|
66
|
+
/** 倒序读取梦境(新梦在前);损坏行跳过。 */
|
|
67
|
+
export function readDreams(journalDir, limit) {
|
|
68
|
+
return readDreamsDetailed(journalDir, limit).dreams;
|
|
52
69
|
}
|
|
53
70
|
/**
|
|
54
71
|
* 统计梦境(基于全量日记)。
|
|
@@ -58,8 +75,10 @@ export function readDreams(journalDir, limit) {
|
|
|
58
75
|
* 大小写,不再二次 flatMap(否则同一条教训的三个数值会来自不同的扫描)。
|
|
59
76
|
*/
|
|
60
77
|
export function dreamStats(journalDir) {
|
|
61
|
-
const dreams =
|
|
62
|
-
|
|
78
|
+
const { dreams, skippedLines } = readDreamsDetailed(journalDir, FULL_SCAN_LIMIT);
|
|
79
|
+
// 无原型对象:心境是用户可控文本,'__proto__' / 'constructor' 这类键
|
|
80
|
+
// 在普通对象上会被原型吞掉或把计数变成字符串(评审实测)。
|
|
81
|
+
const moods = Object.create(null);
|
|
63
82
|
const lessons = new Map();
|
|
64
83
|
for (const dream of dreams) {
|
|
65
84
|
const mood = dream.mood !== '' ? dream.mood : '平静';
|
|
@@ -78,7 +97,9 @@ export function dreamStats(journalDir) {
|
|
|
78
97
|
const topLessons = [...lessons.values()]
|
|
79
98
|
.sort((a, b) => b.count - a.count || b.lastAt.localeCompare(a.lastAt))
|
|
80
99
|
.slice(0, 10);
|
|
81
|
-
|
|
100
|
+
// 对外给普通对象副本:内部用无原型对象防键污染,但消费方(deepStrictEqual / 原型判断)
|
|
101
|
+
// 期望的是普通对象。展开赋值走 CreateDataProperty,不会触发 __proto__ setter。
|
|
102
|
+
return { total: dreams.length, moods: { ...moods }, topLessons, skippedLines };
|
|
82
103
|
}
|
|
83
104
|
/** 关键词检索梦境(不区分大小写,命中 reflection/lessons;扫描全量,上限见 FULL_SCAN_LIMIT)。 */
|
|
84
105
|
export function searchDreams(journalDir, query, limit) {
|
package/lib/knowledge-store.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface KnowledgeStoreOptions {
|
|
|
11
11
|
lockTimeoutMs?: number;
|
|
12
12
|
/** 锁年龄超过该毫秒数且 pid 不存活才允许接管;默认 30000。 */
|
|
13
13
|
lockStaleMs?: number;
|
|
14
|
+
/** 锁硬上限:超过且心跳停滞时给可操作错误、不静默抢占;默认 600000(10 分钟)。 */
|
|
15
|
+
lockHardLimitMs?: number;
|
|
16
|
+
/** 心跳刷新间隔毫秒;默认 5000。长回放/压缩也会按行数主动 touch。 */
|
|
17
|
+
lockHeartbeatMs?: number;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* 经验知识存储。无文件时所有读取方法安全返回空,写入方法在首次写入时自建目录。
|
|
@@ -25,6 +29,9 @@ export declare class KnowledgeStore {
|
|
|
25
29
|
private readonly maxReplayEvents;
|
|
26
30
|
private readonly lockTimeoutMs;
|
|
27
31
|
private readonly lockStaleMs;
|
|
32
|
+
private readonly lockHardLimitMs;
|
|
33
|
+
private readonly lockHeartbeatMs;
|
|
34
|
+
private lastIndexError?;
|
|
28
35
|
constructor(knowledgeDir: string, options?: KnowledgeStoreOptions);
|
|
29
36
|
/** 全部经验,按 updatedAt 降序(同刻按 id 稳定排序)。 */
|
|
30
37
|
listLessons(): Lesson[];
|
|
@@ -38,22 +45,23 @@ export declare class KnowledgeStore {
|
|
|
38
45
|
created: boolean;
|
|
39
46
|
};
|
|
40
47
|
/**
|
|
41
|
-
* 创建候选经验。同 idempotencyKey 幂等;同指纹(文本 + 范围相同)确定性合并,
|
|
48
|
+
* 创建候选经验。同 idempotencyKey + 同 requestHash 幂等;同指纹(文本 + 范围相同)确定性合并,
|
|
42
49
|
* 只把新证据并入已有经验,不新增经验。
|
|
50
|
+
* R2′:有 read 证据 → 初始 usable(含 user-correction read 时 review=accepted/user);只有 claimed 或无证据 → candidate。
|
|
43
51
|
*/
|
|
44
52
|
createLesson(input: LessonInput, idempotencyKey: string): {
|
|
45
53
|
lesson: Lesson;
|
|
46
54
|
created: boolean;
|
|
47
55
|
};
|
|
48
|
-
/** 按 patch 更新经验;revision 不符抛 KnowledgeError('revision')
|
|
56
|
+
/** 按 patch 更新经验;revision 不符抛 KnowledgeError('revision')(details 带 currentRevision)且不写。 */
|
|
49
57
|
updateLesson(id: string, patch: Partial<Lesson>, expectedRevision: number, idempotencyKey: string): Lesson;
|
|
50
58
|
/** 审阅经验:accepted → usable,rejected → rejected,unreviewed → candidate。 */
|
|
51
59
|
reviewLesson(id: string, decision: ReviewDecision, expectedRevision: number, idempotencyKey: string, actor?: string): Lesson;
|
|
52
60
|
/** 状态迁移;不允许的迁移抛 KnowledgeError('invalid') 且不写。 */
|
|
53
61
|
applyTransition(id: string, to: LessonState, expectedRevision: number, idempotencyKey: string): Lesson;
|
|
54
|
-
/**
|
|
62
|
+
/** 从事件完整重建派生索引与 checkpoint;不删除、不改写 events.jsonl / evidence.jsonl。 */
|
|
55
63
|
rebuildIndex(): void;
|
|
56
|
-
/** 统计。truncated=true
|
|
64
|
+
/** 统计。truncated=true 表示事件总量超过有界回放上限(不静默);快照模式下实体已折叠进 checkpoint。 */
|
|
57
65
|
stats(): {
|
|
58
66
|
lessons: number;
|
|
59
67
|
evidence: number;
|
|
@@ -61,15 +69,88 @@ export declare class KnowledgeStore {
|
|
|
61
69
|
byState: Record<LessonState, number>;
|
|
62
70
|
truncated: boolean;
|
|
63
71
|
badLines: number;
|
|
72
|
+
orphanEvents: number;
|
|
73
|
+
unsupportedVersions: number;
|
|
74
|
+
firstReplayedEventId?: string;
|
|
75
|
+
replayMode: 'full' | 'snapshot+tail';
|
|
76
|
+
replayedEvents: number;
|
|
77
|
+
skippedEvents: number;
|
|
78
|
+
};
|
|
79
|
+
/** 只读诊断:锁、文件、计数与最近一次派生索引写失败;绝不获取锁、绝不写盘。 */
|
|
80
|
+
diagnose(): {
|
|
81
|
+
writable: boolean;
|
|
82
|
+
lock: {
|
|
83
|
+
present: boolean;
|
|
84
|
+
ageMs?: number;
|
|
85
|
+
pid?: number;
|
|
86
|
+
alive?: boolean;
|
|
87
|
+
bootId?: string;
|
|
88
|
+
stale: boolean;
|
|
89
|
+
};
|
|
90
|
+
files: {
|
|
91
|
+
dir: string;
|
|
92
|
+
events: {
|
|
93
|
+
path: string;
|
|
94
|
+
exists: boolean;
|
|
95
|
+
bytes: number;
|
|
96
|
+
};
|
|
97
|
+
evidence: {
|
|
98
|
+
path: string;
|
|
99
|
+
exists: boolean;
|
|
100
|
+
bytes: number;
|
|
101
|
+
};
|
|
102
|
+
index: {
|
|
103
|
+
path: string;
|
|
104
|
+
exists: boolean;
|
|
105
|
+
bytes: number;
|
|
106
|
+
generatedAt?: string;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
counts: {
|
|
110
|
+
lessons: number;
|
|
111
|
+
evidence: number;
|
|
112
|
+
events: number;
|
|
113
|
+
badLines: number;
|
|
114
|
+
orphanEvents: number;
|
|
115
|
+
unsupportedVersions: number;
|
|
116
|
+
truncated: boolean;
|
|
117
|
+
byState: Record<LessonState, number>;
|
|
118
|
+
};
|
|
119
|
+
lastIndexError?: {
|
|
120
|
+
message: string;
|
|
121
|
+
at: string;
|
|
122
|
+
};
|
|
64
123
|
};
|
|
65
124
|
private ensureDir;
|
|
66
125
|
private withLock;
|
|
67
|
-
|
|
126
|
+
private fileInfo;
|
|
127
|
+
private hashRequest;
|
|
128
|
+
private hashablePatch;
|
|
129
|
+
private cleanPatch;
|
|
130
|
+
private assertSameRequest;
|
|
131
|
+
/**
|
|
132
|
+
* 恢复"首次结果":
|
|
133
|
+
* - 尾部回放产生的 entry 自带快照,直接返回;
|
|
134
|
+
* - checkpoint 里的 entry 只有 lessonId/requestHash,则做一次有针对性的完整扫描,
|
|
135
|
+
* 在命中该幂等键的那一刻取快照;失败再退回当前实体(best effort)。
|
|
136
|
+
*/
|
|
137
|
+
private resolveIdempotentLesson;
|
|
138
|
+
private replayUntilIdempotencyKey;
|
|
139
|
+
/** 顺序读取 JSONL 的非空行;每 TOUCH_EVERY_LINES 行调用一次 touch(长回放锁心跳)。 */
|
|
140
|
+
private forEachLine;
|
|
141
|
+
/** 有界读取:只保留最后 maxReplayEvents 条非空行,并报告总行数与是否截断。 */
|
|
68
142
|
private readBoundedLines;
|
|
69
|
-
/**
|
|
143
|
+
/** 快照 + 尾部增量(checkpoint 有效时);否则有界全量回放。 */
|
|
70
144
|
private loadEvents;
|
|
145
|
+
private loadEventsBounded;
|
|
146
|
+
/** 无界完整回放(显式压缩 / 写路径发现截断或状态不完整时使用)。 */
|
|
147
|
+
private loadAllUnbounded;
|
|
71
148
|
/** 事件流 + evidence.jsonl(两条路径都留痕,按 id 合并去重)。 */
|
|
72
149
|
private loadAll;
|
|
150
|
+
/** 写路径:状态不完整、证据疑似缺失或截断未折叠时,自动做一次完整回放。 */
|
|
151
|
+
private loadCompleteState;
|
|
152
|
+
private hasMissingEvidenceRefs;
|
|
153
|
+
private mergeEvidenceFile;
|
|
73
154
|
private applyEventLine;
|
|
74
155
|
private appendEvent;
|
|
75
156
|
private appendLine;
|
|
@@ -80,8 +161,12 @@ export declare class KnowledgeStore {
|
|
|
80
161
|
private appendLessonUpdate;
|
|
81
162
|
private maskLesson;
|
|
82
163
|
private maskEvidence;
|
|
164
|
+
private hashPrefix;
|
|
165
|
+
private readLastLineId;
|
|
166
|
+
private tryLoadCheckpoint;
|
|
167
|
+
private buildCheckpoint;
|
|
83
168
|
private buildIndexPayload;
|
|
84
|
-
/**
|
|
169
|
+
/** 自动更新派生索引:失败降级(权威事件已落盘),记录 lastIndexError 供 diagnose()。 */
|
|
85
170
|
private writeIndex;
|
|
86
171
|
private writeIndexStrict;
|
|
87
172
|
}
|