dsh-layered-memory 0.8.12 → 0.9.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.
- package/README.en.md +63 -41
- package/README.md +44 -28
- package/assets/changelog/0.9.0/01-/350/256/260/345/277/206/350/212/257/347/211/207/347/272/247/350/201/224/350/217/234/345/215/225.png +0 -0
- package/assets/changelog/0.9.0/02-/345/267/245/344/275/234/345/217/260/346/200/273/350/247/210.png +0 -0
- package/assets/changelog/0.9.0/03-/350/256/260/345/277/206/345/272/223.png +0 -0
- package/assets/changelog/0.9.0/04-/346/264/236/345/257/237/346/264/273/345/212/250.png +0 -0
- package/assets/img/MemoryChip.png +0 -0
- package/assets/img/ui-dark.png +0 -0
- package/assets/img/ui-light.png +0 -0
- package/dist/client.js +2652 -2001
- package/dist/contract.d.ts +117 -1
- package/dist/hooks/recall.d.ts +2 -0
- package/dist/hooks/recall.js +2 -0
- package/dist/index.js +2 -0
- package/dist/stats.d.ts +2 -0
- package/dist/stats.js +41 -0
- package/dist/store/l1.d.ts +7 -1
- package/dist/store/l1.js +5 -1
- package/dist/store/persona.d.ts +2 -0
- package/dist/store/persona.js +9 -0
- package/dist/store/scenes.d.ts +3 -0
- package/dist/store/scenes.js +3 -2
- package/dist/store/session-modes.d.ts +14 -1
- package/dist/store/session-modes.js +37 -3
- package/dist/store/sqlite.d.ts +8 -1
- package/dist/store/sqlite.js +24 -4
- package/dist/workspace-aggregates.d.ts +44 -0
- package/dist/workspace-aggregates.js +262 -0
- package/package.json +1 -1
- package/assets/img/Modes.png +0 -0
- package/assets/img/ui-dark.jpg +0 -0
- package/assets/img/ui-light.jpg +0 -0
package/dist/contract.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RPC 契约单一事实源(types-only,零运行时代码)。
|
|
3
3
|
*
|
|
4
4
|
* 把 host(src/stats.ts 的 case 表)与 client(client/src,浏览器侧)共用的
|
|
5
|
-
*
|
|
5
|
+
* 26 个 `dsh-memory/*` 端点请求/响应类型收敛到本模块——两端各自 `import type`,
|
|
6
6
|
* 契约漂移在编译期暴露,而不是等 UI 渲染出 undefined 才发现。
|
|
7
7
|
*
|
|
8
8
|
* 铁律:
|
|
@@ -315,6 +315,105 @@ export interface EmbeddingStateView {
|
|
|
315
315
|
reindex: ReindexProgressState;
|
|
316
316
|
activeNote?: string;
|
|
317
317
|
}
|
|
318
|
+
/** 资产活动条目:L1 记忆 / L2 场景 / L3 画像按更新时间混排(资产视图,非变更审计)。 */
|
|
319
|
+
export interface AssetActivityItem {
|
|
320
|
+
/** 稳定 id:l1:<recordId> / l2:<family>:<path> / l3:<family>。 */
|
|
321
|
+
id: string;
|
|
322
|
+
kind: 'l1' | 'l2' | 'l3';
|
|
323
|
+
/** new = 首次形成;upd = 蒸馏演进(L1 version>1 / L2 updated>created / L3 恒 upd)。 */
|
|
324
|
+
verb: 'new' | 'upd';
|
|
325
|
+
family: MemoryFamily;
|
|
326
|
+
/** 标题:L1 内容首行截断 / L2 场景名 / L3 画像文件名(本地化装饰在 client 侧)。 */
|
|
327
|
+
title: string;
|
|
328
|
+
/** 完整内容(L1 原文 / L2 摘要 + 正文 / L3 画像正文)。 */
|
|
329
|
+
content: string;
|
|
330
|
+
createdAt: string | null;
|
|
331
|
+
updatedAt: string | null;
|
|
332
|
+
version: number | null;
|
|
333
|
+
/** L1 来源会话(缺省 default);L2/L3 恒 null。 */
|
|
334
|
+
sourceSession: string | null;
|
|
335
|
+
/** L1 情境名;L2/L3 null。 */
|
|
336
|
+
scene: string | null;
|
|
337
|
+
/** L1 的 7 类类型键(persona/episodic/…);L2/L3 null。 */
|
|
338
|
+
l1Type: string | null;
|
|
339
|
+
}
|
|
340
|
+
/** dsh-memory/asset-activity(记忆库活动流;游标分页 + 筛选,只读)。 */
|
|
341
|
+
export interface AssetActivityRequest {
|
|
342
|
+
/** 关键词:L1 走 FTS 索引检索缝(与召回同源);L2/L3 小数据内存子串匹配。 */
|
|
343
|
+
query?: string;
|
|
344
|
+
/** 空/缺省 = 全部类型。 */
|
|
345
|
+
kind?: '' | 'l1' | 'l2' | 'l3';
|
|
346
|
+
/** 空/缺省 = 两族混排。 */
|
|
347
|
+
family?: '' | 'chat' | 'work';
|
|
348
|
+
/** 时间下限(epoch ms;0/缺省 = 全部)。 */
|
|
349
|
+
since?: number;
|
|
350
|
+
/** 1~100,默认 30。 */
|
|
351
|
+
limit?: number;
|
|
352
|
+
/** 上一页返回的 opaque 游标;null/缺省 = 第一页。 */
|
|
353
|
+
cursor?: string | null;
|
|
354
|
+
}
|
|
355
|
+
export interface AssetActivityResponse {
|
|
356
|
+
items: AssetActivityItem[];
|
|
357
|
+
/** 还有下一页时非 null(opaque,原样回传)。 */
|
|
358
|
+
nextCursor: string | null;
|
|
359
|
+
/** 检索路径触达上限(200),结果可能不完整。 */
|
|
360
|
+
truncated: boolean;
|
|
361
|
+
}
|
|
362
|
+
/** dsh-memory/workspace-overview(总览单发聚合,免去 client N+1 拉取)。 */
|
|
363
|
+
export interface WorkspaceOverviewResponse {
|
|
364
|
+
ok: true;
|
|
365
|
+
degraded: boolean;
|
|
366
|
+
/** 检索能力位(sessionInfo 缺席时 null = 未知,不渲染该行)。 */
|
|
367
|
+
retrieval: 'hybrid' | 'vector' | 'keyword' | 'none' | null;
|
|
368
|
+
pendingExtract: number;
|
|
369
|
+
l0Today: number;
|
|
370
|
+
l1Count: number;
|
|
371
|
+
sceneCount: number;
|
|
372
|
+
personaChars: number;
|
|
373
|
+
/** 全部派生层为空(引导式空状态判据)。 */
|
|
374
|
+
empty: boolean;
|
|
375
|
+
lastExtractAt: string | null;
|
|
376
|
+
lastL2At: string | null;
|
|
377
|
+
lastL3At: string | null;
|
|
378
|
+
/** 本周蒸馏成本(token-cost 周窗口;无调用 null)。 */
|
|
379
|
+
week: {
|
|
380
|
+
calls: number;
|
|
381
|
+
outputTokens: number;
|
|
382
|
+
reasoningTokens: number;
|
|
383
|
+
} | null;
|
|
384
|
+
/** 最近活动(asset-activity 同口径前 5 条)。 */
|
|
385
|
+
recent: AssetActivityItem[];
|
|
386
|
+
}
|
|
387
|
+
/** dsh-memory/runtime-insights(洞察:活动/召回只读聚合;成本子页直接用 token-cost)。 */
|
|
388
|
+
export interface RuntimeInsightsResponse {
|
|
389
|
+
ok: true;
|
|
390
|
+
/** 近 7 天(含当日)逐日资产活动,day 为 ISO 日期 YYYY-MM-DD(与 L1 SQL 口径一致)。 */
|
|
391
|
+
activityDays: Array<{
|
|
392
|
+
day: string;
|
|
393
|
+
l1: number;
|
|
394
|
+
l2: number;
|
|
395
|
+
l3: number;
|
|
396
|
+
}>;
|
|
397
|
+
/** 蒸馏调用与失败(进程生命周期累计计数器,llm-usage 口径)。 */
|
|
398
|
+
distill: Array<{
|
|
399
|
+
layer: string;
|
|
400
|
+
calls: number;
|
|
401
|
+
failures: number;
|
|
402
|
+
}>;
|
|
403
|
+
recall: {
|
|
404
|
+
/** 有过检索的会话数 + 全量累计(进程内召回注册表;重启归零)。 */
|
|
405
|
+
sessions: number;
|
|
406
|
+
injectedTurns: number;
|
|
407
|
+
hitTurns: number;
|
|
408
|
+
totalHits: number;
|
|
409
|
+
timeouts: number;
|
|
410
|
+
suppressedRecalls: number;
|
|
411
|
+
/** 停用侧写:全局注入开关当前态 + 持久化会话分布。 */
|
|
412
|
+
globalRecall: boolean;
|
|
413
|
+
sessionsModeOff: number;
|
|
414
|
+
sessionsWriteOnly: number;
|
|
415
|
+
};
|
|
416
|
+
}
|
|
318
417
|
/** dsh-memory/stats */
|
|
319
418
|
export interface StatsResponse extends MemoryStats {
|
|
320
419
|
}
|
|
@@ -340,6 +439,12 @@ export interface SessionModeGetResponse {
|
|
|
340
439
|
/** host 解析后的注入生效值(会话覆盖 ?? 全局开关):pill 面文直接消费,
|
|
341
440
|
* client 无需另知全局开关。 */
|
|
342
441
|
recallResolved: boolean;
|
|
442
|
+
/** 暂停恢复快照(UI 重构分散式):进入 off 档时记录的暂停前范围与注入覆盖,
|
|
443
|
+
* 非 off 档为 null;旧 host 缺省该字段时 client 回退默认档显示。 */
|
|
444
|
+
resume?: {
|
|
445
|
+
scope: MemoryMode;
|
|
446
|
+
recall: boolean | null;
|
|
447
|
+
} | null;
|
|
343
448
|
}
|
|
344
449
|
export interface SessionModeSetRequest {
|
|
345
450
|
sessionId: string;
|
|
@@ -355,6 +460,11 @@ export interface SessionModeSetResponse {
|
|
|
355
460
|
recall: boolean | null;
|
|
356
461
|
/** 设置后的注入生效值(client 面文直接消费;清除覆盖后由 host 告知解析结果)。 */
|
|
357
462
|
recallResolved: boolean;
|
|
463
|
+
/** 同 get:暂停恢复快照(进入 off 时写入,恢复/覆盖变更时清空)。 */
|
|
464
|
+
resume?: {
|
|
465
|
+
scope: MemoryMode;
|
|
466
|
+
recall: boolean | null;
|
|
467
|
+
} | null;
|
|
358
468
|
}
|
|
359
469
|
/** dsh-memory/session-stats(悬浮卡信息区;热路径端点)。 */
|
|
360
470
|
export interface SessionStatsRequest {
|
|
@@ -619,6 +729,9 @@ export interface EmbeddingModelDeleteResponse {
|
|
|
619
729
|
}
|
|
620
730
|
export interface DshMemoryRequestMap {
|
|
621
731
|
'dsh-memory/stats': Record<string, never>;
|
|
732
|
+
'dsh-memory/workspace-overview': Record<string, never>;
|
|
733
|
+
'dsh-memory/asset-activity': AssetActivityRequest;
|
|
734
|
+
'dsh-memory/runtime-insights': Record<string, never>;
|
|
622
735
|
'dsh-memory/token-cost': TokenCostRequest;
|
|
623
736
|
'dsh-memory/session-mode-get': SessionModeGetRequest;
|
|
624
737
|
'dsh-memory/session-mode-set': SessionModeSetRequest;
|
|
@@ -644,6 +757,9 @@ export interface DshMemoryRequestMap {
|
|
|
644
757
|
}
|
|
645
758
|
export interface DshMemoryResponseMap {
|
|
646
759
|
'dsh-memory/stats': StatsResponse;
|
|
760
|
+
'dsh-memory/workspace-overview': WorkspaceOverviewResponse;
|
|
761
|
+
'dsh-memory/asset-activity': AssetActivityResponse;
|
|
762
|
+
'dsh-memory/runtime-insights': RuntimeInsightsResponse;
|
|
647
763
|
'dsh-memory/token-cost': TokenCostResponse;
|
|
648
764
|
'dsh-memory/session-mode-get': SessionModeGetResponse;
|
|
649
765
|
'dsh-memory/session-mode-set': SessionModeSetResponse;
|
package/dist/hooks/recall.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface RecallHooks {
|
|
|
45
45
|
invalidateProfile(): void;
|
|
46
46
|
/** 会话召回统计只读视图(未发生过检索的会话返回 undefined)。 */
|
|
47
47
|
stats(sessionId: string): RecallSessionStats | undefined;
|
|
48
|
+
/** 全量会话召回统计(工作台洞察聚合;进程内注册表快照,重启归零)。 */
|
|
49
|
+
statsAll(): RecallSessionStats[];
|
|
48
50
|
/** 记忆占用账本只读出口:内存优先,miss 时从流水复生(重启后历史会话);从未注入返回 null。 */
|
|
49
51
|
occupancy(sessionId: string): OccupancyLedger | null;
|
|
50
52
|
/**
|
package/dist/hooks/recall.js
CHANGED
|
@@ -362,6 +362,8 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes, dataDir) {
|
|
|
362
362
|
return {
|
|
363
363
|
invalidateProfile,
|
|
364
364
|
stats: (id) => recallStats.get(id),
|
|
365
|
+
/** 全量会话召回统计(工作台洞察聚合;进程内注册表深拷贝,重启归零)。 */
|
|
366
|
+
statsAll: () => Array.from(recallStats.values(), (s) => ({ ...s })),
|
|
365
367
|
/** 占用账本只读出口:内存优先,miss 时从流水复生(重启后历史会话);从未注入返回 null。 */
|
|
366
368
|
occupancy: (id) => {
|
|
367
369
|
const led = occupancyByAgent.get(id) ?? occupancyStore.load(id);
|
package/dist/index.js
CHANGED
|
@@ -313,6 +313,8 @@ export async function apply(ctx, config) {
|
|
|
313
313
|
runnerView: (sid, mode) => runner.sessionView(sid, mode),
|
|
314
314
|
l0Count: (sid) => stores.l0.countBySession(sid),
|
|
315
315
|
capabilities: () => db.getCapabilities(),
|
|
316
|
+
// 工作台洞察聚合(runtime-insights;进程内注册表读取,同样零文件 I/O)
|
|
317
|
+
recallStatsAll: () => recall.statsAll(),
|
|
316
318
|
});
|
|
317
319
|
// bench 控制服务(config.benchControl 门控,默认关):仅基准/调试部署注册,
|
|
318
320
|
// 供同进程的 bench-runner lifecycle 赛道触发 rebuild / 设置会话档位
|
package/dist/stats.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface SessionInfoSource {
|
|
|
49
49
|
ftsSearch: boolean;
|
|
50
50
|
vectorSearch: boolean;
|
|
51
51
|
};
|
|
52
|
+
/** 全量会话召回统计(工作台洞察聚合;缺省 = 装配未提供,洞察召回区隐藏计数)。 */
|
|
53
|
+
recallStatsAll?(): RecallSessionStats[];
|
|
52
54
|
}
|
|
53
55
|
import type { MemoryOccupancy } from './contract.js';
|
|
54
56
|
export type { MemoryStats } from './contract.js';
|
package/dist/stats.js
CHANGED
|
@@ -16,6 +16,7 @@ import { buildRouteChain, decideSendableEffort, LAYER_DEFAULT_BUDGETS, layerChai
|
|
|
16
16
|
import { projectDistillChain, validateDistillChain } from './settings.js';
|
|
17
17
|
import { errDetail } from './util/filelog.js';
|
|
18
18
|
import { snapshotTokenCost } from './token-cost.js';
|
|
19
|
+
import { buildRuntimeInsights, buildWorkspaceOverview, collectAssetActivity, decodeCursor } from './workspace-aggregates.js';
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
export const PLUGIN_VERSION = require('../package.json').version;
|
|
21
22
|
/** 注册状态 RPC(web 侧 connection 服务可选,缺失时跳过,不影响插件主体)。 */
|
|
@@ -150,6 +151,44 @@ async function handleEndpoint(endpoint, payload, deps) {
|
|
|
150
151
|
switch (endpoint) {
|
|
151
152
|
case 'dsh-memory/stats':
|
|
152
153
|
return buildStats(cfg, stores, status);
|
|
154
|
+
// ── 工作台聚合(UI 重构分散式;只读,装配见 workspace-aggregates.ts) ──
|
|
155
|
+
case 'dsh-memory/workspace-overview': {
|
|
156
|
+
const base = await buildStats(cfg, stores, status);
|
|
157
|
+
const caps = sessionInfo?.capabilities();
|
|
158
|
+
const retrieval = caps
|
|
159
|
+
? caps.vectorSearch
|
|
160
|
+
? caps.ftsSearch
|
|
161
|
+
? 'hybrid'
|
|
162
|
+
: 'vector'
|
|
163
|
+
: caps.ftsSearch
|
|
164
|
+
? 'keyword'
|
|
165
|
+
: 'none'
|
|
166
|
+
: null;
|
|
167
|
+
const weekWin = snapshotTokenCost('day', 0).windows.find((w) => w.range === 'week');
|
|
168
|
+
return buildWorkspaceOverview(stores, base, weekWin && weekWin.calls > 0 ? weekWin : null, retrieval);
|
|
169
|
+
}
|
|
170
|
+
case 'dsh-memory/asset-activity': {
|
|
171
|
+
const p = (payload ?? {});
|
|
172
|
+
if (p.query !== undefined && p.query.length > 4096)
|
|
173
|
+
throw new Error('query 过长(≤4096 字符)');
|
|
174
|
+
const kind = p.kind === 'l1' || p.kind === 'l2' || p.kind === 'l3' ? p.kind : '';
|
|
175
|
+
const family = p.family === 'chat' || p.family === 'work' ? p.family : '';
|
|
176
|
+
// 钳到 Date 可表示范围(超 8.64e15ms 会令 toISOString 抛 RangeError,回退=不过滤)
|
|
177
|
+
const sinceNum = Number(p.since);
|
|
178
|
+
const since = Number.isFinite(sinceNum) && sinceNum > 0 && sinceNum <= 8.64e15 ? sinceNum : 0;
|
|
179
|
+
const limit = Math.min(Math.max(Number(p.limit) || 30, 1), 100);
|
|
180
|
+
const offset = Math.min(decodeCursor(typeof p.cursor === 'string' ? p.cursor : null), 1_000_000);
|
|
181
|
+
return collectAssetActivity(stores, {
|
|
182
|
+
query: (p.query ?? '').trim(),
|
|
183
|
+
kind,
|
|
184
|
+
family,
|
|
185
|
+
sinceMs: since,
|
|
186
|
+
limit,
|
|
187
|
+
offset,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
case 'dsh-memory/runtime-insights':
|
|
191
|
+
return buildRuntimeInsights(stores, sessionInfo, modes, live);
|
|
153
192
|
case 'dsh-memory/token-cost': {
|
|
154
193
|
const p = (payload ?? {});
|
|
155
194
|
const granularity = p.granularity === 'week' || p.granularity === 'month' ? p.granularity : 'day';
|
|
@@ -174,6 +213,7 @@ async function handleEndpoint(endpoint, payload, deps) {
|
|
|
174
213
|
defaultMode: modes.default,
|
|
175
214
|
recall: modes.getRecall(sessionId) ?? null,
|
|
176
215
|
recallResolved: modes.resolvedRecall(sessionId, globalRecall),
|
|
216
|
+
resume: modes.getResume(sessionId),
|
|
177
217
|
};
|
|
178
218
|
return v;
|
|
179
219
|
}
|
|
@@ -206,6 +246,7 @@ async function handleEndpoint(endpoint, payload, deps) {
|
|
|
206
246
|
mode: p.mode,
|
|
207
247
|
recall: modes.getRecall(sessionId) ?? null,
|
|
208
248
|
recallResolved: modes.resolvedRecall(sessionId, s?.recall ?? true),
|
|
249
|
+
resume: modes.getResume(sessionId),
|
|
209
250
|
};
|
|
210
251
|
return v;
|
|
211
252
|
}
|
package/dist/store/l1.d.ts
CHANGED
|
@@ -54,17 +54,23 @@ export declare class L1Store {
|
|
|
54
54
|
* 回填(FTS 表无该列;候选池 ≤ limit×3 条主键查询,微秒级)。关闭时零开销。
|
|
55
55
|
*/
|
|
56
56
|
private applyDecay;
|
|
57
|
-
/** 浏览列表(UI
|
|
57
|
+
/** 浏览列表(UI 用):无关键词时按更新时间倒序分页;since 为 ISO 时间下限(可选)。 */
|
|
58
58
|
list(opts: {
|
|
59
59
|
type?: string;
|
|
60
60
|
scene?: string;
|
|
61
61
|
family?: string;
|
|
62
|
+
since?: string;
|
|
62
63
|
limit: number;
|
|
63
64
|
offset: number;
|
|
64
65
|
}): {
|
|
65
66
|
items: MemoryRecord[];
|
|
66
67
|
total: number;
|
|
67
68
|
};
|
|
69
|
+
/** 近 N 天逐日更新计数(工作台活动图;索引化 GROUP BY)。 */
|
|
70
|
+
countByDay(sinceIso: string): Array<{
|
|
71
|
+
day: string;
|
|
72
|
+
n: number;
|
|
73
|
+
}>;
|
|
68
74
|
/** 场景名去重列表(UI 筛选器数据源)。 */
|
|
69
75
|
distinctScenes(): string[];
|
|
70
76
|
/**
|
package/dist/store/l1.js
CHANGED
|
@@ -184,10 +184,14 @@ export class L1Store {
|
|
|
184
184
|
}
|
|
185
185
|
return applyDecayWeight(hits, this.decayHalfLifeDays, (h) => updatedAtById.get(h.id));
|
|
186
186
|
}
|
|
187
|
-
/** 浏览列表(UI
|
|
187
|
+
/** 浏览列表(UI 用):无关键词时按更新时间倒序分页;since 为 ISO 时间下限(可选)。 */
|
|
188
188
|
list(opts) {
|
|
189
189
|
return this.db.listL1(opts);
|
|
190
190
|
}
|
|
191
|
+
/** 近 N 天逐日更新计数(工作台活动图;索引化 GROUP BY)。 */
|
|
192
|
+
countByDay(sinceIso) {
|
|
193
|
+
return this.db.countL1ByDay(sinceIso);
|
|
194
|
+
}
|
|
191
195
|
/** 场景名去重列表(UI 筛选器数据源)。 */
|
|
192
196
|
distinctScenes() {
|
|
193
197
|
return this.db.distinctL1Scenes();
|
package/dist/store/persona.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare class PersonaStore {
|
|
|
9
9
|
init(): Promise<void>;
|
|
10
10
|
/** 读取正文(剥离场景导航部分)。 */
|
|
11
11
|
read(): Promise<string | undefined>;
|
|
12
|
+
/** 文件 mtime(epoch ms;无画像文件 null)——工作台资产活动流的 L3 时间源。 */
|
|
13
|
+
mtime(): Promise<number | null>;
|
|
12
14
|
/** 写入正文(保留已有导航段则拼回尾部)。 */
|
|
13
15
|
write(body: string): Promise<void>;
|
|
14
16
|
}
|
package/dist/store/persona.js
CHANGED
|
@@ -38,6 +38,15 @@ export class PersonaStore {
|
|
|
38
38
|
return undefined;
|
|
39
39
|
return stripSceneNavigation(raw).trim() || undefined;
|
|
40
40
|
}
|
|
41
|
+
/** 文件 mtime(epoch ms;无画像文件 null)——工作台资产活动流的 L3 时间源。 */
|
|
42
|
+
async mtime() {
|
|
43
|
+
try {
|
|
44
|
+
return (await fs.stat(this.file)).mtimeMs;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
41
50
|
/** 写入正文(保留已有导航段则拼回尾部)。 */
|
|
42
51
|
async write(body) {
|
|
43
52
|
const raw = await readTextIfExists(this.file);
|
package/dist/store/scenes.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { MemoryFamily, MemoryLogger, SceneSummary } from '../types.js';
|
|
2
|
+
/** 场景文件 META 块标记(工作台活动流剥正文复用,保持单一事实源)。 */
|
|
3
|
+
export declare const META_START = "-----META-START-----";
|
|
4
|
+
export declare const META_END = "-----META-END-----";
|
|
2
5
|
export declare class SceneStore {
|
|
3
6
|
private readonly logger?;
|
|
4
7
|
private readonly dir;
|
package/dist/store/scenes.js
CHANGED
|
@@ -10,8 +10,9 @@ import { promises as fs } from 'node:fs';
|
|
|
10
10
|
import * as path from 'node:path';
|
|
11
11
|
import { atomicWriteText, ensureDir, readTextIfExists } from './io.js';
|
|
12
12
|
import { NAV_HEADER } from './persona.js';
|
|
13
|
-
|
|
14
|
-
const
|
|
13
|
+
/** 场景文件 META 块标记(工作台活动流剥正文复用,保持单一事实源)。 */
|
|
14
|
+
export const META_START = '-----META-START-----';
|
|
15
|
+
export const META_END = '-----META-END-----';
|
|
15
16
|
const DELETED_MARKER = '[DELETED]';
|
|
16
17
|
export class SceneStore {
|
|
17
18
|
logger;
|
|
@@ -26,7 +26,20 @@ export declare class SessionModeStore {
|
|
|
26
26
|
setRecall(sessionId: string, recall: boolean | undefined): void;
|
|
27
27
|
/** 注册档位切换回调(同步调用;回调异常只记日志不阻断写穿)。 */
|
|
28
28
|
setModeChangeHandler(cb: (sessionId: string, oldMode: MemoryMode, newMode: MemoryMode) => void): void;
|
|
29
|
-
/**
|
|
29
|
+
/** 暂停恢复快照(无则 null)。 */
|
|
30
|
+
getResume(sessionId: string): {
|
|
31
|
+
scope: 'auto' | 'chat' | 'work';
|
|
32
|
+
recall: boolean | null;
|
|
33
|
+
} | null;
|
|
34
|
+
/** 停用侧分布(工作台洞察,只读计数):off = 档位暂停会话;wo = 注入覆盖只写
|
|
35
|
+
* (recall=false 且档位未停——两态互斥计数,与召回四因子短路序对齐)。 */
|
|
36
|
+
countStates(): {
|
|
37
|
+
off: number;
|
|
38
|
+
wo: number;
|
|
39
|
+
};
|
|
40
|
+
/** 设置会话档位(写穿持久化;持久化失败保持内存态生效)。
|
|
41
|
+
* 暂停语义(UI 重构):非 off → off 记录恢复快照(暂停前范围 + 当前注入覆盖);
|
|
42
|
+
* off → 非 off 清空快照(恢复)。 */
|
|
30
43
|
set(sessionId: string, mode: MemoryMode): void;
|
|
31
44
|
/** 等待在途持久化写完成(测试/停机用)。 */
|
|
32
45
|
flush(): Promise<void>;
|
|
@@ -46,6 +46,12 @@ export class SessionModeStore {
|
|
|
46
46
|
mode: entry.mode,
|
|
47
47
|
// 非布尔视为损坏丢弃(= 跟随全局);旧文件无此键同款兼容
|
|
48
48
|
recall: typeof entry.recall === 'boolean' ? entry.recall : undefined,
|
|
49
|
+
// 恢复快照:scope 必须是非 off 档、recall 必须是布尔或 null,否则整块丢弃
|
|
50
|
+
resume: entry.resume &&
|
|
51
|
+
['auto', 'chat', 'work'].includes(entry.resume.scope) &&
|
|
52
|
+
(typeof entry.resume.recall === 'boolean' || entry.resume.recall === null)
|
|
53
|
+
? { scope: entry.resume.scope, recall: entry.resume.recall }
|
|
54
|
+
: undefined,
|
|
49
55
|
updatedAt: entry.updatedAt ?? now,
|
|
50
56
|
});
|
|
51
57
|
count++;
|
|
@@ -75,6 +81,7 @@ export class SessionModeStore {
|
|
|
75
81
|
this.entries.set(sessionId, {
|
|
76
82
|
mode: entry?.mode ?? this.loaded,
|
|
77
83
|
recall,
|
|
84
|
+
resume: entry?.resume,
|
|
78
85
|
updatedAt: Date.now(),
|
|
79
86
|
});
|
|
80
87
|
this.writeChain = this.writeChain.then(() => this.persist());
|
|
@@ -83,12 +90,39 @@ export class SessionModeStore {
|
|
|
83
90
|
setModeChangeHandler(cb) {
|
|
84
91
|
this.onModeChange = cb;
|
|
85
92
|
}
|
|
86
|
-
/**
|
|
93
|
+
/** 暂停恢复快照(无则 null)。 */
|
|
94
|
+
getResume(sessionId) {
|
|
95
|
+
return this.entries.get(sessionId)?.resume ?? null;
|
|
96
|
+
}
|
|
97
|
+
/** 停用侧分布(工作台洞察,只读计数):off = 档位暂停会话;wo = 注入覆盖只写
|
|
98
|
+
* (recall=false 且档位未停——两态互斥计数,与召回四因子短路序对齐)。 */
|
|
99
|
+
countStates() {
|
|
100
|
+
let off = 0;
|
|
101
|
+
let wo = 0;
|
|
102
|
+
for (const e of this.entries.values()) {
|
|
103
|
+
if (e.mode === 'off')
|
|
104
|
+
off++;
|
|
105
|
+
else if (e.recall === false)
|
|
106
|
+
wo++;
|
|
107
|
+
}
|
|
108
|
+
return { off, wo };
|
|
109
|
+
}
|
|
110
|
+
/** 设置会话档位(写穿持久化;持久化失败保持内存态生效)。
|
|
111
|
+
* 暂停语义(UI 重构):非 off → off 记录恢复快照(暂停前范围 + 当前注入覆盖);
|
|
112
|
+
* off → 非 off 清空快照(恢复)。 */
|
|
87
113
|
set(sessionId, mode) {
|
|
88
114
|
const old = this.get(sessionId);
|
|
89
115
|
// 已有注入覆盖跨切档保留(#38:档位与注入正交,切档不动覆盖)
|
|
90
|
-
const
|
|
91
|
-
|
|
116
|
+
const prev = this.entries.get(sessionId);
|
|
117
|
+
const recall = prev?.recall;
|
|
118
|
+
let resume = prev?.resume;
|
|
119
|
+
if (mode === 'off' && old !== 'off') {
|
|
120
|
+
resume = { scope: old, recall: recall ?? null };
|
|
121
|
+
}
|
|
122
|
+
else if (mode !== 'off' && old === 'off') {
|
|
123
|
+
resume = undefined;
|
|
124
|
+
}
|
|
125
|
+
this.entries.set(sessionId, { mode, recall, resume, updatedAt: Date.now() });
|
|
92
126
|
this.writeChain = this.writeChain.then(() => this.persist());
|
|
93
127
|
if (old !== mode && this.onModeChange) {
|
|
94
128
|
try {
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -133,17 +133,24 @@ export declare class MemoryDb {
|
|
|
133
133
|
/** 全量读取(调试/迁移/重嵌入用;检索请走 FTS/向量)。 */
|
|
134
134
|
getAllL1(): MemoryRecord[];
|
|
135
135
|
getL1ByIds(ids: string[]): MemoryRecord[];
|
|
136
|
-
/** 浏览列表(UI
|
|
136
|
+
/** 浏览列表(UI 用):按更新时间倒序,支持类型/场景/族/时间下限过滤与分页。失败返回空。 */
|
|
137
137
|
listL1(opts: {
|
|
138
138
|
type?: string;
|
|
139
139
|
scene?: string;
|
|
140
140
|
family?: string;
|
|
141
|
+
since?: string;
|
|
141
142
|
limit: number;
|
|
142
143
|
offset: number;
|
|
143
144
|
}): {
|
|
144
145
|
items: MemoryRecord[];
|
|
145
146
|
total: number;
|
|
146
147
|
};
|
|
148
|
+
/** 近 N 天逐日 L1 更新计数(工作台活动图;idx_l1_updated 范围扫描 + GROUP BY)。
|
|
149
|
+
* day 为 ISO 日期前缀 YYYY-MM-DD(updated_time 存 ISO 文本,substr 即日期)。 */
|
|
150
|
+
countL1ByDay(sinceIso: string): Array<{
|
|
151
|
+
day: string;
|
|
152
|
+
n: number;
|
|
153
|
+
}>;
|
|
147
154
|
/** 场景名去重列表(UI 筛选器数据源)。失败返回空。 */
|
|
148
155
|
distinctL1Scenes(): string[];
|
|
149
156
|
/** FTS5 BM25 检索(family 缺省不过滤)。失败返回空数组(调用方降级)。 */
|
package/dist/store/sqlite.js
CHANGED
|
@@ -754,7 +754,7 @@ export class MemoryDb {
|
|
|
754
754
|
stmt =
|
|
755
755
|
action === 'delete'
|
|
756
756
|
? this.db.prepare(`DELETE FROM ${table} WHERE record_id IN (${ph})`)
|
|
757
|
-
: this.db.prepare(`SELECT record_id, content, type, priority, scene_name, version, timestamp_str, created_time, updated_time, metadata_json, family FROM ${table} WHERE record_id IN (${ph})`);
|
|
757
|
+
: this.db.prepare(`SELECT record_id, content, type, priority, scene_name, session_id, version, timestamp_str, created_time, updated_time, metadata_json, family FROM ${table} WHERE record_id IN (${ph})`);
|
|
758
758
|
this.inStmts.set(key, stmt);
|
|
759
759
|
}
|
|
760
760
|
return stmt;
|
|
@@ -819,7 +819,7 @@ export class MemoryDb {
|
|
|
819
819
|
if (this.degraded)
|
|
820
820
|
return [];
|
|
821
821
|
const rows = this.db
|
|
822
|
-
.prepare('SELECT record_id, content, type, priority, scene_name, version, timestamp_str, created_time, updated_time, metadata_json, family FROM l1_records')
|
|
822
|
+
.prepare('SELECT record_id, content, type, priority, scene_name, session_id, version, timestamp_str, created_time, updated_time, metadata_json, family FROM l1_records')
|
|
823
823
|
.all();
|
|
824
824
|
return rows.map(rowToRecord);
|
|
825
825
|
}
|
|
@@ -832,7 +832,7 @@ export class MemoryDb {
|
|
|
832
832
|
}
|
|
833
833
|
return rows.map(rowToRecord);
|
|
834
834
|
}
|
|
835
|
-
/** 浏览列表(UI
|
|
835
|
+
/** 浏览列表(UI 用):按更新时间倒序,支持类型/场景/族/时间下限过滤与分页。失败返回空。 */
|
|
836
836
|
listL1(opts) {
|
|
837
837
|
if (this.degraded)
|
|
838
838
|
return { items: [], total: 0 };
|
|
@@ -851,10 +851,15 @@ export class MemoryDb {
|
|
|
851
851
|
where.push('family = ?');
|
|
852
852
|
params.push(opts.family);
|
|
853
853
|
}
|
|
854
|
+
// 时间下限(ISO 字符串;updated_time 存同格式,字典序 = 时间序,走 idx_l1_updated)
|
|
855
|
+
if (opts.since) {
|
|
856
|
+
where.push('updated_time >= ?');
|
|
857
|
+
params.push(opts.since);
|
|
858
|
+
}
|
|
854
859
|
const whereSql = where.length > 0 ? ` WHERE ${where.join(' AND ')}` : '';
|
|
855
860
|
const totalRow = this.db.prepare(`SELECT COUNT(*) AS n FROM l1_records${whereSql}`).get(...params);
|
|
856
861
|
const rows = this.db
|
|
857
|
-
.prepare(`SELECT record_id, content, type, priority, scene_name, version, timestamp_str, created_time, updated_time, metadata_json, family FROM l1_records${whereSql} ORDER BY updated_time DESC LIMIT ? OFFSET ?`)
|
|
862
|
+
.prepare(`SELECT record_id, content, type, priority, scene_name, session_id, version, timestamp_str, created_time, updated_time, metadata_json, family FROM l1_records${whereSql} ORDER BY updated_time DESC LIMIT ? OFFSET ?`)
|
|
858
863
|
.all(...params, opts.limit, opts.offset);
|
|
859
864
|
return { items: rows.map(rowToRecord), total: totalRow?.n ?? 0 };
|
|
860
865
|
}
|
|
@@ -863,6 +868,20 @@ export class MemoryDb {
|
|
|
863
868
|
return { items: [], total: 0 };
|
|
864
869
|
}
|
|
865
870
|
}
|
|
871
|
+
/** 近 N 天逐日 L1 更新计数(工作台活动图;idx_l1_updated 范围扫描 + GROUP BY)。
|
|
872
|
+
* day 为 ISO 日期前缀 YYYY-MM-DD(updated_time 存 ISO 文本,substr 即日期)。 */
|
|
873
|
+
countL1ByDay(sinceIso) {
|
|
874
|
+
if (this.degraded)
|
|
875
|
+
return [];
|
|
876
|
+
try {
|
|
877
|
+
return this.db
|
|
878
|
+
.prepare("SELECT substr(updated_time, 1, 10) AS day, COUNT(*) AS n FROM l1_records WHERE updated_time >= ? AND updated_time <> '' GROUP BY day ORDER BY day")
|
|
879
|
+
.all(sinceIso);
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
}
|
|
866
885
|
/** 场景名去重列表(UI 筛选器数据源)。失败返回空。 */
|
|
867
886
|
distinctL1Scenes() {
|
|
868
887
|
if (this.degraded)
|
|
@@ -1437,6 +1456,7 @@ function rowToRecord(row) {
|
|
|
1437
1456
|
type: row.type,
|
|
1438
1457
|
priority: row.priority,
|
|
1439
1458
|
scene_name: row.scene_name,
|
|
1459
|
+
sessionId: row.session_id || undefined,
|
|
1440
1460
|
timestamps: dbToTimestamps(row.timestamp_str),
|
|
1441
1461
|
createdAt: Date.parse(row.created_time) || 0,
|
|
1442
1462
|
updatedAt: Date.parse(row.updated_time) || 0,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作台只读聚合(UI 重构分散式):总览快照、资产活动流、运行洞察三个端点的
|
|
3
|
+
* 数据装配层。全部只读,不触碰蒸馏/召回语义。
|
|
4
|
+
*
|
|
5
|
+
* 数据源纪律:本组端点按「设置页打开期间低频拉取」设计(非 session-stats
|
|
6
|
+
* 热路径)——L1 走 SQL 索引(idx_l1_updated 范围扫描 / FTS 检索唯一缝);
|
|
7
|
+
* L2 场景与 L3 画像是文件级小数据(dsh-memory/scenes、/persona 端点同款读法)。
|
|
8
|
+
*/
|
|
9
|
+
import type { AssetActivityResponse, MemoryStats, RuntimeInsightsResponse, WorkspaceOverviewResponse } from './contract.js';
|
|
10
|
+
import type { L1Store } from './store/l1.js';
|
|
11
|
+
import type { PersonaStore } from './store/persona.js';
|
|
12
|
+
import type { SceneStore } from './store/scenes.js';
|
|
13
|
+
import type { SessionModeStore } from './store/session-modes.js';
|
|
14
|
+
import type { MemoryFamily } from './types.js';
|
|
15
|
+
import type { SessionInfoSource } from './stats.js';
|
|
16
|
+
import type { LiveSettingsHandle } from './settings.js';
|
|
17
|
+
/** 工作台可见的分族存储聚合(与 registerMemoryRpc 的 stores 同形)。 */
|
|
18
|
+
export type WorkspaceStores = {
|
|
19
|
+
l1: L1Store;
|
|
20
|
+
scenes: Record<MemoryFamily, SceneStore>;
|
|
21
|
+
persona: Record<MemoryFamily, PersonaStore>;
|
|
22
|
+
};
|
|
23
|
+
export interface ActivityQuery {
|
|
24
|
+
query: string;
|
|
25
|
+
kind: '' | 'l1' | 'l2' | 'l3';
|
|
26
|
+
family: '' | 'chat' | 'work';
|
|
27
|
+
sinceMs: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
offset: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function decodeCursor(cursor: string | null | undefined): number;
|
|
32
|
+
/**
|
|
33
|
+
* 混排资产活动流:L1(索引列表/检索缝)+ L2(场景摘要,页内补读正文)+
|
|
34
|
+
* L3(画像 + mtime),按 updatedAt 倒序统一排序后游标切片。
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectAssetActivity(stores: WorkspaceStores, q: ActivityQuery): Promise<AssetActivityResponse>;
|
|
37
|
+
/** 总览聚合:buildStats 既有字段 + 本周成本(token-cost 周窗口)+ 最近活动前 5 条。 */
|
|
38
|
+
export declare function buildWorkspaceOverview(stores: WorkspaceStores, base: MemoryStats, week: {
|
|
39
|
+
calls: number;
|
|
40
|
+
outputTokens: number;
|
|
41
|
+
reasoningTokens: number;
|
|
42
|
+
} | null, retrieval: 'hybrid' | 'vector' | 'keyword' | 'none' | null): Promise<WorkspaceOverviewResponse>;
|
|
43
|
+
/** 洞察聚合:近 7 天逐日资产活动 + 蒸馏调用计数器 + 召回全量累计与停用分布。 */
|
|
44
|
+
export declare function buildRuntimeInsights(stores: WorkspaceStores, sessionInfo: SessionInfoSource | undefined, modes: SessionModeStore | undefined, live: LiveSettingsHandle | undefined): Promise<RuntimeInsightsResponse>;
|