@stardustlc/dsh-dream 0.4.1 → 0.5.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/CHANGELOG.md +11 -0
- package/README.en.md +25 -3
- package/README.md +28 -3
- package/lib/client.js +283 -11
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/knowledge-store.d.ts +87 -0
- package/lib/knowledge-store.js +825 -0
- package/lib/knowledge.d.ts +156 -0
- package/lib/knowledge.js +347 -0
- package/lib/retrieval.d.ts +70 -0
- package/lib/retrieval.js +403 -0
- package/lib/tools.js +480 -13
- package/lib/web.d.ts +43 -18
- package/lib/web.js +162 -39
- package/package.json +1 -1
- package/skills/dream-protocol/SKILL.md +99 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0(2026-09-29)
|
|
4
|
+
|
|
5
|
+
- **从「反思」升级为「有来源的经验」**:新增 `dream_learn`(提交候选经验 + 证据,校验 / 脱敏 / 去重 / 按项目或全局范围保存)、`dream_context`(任务前取回少量适用经验,只读,默认 5 条 / 3000 字符,硬上限 20 条 / 20000 字符)、`dream_review`(采纳 / 驳回 / 标记过期 / 标记冲突 / 补充证据,强制 `expectedRevision`)。六个既有工具行为不变,输出字段只增不减。
|
|
6
|
+
- **新数据目录 `<journalDir>/knowledge/`**:`events.jsonl` 权威追加日志、`evidence.jsonl` 追加证据、`index.json` 可丢弃重建的派生索引;写入使用短期文件锁与幂等键。`dreams.jsonl` 永不重写,旧日记继续按原格式读取。
|
|
7
|
+
- **证据纪律**:证据只保存脱敏摘要与定位信息;`verification: read` 与 `claimed` 分开,claimed 不计入独立支持数;同一证据重复提交幂等,不增加独立来源。所有新写盘路径在 `maskSecrets` 开启时先过 `mask.ts`。
|
|
8
|
+
- **范围与预算**:经验按 `projectId` / `workspaceRoot` / `global` 过滤,未知当前项目时 `dream_context` 只返回全局经验;条件与例外不被截断,预算放不下就整条不返回;已驳回、已过期、冲突未解决的默认不注入任务。
|
|
9
|
+
- **只读面板新增「经验」区块**:展示 title / when / state / scopeLabel / evidenceSummary / lastValidatedAt;候选与有冲突的视觉区分明确,空态独立;只读路由新增 GET /_dsh/dsh-dream/knowledge(返回脱敏后的 `{ lessons, evidence, stats }`,沿用回环 Host / 405 / 403 / 错误信封),面板仍无任何写操作。
|
|
10
|
+
- **技能与文档**:`dream-protocol` 技能区分「第一人称感悟」与「技术经验」,流程改为取回上下文 → 执行真实任务 → 收尾提候选(附证据)→ 必要时 dream_review,并写清预算、禁止靠反复 `dream_save` 刷次数、禁止把主观反思升级成 AGENTS.md 指令;README 中英文新增 M1 章节。
|
|
11
|
+
- **测试**:112 → 169 项全部通过(新增知识存储 / 检索 / 三个新工具的宿主 schema 契约 / 只读知识路由零写盘与脱敏 / 面板经验区块纯函数用例);`tsc`、`node --check lib/client.js` 与官方加载器回归(冷加载 / 缓存 / invalidate 重载)均通过。真实桌面 / Web 目视验收仍待 M1 发布前完成。
|
|
12
|
+
- 本版为 M1 首个发布版本。真实桌面 / Web 目视验收(经验区块配色与空态、窄窗、125%/150% 缩放、深浅色)与 18 插件同载冷启动复验**尚未完成**,完成后单独记录结论。
|
|
13
|
+
|
|
3
14
|
## 0.4.1(2026-09-29)
|
|
4
15
|
|
|
5
16
|
- **修复 Desktop 启动失败**:前端 bundle 的模块注册 ID 与 scoped 包名不一致(注册成 `dsh-dream`,而包名是 `@stardustlc/dsh-dream`),宿主会重试加载并抛 `client-modules: duplicate factory registration for "dsh-dream"`,导致 `web boot: 1 entry did not activate`。现统一为完整包身份。
|
package/README.en.md
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
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.0 update (2026-09-29)
|
|
12
|
+
|
|
13
|
+
- **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
|
+
- **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
|
+
- **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
|
+
- **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.
|
|
18
|
+
|
|
11
19
|
## 0.4.0 update (2026-09-29)
|
|
12
20
|
|
|
13
21
|
Adds a read-only dream-journal panel: open **Settings →「梦境日记」(Dream journal)** to see totals, mood distribution, the lessons board and a card timeline, with keyword search. Long reflections fold automatically and a one-click privacy blur is available. The panel is read-only and the storage format is unchanged — no migration needed.
|
|
@@ -18,7 +26,7 @@ Humans consolidate memories by replaying the day during sleep — dsh-dream give
|
|
|
18
26
|
|
|
19
27
|
## Compatibility
|
|
20
28
|
|
|
21
|
-
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-29.
|
|
29
|
+
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-29. At 0.4.1 all 112 plugin tests passed in an isolated environment and all 18 plugins mounted together; M1 currently has 169 tests passing (knowledge store / retrieval / host schema contracts for the three new tools / zero-write and masking cases for the read-only route). 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. The 18-plugin joint load will be re-run with the new front end before the M1 release. No live ports or external services were exercised in this round; the panel is reachable on loopback only.
|
|
22
30
|
|
|
23
31
|
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.
|
|
24
32
|
|
|
@@ -47,6 +55,7 @@ Once installed, DSH's **Settings →「梦境日记」(Dream journal)** grows a
|
|
|
47
55
|
- **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.
|
|
48
56
|
- **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.
|
|
49
57
|
- **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.
|
|
50
59
|
- **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.
|
|
51
60
|
|
|
52
61
|
## Tools
|
|
@@ -59,18 +68,31 @@ Once installed, DSH's **Settings →「梦境日记」(Dream journal)** grows a
|
|
|
59
68
|
| `dream_recall` | Keyword search across the journal | `query` required |
|
|
60
69
|
| `dream_bridge` | Merge top lessons into AGENTS.md (idempotent marker block) — dreams become long-term memory | `path` required |
|
|
61
70
|
| `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`, `evidence` required |
|
|
72
|
+
| `dream_context` | Retrieve a few applicable lessons before a task (read-only) | `query` required; `projectId` / `workspaceRoot` / `limit` / `maxChars` / `packageVersion` optional |
|
|
73
|
+
| `dream_review` | Review and transition state: accept / reject / mark stale / mark disputed / attach evidence | `action`, `lessonId`, `expectedRevision` required |
|
|
62
74
|
|
|
63
75
|
`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.
|
|
64
76
|
|
|
65
77
|
The bundled `dream-protocol` skill teaches the agent when and how to dream (and the journaling discipline: patterns only, never secrets).
|
|
66
78
|
|
|
79
|
+
## Lesson memory (M1)
|
|
80
|
+
|
|
81
|
+
The dream journal holds subjective reflections; technical lessons that should be reused go to `<journalDir>/knowledge/`:
|
|
82
|
+
|
|
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). Conditions and exceptions are never truncated: if an item does not fit, it is omitted whole. Usable lessons outrank candidates; rejected, stale and unresolved disputed lessons are not injected into tasks by default.
|
|
85
|
+
- **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
|
+
- **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
|
+
- **Removing it**: delete `<journalDir>/knowledge/` to clear lesson memory. The old `dreams.jsonl` journal is unaffected, and older plugin versions can still read it.
|
|
88
|
+
|
|
67
89
|
## Privacy (on by default)
|
|
68
90
|
|
|
69
|
-
Dream material is masked before journaling: sk keys, GitHub/Groq/Slack tokens, AWS keys, JWTs, `password/token/api_key` assignments and long high-entropy strings become `[masked·type]`. Disable with `maskSecrets: false`.
|
|
91
|
+
Dream material is masked before journaling: sk keys, GitHub/Groq/Slack tokens, AWS keys, JWTs, `password/token/api_key` assignments and long high-entropy strings become `[masked·type]`. In M1 the lesson / evidence / error / bridge write paths also run through `mask.ts` when `maskSecrets` is on, and text returned by the read-only panel route is masked again. Disable with `maskSecrets: false`.
|
|
70
92
|
|
|
71
93
|
## Permissions & data
|
|
72
94
|
|
|
73
|
-
Read-only access to the sessions directory; appends JSONL to the journal directory
|
|
95
|
+
Read-only access to the sessions directory; appends JSONL to the journal directory plus lessons/evidence under `journalDir/knowledge/`; no network calls. Session content may be sensitive — the protocol forbids secrets in dreams, but the journal is plaintext: review before sharing.
|
|
74
96
|
|
|
75
97
|
## Development
|
|
76
98
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
- **修复 Desktop 启动失败**:前端模块注册 ID 现与 scoped 包名一致(此前注册成 `dsh-dream`,宿主会重试加载并抛 `duplicate factory registration`,导致 `1 entry did not activate`)。日记数据与配置不变,无迁移。
|
|
8
8
|
|
|
9
|
+
## 0.5.0 更新(2026-09-29)
|
|
10
|
+
|
|
11
|
+
- **新增三个工具**:`dream_learn`(提交带证据的候选经验)、`dream_context`(任务前按项目 / 版本取回少量适用经验,只读,默认最多 5 条、3000 字符)、`dream_review`(采纳 / 驳回 / 标记过期或冲突 / 补充证据,强制携带 revision)。
|
|
12
|
+
- **新数据目录 `<journalDir>/knowledge/`**:`events.jsonl` 是权威追加日志,`evidence.jsonl` 追加证据,`index.json` 是可丢弃重建的派生索引。旧日记 `dreams.jsonl` 仍只读兼容、永不重写。
|
|
13
|
+
- **脱敏与范围承诺**:所有新写盘路径在 `maskSecrets` 开启时先过 `mask.ts`;证据只保存已脱敏摘要和定位信息,不保存隐藏推理、系统提示、工具参数 / 结果正文。经验按项目或全局范围保存,未知当前项目时 `dream_context` 只返回全局经验,绝不跨项目扫描。
|
|
14
|
+
- **兼容性**:旧日记与六个既有工具(dream_digest / dream_save / dream_journal / dream_recall / dream_bridge / dream_health)行为不变、输出字段只增不减;升级不需要迁移,也不会改写旧数据。
|
|
15
|
+
- **只读面板新增「经验」区块**:展示状态(候选 / 可用 / 有冲突 / 待复核 / 已驳回)、适用条件、范围、证据摘要与最近核验时间;候选与有冲突有明确视觉区分,没有经验时是独立空态。面板仍然没有任何写操作。
|
|
16
|
+
|
|
9
17
|
## 0.4.0 更新(2026-09-29)
|
|
10
18
|
|
|
11
19
|
新增只读梦境日记可视化面板:打开「设置 → 梦境日记」即可查看梦境总数、心境分布、教训榜与卡片式时间线,并可按关键词搜索;正文超长自动折叠,可一键开启隐私模糊。面板只读,存储格式不变、无需迁移。
|
|
@@ -18,7 +26,7 @@
|
|
|
18
26
|
|
|
19
27
|
## 兼容性
|
|
20
28
|
|
|
21
|
-
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-29)。112
|
|
29
|
+
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-29)。0.4.1 时 112 项插件测试在隔离环境全部通过,18 个插件共同加载验证通过;0.5.0 共 169 项测试全部通过(新增知识存储 / 检索 / 三个新工具的宿主 schema 契约 / 只读路由零写盘与脱敏用例),本插件注册 9 个工具(6 个既有工具 + M1 的 dream_learn / dream_context / dream_review)与 `dream-protocol` 技能,工具 schema 与健康检查契约通过。18 插件在 M1 发布前会随新版前端再复验一次。本轮未启用真实端口与外部服务;面板仅在本机回环地址下可达。
|
|
22
30
|
|
|
23
31
|
支持 v0/v1/v2/v3/v4 的普通 JSONL 与多帧 zstd、旧 packed-chunk 行及内嵌 stream;升级留下多代文件时只读取每个会话的最新规范文件。PTC 子工具调用保留工具名且不重复计数,不提取系统提示、推理、工具参数或结果正文。
|
|
24
32
|
|
|
@@ -54,6 +62,7 @@ dsh plugin --profile web remove @stardustlc/dsh-dream
|
|
|
54
62
|
- **数据文件**:面板读的就是梦境日记本身,默认 ~/.dsh/.dsh-dream/dreams.jsonl($DSH_HOME/.dsh-dream/dreams.jsonl;配置了 journalDir 时以配置为准)。存储格式不变,仍是每行一条 JSON 的 JSONL,可直接删除或用 dream_journal 读取。
|
|
55
63
|
- **隐私**:面板不做二次脱敏——脱敏只发生在入梦写盘时(maskSecrets),面板原样显示文件里已有的内容。它不上传、不导出、不发起任何网络请求;顶部「隐私模式」可一键模糊正文与教训,但它只是浏览器里的显示效果(DOM 中仍是明文),不是加密。共享屏幕或转发 dreams.jsonl 前请自行确认内容。
|
|
56
64
|
- **还没有梦时**:面板显示空态,提示对 agent 说「做个梦」——它会回放最近的会话、反思之后把第一条梦写进这里;只有一场梦时按单条卡片展示,心境方块与教训榜都不留空、不画失真图表。
|
|
65
|
+
- **经验区块(M1)**:面板下方的只读「经验」区块显示技术经验的状态(候选 / 可用 / 有冲突 / 待复核 / 已驳回)、适用条件、范围、证据摘要与最近核验时间,候选与冲突有独立配色和边框,空态与梦境空态分开。数据来自只读路由 GET /_dsh/dsh-dream/knowledge(仅回环地址可达;非 GET → 405,非本机 Host → 403),没有采纳 / 编辑 / 删除按钮;审阅请用 dream_review。
|
|
57
66
|
- **后续计划**:用 sidecar 记录桥接状态(哪些教训、何时写进了哪个 AGENTS.md)。本轮不做——v1 不读取 AGENTS.md,也不新增任何状态文件。
|
|
58
67
|
|
|
59
68
|
## 工具一览
|
|
@@ -66,6 +75,9 @@ dsh plugin --profile web remove @stardustlc/dsh-dream
|
|
|
66
75
|
| `dream_recall` | 忆梦:关键词检索梦境 | `query` 必填 |
|
|
67
76
|
| `dream_bridge` | 渡梦:把高频教训幂等合并进 AGENTS.md,梦变成长期记忆 | `path` 必填;`maxLessons` 可选 |
|
|
68
77
|
| `dream_health` | 自检:会话目录/梦境计数/配置汇总 | 无 |
|
|
78
|
+
| `dream_learn` | 提交候选技术经验及证据(校验 / 脱敏 / 去重 / 按范围保存;无证据只能留在候选) | `kind`、`title`、`action`、`when`、`evidence` 必填 |
|
|
79
|
+
| `dream_context` | 任务前取回少量适用经验(只读) | `query` 必填;`projectId` / `workspaceRoot` / `limit` / `maxChars` / `packageVersion` 可选 |
|
|
80
|
+
| `dream_review` | 审阅与状态流转:采纳 / 驳回 / 标记过期 / 标记冲突 / 补充证据 | `action`、`lessonId`、`expectedRevision` 必填 |
|
|
69
81
|
|
|
70
82
|
### 示例
|
|
71
83
|
|
|
@@ -74,6 +86,9 @@ dream_digest { maxSessions: 5, mode: 'brief' }
|
|
|
74
86
|
dream_save { reflection: "用户反复遇到输入法问题,偏好先自查再重启", lessons: ["先问是否重启过应用"], mood: "平静" }
|
|
75
87
|
dream_recall { query: "输入法" }
|
|
76
88
|
dream_bridge { path: "AGENTS.md", maxLessons: 10 }
|
|
89
|
+
dream_context { query: "SMTP 取消投递", projectId: "mailer", packageVersion: "9.0.5" }
|
|
90
|
+
dream_learn { kind: "pitfall", title: "池化发送中 close() 不保证终止投递", action: "取消后验证当前连接确实关闭", when: "使用 Nodemailer 9.0.5 池化发送并处理 AbortSignal 时", evidence: [{ kind: "local-artifact", summary: "本机 SMTP 复现:取消后服务器仍收到正文", verification: "read" }] }
|
|
91
|
+
dream_review { action: "accept", lessonId: "lesson-…", expectedRevision: 1 }
|
|
77
92
|
```
|
|
78
93
|
|
|
79
94
|
## 渡梦:让梦变成长期记忆
|
|
@@ -84,6 +99,16 @@ dream_bridge { path: "AGENTS.md", maxLessons: 10 }
|
|
|
84
99
|
|
|
85
100
|
插件附带 `dream-protocol` 技能,教 agent 何时做梦(开场/收尾/距上次做梦超一天)、做梦三步(入梦→解梦→记梦)与记梦纪律(只沉淀规律不复述流水账、密钥隐私不入梦、存疑要标注)。
|
|
86
101
|
|
|
102
|
+
## 经验记忆(M1)
|
|
103
|
+
|
|
104
|
+
梦境日记是主观感悟;真正要复用的技术经验走 `<journalDir>/knowledge/`:
|
|
105
|
+
|
|
106
|
+
- **候选 → 证据 → 审阅**:`dream_learn` 把「何时 / 做什么 / 例外 / 范围」和证据一起保存为 candidate;`verification: read` 表示读到原记录,`claimed` 只表示模型声称(不计入独立支持数)。用户采纳、驳回、标记过期或冲突用 `dream_review`,所有修改都要带 `expectedRevision`,过期写入会被拒绝。
|
|
107
|
+
- **取回有预算**:`dream_context` 默认最多返回 5 条、3000 字符(硬上限 20 条 / 20000 字符);条件与例外不会被截断,放不下就整条不返回。可用经验优先于候选;已驳回、已过期和冲突未解决的默认不注入任务。
|
|
108
|
+
- **频率不是可靠性**:同一条证据重复提交是幂等的;不要在同一个会话里反复 `dream_save` / `dream_learn` 刷次数。同一个会话中重复出现的教训只算一次独立来源。
|
|
109
|
+
- **存储可恢复**:`events.jsonl` 是权威事件日志(追加写),`evidence.jsonl` 追加证据,`index.json` 是可丢弃重建的派生索引;重建索引不删除任何原文件。目录不存在时读取接口返回空结果,不会为了读而创建目录。
|
|
110
|
+
- **删除新数据**:直接删除 `<journalDir>/knowledge/` 即清空经验记忆;这不会影响 `dreams.jsonl` 里的旧日记,旧版本插件也仍能读取旧日记。
|
|
111
|
+
|
|
87
112
|
## 配置
|
|
88
113
|
|
|
89
114
|
在你自己的 profile 的 `cordis.patch.yml` 里覆盖本插件行(缺省时用默认值也能加载):
|
|
@@ -102,12 +127,12 @@ dream_bridge { path: "AGENTS.md", maxLessons: 10 }
|
|
|
102
127
|
|
|
103
128
|
## 隐私保护(默认开启)
|
|
104
129
|
|
|
105
|
-
梦原料入梦前自动脱敏:sk 密钥、GitHub/Groq/Slack 令牌、AWS 密钥、JWT、`password/token/api_key` 赋值、超长高熵串都会被打成 `[已脱敏·类型]
|
|
130
|
+
梦原料入梦前自动脱敏:sk 密钥、GitHub/Groq/Slack 令牌、AWS 密钥、JWT、`password/token/api_key` 赋值、超长高熵串都会被打成 `[已脱敏·类型]`。M1 的经验 / 证据 / 错误 / 桥接写盘路径在 `maskSecrets` 开启时同样先过 `mask.ts`,面板只读路由返回的文本也会再做一次脱敏。不需要时可在配置里设 `maskSecrets: false`。
|
|
106
131
|
|
|
107
132
|
## 权限与数据
|
|
108
133
|
|
|
109
134
|
- 只读访问会话目录(`~/.dsh/sessions`),不修改任何会话文件;
|
|
110
|
-
- 梦境日记以 JSONL 追加写入 `journalDir
|
|
135
|
+
- 梦境日记以 JSONL 追加写入 `journalDir`;经验与证据追加写入 `journalDir/knowledge/`(events.jsonl / evidence.jsonl,index.json 可重建);
|
|
111
136
|
- 不发起任何网络请求;
|
|
112
137
|
- 会话内容可能包含敏感信息——做梦协议明确禁止把密钥/隐私写入梦境,但梦境日记本身是明文存储,请自行评估。
|
|
113
138
|
|
package/lib/client.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// 仓库里没有 src/client.ts,pnpm run build 也不会重写它。改面板请直接改这里,
|
|
3
3
|
// 改完用 node --check lib/client.js 自检(CI 里也会跑这一步)。
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// GET /_dsh/dsh-dream/journal
|
|
5
|
+
// 面板做两件事:把梦境日记(dreams.jsonl)画成人能看懂的样子 —— 概览、心境方块、
|
|
6
|
+
// 教训榜、按时间倒序的梦卡片;再只读展示 M1 的技术经验(<journalDir>/knowledge/)。
|
|
7
|
+
// 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal 与
|
|
8
|
+
// GET /_dsh/dsh-dream/knowledge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
|
|
9
|
+
// 经验区块只展示状态 / 条件 / 范围 / 证据摘要 / 最近核验,不展示权重、向量或原始 JSON。
|
|
8
10
|
// 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
|
|
9
11
|
window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
|
|
10
12
|
var module = { exports: {} }; var exports = module.exports;
|
|
@@ -16,6 +18,9 @@ const h = React.createElement;
|
|
|
16
18
|
|
|
17
19
|
// 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
|
|
18
20
|
const ROUTE = "_dsh/dsh-dream/journal";
|
|
21
|
+
const KNOWLEDGE_ROUTE = "_dsh/dsh-dream/knowledge";
|
|
22
|
+
const KNOWLEDGE_LIMIT = 50;
|
|
23
|
+
const KNOWLEDGE_STATES = ["candidate", "usable", "disputed", "stale", "rejected"];
|
|
19
24
|
const DEFAULT_LIMIT = 50;
|
|
20
25
|
const MORE_LIMIT = 500;
|
|
21
26
|
const FETCH_TIMEOUT_MS = 15000;
|
|
@@ -35,9 +40,9 @@ function timestampOf(iso) {
|
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/** 路由 URL:按 document.baseURI 解析,兼容反代前缀;环境不齐时退回相对路径。 */
|
|
38
|
-
function
|
|
43
|
+
function routeUrlFor(route, query) {
|
|
39
44
|
const suffix = query === "" || query === undefined || query === null ? "" : "?" + query;
|
|
40
|
-
const relative =
|
|
45
|
+
const relative = route + suffix;
|
|
41
46
|
try {
|
|
42
47
|
let base = null;
|
|
43
48
|
if (typeof document !== "undefined" && document !== null && typeof document.baseURI === "string" && document.baseURI !== "") {
|
|
@@ -52,6 +57,11 @@ function routeUrl(query) {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/** 梦境日记路由的快捷方式(保持既有调用语义)。 */
|
|
61
|
+
function routeUrl(query) {
|
|
62
|
+
return routeUrlFor(ROUTE, query);
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
function errorMessageOf(body, status) {
|
|
56
66
|
if (body !== null && typeof body === "object") {
|
|
57
67
|
if (body.error !== null && typeof body.error === "object" && typeof body.error.message === "string" && body.error.message !== "") return body.error.message;
|
|
@@ -87,6 +97,29 @@ async function fetchJournal(query, limit) {
|
|
|
87
97
|
};
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
/** GET /_dsh/dsh-dream/knowledge:只读契约体 { lessons, evidence, stats },无查询参数。 */
|
|
101
|
+
async function fetchKnowledge() {
|
|
102
|
+
const init = { credentials: "same-origin" };
|
|
103
|
+
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") init.signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
104
|
+
const res = await fetch(routeUrlFor(KNOWLEDGE_ROUTE, ""), init);
|
|
105
|
+
let body = null;
|
|
106
|
+
try { body = await res.json(); } catch (error) { body = null; }
|
|
107
|
+
if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
|
|
108
|
+
if (res.ok !== true || body.ok === false) {
|
|
109
|
+
const failure = new Error(errorMessageOf(body, res.status));
|
|
110
|
+
failure.status = res.status;
|
|
111
|
+
failure.code = body.error !== null && typeof body.error === "object" ? body.error.code : undefined;
|
|
112
|
+
throw failure;
|
|
113
|
+
}
|
|
114
|
+
// 契约体就是 { lessons, evidence, stats };万一被包进 { ok, value } 信封也认。
|
|
115
|
+
const data = body.value !== null && typeof body.value === "object" ? body.value : body;
|
|
116
|
+
return {
|
|
117
|
+
lessons: Array.isArray(data.lessons) ? data.lessons : [],
|
|
118
|
+
evidence: Array.isArray(data.evidence) ? data.evidence : [],
|
|
119
|
+
stats: data.stats !== null && typeof data.stats === "object" ? data.stats : null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
90
123
|
function normalizeDreams(value, limit) {
|
|
91
124
|
if (!Array.isArray(value) || limit <= 0) return [];
|
|
92
125
|
const out = [];
|
|
@@ -157,6 +190,112 @@ function normalizeStats(value, dreams) {
|
|
|
157
190
|
return { total: total, moods: moods, topLessons: topLessons };
|
|
158
191
|
}
|
|
159
192
|
|
|
193
|
+
/** 只认契约里的 5 个状态;未知状态不冒充候选。 */
|
|
194
|
+
function normalizeLessonState(value) {
|
|
195
|
+
return KNOWLEDGE_STATES.indexOf(value) !== -1 ? value : "unknown";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 状态 → 中文标签(面板文案集中在这里)。 */
|
|
199
|
+
function lessonStateLabel(state) {
|
|
200
|
+
switch (state) {
|
|
201
|
+
case "candidate": return "候选(未采纳)";
|
|
202
|
+
case "usable": return "可用";
|
|
203
|
+
case "disputed": return "有冲突";
|
|
204
|
+
case "stale": return "待复核";
|
|
205
|
+
case "rejected": return "已驳回";
|
|
206
|
+
default: return "未知状态";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 状态 → 视觉类名:candidate 与 disputed 必须不同色,其余状态各自可辨。 */
|
|
211
|
+
function lessonStateClass(state) {
|
|
212
|
+
switch (state) {
|
|
213
|
+
case "candidate": return "dshd-kstate-candidate";
|
|
214
|
+
case "usable": return "dshd-kstate-usable";
|
|
215
|
+
case "disputed": return "dshd-kstate-disputed";
|
|
216
|
+
case "stale": return "dshd-kstate-stale";
|
|
217
|
+
case "rejected": return "dshd-kstate-rejected";
|
|
218
|
+
default: return "dshd-kstate-unknown";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** 只取字符串字段并去首尾空白;坏形状降级为 "",不让面板崩。 */
|
|
223
|
+
function trimmedText(value) {
|
|
224
|
+
return typeof value === "string" ? value.trim() : "";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 经验视图模型:只投影展示所需字段(title / when / state / scopeLabel /
|
|
229
|
+
* evidenceSummary / lastValidatedAt),不碰权重、向量、原始 JSON。
|
|
230
|
+
* limit 只截断渲染行数,byState 仍按全量统计。
|
|
231
|
+
*/
|
|
232
|
+
function normalizeKnowledge(value, now, limit) {
|
|
233
|
+
const source = value !== null && typeof value === "object" ? value : {};
|
|
234
|
+
const cap = typeof limit === "number" && limit > 0 ? limit : KNOWLEDGE_LIMIT;
|
|
235
|
+
const rawLessons = Array.isArray(source.lessons) ? source.lessons : [];
|
|
236
|
+
const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
|
|
237
|
+
const byState = {};
|
|
238
|
+
const rows = [];
|
|
239
|
+
for (const raw of rawLessons) {
|
|
240
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
241
|
+
const title = trimmedText(raw.title);
|
|
242
|
+
const action = trimmedText(raw.action);
|
|
243
|
+
if (title === "" && action === "") continue;
|
|
244
|
+
const state = normalizeLessonState(raw.state);
|
|
245
|
+
byState[state] = (byState[state] || 0) + 1;
|
|
246
|
+
if (rows.length >= cap) continue;
|
|
247
|
+
const lastValidatedAt = trimmedText(raw.lastValidatedAt);
|
|
248
|
+
rows.push({
|
|
249
|
+
id: trimmedText(raw.id),
|
|
250
|
+
title: title !== "" ? title : action,
|
|
251
|
+
when: trimmedText(raw.when),
|
|
252
|
+
state: state,
|
|
253
|
+
stateLabel: lessonStateLabel(state),
|
|
254
|
+
stateClass: lessonStateClass(state),
|
|
255
|
+
scopeLabel: trimmedText(raw.scopeLabel) || "范围未标注",
|
|
256
|
+
evidenceSummary: trimmedText(raw.evidenceSummary) || "暂无可展示的证据摘要",
|
|
257
|
+
lastValidatedAt: lastValidatedAt,
|
|
258
|
+
lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
|
|
259
|
+
lastTitle: lastValidatedAt === "" ? "没有 lastValidatedAt(从未核验)" : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
|
|
260
|
+
candidate: state === "candidate",
|
|
261
|
+
disputed: state === "disputed",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const total = typeof statsSource.lessons === "number" && statsSource.lessons >= 0 ? statsSource.lessons : rawLessons.length;
|
|
265
|
+
const evidenceTotal = typeof statsSource.evidence === "number" && statsSource.evidence >= 0 ? statsSource.evidence : 0;
|
|
266
|
+
return {
|
|
267
|
+
rows: rows,
|
|
268
|
+
total: total,
|
|
269
|
+
shown: rows.length,
|
|
270
|
+
stats: { lessons: total, evidence: evidenceTotal, byState: byState },
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 经验区块的统计文案:按状态计数,不包装成可靠度。 */
|
|
275
|
+
function knowledgeCountText(stats) {
|
|
276
|
+
const source = stats !== null && typeof stats === "object" ? stats : {};
|
|
277
|
+
const byState = source.byState !== null && typeof source.byState === "object" ? source.byState : {};
|
|
278
|
+
const parts = [];
|
|
279
|
+
for (const state of KNOWLEDGE_STATES) {
|
|
280
|
+
const count = typeof byState[state] === "number" && byState[state] > 0 ? byState[state] : 0;
|
|
281
|
+
if (count > 0) parts.push(lessonStateLabel(state) + " " + String(count));
|
|
282
|
+
}
|
|
283
|
+
if (parts.length === 0) {
|
|
284
|
+
const total = typeof source.lessons === "number" && source.lessons > 0 ? source.lessons : 0;
|
|
285
|
+
return total > 0 ? "共 " + String(total) + " 条" : "还没有经验";
|
|
286
|
+
}
|
|
287
|
+
return parts.join(" · ");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
|
|
291
|
+
function knowledgeEmptyState() {
|
|
292
|
+
return {
|
|
293
|
+
title: "还没有经验记录",
|
|
294
|
+
body: "经验是带证据、有适用条件的技术结论:收尾时用 dream_learn 提交候选,证据足够、范围明确的会出现在这里。",
|
|
295
|
+
hint: "旧日记里的教训不会自动升级为经验;采纳、驳回或标记冲突请用 dream_review。",
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
160
299
|
function spanOf(dreams) {
|
|
161
300
|
let min = null;
|
|
162
301
|
let max = null;
|
|
@@ -483,6 +622,72 @@ function LessonCard(props) {
|
|
|
483
622
|
]);
|
|
484
623
|
}
|
|
485
624
|
|
|
625
|
+
function KnowledgeRow(props) {
|
|
626
|
+
const row = props.row;
|
|
627
|
+
const privacy = props.privacy === true;
|
|
628
|
+
const [peek, setPeek] = useState(false);
|
|
629
|
+
const reveal = privacy === true ? function () { setPeek(peek !== true); } : undefined;
|
|
630
|
+
return h("div", {
|
|
631
|
+
className: "dshd-knowledge " + row.stateClass + (privacy === true && peek === true ? " dshd-peek" : ""),
|
|
632
|
+
onClick: reveal,
|
|
633
|
+
title: privacy === true ? "隐私模式:点击此条临时查看" : undefined,
|
|
634
|
+
}, [
|
|
635
|
+
h("div", { className: "dshd-khead", key: "h" }, [
|
|
636
|
+
h("span", { className: "dshd-kbadge", key: "b" }, row.stateLabel),
|
|
637
|
+
h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
|
|
638
|
+
]),
|
|
639
|
+
h("div", { className: "dshd-kmeta", key: "m" }, [
|
|
640
|
+
h("span", { className: "dshd-kscope", key: "s", title: "适用范围" }, row.scopeLabel),
|
|
641
|
+
h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
|
|
642
|
+
]),
|
|
643
|
+
h("div", { className: "dshd-kbody", key: "body" }, [
|
|
644
|
+
h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
|
|
645
|
+
h("div", { key: "evidence" }, "证据:" + row.evidenceSummary),
|
|
646
|
+
]),
|
|
647
|
+
]);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function KnowledgeCard(props) {
|
|
651
|
+
const vm = props.vm;
|
|
652
|
+
const head = function (extra) {
|
|
653
|
+
return h("div", { className: "dshd-khead", key: "head" }, [
|
|
654
|
+
h("h4", { key: "t" }, "经验"),
|
|
655
|
+
h("span", { className: "dshd-spacer", key: "s" }),
|
|
656
|
+
extra,
|
|
657
|
+
]);
|
|
658
|
+
};
|
|
659
|
+
if (props.error !== null && props.error !== undefined) {
|
|
660
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
661
|
+
head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
|
|
662
|
+
h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取经验失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
|
|
663
|
+
h("div", { className: "dshd-note", key: "n" }, "旧日记不受影响;修好后点「重试」。"),
|
|
664
|
+
]);
|
|
665
|
+
}
|
|
666
|
+
if (props.empty === true) {
|
|
667
|
+
const empty = knowledgeEmptyState();
|
|
668
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
669
|
+
h("h4", { key: "t" }, "经验"),
|
|
670
|
+
h("div", { className: "dshd-kempty", key: "e" }, [
|
|
671
|
+
h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
|
|
672
|
+
h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
|
|
673
|
+
h("p", { className: "dshd-note", key: "h" }, empty.hint),
|
|
674
|
+
]),
|
|
675
|
+
]);
|
|
676
|
+
}
|
|
677
|
+
return h("section", { className: "dshd-card dshd-kcard" }, [
|
|
678
|
+
head(props.loading === true
|
|
679
|
+
? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
|
|
680
|
+
: h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
|
|
681
|
+
h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
|
|
682
|
+
return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
|
|
683
|
+
})),
|
|
684
|
+
vm.shown < vm.total
|
|
685
|
+
? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
|
|
686
|
+
: null,
|
|
687
|
+
h("div", { className: "dshd-note", key: "note" }, "只读展示:候选默认不注入任务;采纳、驳回或标记冲突请用 dream_review。"),
|
|
688
|
+
]);
|
|
689
|
+
}
|
|
690
|
+
|
|
486
691
|
function DreamCard(props) {
|
|
487
692
|
const dream = props.dream;
|
|
488
693
|
const privacy = props.privacy === true;
|
|
@@ -561,6 +766,9 @@ function DreamPanel() {
|
|
|
561
766
|
const [filters, setFilters] = useState({ moods: [], withLessons: false });
|
|
562
767
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
563
768
|
const [limit, setLimit] = useState(DEFAULT_LIMIT);
|
|
769
|
+
const [knowledge, setKnowledge] = useState(null);
|
|
770
|
+
const [knowledgeLoading, setKnowledgeLoading] = useState(true);
|
|
771
|
+
const [knowledgeError, setKnowledgeError] = useState(null);
|
|
564
772
|
|
|
565
773
|
// 搜索防抖:连续打字只发一次请求;回车立即生效。
|
|
566
774
|
useEffect(function () {
|
|
@@ -587,7 +795,26 @@ function DreamPanel() {
|
|
|
587
795
|
return function () { alive = false; };
|
|
588
796
|
}, [query, reloadToken]);
|
|
589
797
|
|
|
798
|
+
// 经验区块独立取数:知识路由挂了但日记读不出来时,经验仍能显示(反之亦然)。
|
|
799
|
+
useEffect(function () {
|
|
800
|
+
let alive = true;
|
|
801
|
+
setKnowledgeLoading(true);
|
|
802
|
+
setKnowledgeError(null);
|
|
803
|
+
fetchKnowledge().then(function (payload) {
|
|
804
|
+
if (alive !== true) return;
|
|
805
|
+
setKnowledge(normalizeKnowledge(payload, new Date()));
|
|
806
|
+
setKnowledgeLoading(false);
|
|
807
|
+
}).catch(function (failure) {
|
|
808
|
+
if (alive !== true) return;
|
|
809
|
+
setKnowledgeError(failure instanceof Error ? failure : new Error(String(failure)));
|
|
810
|
+
setKnowledgeLoading(false);
|
|
811
|
+
setKnowledge(null);
|
|
812
|
+
});
|
|
813
|
+
return function () { alive = false; };
|
|
814
|
+
}, [reloadToken]);
|
|
815
|
+
|
|
590
816
|
const vm = viewModelOf(dreams, stats, now);
|
|
817
|
+
const knowledgeVm = knowledge === null ? { rows: [], total: 0, shown: 0, stats: null } : knowledge;
|
|
591
818
|
const pristine = vm.count === 0 && query === "";
|
|
592
819
|
const filterCount = filterCountOf(filters);
|
|
593
820
|
const visibleDreams = applyFilters(dreams, filters);
|
|
@@ -612,6 +839,16 @@ function DreamPanel() {
|
|
|
612
839
|
};
|
|
613
840
|
const clearFilters = function () { setFilters({ moods: [], withLessons: false }); };
|
|
614
841
|
const showMore = function () { if (limit < MORE_LIMIT) setLimit(MORE_LIMIT); };
|
|
842
|
+
const showKnowledge = function () { if (knowledgeLoading !== true) setReloadToken(function (value) { return value + 1; }); };
|
|
843
|
+
const knowledgeCard = h(KnowledgeCard, {
|
|
844
|
+
key: "knowledge",
|
|
845
|
+
vm: knowledgeVm,
|
|
846
|
+
loading: knowledgeLoading === true,
|
|
847
|
+
error: knowledgeError,
|
|
848
|
+
empty: knowledgeError === null && knowledgeLoading !== true && knowledgeVm.rows.length === 0,
|
|
849
|
+
privacy: privacy === true,
|
|
850
|
+
onRetry: showKnowledge,
|
|
851
|
+
});
|
|
615
852
|
|
|
616
853
|
// 筛选 popover:点面板外任意处收起;纯 node 冒烟没有事件系统就跳过。
|
|
617
854
|
useEffect(function () {
|
|
@@ -705,13 +942,16 @@ function DreamPanel() {
|
|
|
705
942
|
]);
|
|
706
943
|
|
|
707
944
|
const body = function () {
|
|
708
|
-
if (loading === true && vm.count === 0) return h(Skeleton, { key: "skeleton" });
|
|
945
|
+
if (loading === true && vm.count === 0) return h("div", { key: "boot" }, [h(Skeleton, { key: "skeleton" }), knowledgeCard]);
|
|
709
946
|
if (vm.count === 0) {
|
|
710
|
-
if (error !== null) return h("div", {
|
|
711
|
-
h("
|
|
712
|
-
|
|
947
|
+
if (error !== null) return h("div", { key: "failed-wrap" }, [
|
|
948
|
+
h("div", { className: "dshd-empty", key: "failed" }, [
|
|
949
|
+
h("h3", { key: "t" }, "暂时读不到梦境日记"),
|
|
950
|
+
h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
|
|
951
|
+
]),
|
|
952
|
+
knowledgeCard,
|
|
713
953
|
]);
|
|
714
|
-
return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
|
|
954
|
+
return h("div", { key: "empty-wrap" }, [h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch }), knowledgeCard]);
|
|
715
955
|
}
|
|
716
956
|
return h("div", { key: "content" }, [
|
|
717
957
|
vm.showTiles
|
|
@@ -726,6 +966,7 @@ function DreamPanel() {
|
|
|
726
966
|
h(MoodCard, { key: "mood", vm: vm }),
|
|
727
967
|
h(LessonCard, { key: "lessons", vm: vm }),
|
|
728
968
|
]),
|
|
969
|
+
knowledgeCard,
|
|
729
970
|
h("div", { className: "dshd-listhead", key: "listhead" }, [
|
|
730
971
|
h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
|
|
731
972
|
h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
|
|
@@ -857,7 +1098,28 @@ const CSS = [
|
|
|
857
1098
|
".dshd-chip{font-family:ui-monospace,Consolas,monospace;font-size:11px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.05));border-radius:999px;padding:2px 9px;color:var(--dsw-alias-label-tertiary,#6b7280)}",
|
|
858
1099
|
".dshd-skel{height:58px;border-radius:12px;margin-bottom:8px;background:linear-gradient(90deg,var(--dsw-alias-bg-layer-2,rgba(0,0,0,.04)) 25%,color-mix(in srgb,var(--dsw-alias-label-primary,#111) 10%,transparent) 37%,var(--dsw-alias-bg-layer-2,rgba(0,0,0,.04)) 63%);background-size:400% 100%;animation:dshd-shimmer 1.4s ease infinite}",
|
|
859
1100
|
"@keyframes dshd-shimmer{0%{background-position:100% 0}100%{background-position:0 0}}",
|
|
860
|
-
".dshd-footer{margin-top:12px;display:flex;align-items:center;gap:8px;justify-content:space-between;color:var(--dsw-alias-label-tertiary,#6b7280);font-size:11px;flex-wrap:wrap}"
|
|
1101
|
+
".dshd-footer{margin-top:12px;display:flex;align-items:center;gap:8px;justify-content:space-between;color:var(--dsw-alias-label-tertiary,#6b7280);font-size:11px;flex-wrap:wrap}",
|
|
1102
|
+
".dshd-kcard{margin:2px 0 14px}",
|
|
1103
|
+
".dshd-khead{display:flex;align-items:baseline;gap:8px;margin-bottom:8px}",
|
|
1104
|
+
".dshd-khead h4{margin:0;font-size:13px;font-weight:600}",
|
|
1105
|
+
".dshd-krows{display:flex;flex-direction:column;gap:7px}",
|
|
1106
|
+
".dshd-knowledge{border-left:3px solid var(--dsw-alias-label-tertiary,#6b7280);background:var(--dsw-alias-bg-layer-1,#fff);border-radius:9px;padding:8px 10px;min-width:0}",
|
|
1107
|
+
".dshd-knowledge.dshd-kstate-candidate{border-left-color:var(--dsw-alias-state-warn-primary,#f59e0b)}",
|
|
1108
|
+
".dshd-knowledge.dshd-kstate-disputed{border-left-color:var(--dsw-alias-state-error-primary,#dc2626);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 6%,var(--dsw-alias-bg-layer-1,#fff))}",
|
|
1109
|
+
".dshd-knowledge.dshd-kstate-usable{border-left-color:var(--dsw-alias-state-success-primary,#16a34a)}",
|
|
1110
|
+
".dshd-knowledge.dshd-kstate-stale,.dshd-knowledge.dshd-kstate-rejected,.dshd-knowledge.dshd-kstate-unknown{border-left-color:var(--dsw-alias-label-tertiary,#6b7280);opacity:.86}",
|
|
1111
|
+
".dshd-kbadge{flex:0 0 auto;font-size:11px;padding:1px 7px;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-label-primary,#111) 8%,transparent);white-space:nowrap}",
|
|
1112
|
+
".dshd-kstate-candidate .dshd-kbadge{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 22%,transparent)}",
|
|
1113
|
+
".dshd-kstate-disputed .dshd-kbadge{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 20%,transparent);color:var(--dsw-alias-state-error-primary,#dc2626)}",
|
|
1114
|
+
".dshd-kstate-usable .dshd-kbadge{background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 18%,transparent)}",
|
|
1115
|
+
".dshd-ktitle{font-size:12px;font-weight:600;word-break:break-word}",
|
|
1116
|
+
".dshd-kmeta{display:flex;gap:10px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);margin-top:2px}",
|
|
1117
|
+
".dshd-kbody{margin-top:5px;display:flex;flex-direction:column;gap:3px;font-size:12px;word-break:break-word}",
|
|
1118
|
+
".dshd-kempty{padding:14px 10px;text-align:center;color:var(--dsw-alias-label-tertiary,#6b7280)}",
|
|
1119
|
+
".dshd-kempty-title{color:var(--dsw-alias-label-primary,#111);font-weight:600;font-size:13px;margin-bottom:4px}",
|
|
1120
|
+
".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
|
|
1121
|
+
".dshd-privacy .dshd-kbody{filter:blur(4px)}",
|
|
1122
|
+
".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}"
|
|
861
1123
|
].join("");
|
|
862
1124
|
/* @ui-css-end */
|
|
863
1125
|
|
|
@@ -897,6 +1159,16 @@ exports.inject = inject;
|
|
|
897
1159
|
// 纯函数内部件:验证脚本可直接取来测,不必另抄一份算法。
|
|
898
1160
|
exports.__internals = {
|
|
899
1161
|
routeUrl: routeUrl,
|
|
1162
|
+
routeUrlFor: routeUrlFor,
|
|
1163
|
+
KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
|
|
1164
|
+
KNOWLEDGE_LIMIT: KNOWLEDGE_LIMIT,
|
|
1165
|
+
fetchKnowledge: fetchKnowledge,
|
|
1166
|
+
normalizeKnowledge: normalizeKnowledge,
|
|
1167
|
+
normalizeLessonState: normalizeLessonState,
|
|
1168
|
+
lessonStateLabel: lessonStateLabel,
|
|
1169
|
+
lessonStateClass: lessonStateClass,
|
|
1170
|
+
knowledgeCountText: knowledgeCountText,
|
|
1171
|
+
knowledgeEmptyState: knowledgeEmptyState,
|
|
900
1172
|
fetchJournal: fetchJournal,
|
|
901
1173
|
normalizeDreams: normalizeDreams,
|
|
902
1174
|
normalizeStats: normalizeStats,
|
package/lib/index.d.ts
CHANGED