dsh-prime-memory 0.26.21 → 0.26.23
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 +16 -0
- package/dist/clients/drsc-inproc.d.ts +1 -1
- package/dist/clients/drsc-inproc.js +49 -18
- package/dist/tools/index.js +10 -2
- package/dsh.plugin.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# 更新日志(Changelog)
|
|
2
2
|
|
|
3
|
+
## [0.26.23] — 2026-11-02
|
|
4
|
+
|
|
5
|
+
- **方案 B:DRSC 读面改走 collector 的正式门面(`drsReadFacade`)**。`0.26.21` 的进程内直调虽然绕开了宿主鉴权,但代价是**耦合 collector 的五个内部导出名**(`openProjectionStore`/`createQueryContext`/`search`/`sessionDetail`/`listSessions`),并且**自己另开一份投影库**——多占一份常驻 `summaries` 内存与一个读事务。现在 collector `0.2.5` 导出 `drsReadFacade()`,返回绑它**自己活 QueryContext** 的只读对象;本仓优先用这个门面,只认一个稳定符号,也不再开第二份库。
|
|
6
|
+
- **三条路径的优先级与降级**(`src/clients/drsc-inproc.ts`):① collector ≥0.2.5 且库已就绪 → 走门面;② collector 是新版本但库还在 `setImmediate` 初始化 → **本轮抛可重试错、且不缓存**,让下一次读重取门面。这一点是本轮自查出的真设计缺陷:若此时回退自开库,会把自开库结果**永久缓存**,把「优先用门面」变成只在首轮赌运气。降级走既有链路(背景窗当轮空窗、工具面显式报错),语义与旧 HTTP 遇 `503 initializing` 一致;③ collector 是旧版(无该导出)→ 回退自开只读库(保留 `0.26.21` 行为)。
|
|
7
|
+
- **参数映射不分叉**:门面对象被适配成与兼容路径同一个「模块形」,所以 `op`/`q`/`tail`/`limit`/`redact` 的解析口径、非法值整键缺席、`session` 缺 id 返回空行等语义只有一份实现,两条路径共用。
|
|
8
|
+
- **依赖下限抬到 `>=0.2.5 <0.3.0`**(`package.json` dependencies;`dsh.plugin.json` 的 `pluginDependencies` 只列包名不带区间,无需改)。低于该版仍可用,只是走兼容路径。
|
|
9
|
+
- **测试**:`tests/drsc-inproc.test.ts` 新增 3 例——门面可用时绝不调用 `openProjectionStore`;未就绪时报错且不缓存、对端就绪后下一次读拿到门面;旧版 collector 仍回退自开库。全量 **1904 例 / 190 文件**,typecheck/lint/build/build:smoke/smoke 全 0。
|
|
10
|
+
|
|
11
|
+
## [0.26.22] — 2026-11-02
|
|
12
|
+
|
|
13
|
+
- **#27 真机通过**:用户在 desktop 上 `register(work/dsh-host)` → `list` 显示「**现有 59 条**」,与注册前自生长清单里的 `work/dsh-host(59)` 逐位一致;`?? 0` 分支也未被误伤(有绑定即显示真数)。
|
|
14
|
+
- **修掉这次验证顺带查出的口径缺陷**(`memory_room_admin` 的 `action=list`):`grownOnly` 原先用 `roomRegistry.list()`(**含 retired**)排除自生长项,而归类词表面(`room-review.ts`)用 `listActive()`。两侧口径不一致的后果是——**一个 slug 退役后,它既不在注册表活跃词表里、又被从「自生长未注册」清单排除,若它还挂着记录,那些记录的 Room 归属就在两个面上同时隐形**,且没有任何动作能把它找回来(工具面只有 `retire`,无 un-retire;恢复只能走 RPC `dsh-memory/room-admin` 的 `action=retire, active:true`,工具没暴露该参数)。现改为按 `activeSlugs` 排除,退役项回到自生长面继续可见可计数。
|
|
15
|
+
- **额度显示同步**:`注册表 N 条(上限 200)` 原先把 retired 条目也计入 N(占额度视觉位),现 N = 活跃条目数,并在存在退役项时追加「已退役 M 条(不占额度、可用面板「恢复」切回 active)」。退役条目本身仍列在明细里并标 `[source/retired]`,便于恢复。
|
|
16
|
+
- **测试**:新增 `tests/room-admin-list.test.ts` 3 例(注册项真计数 / 退役后两侧口径一致 / 零绑定仍显示 0)。**红绿已验**:把排除集退回修复前的 `registry.some(...)` 写法后 1 例转红,恢复即全绿。全量 **1901 例 / 190 文件**。
|
|
17
|
+
- **冒烟脚本去钉版**:`scripts/verify-desktop-smoke.mjs` 的版本断言从等值改为 `0.26.` 族前缀(上一版每发一次就得改脚本,是自我制造的维护债);同时清掉两个未使用变量使 lint 归零。
|
|
18
|
+
|
|
3
19
|
## [0.26.21] — 2026-11-02
|
|
4
20
|
|
|
5
21
|
- **#20 追加修复:DRSC 读面从 HTTP 改为进程内直调**。桌面宿主在**任何**注册路由之前有一道 pre-dispatch 鉴权(`isAuthenticated` 只认由 `requestAudience(Host)` 派生的签名 cookie,豁免分支仅 `GET /?token=` 与已认证的 `GET /`),而插件服务端 `fetch` 既无 cookie 也无可取用 launch token 的服务面(该字段是宿主 `BrowserAuth` 私有)。真机实测确认:基址修对后请求确实发出、参数形状正确,但仍被回 `401` —— 即上一版只解决了「打错端口」,没解决「打不通」。本版新增 `src/clients/drsc-inproc.ts`,直调 collector 导出的只读查询面(`resolveIndexDir` → `openProjectionStore` → `createQueryContext` → `search` / `sessionDetail` / `listSessions`),全程不碰 `ProjectionSync` 写侧引擎,故不会与 collector 自身的同步器抢水位、也不会多挂一个 interval;索引库为 WAL(实测同目录有 `-wal`/`-shm`),宿主持写连接时本侧只读句柄可并发读,且句柄只开一次(常驻 summaries 是内存 Map,每请求重开等于重扫全表)。
|
|
@@ -38,7 +38,7 @@ export interface CollectorReadModule {
|
|
|
38
38
|
}
|
|
39
39
|
/** 注入面(单测);缺省按裸名动态 import。 */
|
|
40
40
|
export type CollectorLoader = () => Promise<CollectorReadModule>;
|
|
41
|
-
/** 清掉缓存(单测用;也供 collector
|
|
41
|
+
/** 清掉缓存(单测用;也供 collector 换装后强制重取)。 */
|
|
42
42
|
export declare function resetCollectorReadState(): void;
|
|
43
43
|
/**
|
|
44
44
|
* 执行一次读,返回与 HTTP 包络同形的对象(`ok:true` + 各 op 的字段)。
|
|
@@ -22,37 +22,67 @@
|
|
|
22
22
|
const COLLECTOR_PACKAGE = 'dsh-reference-session-collector';
|
|
23
23
|
const defaultLoader = async () => (await import(/* @vite-ignore */ COLLECTOR_PACKAGE));
|
|
24
24
|
let modulePromise;
|
|
25
|
-
let queryContext;
|
|
26
25
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* (句柄真为 undefined 时 createQueryContext 会在首读即报错,不会静默重开)。
|
|
26
|
+
* 兼容路径(旧 collector)的只读句柄(`openProjectionStore` 的返回值)。常驻 summaries
|
|
27
|
+
* 是内存 Map,每请求重开等于重扫全表,故只开一次;undefined 是合法的未开哨兵。
|
|
30
28
|
*/
|
|
31
29
|
let storeHandle;
|
|
32
|
-
/** 清掉缓存(单测用;也供 collector
|
|
30
|
+
/** 清掉缓存(单测用;也供 collector 换装后强制重取)。 */
|
|
33
31
|
export function resetCollectorReadState() {
|
|
34
32
|
modulePromise = undefined;
|
|
35
|
-
queryContext = undefined;
|
|
36
33
|
storeHandle = undefined;
|
|
37
34
|
}
|
|
38
|
-
/**
|
|
35
|
+
/** 把门面对象适配成本模块统一的「模块形」,以便与兼容路径共用同一套参数映射。
|
|
36
|
+
* 前三个成员在门面路径上永不会被调用:moduleOf 命中门面后直接返回,不走进兼容分支。
|
|
37
|
+
*/
|
|
38
|
+
function fromFacade(facade) {
|
|
39
|
+
const unused = () => {
|
|
40
|
+
throw new Error('门面路径不应自开投影库');
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
resolveIndexDir: unused,
|
|
44
|
+
openProjectionStore: unused,
|
|
45
|
+
createQueryContext: unused,
|
|
46
|
+
listSessions: (ctx, offset, limit) => facade.listSessions(offset, limit),
|
|
47
|
+
sessionDetail: (ctx, sessionId, opts) => facade.sessionDetail(sessionId, opts),
|
|
48
|
+
search: (ctx, query, opts) => facade.search(query, opts),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** 取一个可用的「模块形」:优先 collector 门面(>=0.2.5),否则回退自开只读库。 */
|
|
39
52
|
async function moduleOf(load) {
|
|
40
|
-
const pending = (modulePromise ??=
|
|
53
|
+
const pending = (modulePromise ??= (async () => {
|
|
54
|
+
const mod = (await load());
|
|
55
|
+
if (typeof mod.drsReadFacade === 'function') {
|
|
56
|
+
const facade = mod.drsReadFacade();
|
|
57
|
+
// 对端是新版本但库未就绪(collector 的重活推迟在 setImmediate 臂):本轮**不**回退
|
|
58
|
+
// 自开库,而是抛可重试错——否则会把自开库结果永久缓存,把“优先用门面”变成
|
|
59
|
+
// 只在首轮赌运气。抛错走既有降级链(背景窗当轮空窗、工具面显式报错),
|
|
60
|
+
// 且不缓存 rejection,下一次读会重取门面。
|
|
61
|
+
if (facade === undefined) {
|
|
62
|
+
throw new Error('collector 投影库尚未就绪(稍后自动重试)');
|
|
63
|
+
}
|
|
64
|
+
return fromFacade(facade);
|
|
65
|
+
}
|
|
66
|
+
return legacyStoreModule(mod);
|
|
67
|
+
})().catch((err) => {
|
|
41
68
|
if (modulePromise === pending)
|
|
42
69
|
modulePromise = undefined;
|
|
43
70
|
throw err;
|
|
44
71
|
}));
|
|
45
72
|
return pending;
|
|
46
73
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return queryContext;
|
|
50
|
-
const mod = await moduleOf(load);
|
|
74
|
+
/** 旧 collector(无 drsReadFacade)的兼容路径:自己开一份只读库。 */
|
|
75
|
+
async function legacyStoreModule(mod) {
|
|
51
76
|
if (storeHandle === undefined) {
|
|
52
77
|
storeHandle = mod.openProjectionStore(mod.resolveIndexDir());
|
|
53
78
|
}
|
|
54
|
-
|
|
55
|
-
return
|
|
79
|
+
const ctx = mod.createQueryContext(storeHandle);
|
|
80
|
+
return {
|
|
81
|
+
...mod,
|
|
82
|
+
listSessions: (_c, offset, limit) => mod.listSessions(ctx, offset, limit),
|
|
83
|
+
sessionDetail: (_c, sessionId, opts) => mod.sessionDetail(ctx, sessionId, opts),
|
|
84
|
+
search: (_c, query, opts) => mod.search(ctx, query, opts),
|
|
85
|
+
};
|
|
56
86
|
}
|
|
57
87
|
/** 把 HTTP 查询串拆成参数表(与 collector 路由同源解析口径:它也只读 searchParams)。 */
|
|
58
88
|
function paramsOf(query) {
|
|
@@ -66,7 +96,6 @@ function paramsOf(query) {
|
|
|
66
96
|
* 把传输从 fetch 换成函数调用,不顺手改任何语义。
|
|
67
97
|
*/
|
|
68
98
|
export async function callCollectorRead(query, load = defaultLoader) {
|
|
69
|
-
const ctx = await contextFor(load);
|
|
70
99
|
const mod = await moduleOf(load);
|
|
71
100
|
const p = paramsOf(query);
|
|
72
101
|
const op = p.get('op') ?? '';
|
|
@@ -76,7 +105,7 @@ export async function callCollectorRead(query, load = defaultLoader) {
|
|
|
76
105
|
const limit = positiveInt(p.get('limit'));
|
|
77
106
|
return {
|
|
78
107
|
ok: true,
|
|
79
|
-
...mod.search(
|
|
108
|
+
...mod.search(READ_SURFACE, p.get('q') ?? '', {
|
|
80
109
|
// 非法/缺省时整个键缺席,而不是传 `limit: undefined` —— `'limit' in opts`
|
|
81
110
|
// 与 `opts.limit === undefined` 不是一回事,对端可能按前者走钳位分支。
|
|
82
111
|
...(limit === undefined ? {} : { limit }),
|
|
@@ -90,7 +119,7 @@ export async function callCollectorRead(query, load = defaultLoader) {
|
|
|
90
119
|
return { ok: true, session: null, rows: [] };
|
|
91
120
|
}
|
|
92
121
|
const tail = positiveInt(p.get('tail'));
|
|
93
|
-
const detail = mod.sessionDetail(
|
|
122
|
+
const detail = mod.sessionDetail(READ_SURFACE, sessionId, {
|
|
94
123
|
// collector 侧:tail 为正整数才切尾窗,键缺席即全量(#14-B 的整会话臂依赖这点)。
|
|
95
124
|
...(tail === undefined ? {} : { tail }),
|
|
96
125
|
redact,
|
|
@@ -101,7 +130,7 @@ export async function callCollectorRead(query, load = defaultLoader) {
|
|
|
101
130
|
// 这两个是位置参数,undefined 与“没传”在 JS 里同义,无需上面对象字面量那套处置。
|
|
102
131
|
const offset = positiveInt(p.get('offset')) ?? 0;
|
|
103
132
|
const limit = positiveInt(p.get('limit'));
|
|
104
|
-
return { ok: true, ...mod.listSessions(
|
|
133
|
+
return { ok: true, ...mod.listSessions(READ_SURFACE, offset, limit) };
|
|
105
134
|
}
|
|
106
135
|
throw new Error(`collector 未知 op: ${op === '' ? '(missing)' : op}`);
|
|
107
136
|
}
|
|
@@ -112,3 +141,5 @@ function positiveInt(raw) {
|
|
|
112
141
|
const n = Number(raw);
|
|
113
142
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
|
|
114
143
|
}
|
|
144
|
+
/** 统一形后的模块不再需要真 QueryContext,这个占位只用来占位。 */
|
|
145
|
+
const READ_SURFACE = 'inproc-read-surface';
|
package/dist/tools/index.js
CHANGED
|
@@ -390,9 +390,17 @@ graphWrite) {
|
|
|
390
390
|
// 线性 find,上限 200 条注册表 × N 个 Room——语义相同、形状不同就是分叉位。
|
|
391
391
|
const countOf = mergeRoomCatalog(counts);
|
|
392
392
|
const regLines = registry.map((e) => `- ${e.slug}${e.label ? `(${e.label})` : ''} [${e.source}/${e.status}] 现有 ${countOf.get(e.slug) ?? 0} 条${e.description ? ` — ${e.description}` : ''}`);
|
|
393
|
-
|
|
393
|
+
// 口径必须跟词表一致(`room-review.ts` 用 listActive):已退役条目不再进归类词表,
|
|
394
|
+
// 那它的 slug 就应回到「自生长未注册」面。原先用全量 registry 排除,使退役后的
|
|
395
|
+
// slug 两边都不显示(带记录时那些记录在词表上隐形)——退役不可逆地捐掉可见性。
|
|
396
|
+
const activeSlugs = new Set(registry.filter((e) => e.status === 'active').map((e) => e.slug));
|
|
397
|
+
const grownOnly = counts.filter((r) => !activeSlugs.has(r.room));
|
|
398
|
+
const activeCount = activeSlugs.size;
|
|
399
|
+
const retiredCount = registry.length - activeCount;
|
|
394
400
|
return {
|
|
395
|
-
notice:
|
|
401
|
+
notice:
|
|
402
|
+
// 额度与词表同步:上限 200 算的是活跃条目,退役项不占额度(另计)。
|
|
403
|
+
`注册表 ${activeCount} 条(上限 200)${retiredCount > 0 ? `,已退役 ${retiredCount} 条(不占额度、可用面板「恢复」切回 active)` : ''},自生长未注册 ${grownOnly.length} 个。\n` +
|
|
396
404
|
(regLines.join('\n') || '(注册表为空——用 action=register 预注册)') +
|
|
397
405
|
`\n自生长未注册:${grownOnly.map((r) => `${r.room}(${r.count})`).join(", ") || "无"}`,
|
|
398
406
|
rooms: `${registry.length}+${grownOnly.length}`,
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-prime-memory",
|
|
3
3
|
"name": "dsh-prime-memory",
|
|
4
|
-
"version": "0.26.
|
|
4
|
+
"version": "0.26.23",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Layered distilled memory plugin (L0 capture → L1 atomic memory → L2 scenes → L3 persona) with automatic recall injection before each model step.",
|
|
7
7
|
"zh": "L0~L3 分层蒸馏记忆插件:自动捕获对话、抽取原子记忆、整合场景块、蒸馏核心画像,并在模型步骤前自动召回注入。"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-prime-memory",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.23",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org",
|
|
6
6
|
"access": "public",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
100
100
|
"@node-rs/jieba": "^2.0.2",
|
|
101
|
-
"dsh-reference-session-collector": ">=0.2.
|
|
101
|
+
"dsh-reference-session-collector": ">=0.2.5 <0.3.0",
|
|
102
102
|
"sqlite-vec": "^0.1.7-alpha.2",
|
|
103
103
|
"undici": "^7.29.0"
|
|
104
104
|
},
|