@hpyperry/dsh-ref-lib 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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/cordis.patch.yml +6 -0
  4. package/lib/client/RefLibBrowser.d.ts +35 -0
  5. package/lib/client/RefLibBrowser.d.ts.map +1 -0
  6. package/lib/client/RefLibBrowser.js +119 -0
  7. package/lib/client/RefLibBrowser.js.map +1 -0
  8. package/lib/client/RefLibDock.d.ts +44 -0
  9. package/lib/client/RefLibDock.d.ts.map +1 -0
  10. package/lib/client/RefLibDock.js +218 -0
  11. package/lib/client/RefLibDock.js.map +1 -0
  12. package/lib/client/RefLibPanel.d.ts +47 -0
  13. package/lib/client/RefLibPanel.d.ts.map +1 -0
  14. package/lib/client/RefLibPanel.js +59 -0
  15. package/lib/client/RefLibPanel.js.map +1 -0
  16. package/lib/client/data.d.ts +53 -0
  17. package/lib/client/data.d.ts.map +1 -0
  18. package/lib/client/data.js +74 -0
  19. package/lib/client/data.js.map +1 -0
  20. package/lib/client/index.d.ts +32 -0
  21. package/lib/client/index.d.ts.map +1 -0
  22. package/lib/client/index.js +80 -0
  23. package/lib/client/index.js.map +1 -0
  24. package/lib/client/layout.d.ts +34 -0
  25. package/lib/client/layout.d.ts.map +1 -0
  26. package/lib/client/layout.js +65 -0
  27. package/lib/client/layout.js.map +1 -0
  28. package/lib/client/locales.d.ts +80 -0
  29. package/lib/client/locales.d.ts.map +1 -0
  30. package/lib/client/locales.js +84 -0
  31. package/lib/client/locales.js.map +1 -0
  32. package/lib/client/styles.d.ts +15 -0
  33. package/lib/client/styles.d.ts.map +1 -0
  34. package/lib/client/styles.js +513 -0
  35. package/lib/client/styles.js.map +1 -0
  36. package/lib/client.js +1377 -0
  37. package/lib/client.js.map +1 -0
  38. package/lib/commands.d.ts +38 -0
  39. package/lib/commands.d.ts.map +1 -0
  40. package/lib/commands.js +49 -0
  41. package/lib/commands.js.map +1 -0
  42. package/lib/index.d.ts +36 -0
  43. package/lib/index.d.ts.map +1 -0
  44. package/lib/index.js +94 -0
  45. package/lib/index.js.map +1 -0
  46. package/lib/logic.d.ts +32 -0
  47. package/lib/logic.d.ts.map +1 -0
  48. package/lib/logic.js +48 -0
  49. package/lib/logic.js.map +1 -0
  50. package/lib/render.d.ts +18 -0
  51. package/lib/render.d.ts.map +1 -0
  52. package/lib/render.js +40 -0
  53. package/lib/render.js.map +1 -0
  54. package/lib/routes.d.ts +39 -0
  55. package/lib/routes.d.ts.map +1 -0
  56. package/lib/routes.js +210 -0
  57. package/lib/routes.js.map +1 -0
  58. package/lib/service.d.ts +88 -0
  59. package/lib/service.d.ts.map +1 -0
  60. package/lib/service.js +212 -0
  61. package/lib/service.js.map +1 -0
  62. package/lib/spec.d.ts +33 -0
  63. package/lib/spec.d.ts.map +1 -0
  64. package/lib/spec.js +13 -0
  65. package/lib/spec.js.map +1 -0
  66. package/lib/validate.d.ts +17 -0
  67. package/lib/validate.d.ts.map +1 -0
  68. package/lib/validate.js +35 -0
  69. package/lib/validate.js.map +1 -0
  70. package/package.json +64 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAE/D,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAEtF,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,QAAY,CAAA;AAE5C,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAoBnE;AA+BD,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,yBAAyB;IACzB,OAAO,EAAE,aAAa,CAAA;IACtB,8DAA8D;IAC9D,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAA;IAC1D,UAAU;IACV,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,EAAE,CA2HlE"}
package/lib/routes.js ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * ref-lib 管理路由:`/api/ref-lib/*`。
3
+ *
4
+ * client(浏览器端)经**普通同源 fetch** 访问这些路由——这是插件在宿主
5
+ * `ctx.webServer`(`@deepseek-ai/dsh-host-webserver`)上自注册的自定义 HTTP
6
+ * 路由,静默双向、不渲染命令卡片(参照 dsh-ssh / dsh-persona-memory 先例)。
7
+ * v4 起 client 读/写不再走 `/ref-lib` 命令分发。
8
+ *
9
+ * 安全:所有路由 loopback-only(同源 + 回环地址护栏,镜像 dsh-ssh 的
10
+ * `isLoopbackRequest`)——这些端点会读改 sidecar 状态,LAN 暴露的部署不能开放。
11
+ * @module @hpyperry/dsh-ref-lib/src/routes
12
+ */
13
+ import { resolveRefLibPath } from "./commands.js";
14
+ import { RefLibPathError, RefLibUnknownError } from "./service.js";
15
+ /** 请求体大小上限(管理载荷都很小)。 */
16
+ export const MAX_JSON_BODY_BYTES = 64 * 1024;
17
+ /** Loopback 校验:回环源地址 + 同源 Host/Origin(镜像 dsh-ssh 的护栏)。 */
18
+ export function isLoopbackRequest(request) {
19
+ const address = request.socket.remoteAddress;
20
+ if (address !== '127.0.0.1' && address !== '::1' && address !== '::ffff:127.0.0.1')
21
+ return false;
22
+ const host = request.headers.host;
23
+ if (typeof host !== 'string')
24
+ return false;
25
+ let hostUrl;
26
+ try {
27
+ hostUrl = new URL(`http://${host}`);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ if (hostUrl.hostname !== '127.0.0.1' && hostUrl.hostname !== 'localhost' && hostUrl.hostname !== '[::1]')
33
+ return false;
34
+ if (request.headers['sec-fetch-site'] === 'cross-site')
35
+ return false;
36
+ const origin = request.headers.origin;
37
+ if (origin === undefined)
38
+ return true;
39
+ try {
40
+ return new URL(origin).host === hostUrl.host;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /** 一条 JSON 响应。GET /list 的结果是会话状态,禁止任何缓存。 */
47
+ function writeJson(res, status, body) {
48
+ res.writeHead(status, {
49
+ 'content-type': 'application/json; charset=utf-8',
50
+ 'referrer-policy': 'no-referrer',
51
+ 'cache-control': 'no-store',
52
+ });
53
+ res.end(JSON.stringify(body));
54
+ }
55
+ /** 读取 JSON 请求体(过大或不可解析时返回 undefined)。
56
+ * 注意**读满整个流**(排空请求体,keep-alive 连接才可复用),但只保留限额内的字节。 */
57
+ async function readJsonBody(req) {
58
+ const chunks = [];
59
+ let size = 0;
60
+ for await (const chunk of req) {
61
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
62
+ size += buffer.length;
63
+ if (size <= MAX_JSON_BODY_BYTES)
64
+ chunks.push(buffer);
65
+ }
66
+ if (size > MAX_JSON_BODY_BYTES)
67
+ return undefined;
68
+ try {
69
+ const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
70
+ return typeof parsed === 'object' && parsed !== null ? parsed : undefined;
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
76
+ /**
77
+ * 构建 `/api/ref-lib/*` 路由族。
78
+ * - `GET /api/ref-lib/list?session=<id>` → `{ libs }`
79
+ * - `POST /api/ref-lib/add { session, path }` → `{ entry }`
80
+ * - `POST /api/ref-lib/remove { session, id }` → `{ ok: true }`
81
+ * @param deps - 依赖面。
82
+ * @returns 待注册到 `ctx.webServer` 的路由数组。
83
+ */
84
+ export function makeRefLibRoutes(deps) {
85
+ const { refLibs, resolveSession, log } = deps;
86
+ /** 护栏 + 方法检查。 */
87
+ const guard = (req, res, method) => {
88
+ if (!isLoopbackRequest(req)) {
89
+ writeJson(res, 403, { error: 'forbidden: loopback-only' });
90
+ return false;
91
+ }
92
+ if (req.method !== method) {
93
+ writeJson(res, 405, { error: `method not allowed: ${req.method}` });
94
+ return false;
95
+ }
96
+ return true;
97
+ };
98
+ /** 会话解析:不存在(非 live)时写出 404 并返回 undefined。 */
99
+ const requireSession = (res, sessionId) => {
100
+ if (typeof sessionId !== 'string' || sessionId === '') {
101
+ writeJson(res, 400, { error: 'missing session id' });
102
+ return undefined;
103
+ }
104
+ const session = resolveSession(sessionId);
105
+ if (session === undefined) {
106
+ writeJson(res, 404, { error: `session not live: ${sessionId}` });
107
+ return undefined;
108
+ }
109
+ return session;
110
+ };
111
+ /** 业务错误的 wire 错误码(client 据此本地化展示,见 src/client/data.ts)。 */
112
+ const PATH_ERROR_CODES = {
113
+ missing: 'ref-lib/missing',
114
+ 'not-directory': 'ref-lib/not-directory',
115
+ unsafe: 'ref-lib/unsafe',
116
+ };
117
+ /** 统一错误映射:路径/条目类业务错误 400(带 code 与 path/id 供本地化),其余 500。 */
118
+ const writeError = (res, error) => {
119
+ if (error instanceof RefLibPathError) {
120
+ writeJson(res, 400, {
121
+ error: error.message,
122
+ code: PATH_ERROR_CODES[error.reason],
123
+ path: error.path,
124
+ });
125
+ return;
126
+ }
127
+ if (error instanceof RefLibUnknownError) {
128
+ writeJson(res, 400, { error: error.message, code: 'ref-lib/unknown-id', id: error.id });
129
+ return;
130
+ }
131
+ log(`ref-lib route error: ${error instanceof Error ? error.message : String(error)}`);
132
+ writeJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
133
+ };
134
+ return [
135
+ {
136
+ kind: 'exact',
137
+ path: '/api/ref-lib/list',
138
+ handler: async (req, res) => {
139
+ if (!guard(req, res, 'GET'))
140
+ return;
141
+ try {
142
+ const url = new URL(req.url ?? '/', 'http://localhost');
143
+ const session = requireSession(res, url.searchParams.get('session'));
144
+ if (session === undefined)
145
+ return;
146
+ writeJson(res, 200, { libs: refLibs.list(session) });
147
+ }
148
+ catch (error) {
149
+ writeError(res, error);
150
+ }
151
+ },
152
+ },
153
+ {
154
+ kind: 'exact',
155
+ path: '/api/ref-lib/add',
156
+ handler: async (req, res) => {
157
+ if (!guard(req, res, 'POST'))
158
+ return;
159
+ try {
160
+ const body = await readJsonBody(req);
161
+ if (body === undefined) {
162
+ writeJson(res, 400, { error: 'invalid JSON body' });
163
+ return;
164
+ }
165
+ const session = requireSession(res, body.session);
166
+ if (session === undefined)
167
+ return;
168
+ if (typeof body.path !== 'string' || body.path.trim() === '') {
169
+ writeJson(res, 400, { error: 'missing path' });
170
+ return;
171
+ }
172
+ const base = session.header.cwd ?? process.cwd();
173
+ const entry = await refLibs.add(session, resolveRefLibPath(body.path, base));
174
+ writeJson(res, 200, { entry });
175
+ }
176
+ catch (error) {
177
+ writeError(res, error);
178
+ }
179
+ },
180
+ },
181
+ {
182
+ kind: 'exact',
183
+ path: '/api/ref-lib/remove',
184
+ handler: async (req, res) => {
185
+ if (!guard(req, res, 'POST'))
186
+ return;
187
+ try {
188
+ const body = await readJsonBody(req);
189
+ if (body === undefined) {
190
+ writeJson(res, 400, { error: 'invalid JSON body' });
191
+ return;
192
+ }
193
+ const session = requireSession(res, body.session);
194
+ if (session === undefined)
195
+ return;
196
+ if (typeof body.id !== 'string' || body.id === '') {
197
+ writeJson(res, 400, { error: 'missing id' });
198
+ return;
199
+ }
200
+ await refLibs.remove(session, body.id);
201
+ writeJson(res, 200, { ok: true });
202
+ }
203
+ catch (error) {
204
+ writeError(res, error);
205
+ }
206
+ },
207
+ },
208
+ ];
209
+ }
210
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `ctx.refLibs` 服务:per-session 只读参考库的校验与持久化。
3
+ *
4
+ * v3(2026-08-17 事故修复):**不再向会话日志写入自定义事件**。harness 的会话
5
+ * 加载器只认仓库内生成的白名单 `KNOWN_SESSION_EVENT_TYPES` 里的事件类型,白名单
6
+ * 外的必须带 `ignorable: true` 信封标记才允许跳过,而 `session.append()` 不提供
7
+ * 写入该标记的途径——旧版把状态写成 `ref-lib/set` 事件,会导致任何包含该事件的
8
+ * 会话日志被整体拒读(连写它的 harness 自己也读不回,SessionFormatUnsupportedError)。
9
+ * v3 起 per-session 列表存为 dsh home 下的 sidecar JSON
10
+ * (`<dshHome>/plugin-data/ref-lib/<sessionId>.json`);旧日志中的 `ref-lib/set`
11
+ * 事件仅在冷读时折叠迁移一次,会话隔离与 fork 继承语义保持不变。
12
+ * @module @hpyperry/dsh-ref-lib/src/service
13
+ */
14
+ import { Context, Service } from '@deepseek-ai/cordis';
15
+ import type { Session } from '@deepseek-ai/dsh-session';
16
+ import type { RefLibEntry } from './spec.ts';
17
+ /** add 请求指向的路径不存在或不是目录(或含控制字符)。 */
18
+ export declare class RefLibPathError extends Error {
19
+ readonly path: string;
20
+ readonly reason: 'missing' | 'not-directory' | 'unsafe';
21
+ /**
22
+ * @param path - 请求路径。
23
+ * @param reason - 具体原因(不存在 / 不是目录 / 含控制字符)。
24
+ */
25
+ constructor(path: string, reason: 'missing' | 'not-directory' | 'unsafe');
26
+ }
27
+ /** 请求的条目 id 未注册。 */
28
+ export declare class RefLibUnknownError extends Error {
29
+ readonly id: string;
30
+ /**
31
+ * @param id - 未注册的条目 id。
32
+ */
33
+ constructor(id: string);
34
+ }
35
+ declare module '@deepseek-ai/cordis' {
36
+ interface Context {
37
+ refLibs: RefLibService;
38
+ }
39
+ }
40
+ /** RefLibService 构造配置。 */
41
+ export interface RefLibServiceConfig {
42
+ /**
43
+ * 存储根目录;默认 `<dshHome>/plugin-data/ref-lib`(测试可注入临时目录)。
44
+ */
45
+ readonly root?: string;
46
+ }
47
+ /**
48
+ * 只读参考库注册表(**per-session**):列表存为 dsh home 下 sidecar JSON
49
+ * (`<dshHome>/plugin-data/ref-lib/<sessionId>.json`),会话隔离、随重启持久。
50
+ * 路径在 add 时做 realpath 规范化并校验为存在的目录。
51
+ */
52
+ export declare class RefLibService extends Service {
53
+ private readonly root;
54
+ private readonly cache;
55
+ /**
56
+ * @param ctx - 宿主上下文。
57
+ * @param config - 可选存储根目录覆盖(测试用)。
58
+ */
59
+ constructor(ctx: Context, config?: RefLibServiceConfig);
60
+ /** 当前会话的参考库列表(内存缓存 → sidecar 文件 → 旧日志迁移 → 父会话继承)。 */
61
+ list(session: Session): readonly RefLibEntry[];
62
+ /**
63
+ * 为当前会话注册一个只读参考库:realpath 规范化并校验为存在的目录;
64
+ * 同路径已注册时幂等返回现有条目。
65
+ * @param session - 目标会话。
66
+ * @param path - 目录路径(相对路径按宿主 cwd 解析)。
67
+ * @returns 新增(或已存在)的条目。
68
+ * @throws {RefLibPathError} 路径不存在或不是目录。
69
+ */
70
+ add(session: Session, path: string): Promise<RefLibEntry>;
71
+ /**
72
+ * 从当前会话移除一个参考库条目(不删除磁盘目录)。
73
+ * @param session - 目标会话。
74
+ * @param id - 条目 id。
75
+ * @throws {RefLibUnknownError} id 未注册。
76
+ */
77
+ remove(session: Session, id: string): Promise<void>;
78
+ /** 冷读:sidecar 文件 → 旧日志事件迁移 → 父会话继承 → 空列表。 */
79
+ private loadFromStorage;
80
+ private pathOf;
81
+ /** 读取并校验一个 sidecar 文件;缺失/畸形返回 undefined(不覆盖文件,待下次写入)。 */
82
+ private readSidecar;
83
+ /** 原子写入 sidecar(tmp + rename),并同步内存缓存。 */
84
+ private persistSync;
85
+ /** 写缓存并执行容量淘汰(Map 按插入序,淘汰最旧会话)。 */
86
+ private cacheSet;
87
+ }
88
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAGvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAY5C,mCAAmC;AACnC,qBAAa,eAAgB,SAAQ,KAAK;IAMtC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe,GAAG,QAAQ;IANzD;;;OAGG;gBAEQ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,GAAG,eAAe,GAAG,QAAQ;CAW1D;AAED,oBAAoB;AACpB,qBAAa,kBAAmB,SAAQ,KAAK;IAI/B,QAAQ,CAAC,EAAE,EAAE,MAAM;IAH/B;;OAEG;gBACkB,EAAE,EAAE,MAAM;CAIhC;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,OAAO,EAAE,aAAa,CAAA;KACvB;CACF;AAED,0BAA0B;AAC1B,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAkBD;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,OAAO;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAQ;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAElE;;;OAGG;gBACS,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,mBAAwB;IAK1D,qDAAqD;IACrD,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,WAAW,EAAE;IAQ9C;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAqB/D;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzD,6CAA6C;IAC7C,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,MAAM;IAKd,yDAAyD;IACzD,OAAO,CAAC,WAAW;IAanB,0CAA0C;IAC1C,OAAO,CAAC,WAAW;IAQnB,mCAAmC;IACnC,OAAO,CAAC,QAAQ;CAOjB"}
package/lib/service.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * `ctx.refLibs` 服务:per-session 只读参考库的校验与持久化。
3
+ *
4
+ * v3(2026-08-17 事故修复):**不再向会话日志写入自定义事件**。harness 的会话
5
+ * 加载器只认仓库内生成的白名单 `KNOWN_SESSION_EVENT_TYPES` 里的事件类型,白名单
6
+ * 外的必须带 `ignorable: true` 信封标记才允许跳过,而 `session.append()` 不提供
7
+ * 写入该标记的途径——旧版把状态写成 `ref-lib/set` 事件,会导致任何包含该事件的
8
+ * 会话日志被整体拒读(连写它的 harness 自己也读不回,SessionFormatUnsupportedError)。
9
+ * v3 起 per-session 列表存为 dsh home 下的 sidecar JSON
10
+ * (`<dshHome>/plugin-data/ref-lib/<sessionId>.json`);旧日志中的 `ref-lib/set`
11
+ * 事件仅在冷读时折叠迁移一次,会话隔离与 fork 继承语义保持不变。
12
+ * @module @hpyperry/dsh-ref-lib/src/service
13
+ */
14
+ import { randomUUID } from 'node:crypto';
15
+ import { realpath, stat } from 'node:fs/promises';
16
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
17
+ import { dirname, join } from 'node:path';
18
+ import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
19
+ import { Service } from '@deepseek-ai/cordis';
20
+ import { foldRefLibs, removeLib, upsertLib } from "./logic.js";
21
+ import { hasControlCharacters, isRefLibEntry } from "./validate.js";
22
+ /** sidecar 文件内容版本(v3 文件 = `{ version: 2, libs }`)。 */
23
+ const SIDECAR_VERSION = 2;
24
+ /**
25
+ * 内存缓存会话数上限:超出后按插入序淘汰最旧会话(下次访问从 sidecar 重读,
26
+ * 语义不变,只是多一次文件读)。防止长驻进程在大量会话上无限累积内存。
27
+ */
28
+ const CACHE_MAX_SESSIONS = 512;
29
+ /** add 请求指向的路径不存在或不是目录(或含控制字符)。 */
30
+ export class RefLibPathError extends Error {
31
+ path;
32
+ reason;
33
+ /**
34
+ * @param path - 请求路径。
35
+ * @param reason - 具体原因(不存在 / 不是目录 / 含控制字符)。
36
+ */
37
+ constructor(path, reason) {
38
+ super(reason === 'missing'
39
+ ? `参考库路径不存在:${path}`
40
+ : reason === 'not-directory'
41
+ ? `参考库路径不是目录:${path}`
42
+ : `参考库路径包含控制字符,已拒绝(防止破坏上下文注入):${JSON.stringify(path)}`);
43
+ this.path = path;
44
+ this.reason = reason;
45
+ this.name = 'RefLibPathError';
46
+ }
47
+ }
48
+ /** 请求的条目 id 未注册。 */
49
+ export class RefLibUnknownError extends Error {
50
+ id;
51
+ /**
52
+ * @param id - 未注册的条目 id。
53
+ */
54
+ constructor(id) {
55
+ super(`未找到参考库条目:${id}(先用 /ref-lib list 查看本会话已注册条目)`);
56
+ this.id = id;
57
+ this.name = 'RefLibUnknownError';
58
+ }
59
+ }
60
+ /** 把 session id 编码为安全路径段(同 harness encodeSegment 方案:安全字符原样,其余 ~XXXX)。 */
61
+ function encodeSegment(raw) {
62
+ if (raw.length === 0)
63
+ throw new Error('cannot encode an empty session id');
64
+ let out = '';
65
+ for (let i = 0; i < raw.length; i++) {
66
+ const code = raw.charCodeAt(i);
67
+ const ch = String.fromCharCode(code);
68
+ if (ch !== '~' && /^[A-Za-z0-9._-]$/.test(ch)) {
69
+ out += ch;
70
+ }
71
+ else {
72
+ out += '~' + code.toString(16).toUpperCase().padStart(4, '0');
73
+ }
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * 只读参考库注册表(**per-session**):列表存为 dsh home 下 sidecar JSON
79
+ * (`<dshHome>/plugin-data/ref-lib/<sessionId>.json`),会话隔离、随重启持久。
80
+ * 路径在 add 时做 realpath 规范化并校验为存在的目录。
81
+ */
82
+ export class RefLibService extends Service {
83
+ root;
84
+ cache = new Map();
85
+ /**
86
+ * @param ctx - 宿主上下文。
87
+ * @param config - 可选存储根目录覆盖(测试用)。
88
+ */
89
+ constructor(ctx, config = {}) {
90
+ super(ctx, 'refLibs');
91
+ this.root = config.root ?? join(dshHomePath('plugin-data', 'ref-lib'));
92
+ }
93
+ /** 当前会话的参考库列表(内存缓存 → sidecar 文件 → 旧日志迁移 → 父会话继承)。 */
94
+ list(session) {
95
+ const cached = this.cache.get(session.id);
96
+ if (cached !== undefined)
97
+ return cached;
98
+ const libs = this.loadFromStorage(session);
99
+ this.cacheSet(session.id, libs);
100
+ return libs;
101
+ }
102
+ /**
103
+ * 为当前会话注册一个只读参考库:realpath 规范化并校验为存在的目录;
104
+ * 同路径已注册时幂等返回现有条目。
105
+ * @param session - 目标会话。
106
+ * @param path - 目录路径(相对路径按宿主 cwd 解析)。
107
+ * @returns 新增(或已存在)的条目。
108
+ * @throws {RefLibPathError} 路径不存在或不是目录。
109
+ */
110
+ async add(session, path) {
111
+ const canonical = await realpath(path).catch(() => {
112
+ throw new RefLibPathError(path, 'missing');
113
+ });
114
+ // 控制字符路径会破坏 systemPrompt 注入格式(提示词注入卫生):POSIX 允许
115
+ // 目录名含换行等,端点可到达时拒绝,渲染层另有兜底消毒(render.ts)。
116
+ if (hasControlCharacters(canonical))
117
+ throw new RefLibPathError(canonical, 'unsafe');
118
+ const info = await stat(canonical).catch(() => {
119
+ throw new RefLibPathError(path, 'missing');
120
+ });
121
+ if (!info.isDirectory())
122
+ throw new RefLibPathError(path, 'not-directory');
123
+ const current = this.list(session);
124
+ const existing = current.find((entry) => entry.path === canonical);
125
+ if (existing !== undefined)
126
+ return existing;
127
+ const entry = { id: randomUUID(), path: canonical };
128
+ const next = upsertLib(current, entry);
129
+ this.persistSync(session.id, next);
130
+ this.cacheSet(session.id, next);
131
+ return entry;
132
+ }
133
+ /**
134
+ * 从当前会话移除一个参考库条目(不删除磁盘目录)。
135
+ * @param session - 目标会话。
136
+ * @param id - 条目 id。
137
+ * @throws {RefLibUnknownError} id 未注册。
138
+ */
139
+ async remove(session, id) {
140
+ const current = this.list(session);
141
+ if (!current.some((entry) => entry.id === id))
142
+ throw new RefLibUnknownError(id);
143
+ const next = removeLib(current, id);
144
+ this.persistSync(session.id, next);
145
+ this.cacheSet(session.id, next);
146
+ }
147
+ /** 冷读:sidecar 文件 → 旧日志事件迁移 → 父会话继承 → 空列表。 */
148
+ loadFromStorage(session) {
149
+ const file = this.pathOf(session.id);
150
+ if (existsSync(file)) {
151
+ const stored = this.readSidecar(file);
152
+ if (stored !== undefined)
153
+ return stored;
154
+ }
155
+ // v1/v2 遗留:从会话日志的 `ref-lib/set` 事件折叠(取最后一个完整快照)。
156
+ // 一次性落盘,保证迁移结果对父会话继承可见。
157
+ const fromEvents = foldRefLibs(session.events);
158
+ if (fromEvents.length > 0) {
159
+ this.persistSync(session.id, fromEvents);
160
+ return fromEvents;
161
+ }
162
+ // fork 继承:子会话无自身状态时继承父会话的列表(与旧事件 seed 行为一致)。
163
+ const parentId = session.header.parentSession;
164
+ if (parentId !== undefined) {
165
+ const parentFile = this.pathOf(parentId);
166
+ if (existsSync(parentFile)) {
167
+ const inherited = this.readSidecar(parentFile);
168
+ if (inherited !== undefined)
169
+ return inherited;
170
+ }
171
+ }
172
+ return [];
173
+ }
174
+ pathOf(sessionId) {
175
+ // `.json` 后缀:避免与持久化后端的会话目录命名(`<root>/<sessionId>/`)撞名
176
+ return join(this.root, encodeSegment(sessionId) + '.json');
177
+ }
178
+ /** 读取并校验一个 sidecar 文件;缺失/畸形返回 undefined(不覆盖文件,待下次写入)。 */
179
+ readSidecar(file) {
180
+ try {
181
+ const value = JSON.parse(readFileSync(file, 'utf8'));
182
+ if (typeof value !== 'object' || value === null)
183
+ return undefined;
184
+ const { libs } = value;
185
+ if (!Array.isArray(libs))
186
+ return undefined;
187
+ return libs.filter(isRefLibEntry);
188
+ }
189
+ catch (error) {
190
+ this.ctx.logger.warn(`ref-lib: 无法读取 sidecar ${file}:${String(error)}(按空列表处理)`);
191
+ return undefined;
192
+ }
193
+ }
194
+ /** 原子写入 sidecar(tmp + rename),并同步内存缓存。 */
195
+ persistSync(sessionId, libs) {
196
+ const file = this.pathOf(sessionId);
197
+ mkdirSync(dirname(file), { recursive: true });
198
+ const tmp = `${file}.${randomUUID()}.tmp`;
199
+ writeFileSync(tmp, JSON.stringify({ version: SIDECAR_VERSION, libs }));
200
+ renameSync(tmp, file);
201
+ }
202
+ /** 写缓存并执行容量淘汰(Map 按插入序,淘汰最旧会话)。 */
203
+ cacheSet(sessionId, libs) {
204
+ this.cache.set(sessionId, libs);
205
+ if (this.cache.size > CACHE_MAX_SESSIONS) {
206
+ const oldest = this.cache.keys().next();
207
+ if (!oldest.done)
208
+ this.cache.delete(oldest.value);
209
+ }
210
+ }
211
+ }
212
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/lib/spec.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ref-lib 类型与事件声明。
3
+ *
4
+ * 参考库列表是 **per-session** 数据。**v3(2026-08-17 事故修复)起不再写入
5
+ * `ref-lib/set` 会话事件**:harness 加载器不认仓库白名单外的自定义事件类型
6
+ * (且 `session.append()` 无法写入 `ignorable` 标记),会把整条会话日志拒读。
7
+ * v3 状态存于 dsh home 下 sidecar JSON(见 `service.ts`)。下方的
8
+ * `SessionEventMap` 声明仅保留供**旧日志迁移折叠**(`foldRefLibs` 类型安全地
9
+ * 读取历史 `ref-lib/set` 事件)。无全局配置。
10
+ * @module @hpyperry/dsh-ref-lib/src/spec
11
+ */
12
+ /** 一个只读参考库条目。 */
13
+ export interface RefLibEntry {
14
+ /** 稳定 id(uuid),与路径解耦(路径可规范化重写)。 */
15
+ readonly id: string;
16
+ /** 规范化(realpath)后的目录绝对路径。 */
17
+ readonly path: string;
18
+ /** 可选用途说明,注入上下文时展示。 */
19
+ readonly note?: string;
20
+ }
21
+ declare module '@deepseek-ai/dsh-session/types' {
22
+ interface SessionEventMap {
23
+ /**
24
+ * 参考库完整列表快照(log-only,per-session):**v1/v2 遗留事件,v3 起不再
25
+ * 写入**——该类型不在 harness 白名单内且无法标记 ignorable,写入会拒读整个
26
+ * 会话日志。仅供迁移折叠(`foldRefLibs`)。
27
+ */
28
+ 'ref-lib/set': {
29
+ libs: readonly RefLibEntry[];
30
+ };
31
+ }
32
+ }
33
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,iBAAiB;AACjB,MAAM,WAAW,WAAW;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,OAAO,QAAQ,gCAAgC,CAAC;IAC9C,UAAU,eAAe;QACvB;;;;WAIG;QACH,aAAa,EAAE;YAAE,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;SAAE,CAAA;KAChD;CACF"}
package/lib/spec.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ref-lib 类型与事件声明。
3
+ *
4
+ * 参考库列表是 **per-session** 数据。**v3(2026-08-17 事故修复)起不再写入
5
+ * `ref-lib/set` 会话事件**:harness 加载器不认仓库白名单外的自定义事件类型
6
+ * (且 `session.append()` 无法写入 `ignorable` 标记),会把整条会话日志拒读。
7
+ * v3 状态存于 dsh home 下 sidecar JSON(见 `service.ts`)。下方的
8
+ * `SessionEventMap` 声明仅保留供**旧日志迁移折叠**(`foldRefLibs` 类型安全地
9
+ * 读取历史 `ref-lib/set` 事件)。无全局配置。
10
+ * @module @hpyperry/dsh-ref-lib/src/spec
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 跨 node/client 共享的**零依赖**校验与消毒纯函数。
3
+ *
4
+ * node half(service/logic/render)与 client half(client/data.ts)分属两个构建
5
+ * 产物,无法共享带 node 依赖的模块;本模块只依赖类型(spec.ts,type-only 导入
6
+ * 在打包时被擦除),两半均可直接引用——消除 `isRefLibEntry` 在 logic.ts 与
7
+ * client/data.ts 中的逐字重复,控制字符集合也在此统一维护。
8
+ * @module @hpyperry/dsh-ref-lib/src/validate
9
+ */
10
+ import type { RefLibEntry } from './spec.ts';
11
+ /** 文本是否含不可见控制字符。 */
12
+ export declare function hasControlCharacters(text: string): boolean;
13
+ /** 把全部不可见控制字符替换为 U+FFFD(路径/备注最终会进入系统提示词,兜底消毒)。 */
14
+ export declare function sanitizeControlCharacters(text: string): string;
15
+ /** 校验一个未知对象是否为合法条目(宽松:仅要求 id/path 字符串、note 可选字符串)。 */
16
+ export declare function isRefLibEntry(value: unknown): value is RefLibEntry;
17
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAW5C,oBAAoB;AACpB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,kDAAkD;AAClD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAKlE"}