mingdao-harness 0.6.45 → 0.7.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/docs/ARCHITECTURE.md +1 -1
- package/docs/CONFIG.md +16 -8
- package/package.json +1 -1
- package/packs/example-hello/pack.json +1 -1
- package/src/config.js +44 -13
- package/src/session.js +6 -5
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -96,7 +96,7 @@ MingDao 是一个「模型循环 + 工具 + 权限」内核,纯 Node.js ≥ 18
|
|
|
96
96
|
### 2.4 会话 / 记忆 / 任务(11)
|
|
97
97
|
|
|
98
98
|
- `session.js` —— 会话持久化:`<home>/sessions/<时间戳>-<随机>.jsonl` 每轮追加,`--continue` 载入最近一次,`--resume` 打开选择器 —— 会话文件是消息结构的唯一权威,检查点等旁挂状态另存侧车文件。
|
|
99
|
-
- `session-events.js` —— 会话事件日志(v0.7.0 §1.1 切片 A / B2-a):`<session>.events.ndjson` 追加 13 类事件 `{seq,time,type,data}`(session/turn/step/tool/message/usage/compact/messages.replace/interrupt/checkpoint)—— **seq 是唯一关联键**:跨进程短临界区锁内「读尾行 last+1」、单次 append 后回读校验(单条事件超过尾窗时回退整文件校验最后一行);读侧跳过 torn 行/未知 type 并返回 `skipped`(可忽略性:旧读者遇到新类型只当它不存在);`replayToMessages()` 是纯函数(`message.append` 提供消息本体、`compact`/`messages.replace` 重置基线、`interrupt` 延迟补 tool 占位),由事件流重放的消息数组必须与原消息数组逐字节等价;事件文件是**旁路**:`loadSession`
|
|
99
|
+
- `session-events.js` —— 会话事件日志(v0.7.0 §1.1 切片 A / B2-a):`<session>.events.ndjson` 追加 13 类事件 `{seq,time,type,data}`(session/turn/step/tool/message/usage/compact/messages.replace/interrupt/checkpoint)—— **seq 是唯一关联键**:跨进程短临界区锁内「读尾行 last+1」、单次 append 后回读校验(单条事件超过尾窗时回退整文件校验最后一行);读侧跳过 torn 行/未知 type 并返回 `skipped`(可忽略性:旧读者遇到新类型只当它不存在);`replayToMessages()` 是纯函数(`message.append` 提供消息本体、`compact`/`messages.replace` 重置基线、`interrupt` 延迟补 tool 占位),由事件流重放的消息数组必须与原消息数组逐字节等价;事件文件是**旁路**:`loadSession` 仅在事件源模式(v0.7.0 起默认)读它、对拍不一致即回退消息数组,`rewriteSession` 只在会话写盘成功后追加 `messages.replace`(写失败只 warn,不改变重写结果),`cfg.eventLog=false` 或 `MINGDAO_EVENT_LOG=0` 可整体回滚;切片 B1 起新增两个纯函数供传输层使用——`readAfter(eventFile, afterSeq)` 只读游标读取(`seq > after`,返回 `{ok,events,lastSeq,skipped}`,文件不存在按空窗口)与 `continuityOf(seqs)` 连续性自检(`{ok,gaps:[{after,before}]}`,seq 必须从 1 起逐条 +1)——仅 `GET /api/events?file=&after=` 与 `GET /api/session` 的 `lastEventSeq` 读事件文件,SSE 帧/UI 仍不读。
|
|
100
100
|
- `session-index.js` —— 会话检索索引:词表倒排 + 按 mtime/size 的增量同步,分片存 `<home>/sessions-index/<2位sha1前缀>.json`(256 片)—— 未变化直接用缓存词表,>8MB 文件跳过并移除条目;中文按 bigram + 单字、查询串同口径分词后 AND 匹配。
|
|
101
101
|
- `memory.js` —— 长记忆与自主进化:用户记忆 `~/.mingdao/AGENTS.md`(手动 + 会话结束自动提取用户偏好并去重追加)、会话日志 `journal.jsonl`(默认不注入系统提示,`--journal` / WebUI 勾选才取最近 3 条)—— 自动提取用 executor 模型(约几十 token),`autoMemory: false` 可关。
|
|
102
102
|
- `tasks.js` —— 多会话后台任务面板:`mingdao run` 拉起独立 worker 子进程,状态落 `<home>/tasks/<id>.json`,命令族 `run` / `tasks` / `tasks watch` / `tasks kill` —— taskId 用不可枚举随机值;kill 前必须过 PID 归属校验(见 `proc.js`)。
|
package/docs/CONFIG.md
CHANGED
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
可选字段:`temperature`、`maxOutputTokens`、`includeUsage`(流式请求 usage 统计,个别网关不支持
|
|
29
29
|
`stream_options` 时设 `false`)、`autoTitle`(自动生成会话标题,默认开)、`notify`(任务桌面通知,默认开)、
|
|
30
30
|
`autoCompact`(上下文自动压缩,默认开,见下)、`audit`(工具调用审计,默认开,见下)、
|
|
31
|
-
`eventLog`(会话事件日志双写,默认开,见下)、`eventSource`(会话读取真源,`
|
|
32
|
-
`
|
|
33
|
-
|
|
31
|
+
`eventLog`(会话事件日志双写,默认开,见下)、`eventSource`(会话读取真源,`events`(默认)/
|
|
32
|
+
`messages`,见下——**默认已翻到事件源**;出问题可设环境变量 `MINGDAO_EVENT_SOURCE=messages`
|
|
33
|
+
一键回退,不必改配置。**风险一句话**:`events` 用事件日志派生消息,与消息文件对拍不一致时
|
|
34
|
+
warn 并回退到消息数组;旧格式日志/无侧车/读失败静默回退消息数组)。
|
|
34
35
|
|
|
35
36
|
### WebUI 设置项的「默认值」列(只读,不落盘)
|
|
36
37
|
|
|
@@ -107,12 +108,19 @@ MingDao 先用 executor 模型(路由关闭时为当前模型)把被裁段
|
|
|
107
108
|
resume / 空会话 / `tool_calls` 等场景与消息数组 deepEqual。
|
|
108
109
|
- **回滚开关**:`"eventLog": false`(默认开)或环境变量 `MINGDAO_EVENT_LOG=0`。关闭后**完全**
|
|
109
110
|
停止事件写入(含 `message.append` 与 `messages.replace` 双写),会话文件照常写入。
|
|
110
|
-
-
|
|
111
|
+
- **读取真源(默认 `events`)**:`"eventSource": "events"`(默认)时 `loadSession()` 用事件日志
|
|
111
112
|
重放派生消息;只有 `session.start.data.format >= 2` **且**与 `.jsonl` 消息数组逐条对拍一致才
|
|
112
|
-
采用。日志缺失 / 格式戳缺失或过旧 / 读失败 →
|
|
113
|
-
对拍不一致 → warn 一行(含文件与首个差异位置:`第 N
|
|
114
|
-
`"messages"`
|
|
115
|
-
|
|
113
|
+
采用。日志缺失 / 格式戳缺失或过旧 / 读失败 → **静默**回退消息数组(旧会话常态,不刷 warn);
|
|
114
|
+
对拍不一致 → warn 一行(含文件与首个差异位置:`第 N 条`)并回退。返回值形状不变。
|
|
115
|
+
显式写 `"eventSource": "messages"` 时只读消息数组、与 0.6.x 逐字节一致(配置优先于默认)。
|
|
116
|
+
翻默认的依据是真实会话证据:`node scripts/event-parity.mjs` 在 4 类真实写入路径上
|
|
117
|
+
**fail = 0**、双读 `deepEqual` 4/4(见 [EVENT-SOURCE-EVIDENCE.md](EVENT-SOURCE-EVIDENCE.md))。
|
|
118
|
+
- **一键回退(运维级)**:环境变量 `MINGDAO_EVENT_SOURCE=messages` **优先于配置文件**,
|
|
119
|
+
出事时不必改 config.json、不必换文件——设这个变量重启进程即退回旧真源;撤销变量即恢复
|
|
120
|
+
配置里的值(环境变量不会被写进配置文件)。`MINGDAO_EVENT_SOURCE=events` 可反向强制事件源;
|
|
121
|
+
非法值按「未设」处理,继续走配置/默认 `events`(**不会**悄悄回落 `messages`)。
|
|
122
|
+
**风险一句话**:事件源是派生真源,日志缺失/损坏/对拍不一致时以消息数组兜底;若发现历史
|
|
123
|
+
变形,立即设 `MINGDAO_EVENT_SOURCE=messages` 并保留现场,再用 `scripts/event-parity.mjs` 定位。
|
|
116
124
|
- **兼容**:新增类型只追加、不改变既有事件形状;读侧对未知 type 一律跳过并计入 `skipped`,
|
|
117
125
|
因此旧版本读到 `messages.replace` 只会当它不存在,旧日志(没有该类型)在新版本照常可读。
|
|
118
126
|
旧会话没有 events 文件时读取返回空流;事件侧车不会出现在会话列表/最近会话中;事件文件不
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -228,39 +228,70 @@ function applyModelLabels(/** @type {any} */ cfg) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
|
-
* 会话读取真源(v0.7.0 B2-b
|
|
231
|
+
* 会话读取真源(v0.7.0 B2-b:默认已翻到 `events`)。
|
|
232
232
|
*
|
|
233
|
-
*
|
|
234
|
-
* `events`:用事件日志重放派生消息,但**必须与消息文件对拍一致**才采用;不一致就 warn 并
|
|
235
|
-
* 回退到消息数组。这只用于灰度验证「事件流能否当唯一真源」,**不是**新的默认值:
|
|
236
|
-
* 翻默认要等真实会话的 `scripts/event-parity.mjs` 证据(pass 覆盖足够广、incomplete 清零)。
|
|
233
|
+
* 优先级:环境变量 `MINGDAO_EVENT_SOURCE` > `config.eventSource` > 默认值 `events`。
|
|
237
234
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
235
|
+
* `events`(默认):用事件日志重放派生消息,但**必须与消息文件对拍一致**才采用;不一致就
|
|
236
|
+
* warn 并回退到消息数组(消息数组永远是最后兜底)。格式戳缺失 / 旧格式(format < 2)/
|
|
237
|
+
* 无侧车 / 读失败一律静默回退——旧会话永远从消息数组读。
|
|
238
|
+
* `messages`:继续只读 `.jsonl` 消息数组——与 0.6.x 完全一致,事件侧车坏掉也不影响。
|
|
239
|
+
*
|
|
240
|
+
* 翻默认的证据:`docs/EVENT-SOURCE-EVIDENCE.md`(4 类真实会话、fail=0、双读 deepEqual 4/4)。
|
|
241
|
+
* 回归:`scripts/event-parity.mjs` + `test/event-source.js`。
|
|
242
|
+
*
|
|
243
|
+
* 校验口径:只认字面量 `'messages'` / `'events'`,其余(缺省 / 大小写不符 / 任意非法值)
|
|
244
|
+
* 回落默认值,绝不因为一个拼错的值悄悄把真源切回旧路径。
|
|
240
245
|
*/
|
|
241
246
|
export const EVENT_SOURCE_MESSAGES = 'messages';
|
|
242
247
|
export const EVENT_SOURCE_EVENTS = 'events';
|
|
243
|
-
/**
|
|
244
|
-
export const
|
|
248
|
+
/** 运维级回退开关:`MINGDAO_EVENT_SOURCE=messages` 立即回到旧真源(优先于配置文件)。 */
|
|
249
|
+
export const EVENT_SOURCE_ENV = 'MINGDAO_EVENT_SOURCE';
|
|
250
|
+
/** 默认真源:`events`(v0.7.0 B2-b;证据见 docs/EVENT-SOURCE-EVIDENCE.md)。 */
|
|
251
|
+
export const DEFAULT_EVENT_SOURCE = EVENT_SOURCE_EVENTS;
|
|
245
252
|
const EVENT_SOURCE_SET = new Set([EVENT_SOURCE_MESSAGES, EVENT_SOURCE_EVENTS]);
|
|
246
253
|
|
|
247
254
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
255
|
+
* 读环境变量覆盖:只认精确的 `messages` / `events`;未设或非法返回 null(视为「没有覆盖」)。
|
|
256
|
+
* 非法值**不**回落 `messages`——否则一个拼错的变量会悄悄把真源切回旧路径。
|
|
257
|
+
* @returns {'messages'|'events'|null}
|
|
258
|
+
*/
|
|
259
|
+
function eventSourceFromEnv() {
|
|
260
|
+
// 空字符串按「未设」处理;Set.has('') 为 false,正好走回落。
|
|
261
|
+
const raw = process.env[EVENT_SOURCE_ENV] || '';
|
|
262
|
+
return EVENT_SOURCE_SET.has(raw) ? /** @type {'messages'|'events'} */ (raw) : null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 归一化**配置自身的值**(不含环境变量覆盖):合法枚举原样返回,其余回落默认值 `events`。
|
|
267
|
+
* 写配置/落盘走这条,避免运维期的临时环境变量被写进 config.json。
|
|
250
268
|
* @param {any} value
|
|
251
269
|
* @returns {'messages'|'events'}
|
|
252
270
|
*/
|
|
253
|
-
|
|
271
|
+
function normalizeConfiguredEventSource(value) {
|
|
254
272
|
return EVENT_SOURCE_SET.has(value) ? value : DEFAULT_EVENT_SOURCE;
|
|
255
273
|
}
|
|
256
274
|
|
|
275
|
+
/**
|
|
276
|
+
* 解析**运行期**真源:环境变量 > 配置值 > 默认值 `events`。
|
|
277
|
+
* 纯函数,读配置/直接调用 `loadSession(file, cfg)` 共用同一判据;环境变量只影响本次进程,
|
|
278
|
+
* 撤销变量即恢复配置文件里的值(回退无需改配置、无需改文件)。
|
|
279
|
+
* @param {any} value
|
|
280
|
+
* @returns {'messages'|'events'}
|
|
281
|
+
*/
|
|
282
|
+
export function normalizeEventSource(value) {
|
|
283
|
+
const fromEnv = eventSourceFromEnv();
|
|
284
|
+
return fromEnv ?? normalizeConfiguredEventSource(value);
|
|
285
|
+
}
|
|
286
|
+
|
|
257
287
|
/**
|
|
258
288
|
* 就地归一化配置对象里的 `eventSource`(字段不存在时**不新增**,保持旧配置文件字节不变)。
|
|
289
|
+
* 注意:这里**不**应用 `MINGDAO_EVENT_SOURCE`——环境变量只在读取会话时解析,绝不落盘。
|
|
259
290
|
* @param {any} cfg
|
|
260
291
|
*/
|
|
261
292
|
function applyEventSource(/** @type {any} */ cfg) {
|
|
262
293
|
if (!isConfigObject(cfg) || cfg.eventSource === undefined) return cfg;
|
|
263
|
-
cfg.eventSource =
|
|
294
|
+
cfg.eventSource = normalizeConfiguredEventSource(cfg.eventSource);
|
|
264
295
|
return cfg;
|
|
265
296
|
}
|
|
266
297
|
|
package/src/session.js
CHANGED
|
@@ -293,11 +293,12 @@ function messagesFromEvents(file) {
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/**
|
|
296
|
-
* 读取会话。默认(`eventSource` 缺省或 `
|
|
296
|
+
* 读取会话。默认(`eventSource` 缺省或 `events`)尝试事件日志派生,但必须通过**对拍兜底**:
|
|
297
|
+
* 派生结果与消息文件逐条等价才采用;不一致就 warn(带文件与首个差异位置)并回退到消息数组。
|
|
298
|
+
* 事件源缺失 / 格式旧 / 读失败一律静默回退。`cfg.eventSource === 'messages'`(或环境变量
|
|
299
|
+
* `MINGDAO_EVENT_SOURCE=messages`)时只读 `.jsonl` 消息数组,行为与 0.6.x 完全一致。
|
|
297
300
|
*
|
|
298
|
-
*
|
|
299
|
-
* **对拍兜底**:派生结果与消息文件逐条等价才采用;不一致就 warn(带文件与首个差异位置)并
|
|
300
|
-
* 回退到消息数组。事件源缺失 / 格式旧 / 读失败一律静默回退。
|
|
301
|
+
* v0.7.0 B2-b:默认已翻到 `events`;证据见 `docs/EVENT-SOURCE-EVIDENCE.md`。
|
|
301
302
|
*
|
|
302
303
|
* @param {any} file
|
|
303
304
|
* @param {any} [cfg] 传入时尊重 `cfg.eventSource` 与 `cfg.eventLog === false`(后者只影响修补器)
|
|
@@ -328,7 +329,7 @@ export function loadSession(file, cfg = null) {
|
|
|
328
329
|
try {
|
|
329
330
|
console.warn(
|
|
330
331
|
`[MingDao] ⚠ 会话事件源对拍不一致(${file}):${d.reason};差异位置:${at}——已回退到消息数组` +
|
|
331
|
-
|
|
332
|
+
`(消息数组始终是兜底真源;可设 MINGDAO_EVENT_SOURCE=messages 强制回退)。`
|
|
332
333
|
);
|
|
333
334
|
} catch {}
|
|
334
335
|
}
|