dsh-prime-memory 0.26.19 → 0.26.21
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 +12 -0
- package/dist/clients/drsc-inproc.d.ts +50 -0
- package/dist/clients/drsc-inproc.js +114 -0
- package/dist/clients/drsc.d.ts +15 -28
- package/dist/clients/drsc.js +56 -0
- package/dist/room-review.d.ts +11 -6
- package/dist/room-review.js +12 -7
- package/dist/stats.js +5 -2
- package/dist/tools/index.js +3 -3
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# 更新日志(Changelog)
|
|
2
2
|
|
|
3
|
+
## [0.26.21] — 2026-11-02
|
|
4
|
+
|
|
5
|
+
- **#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,每请求重开等于重扫全表)。
|
|
6
|
+
- **传输分流规则**:只有「显式 HTTP 意图」才走 fetch —— 即配了 `baseUrl`(跨机/自托管 collector)或调用方注入 `fetchImpl`。两者皆未给时走进程内。四个读点仍汇聚在 `getJson`,查询串形状与旧 HTTP 逐字一致,因此字段映射、「`session=null` → 空数组」「limit 钳位」「`redact` 硬臂」这些口径全部不变;进程内读失败同样包成 `DrscClientError`,调用方的「显式报错 vs 空窗降级」分面处理不受影响。
|
|
7
|
+
- **真数据端到端自证**:以真实 collector 模块 + 真实索引库驱动,直调与走适配器的 `tail:4` / 全量两条臂返回**逐位一致的 seq 序列**(4 行 / 1770 行);`DrscClient.sessionTail` 得到 4 行且 `role/kind/time/text` 映射正常,`search('端口')` 命中 3 条含 snippet,`listSessions` total=103。新增 `tests/drsc-inproc.test.ts`(参数映射 + 句柄单次开启 + 载入失败不缓存 rejection 以自愈)与 `drsc-client.test.ts` 的传输分流用例,合计 12 例;全量 **1898 例 / 189 文件**。
|
|
8
|
+
- **过程中修掉两处自身缺陷**:① `{ limit: undefined }` 会让 `'limit' in opts` 为真,而 collector 可能据此走钳位分支 —— 改为非法/缺省时整个键缺席;② 适配器误把 `openProjectionStore` 的返回值丢弃、试图从模块上读一个不存在的属性取句柄 —— 已改回持有返回值。
|
|
9
|
+
|
|
10
|
+
## [0.26.20] — 2026-11-02
|
|
11
|
+
|
|
12
|
+
- **内部命名与计划对齐**:共用取数助手按其计划原名 `mergeRoomCatalog()` 落地(0.26.18/0.26.19 线上产物里叫 `roomCountsBySlug()`,功能与形状不变,纯内部导出名,无外部消费方)。同时重写该函数的文档注释:原先「四处同形中三处已正确」的表述不准确,现按实证写明 rooms-export / room-admin list / 候选词表三处已接,而 rooms-get 端点除计数外还要把注册表 `source`/`label` 覆盖回已存在条目,属不同形状,有意不接(该处就地留了说明注释)。
|
|
13
|
+
- **真机启动自证(新)**:在隔离的一次性 profile `t44probe`(`--from-default-profile web` 新建,不触碰任何现有 profile)上安装 `dsh-prime-memory@0.26.19` 与 `dsh-reference-session-collector`,以 `dsh --profile t44probe` 启动宿主:正常起立于 `http://127.0.0.1:3080/`,token 换 cookie 后根路径 200,装载全程无 compat 报错、无拒载(该线 `engines.dsh` 0.2.0-rc.2 在声明内)。**注意边界**:本自证只覆盖「装载与启动不异常」,#20 的随机端口探针分支仍未在此验证 —— web 线宿主端口是固定默认 3080,随机监听口只存在于桌面端宿主,该分支仍待桌面端装机冒烟。
|
|
14
|
+
|
|
3
15
|
## [0.26.19] — 2026-11-02
|
|
4
16
|
|
|
5
17
|
- **#20 加固(属性测试 + 形状一致)**:新增 `resolveDrscBaseUrl` 优先级的确定性属性测试(固定种子 mulberry32,400 + 300 组随机组合),钉住四条不变式:显式值恒为终值、有非空 trustedHosts 条目时 origin 不得抢位、origin 只替换那句 8080、前两档之外的来源一律不回显 Host 头。**该测试首遍即抓出一处真实形状不一致**:显式值/条目分支会给裸 authority 补 `http://` 前缀,而新增的 origin 分支原样透传不补——若调用方递的是 `127.0.0.1:<port>` 这类 authority(而非宿主 `getOrigin()` 返回的全 URL),就会拼出无 scheme 的基址。已抽单一 `withScheme()` 让三档共用同一规范化。
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DRSC 进程内读适配器(issue #20 的追加修复)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要换掉 HTTP:桌面宿主在**任何**注册路由之前有一道 pre-dispatch 鉴权
|
|
5
|
+
* (`isAuthenticated` 只认由 `requestAudience(Host)` 派生的签名 cookie,豁免分支
|
|
6
|
+
* 只有 `GET /?token=` 与已认证的 `GET /`)。插件服务端 `fetch` 没有 cookie,也没有
|
|
7
|
+
* 任何可取用该 token 的服务面(`launchToken` 是宿主 `BrowserAuth` 的私有字段),
|
|
8
|
+
* 于是基址修对了也只会拿到 401 —— 真机已实测(见 #20 评论)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块改为直调 collector 导出的**只读查询面**:
|
|
11
|
+
* resolveIndexDir() → openProjectionStore(dir) → createQueryContext(store)
|
|
12
|
+
* → search / sessionDetail / listSessions
|
|
13
|
+
* 全程不碰 `ProjectionSync`(写侧引擎),因此不会与 collector 自己的同步器抢水位、
|
|
14
|
+
* 也不会多挂一个 interval。索引库是 WAL(实测同目录有 -wal/-shm),宿主持写连接时
|
|
15
|
+
* 本侧只读句柄可并发读。
|
|
16
|
+
*
|
|
17
|
+
* 包名用变量做动态 import:本仓 devDeps 没有该包,静态 import 会让 typecheck 挂;
|
|
18
|
+
* 运行期它由 profile 顶层 node_modules 提供(本插件已把它列为 dependency 与
|
|
19
|
+
* pluginDependencies,装载序在前,故首次读时必已就位)。
|
|
20
|
+
*/
|
|
21
|
+
/** 只用到读面,形状按 collector 的 d.ts 手工声明,不引它的类型(避免跨包类型依赖)。 */
|
|
22
|
+
export interface CollectorReadModule {
|
|
23
|
+
resolveIndexDir: (explicit?: string, env?: NodeJS.ProcessEnv) => string;
|
|
24
|
+
openProjectionStore: (dir: string) => unknown;
|
|
25
|
+
createQueryContext: (store: unknown) => unknown;
|
|
26
|
+
search: (ctx: unknown, query: string, opts?: {
|
|
27
|
+
session?: string;
|
|
28
|
+
limit?: number;
|
|
29
|
+
redact?: boolean;
|
|
30
|
+
}) => Record<string, unknown>;
|
|
31
|
+
sessionDetail: (ctx: unknown, sessionId: string, opts?: {
|
|
32
|
+
turn?: string;
|
|
33
|
+
fields?: string;
|
|
34
|
+
tail?: number;
|
|
35
|
+
redact?: boolean;
|
|
36
|
+
}) => Record<string, unknown>;
|
|
37
|
+
listSessions: (ctx: unknown, offset?: number, limit?: number) => Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/** 注入面(单测);缺省按裸名动态 import。 */
|
|
40
|
+
export type CollectorLoader = () => Promise<CollectorReadModule>;
|
|
41
|
+
/** 清掉缓存(单测用;也供 collector 换装后强制重开)。 */
|
|
42
|
+
export declare function resetCollectorReadState(): void;
|
|
43
|
+
/**
|
|
44
|
+
* 执行一次读,返回与 HTTP 包络同形的对象(`ok:true` + 各 op 的字段)。
|
|
45
|
+
*
|
|
46
|
+
* 保持同形是有意的:`DrscClient` 的四个读点全部经由 `getJson`,字段映射、
|
|
47
|
+
* 「session=null → 空数组」「limit 钳位」这些口径都已在那边测过;这里只负责
|
|
48
|
+
* 把传输从 fetch 换成函数调用,不顺手改任何语义。
|
|
49
|
+
*/
|
|
50
|
+
export declare function callCollectorRead(query: string, load?: CollectorLoader): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DRSC 进程内读适配器(issue #20 的追加修复)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要换掉 HTTP:桌面宿主在**任何**注册路由之前有一道 pre-dispatch 鉴权
|
|
5
|
+
* (`isAuthenticated` 只认由 `requestAudience(Host)` 派生的签名 cookie,豁免分支
|
|
6
|
+
* 只有 `GET /?token=` 与已认证的 `GET /`)。插件服务端 `fetch` 没有 cookie,也没有
|
|
7
|
+
* 任何可取用该 token 的服务面(`launchToken` 是宿主 `BrowserAuth` 的私有字段),
|
|
8
|
+
* 于是基址修对了也只会拿到 401 —— 真机已实测(见 #20 评论)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块改为直调 collector 导出的**只读查询面**:
|
|
11
|
+
* resolveIndexDir() → openProjectionStore(dir) → createQueryContext(store)
|
|
12
|
+
* → search / sessionDetail / listSessions
|
|
13
|
+
* 全程不碰 `ProjectionSync`(写侧引擎),因此不会与 collector 自己的同步器抢水位、
|
|
14
|
+
* 也不会多挂一个 interval。索引库是 WAL(实测同目录有 -wal/-shm),宿主持写连接时
|
|
15
|
+
* 本侧只读句柄可并发读。
|
|
16
|
+
*
|
|
17
|
+
* 包名用变量做动态 import:本仓 devDeps 没有该包,静态 import 会让 typecheck 挂;
|
|
18
|
+
* 运行期它由 profile 顶层 node_modules 提供(本插件已把它列为 dependency 与
|
|
19
|
+
* pluginDependencies,装载序在前,故首次读时必已就位)。
|
|
20
|
+
*/
|
|
21
|
+
/** collector 包入口(裸名,由 profile 顶层 node_modules 解析)。 */
|
|
22
|
+
const COLLECTOR_PACKAGE = 'dsh-reference-session-collector';
|
|
23
|
+
const defaultLoader = async () => (await import(/* @vite-ignore */ COLLECTOR_PACKAGE));
|
|
24
|
+
let modulePromise;
|
|
25
|
+
let queryContext;
|
|
26
|
+
/**
|
|
27
|
+
* 只读句柄(`openProjectionStore` 的返回值)。常驻 summaries 是内存 Map,
|
|
28
|
+
* 每请求重开等于重扫全表,故只开一次;undefined 是合法的未开哨兵
|
|
29
|
+
* (句柄真为 undefined 时 createQueryContext 会在首读即报错,不会静默重开)。
|
|
30
|
+
*/
|
|
31
|
+
let storeHandle;
|
|
32
|
+
/** 清掉缓存(单测用;也供 collector 换装后强制重开)。 */
|
|
33
|
+
export function resetCollectorReadState() {
|
|
34
|
+
modulePromise = undefined;
|
|
35
|
+
queryContext = undefined;
|
|
36
|
+
storeHandle = undefined;
|
|
37
|
+
}
|
|
38
|
+
/** 载入并缓存 collector 模块;失败不缓存 rejection,否则一次抖动会永久打死后续所有读。 */
|
|
39
|
+
async function moduleOf(load) {
|
|
40
|
+
const pending = (modulePromise ??= load().catch((err) => {
|
|
41
|
+
if (modulePromise === pending)
|
|
42
|
+
modulePromise = undefined;
|
|
43
|
+
throw err;
|
|
44
|
+
}));
|
|
45
|
+
return pending;
|
|
46
|
+
}
|
|
47
|
+
async function contextFor(load) {
|
|
48
|
+
if (queryContext !== undefined)
|
|
49
|
+
return queryContext;
|
|
50
|
+
const mod = await moduleOf(load);
|
|
51
|
+
if (storeHandle === undefined) {
|
|
52
|
+
storeHandle = mod.openProjectionStore(mod.resolveIndexDir());
|
|
53
|
+
}
|
|
54
|
+
queryContext = mod.createQueryContext(storeHandle);
|
|
55
|
+
return queryContext;
|
|
56
|
+
}
|
|
57
|
+
/** 把 HTTP 查询串拆成参数表(与 collector 路由同源解析口径:它也只读 searchParams)。 */
|
|
58
|
+
function paramsOf(query) {
|
|
59
|
+
return new URLSearchParams(query.startsWith('?') ? query.slice(1) : query);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 执行一次读,返回与 HTTP 包络同形的对象(`ok:true` + 各 op 的字段)。
|
|
63
|
+
*
|
|
64
|
+
* 保持同形是有意的:`DrscClient` 的四个读点全部经由 `getJson`,字段映射、
|
|
65
|
+
* 「session=null → 空数组」「limit 钳位」这些口径都已在那边测过;这里只负责
|
|
66
|
+
* 把传输从 fetch 换成函数调用,不顺手改任何语义。
|
|
67
|
+
*/
|
|
68
|
+
export async function callCollectorRead(query, load = defaultLoader) {
|
|
69
|
+
const ctx = await contextFor(load);
|
|
70
|
+
const mod = await moduleOf(load);
|
|
71
|
+
const p = paramsOf(query);
|
|
72
|
+
const op = p.get('op') ?? '';
|
|
73
|
+
// collector 的 redact 臂:HTTP 侧是 `redact=1`;缺省是原文臂。我们所有读点都硬要求脱敏。
|
|
74
|
+
const redact = p.get('redact') !== null && p.get('redact') !== '0';
|
|
75
|
+
if (op === 'search') {
|
|
76
|
+
const limit = positiveInt(p.get('limit'));
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
...mod.search(ctx, p.get('q') ?? '', {
|
|
80
|
+
// 非法/缺省时整个键缺席,而不是传 `limit: undefined` —— `'limit' in opts`
|
|
81
|
+
// 与 `opts.limit === undefined` 不是一回事,对端可能按前者走钳位分支。
|
|
82
|
+
...(limit === undefined ? {} : { limit }),
|
|
83
|
+
redact,
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (op === 'session') {
|
|
88
|
+
const sessionId = p.get('session');
|
|
89
|
+
if (sessionId === null || sessionId === '') {
|
|
90
|
+
return { ok: true, session: null, rows: [] };
|
|
91
|
+
}
|
|
92
|
+
const tail = positiveInt(p.get('tail'));
|
|
93
|
+
const detail = mod.sessionDetail(ctx, sessionId, {
|
|
94
|
+
// collector 侧:tail 为正整数才切尾窗,键缺席即全量(#14-B 的整会话臂依赖这点)。
|
|
95
|
+
...(tail === undefined ? {} : { tail }),
|
|
96
|
+
redact,
|
|
97
|
+
});
|
|
98
|
+
return { ok: true, ...detail };
|
|
99
|
+
}
|
|
100
|
+
if (op === 'sessions') {
|
|
101
|
+
// 这两个是位置参数,undefined 与“没传”在 JS 里同义,无需上面对象字面量那套处置。
|
|
102
|
+
const offset = positiveInt(p.get('offset')) ?? 0;
|
|
103
|
+
const limit = positiveInt(p.get('limit'));
|
|
104
|
+
return { ok: true, ...mod.listSessions(ctx, offset, limit) };
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`collector 未知 op: ${op === '' ? '(missing)' : op}`);
|
|
107
|
+
}
|
|
108
|
+
/** 只接受「正整数」形式的参数;非法/缺省一律 undefined(交由调用点决定缺席语义)。 */
|
|
109
|
+
function positiveInt(raw) {
|
|
110
|
+
if (raw === null || raw.trim() === '')
|
|
111
|
+
return undefined;
|
|
112
|
+
const n = Number(raw);
|
|
113
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
|
|
114
|
+
}
|
package/dist/clients/drsc.d.ts
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DrscClient(T8.4 读切,drsc-redact-memory-wiring task_18):memory 读历史
|
|
3
|
-
* 面的 dsh-reference-session-collector HTTP 客户端。
|
|
4
|
-
*
|
|
5
|
-
* 两个读点(R7):
|
|
6
|
-
* - 蒸馏背景窗(recentBySession 等价供给)→ `op=session&tail=N&redact=1`
|
|
7
|
-
* (collector task_11 尾窗升序 + task_12 redact 臂);
|
|
8
|
-
* - 工具面 conversation_search → `op=search&redact=1`(命中基于原文 fts,
|
|
9
|
-
* 返回 snippet 取脱敏锚定文本)。
|
|
10
|
-
*
|
|
11
|
-
* 加上 issue #14-B 的**重建事实源**:`op=sessions` 枚举 + `op=session`(**不带 tail**)
|
|
12
|
-
* 逐会话拉全量——对全量重建来说“尾窗”不够用,而 collector 侧不带 tail 即 `ORDER BY seq ASC`
|
|
13
|
-
* 无 LIMIT。此路径同样恒带 `redact=1`。
|
|
14
|
-
*
|
|
15
|
-
* 硬依赖纪律(对齐 T7.3 裁定):collector 缺席/超时/非 2xx/ok=false 一律抛
|
|
16
|
-
* {@link DrscClientError}——**绝不回退本地 l0**(表已退役,无退路);调用方
|
|
17
|
-
* 分口径处理:蒸馏背景窗空窗降级(runner),工具面显式报错(tools)。
|
|
18
|
-
*
|
|
19
|
-
* 同源 Host 解析(steward drs-source 的 resolveDrsBaseUrl 同款):
|
|
20
|
-
* 显式 baseUrl > 首个 trustedHosts 条目(补 http://)> 宿主实际监听 origin
|
|
21
|
-
* (issue #20:Desktop App 走随机端口,写死 8080 永远对不上)> `http://127.0.0.1:8080`
|
|
22
|
-
* (宿主缺省口)。Host 头**只回显被选作基址的那个 trustedHosts 条目**:collector
|
|
23
|
-
* 信任门是精确匹配,条目省略端口而 URL 带端口会失配——所以基址来自 origin 兜底时
|
|
24
|
-
* 索性不回显(等价于旧版 trustedHosts 为空时的形状),而不是拿另一个 authority 去撞门。
|
|
25
|
-
* trustedHosts / origin 均惰性读取(webStartup、webServer 可能晚于本插件就绪),
|
|
26
|
-
* 每次请求现算,无缓存陈旧面。
|
|
27
|
-
*/
|
|
28
1
|
import type { ConversationMessage } from '../types.js';
|
|
29
2
|
/** collector API 路径(与其 DRS_API_PATH 字面一致;不跨包 import)。 */
|
|
30
3
|
export declare const DRSC_API_PATH = "/api/dsh-reference-session-collector";
|
|
@@ -105,8 +78,17 @@ export interface DrscClientOptions {
|
|
|
105
78
|
* 下线,故与 trustedHosts 同款每次现取;取不到即返回 undefined → 落回 8080 老行为。
|
|
106
79
|
*/
|
|
107
80
|
origin?: () => string | undefined;
|
|
108
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* 注入面(单测);缺省全局 fetch。**显式传入即表示要走 HTTP**(见 getJson 分流)。
|
|
83
|
+
*/
|
|
109
84
|
fetchImpl?: typeof fetch;
|
|
85
|
+
/**
|
|
86
|
+
* 进程内读面(issue #20 追加修复,缺省 {@link callCollectorRead}):直调
|
|
87
|
+
* collector 导出的只读查询函数。宿主对所有注册路由有 pre-dispatch 鉴权
|
|
88
|
+
* (插件服务端无 cookie 可用,真机实测 401),HTTP 路在桌面宿主上走不通;
|
|
89
|
+
* 只有显式给了 `baseUrl`(跨机/外部 collector)时才保留 HTTP 退路。
|
|
90
|
+
*/
|
|
91
|
+
read?: (query: string) => Promise<Record<string, unknown>>;
|
|
110
92
|
/** 单请求超时 ms;缺省 {@link DRSC_DEFAULT_TIMEOUT_MS}。 */
|
|
111
93
|
timeoutMs?: number;
|
|
112
94
|
}
|
|
@@ -119,6 +101,11 @@ export declare class DrscClient {
|
|
|
119
101
|
private base;
|
|
120
102
|
/** origin 探针失败(服务未就绪/属性读抛)不得影响请求:视作取不到,落老行为。 */
|
|
121
103
|
private safeOrigin;
|
|
104
|
+
/**
|
|
105
|
+
* 一次读:优先进程内直调,失败/缺席一律包成 DrscClientError(与 HTTP 路同口径,
|
|
106
|
+
* 调用方的「显式报错 vs 空窗降级」分面处理不变)。
|
|
107
|
+
*/
|
|
108
|
+
private readInproc;
|
|
122
109
|
/** 一个 GET 的 JSON 包络(非 2xx / ok!==true / 超时 / 断网一律抛 DrscClientError)。 */
|
|
123
110
|
private getJson;
|
|
124
111
|
/**
|
package/dist/clients/drsc.js
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DrscClient(T8.4 读切,drsc-redact-memory-wiring task_18):memory 读历史
|
|
3
|
+
* 面的 dsh-reference-session-collector HTTP 客户端。
|
|
4
|
+
*
|
|
5
|
+
* 两个读点(R7):
|
|
6
|
+
* - 蒸馏背景窗(recentBySession 等价供给)→ `op=session&tail=N&redact=1`
|
|
7
|
+
* (collector task_11 尾窗升序 + task_12 redact 臂);
|
|
8
|
+
* - 工具面 conversation_search → `op=search&redact=1`(命中基于原文 fts,
|
|
9
|
+
* 返回 snippet 取脱敏锚定文本)。
|
|
10
|
+
*
|
|
11
|
+
* 加上 issue #14-B 的**重建事实源**:`op=sessions` 枚举 + `op=session`(**不带 tail**)
|
|
12
|
+
* 逐会话拉全量——对全量重建来说“尾窗”不够用,而 collector 侧不带 tail 即 `ORDER BY seq ASC`
|
|
13
|
+
* 无 LIMIT。此路径同样恒带 `redact=1`。
|
|
14
|
+
*
|
|
15
|
+
* 硬依赖纪律(对齐 T7.3 裁定):collector 缺席/超时/非 2xx/ok=false 一律抛
|
|
16
|
+
* {@link DrscClientError}——**绝不回退本地 l0**(表已退役,无退路);调用方
|
|
17
|
+
* 分口径处理:蒸馏背景窗空窗降级(runner),工具面显式报错(tools)。
|
|
18
|
+
*
|
|
19
|
+
* 同源 Host 解析(steward drs-source 的 resolveDrsBaseUrl 同款):
|
|
20
|
+
* 显式 baseUrl > 首个 trustedHosts 条目(补 http://)> 宿主实际监听 origin
|
|
21
|
+
* (issue #20:Desktop App 走随机端口,写死 8080 永远对不上)> `http://127.0.0.1:8080`
|
|
22
|
+
* (宿主缺省口)。Host 头**只回显被选作基址的那个 trustedHosts 条目**:collector
|
|
23
|
+
* 信任门是精确匹配,条目省略端口而 URL 带端口会失配——所以基址来自 origin 兜底时
|
|
24
|
+
* 索性不回显(等价于旧版 trustedHosts 为空时的形状),而不是拿另一个 authority 去撞门。
|
|
25
|
+
* trustedHosts / origin 均惰性读取(webStartup、webServer 可能晚于本插件就绪),
|
|
26
|
+
* 每次请求现算,无缓存陈旧面。
|
|
27
|
+
*/
|
|
28
|
+
import { callCollectorRead } from './drsc-inproc.js';
|
|
1
29
|
/** collector API 路径(与其 DRS_API_PATH 字面一致;不跨包 import)。 */
|
|
2
30
|
export const DRSC_API_PATH = '/api/dsh-reference-session-collector';
|
|
3
31
|
/** 客户端缺省超时:loopback 正常 <10ms,5s 是给冷启动 GC/断点的天花板。 */
|
|
@@ -125,8 +153,36 @@ export class DrscClient {
|
|
|
125
153
|
return undefined;
|
|
126
154
|
}
|
|
127
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* 一次读:优先进程内直调,失败/缺席一律包成 DrscClientError(与 HTTP 路同口径,
|
|
158
|
+
* 调用方的「显式报错 vs 空窗降级」分面处理不变)。
|
|
159
|
+
*/
|
|
160
|
+
async readInproc(query) {
|
|
161
|
+
const read = this.options.read ?? callCollectorRead;
|
|
162
|
+
try {
|
|
163
|
+
const body = await read(query);
|
|
164
|
+
// body 已是 Record<string, unknown>,不必也不应再转 DrscEnvelope(TS2352)。
|
|
165
|
+
if (body === null || typeof body !== 'object' || body.ok !== true) {
|
|
166
|
+
throw new Error('返回包络缺 ok:true');
|
|
167
|
+
}
|
|
168
|
+
return body;
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (err instanceof DrscClientError)
|
|
172
|
+
throw err;
|
|
173
|
+
throw new DrscClientError(`collector 进程内读失败(${query}): ${err instanceof Error ? err.message : String(err)}`, {
|
|
174
|
+
cause: err,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
128
178
|
/** 一个 GET 的 JSON 包络(非 2xx / ok!==true / 超时 / 断网一律抛 DrscClientError)。 */
|
|
129
179
|
async getJson(query) {
|
|
180
|
+
// 分流规则:只有「显式 HTTP 意图」才走 fetch —— 即配了 baseUrl(跨机/自托管
|
|
181
|
+
// collector)或调用方自己注入了 fetchImpl(单测面)。两者都没给时走进程内:
|
|
182
|
+
// 桌面宿主上插件服务端的 HTTP 必被 pre-dispatch 鉴权拦成 401(#20 真机实证)。
|
|
183
|
+
const explicit = this.options.baseUrl?.trim() ?? '';
|
|
184
|
+
if (explicit === '' && this.options.fetchImpl === undefined)
|
|
185
|
+
return this.readInproc(query);
|
|
130
186
|
const { url, hostHeader } = this.base();
|
|
131
187
|
let response;
|
|
132
188
|
try {
|
package/dist/room-review.d.ts
CHANGED
|
@@ -27,14 +27,19 @@ export declare const ROOM_CANDIDATES_KEY = "roomCandidates";
|
|
|
27
27
|
export declare const ROOM_REVIEW_KEY = "roomReview";
|
|
28
28
|
export type RoomReviewState = 'pending' | 'confirmed' | 'skipped';
|
|
29
29
|
/**
|
|
30
|
-
* `listRooms()`
|
|
30
|
+
* `listRooms()` 结果的 slug→计数索引(单点取数口径,名字沿用计划里的
|
|
31
|
+
* `mergeRoomCatalog`:它要收的就是「注册表条目 × 自生长条目的合并词表」这一步的
|
|
32
|
+
* 取数语义,返回值本身是查表用的 Map,不产出合并后的目录)。
|
|
31
33
|
*
|
|
32
|
-
* 为何需要它(issue #27
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* 为何需要它(issue #27):同一句「注册表条目的真实计数」在本仓写过四处,其中三处
|
|
35
|
+
* 已接这里(本文件的候选词表、`stats.ts` 的 rooms-export、`tools` 的 room-admin
|
|
36
|
+
* list),只有候选词表那一处历史上把算好的计数**写死成 0**。计数一旦有本地定义就有
|
|
37
|
+
* 分叉位,所以取数口径收到这里,各调用点只剩「查不到时给多少」这一个本地决策。
|
|
38
|
+
*
|
|
39
|
+
* 第四处 `stats.ts` 的 rooms-get 有意不接:它除计数外还要把注册表的
|
|
40
|
+
* `source`/`label` 覆盖回已存在条目,不属同一个形状(见那处的注释)。
|
|
36
41
|
*/
|
|
37
|
-
export declare function
|
|
42
|
+
export declare function mergeRoomCatalog(rooms: readonly RoomCount[]): Map<string, number>;
|
|
38
43
|
/** 复查与候选操作对 L1 的最小依赖面(便于测试注入)。 */
|
|
39
44
|
export interface RoomReviewStore {
|
|
40
45
|
list(opts: {
|
package/dist/room-review.js
CHANGED
|
@@ -3,14 +3,19 @@ import { callLLM, parseJsonLogged } from './llm.js';
|
|
|
3
3
|
export const ROOM_CANDIDATES_KEY = 'roomCandidates';
|
|
4
4
|
export const ROOM_REVIEW_KEY = 'roomReview';
|
|
5
5
|
/**
|
|
6
|
-
* `listRooms()`
|
|
6
|
+
* `listRooms()` 结果的 slug→计数索引(单点取数口径,名字沿用计划里的
|
|
7
|
+
* `mergeRoomCatalog`:它要收的就是「注册表条目 × 自生长条目的合并词表」这一步的
|
|
8
|
+
* 取数语义,返回值本身是查表用的 Map,不产出合并后的目录)。
|
|
7
9
|
*
|
|
8
|
-
* 为何需要它(issue #27
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* 为何需要它(issue #27):同一句「注册表条目的真实计数」在本仓写过四处,其中三处
|
|
11
|
+
* 已接这里(本文件的候选词表、`stats.ts` 的 rooms-export、`tools` 的 room-admin
|
|
12
|
+
* list),只有候选词表那一处历史上把算好的计数**写死成 0**。计数一旦有本地定义就有
|
|
13
|
+
* 分叉位,所以取数口径收到这里,各调用点只剩「查不到时给多少」这一个本地决策。
|
|
14
|
+
*
|
|
15
|
+
* 第四处 `stats.ts` 的 rooms-get 有意不接:它除计数外还要把注册表的
|
|
16
|
+
* `source`/`label` 覆盖回已存在条目,不属同一个形状(见那处的注释)。
|
|
12
17
|
*/
|
|
13
|
-
export function
|
|
18
|
+
export function mergeRoomCatalog(rooms) {
|
|
14
19
|
return new Map(rooms.map((r) => [r.room, r.count]));
|
|
15
20
|
}
|
|
16
21
|
/** 解析标注器输出的逐条候选(容错:id 配对失败丢弃;isRoomSlug 校验(两级制);目录内优先,≤3)。 */
|
|
@@ -124,7 +129,7 @@ export async function annotateOrphanCandidates(ctx, cfg, io, limit = 40, overrid
|
|
|
124
129
|
// 后果是方向性的:词表里只有未注册项显示真数,归类器收到的唯一规模证据恰好与
|
|
125
130
|
// 「优先挂靠现有词表」这条规则反向——按推荐用法先 pre-register 的词表反而全变空壳。
|
|
126
131
|
// `?? 0` 保留原语义:只有「注册了但库里确实零绑定」的新 Room 才显示 0。
|
|
127
|
-
const grownCounts =
|
|
132
|
+
const grownCounts = mergeRoomCatalog(grown);
|
|
128
133
|
const merged = [
|
|
129
134
|
...regActive.map((e) => ({ room: e.slug, count: grownCounts.get(e.slug) ?? 0, label: e.label, description: e.description })),
|
|
130
135
|
...grown.filter((r) => !regSet.has(r.room)).map((r) => ({ room: r.room, count: r.count })),
|
package/dist/stats.js
CHANGED
|
@@ -19,7 +19,7 @@ import { typeCatalogEntries } from './memory-types.js';
|
|
|
19
19
|
import { effectiveCfg } from './pipeline/runner.js';
|
|
20
20
|
import { emptyRecallStats } from './hooks/recall.js';
|
|
21
21
|
import { buildRouteChain, decideSendableEffort, LAYER_DEFAULT_BUDGETS, layerChainOrNull, resolveModelContextWindow, resolveModelEfforts, resolveModelRoute } from './llm.js';
|
|
22
|
-
import { mergeRoom, renameRoom,
|
|
22
|
+
import { mergeRoom, renameRoom, mergeRoomCatalog } from './room-review.js';
|
|
23
23
|
import { scanSources } from './memport/scan.js';
|
|
24
24
|
import { projectDistillChain, validateDistillChain } from './settings.js';
|
|
25
25
|
import { RECEIPTS_QUERY_LIMIT_MAX, dimensionOf, toReceiptView } from './store/receipts.js';
|
|
@@ -805,6 +805,9 @@ export async function handleEndpoint(endpoint, payload, deps) {
|
|
|
805
805
|
// 端点层合并注册表(Agent A 设计):注册表 active 条目 count=0 补齐并带
|
|
806
806
|
// source/label(source 用条目真实来源——收编 grown ≠ 预注册,⭐角标靠它区分);
|
|
807
807
|
// 自生长条目标 source='grown'。不动 listRooms() 的缓存语义。
|
|
808
|
+
// 本处不接 `mergeRoomCatalog()`(计划点名的第四处):缺失条目这里固定写 0
|
|
809
|
+
// 且不做 `?? 实数` 回查,同时还要把注册表 source/label 覆盖回已存在条目(下方
|
|
810
|
+
// else 支)——取数以外的富形状,进共用助手需拆第二个返回面,不划算。
|
|
808
811
|
const merged = rooms.map((r) => ({ ...r, source: 'grown' }));
|
|
809
812
|
const registry = roomRegistry?.listActive() ?? [];
|
|
810
813
|
for (const e of registry) {
|
|
@@ -1149,7 +1152,7 @@ export async function handleEndpoint(endpoint, payload, deps) {
|
|
|
1149
1152
|
};
|
|
1150
1153
|
if (kind === 'rooms') {
|
|
1151
1154
|
const registry = roomRegistry?.list() ?? [];
|
|
1152
|
-
const counts =
|
|
1155
|
+
const counts = mergeRoomCatalog(stores.l1.listRooms());
|
|
1153
1156
|
const lines = ['slug,count,source,label,description,status,aliases'];
|
|
1154
1157
|
let n = 0;
|
|
1155
1158
|
const seen = new Set();
|
package/dist/tools/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { isRetired, readSupersedeMarker, stripSupersedeMarker } from '../store/s
|
|
|
23
23
|
import { listConflictPairs, renderConflictResolution, renderConflicts, resolveConflictPair } from '../conflict-service.js';
|
|
24
24
|
import { WING_CATALOG, WING_FALLBACK, normPersistence, normScope, resolveRecordScope } from '../types.js';
|
|
25
25
|
import { scopeFilterOf, workspaceIdOf } from '../workspace.js';
|
|
26
|
-
import { annotateOrphanCandidates, confirmReview, mergeRoom, nextReview, pendingReviewCount,
|
|
26
|
+
import { annotateOrphanCandidates, confirmReview, mergeRoom, nextReview, pendingReviewCount, mergeRoomCatalog, renameRoom, skipReview, } from '../room-review.js';
|
|
27
27
|
import { applyGovernanceWeights } from '../store/governance.js';
|
|
28
28
|
import { resolveRepoScope } from '../repo-scope.js';
|
|
29
29
|
import { GRAPH_STATUS_LABELS } from '../prompts/graph-projection.js';
|
|
@@ -386,9 +386,9 @@ graphWrite) {
|
|
|
386
386
|
return { notice: 'Room 注册表未装配(需要插件重载以初始化)。' };
|
|
387
387
|
const registry = roomRegistry.list();
|
|
388
388
|
const counts = stores.l1.listRooms();
|
|
389
|
-
// 取数口径单点(`
|
|
389
|
+
// 取数口径单点(`mergeRoomCatalog` 注释说明了为何收它):本行原先对每个注册条目
|
|
390
390
|
// 线性 find,上限 200 条注册表 × N 个 Room——语义相同、形状不同就是分叉位。
|
|
391
|
-
const countOf =
|
|
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
|
const grownOnly = counts.filter((r) => !registry.some((e) => e.slug === r.room));
|
|
394
394
|
return {
|
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.21",
|
|
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 分层蒸馏记忆插件:自动捕获对话、抽取原子记忆、整合场景块、蒸馏核心画像,并在模型步骤前自动召回注入。"
|