@modusensus/dsh-mneme 0.7.23 → 0.7.24
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 +4 -3
- package/README.md +5 -3
- package/lib/api.js +9 -1
- package/lib/index.js +15 -5
- package/package.json +1 -1
- package/src/api.js +9 -1
- package/src/index.js +15 -5
- package/test/webServer-optional.test.js +49 -0
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@ English | [简体中文](README.md)
|
|
|
7
7
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
11
11
|
|
|
12
12
|
> A cross-session memory plugin for DeepSeek Harness: it lets the Agent remember you, remember your projects, and organize memories automatically. **Mneme** (Μνήμη) — named after Mnemosyne, the Greek goddess of memory who presides over memory and dreams, just as autoDream consolidates memories in the background.
|
|
13
13
|
|
|
@@ -166,6 +166,7 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
|
|
|
166
166
|
|
|
167
167
|
| Version | Highlights |
|
|
168
168
|
|------|------|
|
|
169
|
+
| **v0.7.24** | Fixed DSH Desktop plugin-tree load crash (v0.7.23 regression): cordis 4's ctx is a Proxy — accessing a property not declared in `inject` throws `cannot get property "webServer" without inject` (not `undefined`), and removing webServer from inject meant cordis no longer waited for the host service, so Desktop crashed on restart; fix: restored webServer to inject (cordis applies the plugin only after the host service is ready) + apply/register guard switched to `ctx.reflect.get` (inject-free read, returns `undefined` when absent, never throws); verified with a real cordis + dsh-host-webserver plugin (API routes 200, unknown path 404, headless silently inactive); 714 tests green |
|
|
169
170
|
| **v0.7.23** | Root-caused "memory consolidation keeps failing": a legal empty decision array `[]` from consolidation is no longer treated as a failure (CONSOLIDATION_PROMPT explicitly allows "no output when nothing needs changing", so a model with a healthy, non-redundant memory legitimately returns `[]` — yet `validateDecisions` hard-rejected it as `decision list must be a non-empty array`, failing the whole run and flooding the audit with failures; **model-agnostic** — ChatGPT/Claude hit the same trap; fix: empty array short-circuits to `ok:true` no-op instead of tripping the implicit-keep coverage check). Plus: empty-body fix part 2 (`dreamMaxTokens` default 8192→32768 so thinking models don't burn the whole budget on reasoning) + skipInvalid splice residue bug (length equality ≠ content equality, skipped decisions leaked into apply/audit); 712 tests green |
|
|
170
171
|
| **v0.7.22** | Restored the v0.6.9 skipInvalid tolerant-validation path (issue #89 regression, lost in the v0.7.11 rewrite): `dreamSkipInvalid` (default true) skips individual invalid decisions, applies the valid subset, and marks the run degraded; `allowCrossTypeMerge` (default false) explicitly relaxes cross-type merging — weak models (e.g. qwen3.8-flash) with jittery schema compliance no longer fail the whole batch and burn LLM calls. Strict mode and the sleep path behave unchanged; global caps/coverage floors still reject the whole run (running over cap = broken model, not minor schema drift). New `dreamMinIntervalMinutes` (0–10080, default 0 = unlimited) minimum autoDream trigger interval — failed/degraded runs also consume the interval (throttling exists to stop back-to-back failing calls); feature_flags whitelist now 34 keys; 696 tests green |
|
|
171
172
|
| **v0.7.21** | Fixed autoDream/sleep effort fallback being dead code on the stream path (the catch-based retry from v0.7.16 never fired): dsh-llm rc.1 turns adapter-stage failures (incl. `UNSUPPORTED_REASONING_EFFORT`) into a terminal error finish chunk instead of a throw; `streamText` now captures the finish-chunk failure cause (`describeStreamFailure` normalizes `{code,message}`) + `withEffortFallback` gains a `getStreamError` accessor (retries without effort when rejected) + `runAuditedLlm` supports `spec.streamError` (audit `error_message` carries the real cause; `run.error` stays a stable `"llm failed"`); 688 tests green |
|
|
@@ -441,7 +442,7 @@ src/
|
|
|
441
442
|
lib/
|
|
442
443
|
├── client.js # Web 面板(手写 ModuleLoader bundle)
|
|
443
444
|
└── *.js # src 的同步分发产物
|
|
444
|
-
test/ #
|
|
445
|
+
test/ # 714 node:test tests (audit + three-axis stress invariants)
|
|
445
446
|
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步
|
|
446
447
|
```
|
|
447
448
|
|
|
@@ -450,7 +451,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
|
|
|
450
451
|
```bash
|
|
451
452
|
cd dsh-mneme
|
|
452
453
|
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
453
|
-
npm test # 运行
|
|
454
|
+
npm test # 运行 714 个测试
|
|
454
455
|
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
455
456
|
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
456
457
|
```
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
9
9
|
[](https://github.com/modusensus/dsh-mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -207,6 +207,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
207
207
|
|
|
208
208
|
| 版本 | 亮点 |
|
|
209
209
|
|------|------|
|
|
210
|
+
| **v0.7.24** | 修复 DSH Desktop 插件树加载崩溃(v0.7.23 回归):cordis 4 的 ctx 是 Proxy,访问未在 inject 声明的 `webServer` 会抛 `cannot get property without inject`(而非返回 undefined),且去掉 inject 后 cordis 不再等待宿主服务 → 桌面端重启即崩;修复:恢复 webServer 到 inject(cordis 等宿主就绪再 apply)+ apply/register 守卫改 `ctx.reflect.get`(免 inject 读取、未提供返回 undefined 不抛错);真实 cordis + dsh-host-webserver 插件实测;714 测试全绿 |
|
|
210
211
|
| **v0.7.23** | 记忆沉淀「反复失败」根治:consolidation 合法空数组 `[]` 不再误判 failed(CONSOLIDATION_PROMPT 允许「无问题无需输出」,模型无冗余时合法返回 `[]`——此前 `validateDecisions` 硬判 non-empty → 整单 failed、审计反复失败,且与模型无关,ChatGPT/Claude 同样踩中;修复:空数组显式短路 `ok:true` no-op)+ 空体修复第二段(`dreamMaxTokens` 默认 8192→32768,思考模型推理烧光预算的根治余量,设置面板可调)+ skipInvalid splice 残留 bug(长度相等≠内容一致,被跳决策残留);712 测试全绿 |
|
|
211
212
|
| **v0.7.22** | 恢复 v0.6.9 的 skipInvalid 宽容校验路径(issue #89 回归,v0.7.11 重写丢失):`dreamSkipInvalid`(默认 true)单条非法决策跳过 + 合法子集应用 + run 记 degraded,`allowCrossTypeMerge`(默认 false)显式放宽跨类型合并——弱模型(如 qwen3.8-flash)决策合规抖动不再整单拒绝白烧 LLM 调用;严格模式/sleep 路径行为不变,全局上限/覆盖率下限仍整单拒绝(刷爆上限=模型坏了,非轻微 schema 漂移);新增 `dreamMinIntervalMinutes`(0-10080,默认 0=不限)autoDream 最小触发间隔,失败/degraded run 也占用间隔(节流防失败调用连发);feature_flags 白名单 34 键;696 测试全绿 |
|
|
212
213
|
| **v0.7.21** | 修复 autoDream/sleep 的 effort 回退在流式路径失效(v0.7.16 的 catch 式回退是死代码):dsh-llm rc.1 把 adapter 阶段异常(含 `UNSUPPORTED_REASONING_EFFORT`)转成终态 error finish chunk 不再抛出;`streamText` 现捕获 finish-chunk 失败原因(新增 `describeStreamFailure` 归一化 `{code,message}`)+ `withEffortFallback` 增加 `getStreamError` 访问器(effort 被拒时去掉重试一次)+ `runAuditedLlm` 支持 `spec.streamError`(audit 行 `error_message` 携带真实原因,`run.error` 稳定 `"llm failed"` 不变);688 测试全绿 |
|
|
@@ -293,6 +294,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
293
294
|
| **v0.7.18** | ✅ 完成 | 生态第一步 + 查询收敛 | better-sidebar 软集成(inject 声明 + optional peer `dsh-better-sidebar` + registerTab 复用四视图,未装安全跳过;窄容器 `@container` 适配)+ `/list?deposited=only` 沉淀视图(receipt_chain ∪ source=dream)+ 记忆库沉淀/已归档筛选 chip + 状态页仪表盘化(统计 + 查看全部跳转预置筛选)+ 抽屉归档记忆「恢复」;667 测试全绿 |
|
|
294
295
|
| **v0.7.20** | ✅ 完成 | heat 回归 + 阶段二前端 + better-sidebar 修复 | heat 热度模型完整找回(issue #87,v0.7.10 移植:幂律衰减 + TYPE_DECAY + sleep 热联合双保护 + 实体热投影)+ 验收清单落地(heatEnabled 默认关 / feature_flags 31 键 / lightMode 联动 / sleep 降级审计暴露 / updated_at⊥last_accessed_at 契约)+ 阶段二前端(/list heat 投影、HeatBadge 三档、order=heat 页内排序)+ better-sidebar 修复(issue #88:内层动态子插件);685 测试全绿 |
|
|
295
296
|
| **v0.7.21** | ✅ 完成 | effort 回退流式修复 | autoDream/sleep 的 catch 式 effort 回退在流式路径是死代码(dsh-llm rc.1 把 adapter 异常转成终态 error finish chunk 不再抛出)→ `streamText` 捕获 finish-chunk 失败原因(`describeStreamFailure` 归一化)+ `withEffortFallback` 增加 `getStreamError` 访问器(effort 被拒去重试)+ `runAuditedLlm` 支持 `spec.streamError`(audit 记真实原因);688 测试全绿 |
|
|
297
|
+
| **v0.7.24** | ✅ 完成 | 桌面端崩溃紧急修复 | v0.7.23 把 webServer 移出 inject 致 cordis Proxy 抛 `cannot get property "webServer" without inject`(未注入属性直接访问抛错而非 undefined),桌面端重启插件树加载失败;修复:恢复 webServer 到 inject(cordis 等宿主就绪再 apply)+ apply/register 守卫改 `ctx.reflect.get`(免 inject 读取、未提供返回 undefined 不抛错,未来 headless 移出 inject 也安全);真实 cordis + dsh-host-webserver 实测 API 路由 200 / 未知路径 404 / headless 静默不激活;714 测试全绿 |
|
|
296
298
|
| **v0.7.23** | ✅ 完成 | 记忆沉淀「反复失败」根治 + 空体第二段 + skipInvalid splice 修复 | consolidation 合法空数组 `[]` no-op(CONSOLIDATION_PROMPT 允许无问题无需输出;此前 validateDecisions 硬判 non-empty → 整单 failed,模型无关、ChatGPT/Claude 同样踩中;修复:空数组显式短路 ok,不再触发隐式 keep 覆盖率误判);`dreamMaxTokens` 默认 8192→32768(思考模型推理烧光预算根治余量);skipInvalid splice 残留 bug(长度相等≠内容一致,被跳决策残留进 apply);712 测试全绿 |
|
|
297
299
|
| **v0.7.22** | ✅ 完成 | skipInvalid 宽容校验回归(issue #89)+ autoDream 节流 | 恢复 v0.6.9 的 skipInvalid 双轨结构(v0.7.11 重写丢失):`dreamSkipInvalid` 单条非法决策跳过 + 合法子集应用 + run 记 degraded,`allowCrossTypeMerge` 显式放宽跨类型合并;弱模型(qwen3.8-flash)决策合规抖动不再整单拒绝;新增 `dreamMinIntervalMinutes`(0-10080,默认 0=不限)最小触发间隔,失败/degraded run 也占用间隔;严格模式/sleep 路径行为不变;feature_flags 白名单 34 键;696 测试全绿 |
|
|
298
300
|
| **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
|
|
@@ -529,7 +531,7 @@ src/
|
|
|
529
531
|
├── api.js # HTTP 路由(Web 面板数据通道)
|
|
530
532
|
└── index.js # 插件接线
|
|
531
533
|
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
|
|
532
|
-
test/ #
|
|
534
|
+
test/ # 714 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
|
|
533
535
|
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准
|
|
534
536
|
```
|
|
535
537
|
|
|
@@ -538,7 +540,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
|
|
|
538
540
|
```bash
|
|
539
541
|
cd dsh-mneme
|
|
540
542
|
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
541
|
-
npm test # 运行
|
|
543
|
+
npm test # 运行 714 个测试
|
|
542
544
|
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
543
545
|
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
544
546
|
```
|
package/lib/api.js
CHANGED
|
@@ -98,6 +98,14 @@ function parseBody(text) {
|
|
|
98
98
|
export function createApi(ctx, service, settings, commands, embedder, semantic = null, apiToken = "", config = null) {
|
|
99
99
|
const disposers = [];
|
|
100
100
|
|
|
101
|
+
// webServer 在 index.js 的 inject 声明中(cordis 等宿主服务就绪后 apply),
|
|
102
|
+
// 这里做防御性读取:cordis ctx 的 Proxy 不允许直接访问未 inject 的属性
|
|
103
|
+
// (会抛 "cannot get property without inject"),用 ctx.reflect.get 免
|
|
104
|
+
// inject 读取(未提供返回 undefined);对象字面量 mock ctx(测试)没有
|
|
105
|
+
// reflect,退回直接属性访问。createApi 只在有 webServer 时被调用(index.js
|
|
106
|
+
// 守卫 + 测试 mock),故下方 register 用之非空。
|
|
107
|
+
const webServer = typeof ctx.reflect?.get === "function" ? ctx.reflect.get("webServer") : ctx.webServer;
|
|
108
|
+
|
|
101
109
|
// feature flags 快照(GET/PUT 共用):overrides 是持久化的用户显式覆盖;
|
|
102
110
|
// effective 是启动配置在白名单键上被 overrides 覆盖后的最终值。config 缺席
|
|
103
111
|
// (旧调用方直连、未传 cfg)时只报被覆盖的键,不把不存在的默认值编造给前端。
|
|
@@ -140,7 +148,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
const register = (route) => {
|
|
143
|
-
disposers.push(
|
|
151
|
+
disposers.push(webServer.register(route));
|
|
144
152
|
};
|
|
145
153
|
|
|
146
154
|
// /api/dsh-mneme prefix fallback → 404 JSON for unknown sub-paths
|
package/lib/index.js
CHANGED
|
@@ -21,10 +21,13 @@ import { join } from "node:path";
|
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
|
|
23
23
|
export const name = "dsh-mneme";
|
|
24
|
-
// webServer
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
24
|
+
// webServer 在 inject 声明中:cordis 会等宿主 webServer 服务激活后才 apply 本
|
|
25
|
+
// 插件,保证 apply 时路由注册不落时序(v0.7.23 曾移出 inject 想支持 headless,
|
|
26
|
+
// 结果 cordis 不再等待,apply 时宿主 webServer 未就绪 → 桌面端 "cannot get
|
|
27
|
+
// property without inject" 崩溃)。守卫用 ctx.reflect.get(免 inject 读取,
|
|
28
|
+
// 未提供返回 undefined)而非 if (ctx.webServer):直接访问未注入属性在 cordis
|
|
29
|
+
// Proxy 下会抛错而非返回 undefined。
|
|
30
|
+
export const inject = ["tools", "systemPrompt", "llm", "agentDefaultModel", "commands", "webServer"];
|
|
28
31
|
export { Config };
|
|
29
32
|
|
|
30
33
|
// Arrow (not function declaration): cordis 4 treats any apply with a
|
|
@@ -362,7 +365,14 @@ export const apply = (ctx, config) => {
|
|
|
362
365
|
const summarizer = createSummarizer(ctx, service, cfg);
|
|
363
366
|
disposers.push(summarizer.dispose);
|
|
364
367
|
|
|
365
|
-
|
|
368
|
+
// webServer 可选依赖:cordis 4 的 ctx 是 Proxy,直接访问未在 inject 声明的
|
|
369
|
+
// 属性会抛 "cannot get property without inject"(不会返回 undefined),所以
|
|
370
|
+
// 不能用 if (ctx.webServer) 守卫。ctx.reflect.get 是 cordis 提供的免 inject
|
|
371
|
+
// 读取(未提供时返回 undefined);对象字面量 mock ctx(测试)没有 reflect,
|
|
372
|
+
// 退回直接属性访问。headless/无 UI 宿主无 webServer 时跳过 API 注册,其余
|
|
373
|
+
// 功能(工具/注入/dream)照常。
|
|
374
|
+
const webServer = typeof ctx.reflect?.get === "function" ? ctx.reflect.get("webServer") : ctx.webServer;
|
|
375
|
+
if (webServer) {
|
|
366
376
|
const api = createApi(ctx, service, settings, commands ?? {
|
|
367
377
|
add: () => { throw new Error("commands unavailable"); },
|
|
368
378
|
remove: () => false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modusensus/dsh-mneme",
|
|
3
3
|
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.24",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/api.js
CHANGED
|
@@ -98,6 +98,14 @@ function parseBody(text) {
|
|
|
98
98
|
export function createApi(ctx, service, settings, commands, embedder, semantic = null, apiToken = "", config = null) {
|
|
99
99
|
const disposers = [];
|
|
100
100
|
|
|
101
|
+
// webServer 在 index.js 的 inject 声明中(cordis 等宿主服务就绪后 apply),
|
|
102
|
+
// 这里做防御性读取:cordis ctx 的 Proxy 不允许直接访问未 inject 的属性
|
|
103
|
+
// (会抛 "cannot get property without inject"),用 ctx.reflect.get 免
|
|
104
|
+
// inject 读取(未提供返回 undefined);对象字面量 mock ctx(测试)没有
|
|
105
|
+
// reflect,退回直接属性访问。createApi 只在有 webServer 时被调用(index.js
|
|
106
|
+
// 守卫 + 测试 mock),故下方 register 用之非空。
|
|
107
|
+
const webServer = typeof ctx.reflect?.get === "function" ? ctx.reflect.get("webServer") : ctx.webServer;
|
|
108
|
+
|
|
101
109
|
// feature flags 快照(GET/PUT 共用):overrides 是持久化的用户显式覆盖;
|
|
102
110
|
// effective 是启动配置在白名单键上被 overrides 覆盖后的最终值。config 缺席
|
|
103
111
|
// (旧调用方直连、未传 cfg)时只报被覆盖的键,不把不存在的默认值编造给前端。
|
|
@@ -140,7 +148,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
const register = (route) => {
|
|
143
|
-
disposers.push(
|
|
151
|
+
disposers.push(webServer.register(route));
|
|
144
152
|
};
|
|
145
153
|
|
|
146
154
|
// /api/dsh-mneme prefix fallback → 404 JSON for unknown sub-paths
|
package/src/index.js
CHANGED
|
@@ -21,10 +21,13 @@ import { join } from "node:path";
|
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
|
|
23
23
|
export const name = "dsh-mneme";
|
|
24
|
-
// webServer
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
24
|
+
// webServer 在 inject 声明中:cordis 会等宿主 webServer 服务激活后才 apply 本
|
|
25
|
+
// 插件,保证 apply 时路由注册不落时序(v0.7.23 曾移出 inject 想支持 headless,
|
|
26
|
+
// 结果 cordis 不再等待,apply 时宿主 webServer 未就绪 → 桌面端 "cannot get
|
|
27
|
+
// property without inject" 崩溃)。守卫用 ctx.reflect.get(免 inject 读取,
|
|
28
|
+
// 未提供返回 undefined)而非 if (ctx.webServer):直接访问未注入属性在 cordis
|
|
29
|
+
// Proxy 下会抛错而非返回 undefined。
|
|
30
|
+
export const inject = ["tools", "systemPrompt", "llm", "agentDefaultModel", "commands", "webServer"];
|
|
28
31
|
export { Config };
|
|
29
32
|
|
|
30
33
|
// Arrow (not function declaration): cordis 4 treats any apply with a
|
|
@@ -362,7 +365,14 @@ export const apply = (ctx, config) => {
|
|
|
362
365
|
const summarizer = createSummarizer(ctx, service, cfg);
|
|
363
366
|
disposers.push(summarizer.dispose);
|
|
364
367
|
|
|
365
|
-
|
|
368
|
+
// webServer 可选依赖:cordis 4 的 ctx 是 Proxy,直接访问未在 inject 声明的
|
|
369
|
+
// 属性会抛 "cannot get property without inject"(不会返回 undefined),所以
|
|
370
|
+
// 不能用 if (ctx.webServer) 守卫。ctx.reflect.get 是 cordis 提供的免 inject
|
|
371
|
+
// 读取(未提供时返回 undefined);对象字面量 mock ctx(测试)没有 reflect,
|
|
372
|
+
// 退回直接属性访问。headless/无 UI 宿主无 webServer 时跳过 API 注册,其余
|
|
373
|
+
// 功能(工具/注入/dream)照常。
|
|
374
|
+
const webServer = typeof ctx.reflect?.get === "function" ? ctx.reflect.get("webServer") : ctx.webServer;
|
|
375
|
+
if (webServer) {
|
|
366
376
|
const api = createApi(ctx, service, settings, commands ?? {
|
|
367
377
|
add: () => { throw new Error("commands unavailable"); },
|
|
368
378
|
remove: () => false,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import { mkdtempSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import * as mneme from "../lib/index.js";
|
|
8
|
+
|
|
9
|
+
// webServer 可选依赖回归(v0.7.24 修复):v0.7.23 曾把 webServer 从 inject 声明
|
|
10
|
+
// 中去掉想支持 headless,但 apply 里 `if (ctx.webServer)` 在 cordis 4 的 Proxy
|
|
11
|
+
// 下访问未注入属性会抛 "cannot get property without inject"(不会返回
|
|
12
|
+
// undefined),桌面端插件树直接加载失败。本测试用真实 cordis Context 复现两种
|
|
13
|
+
// 宿主形态,守住「有 webServer 时注册 API 路由 / 无 webServer 时不崩」两条底线。
|
|
14
|
+
//
|
|
15
|
+
// 修复后 webServer 保留在 inject(cordis 等待宿主服务就绪后再 apply,路由注册
|
|
16
|
+
// 不落时序),apply 守卫用 ctx.reflect.get 而非直接属性访问。headless 宿主
|
|
17
|
+
// 缺 webServer 时 fiber 静默不激活(与 v0.7.22 一致),不抛错。
|
|
18
|
+
|
|
19
|
+
function buildHost(withWebServer) {
|
|
20
|
+
const ctx = new Context();
|
|
21
|
+
const apiRoutes = [];
|
|
22
|
+
ctx.provide("tools", { register() { return () => {}; } });
|
|
23
|
+
ctx.provide("commands", { register() { return () => {}; } });
|
|
24
|
+
ctx.provide("systemPrompt", { context() { return () => {}; } });
|
|
25
|
+
ctx.provide("agentDefaultModel", { currentSelection() { return { provider: "mock", model: "mock" }; } });
|
|
26
|
+
ctx.provide("llm", {
|
|
27
|
+
async *stream() { yield { type: "finish", reason: { kind: "stop" } }; }
|
|
28
|
+
});
|
|
29
|
+
if (withWebServer) {
|
|
30
|
+
ctx.provide("webServer", { register(route) { apiRoutes.push(route); return () => {}; } });
|
|
31
|
+
}
|
|
32
|
+
return { ctx, apiRoutes };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("desktop host (webServer present): plugin boots and registers API routes", async () => {
|
|
36
|
+
const { ctx, apiRoutes } = buildHost(true);
|
|
37
|
+
const fiber = ctx.plugin(mneme, { memoryDir: mkdtempSync(join(tmpdir(), "mneme-desk-")) });
|
|
38
|
+
await fiber;
|
|
39
|
+
assert.ok(apiRoutes.length > 0, "webServer routes must be registered on desktop");
|
|
40
|
+
assert.ok(apiRoutes.some((r) => r.kind === "exact"), "expect exact routes, got: " + apiRoutes.map((r) => r.kind).join(","));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("headless host (no webServer): plugin must not throw", async () => {
|
|
44
|
+
const { ctx } = buildHost(false);
|
|
45
|
+
const fiber = ctx.plugin(mneme, { memoryDir: mkdtempSync(join(tmpdir(), "mneme-head-")) });
|
|
46
|
+
// INACTIVE fiber 静默等待依赖,fiber promise 不会 reject;只要不抛错即为通过。
|
|
47
|
+
await fiber;
|
|
48
|
+
assert.ok(true, "headless load must not throw");
|
|
49
|
+
});
|