@stardustlc/dsh-dream 0.3.1 → 0.3.3
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.en.md +8 -2
- package/README.md +7 -3
- package/lib/config.d.ts +1 -1
- package/lib/config.js +4 -4
- package/lib/sessions.js +82 -30
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
[简体中文](README.md)
|
|
2
2
|
|
|
3
|
+
   
|
|
4
|
+
|
|
3
5
|
# dsh-dream
|
|
4
6
|
|
|
5
7
|
> **An agent that dreams**: session replay (dream material) → reflection (interpretation) → dream journal (memory consolidation).
|
|
@@ -8,7 +10,11 @@ Humans consolidate memories by replaying the day during sleep — dsh-dream give
|
|
|
8
10
|
|
|
9
11
|
## Compatibility
|
|
10
12
|
|
|
11
|
-
Verified
|
|
13
|
+
Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
|
|
14
|
+
|
|
15
|
+
Reads v0/v1/v2/v3 plaintext JSONL and multi-frame zstd, legacy packed chunks and v2/v3 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.
|
|
16
|
+
|
|
17
|
+
Default data directories follow `DSH_HOME`: `sessions` for session logs and `.dsh-dream` for the journal. Without `DSH_HOME`, the base is `~/.dsh`. Explicit `sessionsRoot` and `journalDir` settings take precedence.
|
|
12
18
|
|
|
13
19
|
## Install / Uninstall
|
|
14
20
|
|
|
@@ -48,7 +54,7 @@ Read-only access to the sessions directory; appends JSONL to the journal directo
|
|
|
48
54
|
|
|
49
55
|
```bash
|
|
50
56
|
pnpm install
|
|
51
|
-
pnpm test # build +
|
|
57
|
+
pnpm test # build + offline tests (session generations, zstd, streams, masking, journal, bridge, registration)
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
## License
|
package/README.md
CHANGED
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
> **会做梦的 agent**:会话回放(梦原料)→ 反思(解梦)→ 梦境日记(记忆巩固)。
|
|
6
6
|
|
|
7
|
-
 
|
|
7
|
+
   
|
|
8
8
|
|
|
9
9
|
人睡觉时大脑回放白天的经历、巩固记忆——dsh-dream 让 DeepSeek Harness 的 agent 也拥有这个能力:读取你的历史会话(官方多帧 zstd 会话日志,零依赖解析),提炼梦原料,反思后写入永久梦境日记,下次醒来可以忆梦。
|
|
10
10
|
|
|
11
11
|
## 兼容性
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
已在官方 `@deepseek-ai/dsh@0.1.5-rc.1`、Node `24.16.0` 上验证(2026-09-11):18 个组件与 Modlens 同载,工具 schema、技能注册及离线只读调用检查通过。采用 `cordis.patch.yml` + `dsh.bundle.patch` 组合包模型。Node 要求与该版本 Harness 一致:22.19 及以上的 22.x,或 24 及以上。外部服务的实际业务操作需按各组件配置单独验证。
|
|
14
|
+
|
|
15
|
+
支持 v0/v1/v2/v3 的普通 JSONL 与多帧 zstd、旧 packed-chunk 行及 v2/v3 内嵌 stream;升级留下多代文件时只读取每个会话的最新规范文件。PTC 子工具调用保留工具名且不重复计数,不提取系统提示、推理、工具参数或结果正文。
|
|
16
|
+
|
|
17
|
+
默认从 `DSH_HOME/sessions` 读取会话、在 `DSH_HOME/.dsh-dream` 保存日记;未设置 `DSH_HOME` 时使用 `~/.dsh`。配置中的 `sessionsRoot`、`journalDir` 始终优先。
|
|
14
18
|
|
|
15
19
|
## 安装
|
|
16
20
|
|
|
@@ -95,7 +99,7 @@ dream_bridge { path: "AGENTS.md", maxLessons: 10 }
|
|
|
95
99
|
|
|
96
100
|
```bash
|
|
97
101
|
pnpm install
|
|
98
|
-
pnpm test # 构建 +
|
|
102
|
+
pnpm test # 构建 + 离线测试(会话代际、多帧 zstd、流式记录、隐私脱敏、日记、桥接与注册生命周期)
|
|
99
103
|
```
|
|
100
104
|
|
|
101
105
|
## License
|
package/lib/config.d.ts
CHANGED
|
@@ -14,4 +14,4 @@ export interface ResolvedDreamConfig {
|
|
|
14
14
|
maskSecrets: boolean;
|
|
15
15
|
}
|
|
16
16
|
/** 解析并校验插件配置。 */
|
|
17
|
-
export declare function resolveConfig(raw?: Record<string, unknown> | null): ResolvedDreamConfig;
|
|
17
|
+
export declare function resolveConfig(raw?: Record<string, unknown> | null, env?: NodeJS.ProcessEnv): ResolvedDreamConfig;
|
package/lib/config.js
CHANGED
|
@@ -14,12 +14,12 @@ function clamp(value, fallback, min, max) {
|
|
|
14
14
|
return Math.min(max, Math.max(min, Math.round(value)));
|
|
15
15
|
}
|
|
16
16
|
/** 解析并校验插件配置。 */
|
|
17
|
-
export function resolveConfig(raw) {
|
|
17
|
+
export function resolveConfig(raw, env = process.env) {
|
|
18
18
|
const cfg = raw ?? {};
|
|
19
|
-
const
|
|
19
|
+
const dshHome = str(env.DSH_HOME, join(homedir(), '.dsh'));
|
|
20
20
|
return {
|
|
21
|
-
sessionsRoot: str(cfg.sessionsRoot, join(
|
|
22
|
-
journalDir: str(cfg.journalDir, join(
|
|
21
|
+
sessionsRoot: str(cfg.sessionsRoot, join(dshHome, 'sessions')),
|
|
22
|
+
journalDir: str(cfg.journalDir, join(dshHome, '.dsh-dream')),
|
|
23
23
|
maxSessions: clamp(cfg.maxSessions, 10, 1, 50),
|
|
24
24
|
maxCharsPerSession: clamp(cfg.maxCharsPerSession, 6000, 500, 50000),
|
|
25
25
|
maxUserMessages: clamp(cfg.maxUserMessages, 5, 1, 20),
|
package/lib/sessions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DSH
|
|
2
|
+
* DSH 会话日志读取:兼容 v0/v1/v2/v3 的 JSONL 与多帧 zstd。
|
|
3
3
|
* 首个逻辑行是会话头({ type: 'session', ... }),其后每行一条存储记录({ type, seq, time, data })。
|
|
4
4
|
* 本模块只读不写;损坏帧/行一律容错跳过。
|
|
5
5
|
*
|
|
@@ -78,7 +78,8 @@ export function digestSessionFile(filePath, maxUserMessages) {
|
|
|
78
78
|
return null;
|
|
79
79
|
let text;
|
|
80
80
|
try {
|
|
81
|
-
|
|
81
|
+
const bytes = readFileSync(filePath);
|
|
82
|
+
text = filePath.endsWith('.zstd') ? decompressAll(bytes) : bytes.toString('utf8');
|
|
82
83
|
}
|
|
83
84
|
catch {
|
|
84
85
|
return null;
|
|
@@ -91,6 +92,8 @@ export function digestSessionFile(filePath, maxUserMessages) {
|
|
|
91
92
|
const first = JSON.parse(lines[0]);
|
|
92
93
|
if (first.type !== 'session')
|
|
93
94
|
return null;
|
|
95
|
+
if (first.version !== undefined && ![0, 1, 2, 3].includes(first.version))
|
|
96
|
+
return null;
|
|
94
97
|
header = {
|
|
95
98
|
id: String(first.id ?? ''),
|
|
96
99
|
createdAt: typeof first.createdAt === 'number' ? first.createdAt : 0,
|
|
@@ -113,6 +116,31 @@ export function digestSessionFile(filePath, maxUserMessages) {
|
|
|
113
116
|
streamTail: '',
|
|
114
117
|
endedAt: null,
|
|
115
118
|
};
|
|
119
|
+
const appendStreamText = (text) => {
|
|
120
|
+
digest.streamTail = (digest.streamTail + text).slice(-4000);
|
|
121
|
+
};
|
|
122
|
+
const appendToolName = (name) => {
|
|
123
|
+
if (typeof name === 'string' && name !== '' && digest.toolCalls.length < 200)
|
|
124
|
+
digest.toolCalls.push(name);
|
|
125
|
+
};
|
|
126
|
+
const readStreamRecord = (value) => {
|
|
127
|
+
if (typeof value !== 'object' || value === null)
|
|
128
|
+
return;
|
|
129
|
+
const record = value;
|
|
130
|
+
if (record.type === 'text-chunks' && Array.isArray(record.texts)) {
|
|
131
|
+
appendStreamText(record.texts.filter((part) => typeof part === 'string').join(''));
|
|
132
|
+
}
|
|
133
|
+
else if (record.type === 'tool-call-chunks') {
|
|
134
|
+
appendToolName(record.name);
|
|
135
|
+
}
|
|
136
|
+
else if (record.type === 'chunk' && typeof record.chunk === 'object' && record.chunk !== null) {
|
|
137
|
+
const chunk = record.chunk;
|
|
138
|
+
if (chunk.type === 'text-delta' && typeof chunk.text === 'string')
|
|
139
|
+
appendStreamText(chunk.text);
|
|
140
|
+
if (chunk.type === 'tool-call-start')
|
|
141
|
+
appendToolName(chunk.name);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
116
144
|
for (const line of lines.slice(1)) {
|
|
117
145
|
let rec;
|
|
118
146
|
try {
|
|
@@ -121,11 +149,20 @@ export function digestSessionFile(filePath, maxUserMessages) {
|
|
|
121
149
|
catch {
|
|
122
150
|
continue;
|
|
123
151
|
}
|
|
152
|
+
if (typeof rec !== 'object' || rec === null || Array.isArray(rec))
|
|
153
|
+
continue;
|
|
124
154
|
const type = String(rec.type ?? '');
|
|
125
155
|
const data = rec.data;
|
|
126
|
-
const time = typeof rec.time === 'number' ? rec.time : null;
|
|
156
|
+
const time = typeof rec.time === 'number' ? rec.time : typeof rec.time0 === 'number' ? rec.time0 : null;
|
|
127
157
|
if (time !== null && (digest.endedAt === null || time > digest.endedAt))
|
|
128
158
|
digest.endedAt = time;
|
|
159
|
+
// v2/v3 把流式片段内嵌进 message/attempt;v0 的独立打包行继续兼容。
|
|
160
|
+
if ((type === 'assistant/message' || type === 'assistant/attempt') && typeof data === 'object' && data !== null) {
|
|
161
|
+
const stream = data.stream;
|
|
162
|
+
if (Array.isArray(stream))
|
|
163
|
+
for (const record of stream)
|
|
164
|
+
readStreamRecord(record);
|
|
165
|
+
}
|
|
129
166
|
if (type === 'session/title') {
|
|
130
167
|
digest.title = textOf(data);
|
|
131
168
|
}
|
|
@@ -145,25 +182,17 @@ export function digestSessionFile(filePath, maxUserMessages) {
|
|
|
145
182
|
digest.assistantTail.shift();
|
|
146
183
|
}
|
|
147
184
|
}
|
|
148
|
-
else if (type === 'tool/call') {
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else if (type === 'text-chunks') {
|
|
154
|
-
// 官方打包行:{ data: { texts: string[], ... } },还原流式正文
|
|
155
|
-
const texts = typeof data === 'object' && data !== null ? data.texts : undefined;
|
|
156
|
-
if (Array.isArray(texts)) {
|
|
157
|
-
digest.streamTail += texts.filter((t) => typeof t === 'string').join('');
|
|
158
|
-
if (digest.streamTail.length > 4000)
|
|
159
|
-
digest.streamTail = digest.streamTail.slice(-4000);
|
|
160
|
-
}
|
|
185
|
+
else if (type === 'tool/call' || type === 'tool/code-dispatch-start' || type === 'tool/ptc-dispatch-start') {
|
|
186
|
+
// PTC 子调用只记录开始事件,避免与完成事件重复计算;不提取参数或结果正文。
|
|
187
|
+
if (typeof data === 'object' && data !== null)
|
|
188
|
+
appendToolName(data.name);
|
|
161
189
|
}
|
|
162
|
-
else if (type === 'tool-call-chunks') {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
190
|
+
else if (type === 'text-chunks' || type === 'tool-call-chunks') {
|
|
191
|
+
if (typeof data === 'object' && data !== null)
|
|
192
|
+
readStreamRecord({ ...data, type });
|
|
193
|
+
}
|
|
194
|
+
else if (type === 'assistant/chunk' && typeof data === 'object' && data !== null) {
|
|
195
|
+
readStreamRecord({ type: 'chunk', chunk: data.chunk });
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
digest.userMessages = digest.userMessages.slice(0, maxUserMessages);
|
|
@@ -174,7 +203,15 @@ export function listSessionFiles(sessionsRoot, limit) {
|
|
|
174
203
|
if (!existsSync(sessionsRoot))
|
|
175
204
|
return [];
|
|
176
205
|
const out = [];
|
|
177
|
-
|
|
206
|
+
const listDirectory = (path) => {
|
|
207
|
+
try {
|
|
208
|
+
return readdirSync(path);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
for (const project of listDirectory(sessionsRoot)) {
|
|
178
215
|
const projectDir = join(sessionsRoot, project);
|
|
179
216
|
try {
|
|
180
217
|
if (!statSync(projectDir).isDirectory())
|
|
@@ -183,15 +220,30 @@ export function listSessionFiles(sessionsRoot, limit) {
|
|
|
183
220
|
catch {
|
|
184
221
|
continue;
|
|
185
222
|
}
|
|
186
|
-
for (const session of
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
223
|
+
for (const session of listDirectory(projectDir)) {
|
|
224
|
+
const sessionDir = join(projectDir, session);
|
|
225
|
+
// 迁移会保留旧代文件;每个会话只读最新已提交的规范文件,避免重复或读旧副本。
|
|
226
|
+
const candidates = listDirectory(sessionDir).flatMap((name) => {
|
|
227
|
+
const match = /^session(?:\.v([1-9][0-9]*))?\.jsonl(?:\.zstd)?$/.exec(name);
|
|
228
|
+
if (match === null)
|
|
229
|
+
return [];
|
|
230
|
+
const version = match[1] === undefined ? 0 : Number(match[1]);
|
|
231
|
+
if (!Number.isSafeInteger(version))
|
|
232
|
+
return [];
|
|
233
|
+
const file = join(sessionDir, name);
|
|
234
|
+
try {
|
|
235
|
+
const stat = statSync(file);
|
|
236
|
+
return stat.isFile() ? [{ file, version, mtime: stat.mtimeMs }] : [];
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}).sort((a, b) => b.version - a.version || Number(b.file.endsWith('.zstd')) - Number(a.file.endsWith('.zstd')));
|
|
242
|
+
if (candidates[0] !== undefined)
|
|
243
|
+
out.push(candidates[0]);
|
|
193
244
|
}
|
|
194
245
|
}
|
|
195
246
|
out.sort((a, b) => b.mtime - a.mtime);
|
|
196
|
-
|
|
247
|
+
const capped = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
|
|
248
|
+
return out.slice(0, capped).map((item) => item.file);
|
|
197
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardustlc/dsh-dream",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "DSH 做梦插件:agent 在会话间隙回放近期对话(dream_digest)、反思并写入梦境日记(dream_save/journal/recall),高频教训幂等桥接进 AGENTS.md(dream_bridge),六工具含健康自检。多帧 zstd 会话读取,默认隐私脱敏,零运行时依赖。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"engines": {
|
|
46
|
-
"node": "
|
|
46
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {},
|
|
49
49
|
"devDependencies": {
|