mingdao-harness 0.6.43 → 0.6.45

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.
@@ -100,14 +100,20 @@ macOS 作业的 `--mac dmg` 在检测到 `CSC_LINK` + Apple 凭证时自动完
100
100
  > 两者互不替代:代码签名不能阻止应用从被投毒的更新源装一个**同样签了名**的包,来源签名也不负责
101
101
  > 消除 SmartScreen 提示。
102
102
  >
103
- > **本轮状态(诚实标注)**:代码已就位并 fail-closed,但**发布链路尚未接入签名**——
104
- > `desktop/gen-update-yml.mjs` 与服务器发布脚本都没有签名步骤,feed 里**没有** `.sig` 文件。
105
- > 因此客户端当前实际走的是**默认档 `warn-and-install`**:照旧安装,弹窗给两行——
106
- > 「本次更新未验证来源签名(发布链路尚未接入签名)——已按默认策略安装。」+
107
- > 「高安全场景可设 `MINGDAO_REQUIRE_UPDATE_SIGNATURE=1` 拒绝无签名更新;详情见日志与官网。」
108
- > 完整解释(为什么无法用密码学证明来源、feed 被投毒时无法区分、不改配置与会话)写在**日志**里:
109
- > 桌面版 `<userData>/logs/mingdao.log` 的 `更新来源校验:…` 行。要让"没签名就拒绝"生效,发行方需要:
110
- > ① 按下面步骤接入签名;② 在客户端设 `MINGDAO_REQUIRE_UPDATE_SIGNATURE=1`(或改成默认值)。
103
+ > **当前状态(v0.6.45 起,已接入)**:签名由 CI 产出(`scripts/sign-release-assets.mjs` → GitHub Release),
104
+ > `scripts/publish-update-sigs.mjs <版本>` 再把每版的 `<安装包>.sig` 发到
105
+ > `https://harness.mingdao.ai/updates/`。客户端取到签名并用**内置公钥**(密钥环,0.6.36 起内置
106
+ > `current` 公钥)验证,通过即走 `install`,**不再弹"未验证来源签名"**。
107
+ >
108
+ > 已实测(2026-11-08):`latest.yml` → `mingdao-setup-0.6.44-x64.exe`(80,202,343 字节)
109
+ > → `/updates/mingdao-setup-0.6.44-x64.exe.sig` → 用 `current` 公钥验签**通过**。
110
+ >
111
+ > 仍是**默认档 `warn-and-install`** 的原因:签名缺失(发布侧故障 / 网络取不到 / feed 被剥离签名)
112
+ > 时宁可提示后放行,也不把用户挡在更新之外。要 fail-closed,设 `MINGDAO_REQUIRE_UPDATE_SIGNATURE=1`。
113
+ >
114
+ > **客户端的版本门槛**:`current` 公钥是 **0.6.36** 起内置的。0.6.36+ 的客户端能验签;
115
+ > **≤0.6.35 的客户端**会看到"更新包来源不可信,已拒绝"(它们只认 `legacy-2025` 那把已丢弃私钥的公钥),
116
+ > 需要手动下载一次。回滚方式:删掉源站 `/updates/*.sig`,立即回到"提示后照常安装"。
111
117
 
112
118
  ### 5.1 为什么不是"再算一次 sha512"
113
119
 
@@ -149,6 +155,11 @@ node scripts/update-sign.mjs --sign desktop/dist/mingdao-<ver>-x86_64.AppImage
149
155
  node scripts/update-sign.mjs --verify desktop/dist/mingdao-setup-<ver>-x64.exe --pub update-signing-pub.pem
150
156
 
151
157
  # ④ 上传:安装包 + 它的 .sig **一起**放进 feed 目录(官网 /updates/,与 latest*.yml 同级)
158
+ # 现实做法(v0.6.45 起):CI 已把 .sig 传进 Release,用脚本一次性搬到 feed 目录:
159
+ MINGDAO_GITHUB_TOKEN=... MDH_SITE_HOST=mingdao-server \
160
+ node scripts/publish-update-sigs.mjs <版本> # 加 --dry-run 只列计划
161
+ # · 只发**本版安装包**的签名;`latest*.yml.sig` 不进 /updates/(否则 `rm *.sig` 的回滚语义会变模糊)
162
+ # · 镜像的 /updates 代理到源站,无需重复上传
152
163
  ```
153
164
 
154
165
  判据与文案在 `desktop/update-verify.js`(纯函数,`test/smoke.js` §135 逐条钉边界):
package/docs/CONFIG.md CHANGED
@@ -28,7 +28,9 @@
28
28
  可选字段:`temperature`、`maxOutputTokens`、`includeUsage`(流式请求 usage 统计,个别网关不支持
29
29
  `stream_options` 时设 `false`)、`autoTitle`(自动生成会话标题,默认开)、`notify`(任务桌面通知,默认开)、
30
30
  `autoCompact`(上下文自动压缩,默认开,见下)、`audit`(工具调用审计,默认开,见下)、
31
- `eventLog`(会话事件日志双写,默认开,见下)。
31
+ `eventLog`(会话事件日志双写,默认开,见下)、`eventSource`(会话读取真源,`messages`(默认)/
32
+ `events`,见下——**灰度开关,风险提示**:`events` 会用事件日志派生消息,一旦与消息文件对拍不一致
33
+ 就 warn 并回退到消息数组;只在验证「事件流能否当唯一真源」时临时开启,**不要长期依赖**)。
32
34
 
33
35
  ### WebUI 设置项的「默认值」列(只读,不落盘)
34
36
 
@@ -105,6 +107,12 @@ MingDao 先用 executor 模型(路由关闭时为当前模型)把被裁段
105
107
  resume / 空会话 / `tool_calls` 等场景与消息数组 deepEqual。
106
108
  - **回滚开关**:`"eventLog": false`(默认开)或环境变量 `MINGDAO_EVENT_LOG=0`。关闭后**完全**
107
109
  停止事件写入(含 `message.append` 与 `messages.replace` 双写),会话文件照常写入。
110
+ - **读取真源(灰度开关,默认 `messages`)**:`"eventSource": "events"` 时 `loadSession()` 尝试用事件日志
111
+ 重放派生消息;只有 `session.start.data.format >= 2` **且**与 `.jsonl` 消息数组逐条对拍一致才
112
+ 采用。日志缺失 / 格式戳缺失或过旧 / 读失败 → **静默**回退消息数组(灰度期常态,不刷 warn);
113
+ 对拍不一致 → warn 一行(含文件与首个差异位置:`第 N 条`)并回退。返回值形状不变。默认
114
+ `"messages"` 保持只读消息数组、与 0.6.x 逐字节一致;**翻默认要等 `scripts/event-parity.mjs`
115
+ 的真实会话证据(不再有 incomplete)**,不要仅凭配置开启就认为事件已是唯一真源。
108
116
  - **兼容**:新增类型只追加、不改变既有事件形状;读侧对未知 type 一律跳过并计入 `skipped`,
109
117
  因此旧版本读到 `messages.replace` 只会当它不存在,旧日志(没有该类型)在新版本照常可读。
110
118
  旧会话没有 events 文件时读取返回空流;事件侧车不会出现在会话列表/最近会话中;事件文件不
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mingdao-harness",
3
- "version": "0.6.43",
3
+ "version": "0.6.45",
4
4
  "description": "MingDao Harness —— 开源智能体框架(Agent Harness)。零依赖、开箱即用,针对 DeepSeek-V4 系列优化,开放主流模型接入。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -164,7 +164,7 @@ export async function runRepl(ctx) {
164
164
  '选择要恢复的会话:',
165
165
  list.map((s) => ({ value: s.file, label: `${relativeTime(s.mtime)} · ${sessionPreview(s.file)}` }))
166
166
  );
167
- const loaded = loadSession(choice);
167
+ const loaded = loadSession(choice, cfg);
168
168
  if (loaded.messages.length) {
169
169
  session = loaded;
170
170
  io.print(style(`✓ 已载入会话 ${path.basename(choice)}(${loaded.messages.length} 条消息)`, C.green));
@@ -174,7 +174,7 @@ export async function runRepl(ctx) {
174
174
  if (!session && opts.continueSession) {
175
175
  const latest = latestSession(home);
176
176
  if (latest) {
177
- const loaded = loadSession(latest.file);
177
+ const loaded = loadSession(latest.file, cfg);
178
178
  if (loaded.messages.length) {
179
179
  session = loaded;
180
180
  io.print(style(`已载入会话 ${latest.name}(${loaded.messages.length} 条消息)`, C.dim));
package/src/config.js CHANGED
@@ -227,6 +227,43 @@ function applyModelLabels(/** @type {any} */ cfg) {
227
227
  return cfg;
228
228
  }
229
229
 
230
+ /**
231
+ * 会话读取真源(v0.7.0 B2-b 灰度开关)。
232
+ *
233
+ * `messages`(默认):继续只读 `.jsonl` 消息数组——与 0.6.x 完全一致,事件侧车坏掉也不影响。
234
+ * `events`:用事件日志重放派生消息,但**必须与消息文件对拍一致**才采用;不一致就 warn 并
235
+ * 回退到消息数组。这只用于灰度验证「事件流能否当唯一真源」,**不是**新的默认值:
236
+ * 翻默认要等真实会话的 `scripts/event-parity.mjs` 证据(pass 覆盖足够广、incomplete 清零)。
237
+ *
238
+ * 校验口径与 `config.eventLog` 一致:只认字面量 `'events'`,其余(缺省 / 大小写不符 /
239
+ * 任意非法值)一律按 `'messages'` 处理,绝不因为一个拼错的值把读取路径切到实验分支。
240
+ */
241
+ export const EVENT_SOURCE_MESSAGES = 'messages';
242
+ export const EVENT_SOURCE_EVENTS = 'events';
243
+ /** 默认真源:`messages`。改这里等于改所有 ENOENT/未配置用户的读取路径,必须等灰度证据。 */
244
+ export const DEFAULT_EVENT_SOURCE = EVENT_SOURCE_MESSAGES;
245
+ const EVENT_SOURCE_SET = new Set([EVENT_SOURCE_MESSAGES, EVENT_SOURCE_EVENTS]);
246
+
247
+ /**
248
+ * 归一化 `config.eventSource`:合法枚举原样返回,其余一律回落默认值 `messages`。
249
+ * 纯函数,读配置/写配置/直接调用 `loadSession(file, cfg)` 三条路径共用同一判据。
250
+ * @param {any} value
251
+ * @returns {'messages'|'events'}
252
+ */
253
+ export function normalizeEventSource(value) {
254
+ return EVENT_SOURCE_SET.has(value) ? value : DEFAULT_EVENT_SOURCE;
255
+ }
256
+
257
+ /**
258
+ * 就地归一化配置对象里的 `eventSource`(字段不存在时**不新增**,保持旧配置文件字节不变)。
259
+ * @param {any} cfg
260
+ */
261
+ function applyEventSource(/** @type {any} */ cfg) {
262
+ if (!isConfigObject(cfg) || cfg.eventSource === undefined) return cfg;
263
+ cfg.eventSource = normalizeEventSource(cfg.eventSource);
264
+ return cfg;
265
+ }
266
+
230
267
  // ---------------------------------------------------------------------------
231
268
  // v0.7.0 §1.3:配置写入的「基线 + 增量」。
232
269
  //
@@ -350,12 +387,12 @@ function prepareConfigForWrite(cfg) {
350
387
  quarantineCorruptConfig(`保存配置时读不出来:${strict.error}`);
351
388
  base = {};
352
389
  }
353
- if (!sameHome) return cfg; // 无基线(新配置/字面量)或换了 home → 保持原语义:整份写入
390
+ if (!sameHome) return applyEventSource(cfg); // 无基线(新配置/字面量)或换了 home → 保持原语义:整份写入
354
391
  /** @type {{set: [string[], any][], del: string[][]}} */
355
392
  const patch = { set: [], del: [] };
356
393
  collectConfigPatch(snapshot.data, cfg, [], patch);
357
394
  applyConfigPatch(base, patch);
358
- return base;
395
+ return applyEventSource(base);
359
396
  }
360
397
 
361
398
  /**
@@ -379,7 +416,7 @@ function applyEventLogGate(cfg) {
379
416
  export function loadConfig() {
380
417
  const r = readConfigStrict();
381
418
  if (!r.ok) return null;
382
- const cfg = applyModelLabels(applyCachedModelDefaults(r.data));
419
+ const cfg = applyEventSource(applyModelLabels(applyCachedModelDefaults(r.data)));
383
420
  rememberConfig(cfg); // v0.7.0 §1.3:记下基线,saveConfig 只合并增量
384
421
  applyEventLogGate(cfg); // v0.7.0 §1.1:config.eventLog=false 在「先落盘、后建 agent」路径也生效
385
422
  return cfg;
@@ -401,7 +438,7 @@ export function loadConfig() {
401
438
  export function loadConfigForWrite(why = '某个写配置的命令') {
402
439
  const r = readConfigStrict();
403
440
  if (r.ok) {
404
- const cfg = r.data || {};
441
+ const cfg = applyEventSource(r.data || {});
405
442
  rememberConfig(cfg);
406
443
  applyEventLogGate(cfg);
407
444
  return cfg;
@@ -418,7 +455,7 @@ export function loadConfigForWrite(why = '某个写配置的命令') {
418
455
  export function ensureMinimalConfig() {
419
456
  const strict = readConfigStrict();
420
457
  if (strict.ok && strict.data) {
421
- const cfg = applyModelLabels(strict.data);
458
+ const cfg = applyEventSource(applyModelLabels(strict.data));
422
459
  rememberConfig(cfg);
423
460
  return cfg;
424
461
  }
@@ -457,6 +494,7 @@ export function ensureMinimalConfig() {
457
494
  export function saveConfig(cfg) {
458
495
  try {
459
496
  ensureHome();
497
+ applyEventSource(cfg); // B2-b:写回前也归一化,保证内存基线与落盘值一致
460
498
  withStateFileLockSync(configLockFile(), () => {
461
499
  writeConfigUnlocked(prepareConfigForWrite(cfg));
462
500
  rememberConfig(cfg); // 成功后把这版对象当作下次增量比较的基线
@@ -479,6 +517,7 @@ export function saveConfig(cfg) {
479
517
  export async function saveConfigAsync(cfg) {
480
518
  try {
481
519
  ensureHome();
520
+ applyEventSource(cfg); // B2-b:写回前也归一化,保证内存基线与落盘值一致
482
521
  await withFileLock(configLockFile(), () => {
483
522
  writeConfigUnlocked(prepareConfigForWrite(cfg));
484
523
  rememberConfig(cfg);
@@ -513,7 +552,7 @@ export function updateConfigSync(/** @type {(cfg: any) => any} */ mutate) {
513
552
  cfg = {};
514
553
  }
515
554
  const next = typeof mutate === 'function' ? mutate(cfg) : cfg;
516
- const out = isConfigObject(next) ? next : cfg;
555
+ const out = applyEventSource(isConfigObject(next) ? next : cfg);
517
556
  writeConfigUnlocked(out);
518
557
  rememberConfig(out);
519
558
  return { ok: true, error: null, config: out };
@@ -543,7 +582,7 @@ export async function updateConfig(/** @type {(cfg: any) => any} */ mutate) {
543
582
  cfg = {};
544
583
  }
545
584
  const next = typeof mutate === 'function' ? mutate(cfg) : cfg;
546
- const out = isConfigObject(next) ? next : cfg;
585
+ const out = applyEventSource(isConfigObject(next) ? next : cfg);
547
586
  writeConfigUnlocked(out);
548
587
  rememberConfig(out);
549
588
  return { ok: true, error: null, config: out };
@@ -777,6 +777,39 @@ export function replayToMessages(events) {
777
777
  return messages;
778
778
  }
779
779
 
780
+ /**
781
+ * 比较两个消息数组(v0.7.0 B2-b 从 `scripts/event-parity.mjs` 抽出的共享判据)。
782
+ *
783
+ * 逐条按 `role + content + tool_calls`(tool_calls 只比 id,排序后)比较;`_` 开头的运行时注记
784
+ * 不落盘、不参与比较。**必须与 scripts/event-parity.mjs 的 `diffMessages` 保持同一语义**:
785
+ * 对拍工具说「等价」的会话,`loadSession({eventSource:'events'})` 才能采用派生结果;两边判据
786
+ * 一旦漂移,对拍结论与灰度读取就会互相打脸。
787
+ *
788
+ * 为什么不直接 import 脚本:`scripts/` 不在 `package.json#files`(npm 安装形态没有它),
789
+ * 且 tsconfig 会把脚本拉进类型检查;因此判据放在 src 侧,脚本侧保留同名实现(硬约束:本切片
790
+ * 不改 `scripts/event-parity.mjs`)。
791
+ *
792
+ * @param {any[]} a
793
+ * @param {any[]} b
794
+ * @returns {{ok:boolean, index:number, reason:string}}
795
+ */
796
+ export function diffMessages(a, b) {
797
+ const A = Array.isArray(a) ? a : [];
798
+ const B = Array.isArray(b) ? b : [];
799
+ if (A.length !== B.length) return { ok: false, index: Math.min(A.length, B.length), reason: `条数不同:消息 ${A.length} vs 重放 ${B.length}` };
800
+ const norm = (/** @type {any} */ m) => {
801
+ /** @type {{role:string, content:any, tool_calls?:string[], tool_call_id?:string}} */
802
+ const out = { role: String(m?.role ?? ''), content: m?.content ?? '' };
803
+ if (Array.isArray(m?.tool_calls)) out.tool_calls = m.tool_calls.map((/** @type {any} */ t) => String(t?.id ?? '')).sort();
804
+ if (m?.tool_call_id != null) out.tool_call_id = String(m.tool_call_id);
805
+ return JSON.stringify(out);
806
+ };
807
+ for (let i = 0; i < A.length; i += 1) {
808
+ if (norm(A[i]) !== norm(B[i])) return { ok: false, index: i, reason: `第 ${i + 1} 条不同` };
809
+ }
810
+ return { ok: true, index: -1, reason: '' };
811
+ }
812
+
780
813
  /**
781
814
  * 供后续 UI/诊断用的 turn 摘要(可选;纯函数)。只从事件自己推导,不读消息正文。
782
815
  * @param {any[]} events
package/src/session.js CHANGED
@@ -3,7 +3,10 @@
3
3
 
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { appendEvent, eventFileFor, eventLogEnabled, isEventLogName, repairEventLog, warnEventFailure, EVENT_LOG_FORMAT } from './session-events.js';
6
+ import { appendEvent, eventFileFor, eventLogEnabled, diffMessages, isEventLogName, readEvents, replayToMessages, repairEventLog, warnEventFailure, EVENT_LOG_FORMAT } from './session-events.js';
7
+ // v0.7.0 B2-b:读取真源开关。归一化放在 config.js(配置读取/写回/直接传 cfg 三条路径共用),
8
+ // 对拍比较函数抽在 session-events.js 的 `diffMessages`(与 scripts/event-parity.mjs 同一判据)。
9
+ import { normalizeEventSource, EVENT_SOURCE_EVENTS } from './config.js';
7
10
 
8
11
  // —— 冲突备份名(单一事实来源,v0.4.6)——
9
12
  // 同步冲突时写入的备份:<会话名>.server-<时间戳>[-<随机后缀>].jsonl(远端版本)与 .remote-(远端拉取版本)。
@@ -262,7 +265,44 @@ export function repairSessionEvents(file, cfg = null) {
262
265
  }
263
266
  }
264
267
 
265
- /** @param {any} file @param {any} [cfg] */
268
+ /**
269
+ * v0.7.0 B2-b:从事件侧车派生消息(灰度读取真源)。
270
+ *
271
+ * 返回 `null` 表示**不采用事件源**——无侧车 / 格式戳缺失或早于本版 / 事件日志读失败。
272
+ * 这三种都是灰度期常态(旧会话本来就没有 `messages.replace`),调用方必须**静默**回退到
273
+ * 消息数组,不刷 warn;只有「格式够新、派生出来了、但与消息文件对拍不一致」才是需要可见的异常。
274
+ * @param {any} file 会话文件(.jsonl)
275
+ * @returns {any[]|null}
276
+ */
277
+ function messagesFromEvents(file) {
278
+ const eventFile = eventFileFor(file);
279
+ if (!eventFile) return null;
280
+ try {
281
+ if (!fs.existsSync(eventFile)) return null;
282
+ const read = readEvents(eventFile);
283
+ if (!read.ok) return null;
284
+ // 与 scripts/event-parity.mjs 同一判据:format >= EVENT_LOG_FORMAT 才认为日志可完整回溯
285
+ // (旧日志没有 rewrite 记录,派生出来必然缺窗口,不能拿它当读取真源)。
286
+ const newFormat = read.events.some((ev) => ev.type === 'session.start' && Number(ev.data?.format) >= EVENT_LOG_FORMAT);
287
+ if (!newFormat) return null;
288
+ return replayToMessages(read.events);
289
+ } catch {
290
+ // 事件侧车是旁路:任何异常都只意味着「这次不用事件源」,绝不能让会话读不出来。
291
+ return null;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * 读取会话。默认(`eventSource` 缺省或 `messages`)只读 `.jsonl` 消息数组,行为与 0.6.x 一致。
297
+ *
298
+ * `cfg.eventSource === 'events'`(v0.7.0 B2-b 灰度开关)时额外尝试事件日志派生,但必须通过
299
+ * **对拍兜底**:派生结果与消息文件逐条等价才采用;不一致就 warn(带文件与首个差异位置)并
300
+ * 回退到消息数组。事件源缺失 / 格式旧 / 读失败一律静默回退。
301
+ *
302
+ * @param {any} file
303
+ * @param {any} [cfg] 传入时尊重 `cfg.eventSource` 与 `cfg.eventLog === false`(后者只影响修补器)
304
+ * @returns {{file:any, messages:any[]}} 形状与旧版完全一致
305
+ */
266
306
  export function loadSession(file, cfg = null) {
267
307
  const raw = fs.readFileSync(file, 'utf8');
268
308
  const messages = [];
@@ -275,10 +315,30 @@ export function loadSession(file, cfg = null) {
275
315
  }
276
316
  } catch {}
277
317
  }
318
+ let result = messages;
319
+ if (normalizeEventSource(cfg?.eventSource) === EVENT_SOURCE_EVENTS) {
320
+ const derived = messagesFromEvents(file);
321
+ if (derived) {
322
+ const d = diffMessages(messages, derived);
323
+ if (d.ok) result = derived;
324
+ else {
325
+ // 对拍失败是**真异常**(新格式日志理应可完整重放):必须可见,并带首个差异位置。
326
+ // 但读取结果仍回退到消息数组——灰度期不能让实验分支改变用户看到的历史。
327
+ const at = d.index >= 0 ? `第 ${d.index + 1} 条` : '未知';
328
+ try {
329
+ console.warn(
330
+ `[MingDao] ⚠ 会话事件源对拍不一致(${file}):${d.reason};差异位置:${at}——已回退到消息数组` +
331
+ `(config.eventSource=events 仅灰度验证用,默认仍是 messages)。`
332
+ );
333
+ } catch {}
334
+ }
335
+ }
336
+ }
278
337
  // v0.6.40:消息读完后顺带闭合悬空事件。失败只 warn(repairSessionEvents 内部吞掉),
279
338
  // 返回值与消息数组一字不动——事件侧车坏了/修不了,会话照样可读。
339
+ // v0.7.0 B2-b:放在事件派生之后,派生读到的是修补前的日志(与 scripts/event-parity.mjs 同口径)。
280
340
  repairSessionEvents(file, cfg);
281
- return { file, messages };
341
+ return { file, messages: result };
282
342
  }
283
343
 
284
344
  // 会话预览:第一条用户消息(用于会话选择器)
@@ -89,7 +89,7 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
89
89
  const file = url.searchParams.get('file');
90
90
  if (!file) return json(res, 400, { error: '缺少 file 参数' });
91
91
  try {
92
- const loaded = loadSession(path.join(home, 'sessions', path.basename(file)));
92
+ const loaded = loadSession(path.join(home, 'sessions', path.basename(file)), deps.cfg);
93
93
  // 载入会话时聚焦其工作空间(P3-4):全局默认切到该会话的目录,前端下拉同步显示
94
94
  const wsDir = getSessionWorkspace(path.basename(file));
95
95
  const ws = workspaceForDir(wsDir);
@@ -189,7 +189,7 @@ export async function handle({ req, res, method, p, url }, deps, shared) {
189
189
  return json(res, 404, { error: `会话不存在:${file}` });
190
190
  }
191
191
  try {
192
- const loaded = loadSession(path.join(home, 'sessions', file));
192
+ const loaded = loadSession(path.join(home, 'sessions', file), deps.cfg);
193
193
  if (loaded.messages.length) {
194
194
  const wsDir = getSessionWorkspace(file) || state.workingDir;
195
195
  const { finalizeSession } = await import('../../../memory.js');
package/src/web/server.js CHANGED
@@ -819,7 +819,7 @@ export async function runWebServer({ host = '127.0.0.1', port = 3820, authToken,
819
819
  const isNew = !body.file;
820
820
  if (body.file) {
821
821
  try {
822
- const loaded = loadSession(path.join(home, 'sessions', path.basename(body.file)));
822
+ const loaded = loadSession(path.join(home, 'sessions', path.basename(body.file)), cfg);
823
823
  if (loaded.messages.length) session = loaded;
824
824
  } catch {}
825
825
  }