dsh-prime-memory 0.11.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 (151) hide show
  1. package/CHANGELOG.en.md +28 -0
  2. package/CHANGELOG.ja.md +30 -0
  3. package/CHANGELOG.ko.md +30 -0
  4. package/CHANGELOG.md +1220 -0
  5. package/ENGINEERING-NOTES.md +452 -0
  6. package/INSTALL.en.md +92 -0
  7. package/INSTALL.ja.md +92 -0
  8. package/INSTALL.ko.md +92 -0
  9. package/INSTALL.md +92 -0
  10. package/LICENSE +21 -0
  11. package/README.en.md +458 -0
  12. package/README.ja.md +306 -0
  13. package/README.ko.md +306 -0
  14. package/README.md +425 -0
  15. package/assets/changelog/0.8.10/01-write-only-pill.png +0 -0
  16. package/assets/changelog/0.8.9/01-panel.png +0 -0
  17. package/assets/changelog/0.8.9/02-halo.png +0 -0
  18. package/assets/changelog/0.8.9/03-layer-segmented-panel.png +0 -0
  19. package/assets/changelog/0.8.9/04-layer-l1-panel.png +0 -0
  20. package/assets/img/EmbeddingSource.png +0 -0
  21. package/assets/img/Hero.png +0 -0
  22. package/assets/img/Layers.png +0 -0
  23. package/assets/img/MemoryTools.png +0 -0
  24. package/assets/img/Modes.png +0 -0
  25. package/assets/img/ToolTrajectory.png +0 -0
  26. package/assets/img/ui-dark.jpg +0 -0
  27. package/assets/img/ui-light.jpg +0 -0
  28. package/assets/readme/bench-dialog.svg +70 -0
  29. package/assets/readme/bench-workflow.svg +79 -0
  30. package/assets/readme/flow.svg +189 -0
  31. package/assets/readme/storage.svg +115 -0
  32. package/cordis.patch.yml +16 -0
  33. package/dist/bench-control.d.ts +34 -0
  34. package/dist/bench-control.js +16 -0
  35. package/dist/client.js +4293 -0
  36. package/dist/config.d.ts +683 -0
  37. package/dist/config.js +129 -0
  38. package/dist/contract.d.ts +820 -0
  39. package/dist/contract.js +1 -0
  40. package/dist/embedding-worker.cjs +176 -0
  41. package/dist/graph/apply.d.ts +37 -0
  42. package/dist/graph/apply.js +270 -0
  43. package/dist/graph/constraints.d.ts +47 -0
  44. package/dist/graph/constraints.js +38 -0
  45. package/dist/graph/search.d.ts +16 -0
  46. package/dist/graph/search.js +115 -0
  47. package/dist/graph/types.d.ts +142 -0
  48. package/dist/graph/types.js +14 -0
  49. package/dist/hooks/capture.d.ts +32 -0
  50. package/dist/hooks/capture.js +194 -0
  51. package/dist/hooks/recall.d.ts +63 -0
  52. package/dist/hooks/recall.js +429 -0
  53. package/dist/index.d.ts +534 -0
  54. package/dist/index.js +344 -0
  55. package/dist/llm-usage.d.ts +26 -0
  56. package/dist/llm-usage.js +37 -0
  57. package/dist/llm.d.ts +153 -0
  58. package/dist/llm.js +530 -0
  59. package/dist/pipeline/graph.d.ts +35 -0
  60. package/dist/pipeline/graph.js +104 -0
  61. package/dist/pipeline/l1.d.ts +19 -0
  62. package/dist/pipeline/l1.js +271 -0
  63. package/dist/pipeline/l2.d.ts +13 -0
  64. package/dist/pipeline/l2.js +83 -0
  65. package/dist/pipeline/l3.d.ts +15 -0
  66. package/dist/pipeline/l3.js +78 -0
  67. package/dist/pipeline/rebuild.d.ts +61 -0
  68. package/dist/pipeline/rebuild.js +307 -0
  69. package/dist/pipeline/ruminate.d.ts +89 -0
  70. package/dist/pipeline/ruminate.js +298 -0
  71. package/dist/pipeline/runner.d.ts +167 -0
  72. package/dist/pipeline/runner.js +638 -0
  73. package/dist/pipeline/trigger.d.ts +40 -0
  74. package/dist/pipeline/trigger.js +75 -0
  75. package/dist/prompts/graph-projection.d.ts +70 -0
  76. package/dist/prompts/graph-projection.js +167 -0
  77. package/dist/prompts/l1-dedup.d.ts +22 -0
  78. package/dist/prompts/l1-dedup.js +251 -0
  79. package/dist/prompts/l1-extraction.d.ts +22 -0
  80. package/dist/prompts/l1-extraction.js +457 -0
  81. package/dist/prompts/persona.d.ts +23 -0
  82. package/dist/prompts/persona.js +240 -0
  83. package/dist/prompts/scene.d.ts +32 -0
  84. package/dist/prompts/scene.js +414 -0
  85. package/dist/runtime-package-lock.json +982 -0
  86. package/dist/settings.d.ts +50 -0
  87. package/dist/settings.js +355 -0
  88. package/dist/stats.d.ts +109 -0
  89. package/dist/stats.js +929 -0
  90. package/dist/store/bm25.d.ts +19 -0
  91. package/dist/store/bm25.js +63 -0
  92. package/dist/store/cost-ledger.d.ts +75 -0
  93. package/dist/store/cost-ledger.js +171 -0
  94. package/dist/store/download-queue.d.ts +79 -0
  95. package/dist/store/download-queue.js +424 -0
  96. package/dist/store/embedding-source.d.ts +118 -0
  97. package/dist/store/embedding-source.js +443 -0
  98. package/dist/store/embedding.d.ts +90 -0
  99. package/dist/store/embedding.js +206 -0
  100. package/dist/store/graph-store.d.ts +94 -0
  101. package/dist/store/graph-store.js +641 -0
  102. package/dist/store/l0.d.ts +40 -0
  103. package/dist/store/l0.js +197 -0
  104. package/dist/store/l1.d.ts +93 -0
  105. package/dist/store/l1.js +297 -0
  106. package/dist/store/local-embedding.d.ts +89 -0
  107. package/dist/store/local-embedding.js +227 -0
  108. package/dist/store/model-catalog.d.ts +48 -0
  109. package/dist/store/model-catalog.js +81 -0
  110. package/dist/store/occupancy.d.ts +30 -0
  111. package/dist/store/occupancy.js +134 -0
  112. package/dist/store/pending.d.ts +36 -0
  113. package/dist/store/pending.js +103 -0
  114. package/dist/store/persona.d.ts +15 -0
  115. package/dist/store/persona.js +60 -0
  116. package/dist/store/recall-dedupe.d.ts +26 -0
  117. package/dist/store/recall-dedupe.js +138 -0
  118. package/dist/store/runtime-installer.d.ts +59 -0
  119. package/dist/store/runtime-installer.js +243 -0
  120. package/dist/store/scenes.d.ts +24 -0
  121. package/dist/store/scenes.js +160 -0
  122. package/dist/store/search-utils.d.ts +38 -0
  123. package/dist/store/search-utils.js +100 -0
  124. package/dist/store/session-modes.d.ts +35 -0
  125. package/dist/store/session-modes.js +144 -0
  126. package/dist/store/sqlite.d.ts +246 -0
  127. package/dist/store/sqlite.js +1491 -0
  128. package/dist/store/state.d.ts +41 -0
  129. package/dist/store/state.js +72 -0
  130. package/dist/token-cost.d.ts +23 -0
  131. package/dist/token-cost.js +185 -0
  132. package/dist/tools/index.d.ts +34 -0
  133. package/dist/tools/index.js +758 -0
  134. package/dist/types.d.ts +139 -0
  135. package/dist/types.js +38 -0
  136. package/dist/util/context-occupancy.d.ts +68 -0
  137. package/dist/util/context-occupancy.js +92 -0
  138. package/dist/util/filelog.d.ts +6 -0
  139. package/dist/util/filelog.js +108 -0
  140. package/dist/util/io.d.ts +18 -0
  141. package/dist/util/io.js +97 -0
  142. package/dist/util/recall-budget.d.ts +31 -0
  143. package/dist/util/recall-budget.js +84 -0
  144. package/dist/util/sanitize.d.ts +11 -0
  145. package/dist/util/sanitize.js +67 -0
  146. package/dist/util/text.d.ts +16 -0
  147. package/dist/util/text.js +61 -0
  148. package/dist/util/tokenizer.d.ts +9 -0
  149. package/dist/util/tokenizer.js +50 -0
  150. package/dsh.plugin.json +22 -0
  151. package/package.json +118 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 记忆模式运行时开关(官方 settings 服务,live 生效)。
3
+ * 语义:静态 config(cordis.patch.yml,部署上限)AND 运行时开关,两者同时开才工作。
4
+ * settings 服务缺失(如 headless)时退化为恒开——行为与无开关版本一致。
5
+ *
6
+ * v0.9.0 契约:含远程嵌入运行时覆盖四键(embedRemote*)与记忆写删门 memoryMutate
7
+ * (自 dist 逆向补全——settings 缺键 = 用户已存值被静默丢弃,红线)。
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import Schema from '@deepseek-ai/schemastery';
11
+ import type { MemoryLogger } from './types.js';
12
+ import type { DistillChainEntry, MemoryLiveSettings } from './contract.js';
13
+ export type { DistillBudgets, DistillChainEntry, EffortChoice, MemoryLiveSettings } from './contract.js';
14
+ /** 运行时路由链上限(写入门与 UI 同限,防误粘贴巨数组撑爆 settings 存储)。 */
15
+ export declare const DISTILL_CHAIN_MAX = 8;
16
+ /**
17
+ * 运行时统一路由链的展示投影(llm-providers 的 chain.current 数据源):
18
+ * distillChain 非空即原样返回;为空时投影旧运行时键(distillProvider/distillModel
19
+ * 成对 → 单行主路由,旧档位 reasoningEffort 作为该主路由的档位)。注意:生效逻辑
20
+ * (effectiveCfg)只认显式 distillChain、不走本投影——旧键路径在未配链时按旧语义
21
+ * 原样生效。
22
+ */
23
+ export declare function projectDistillChain(s: Partial<MemoryLiveSettings> | undefined): DistillChainEntry[];
24
+ /**
25
+ * settings-set 写入门校验:返回错误文案(null = 通过)。
26
+ * opts.requireExplicitHead(层链用):头行必须 provider+model 双显式——层键出现
27
+ * 即意图覆盖;"双空 = 跟随默认模型"是全局链独有语义,层链禁掉双空头,消除
28
+ * "层链头跟随哪套全局解析"的歧义。
29
+ */
30
+ export declare function validateDistillChain(chain: unknown, opts?: {
31
+ requireExplicitHead?: boolean;
32
+ }): string | null;
33
+ /**
34
+ * settings scope 的本地结构类型:与官方 SettingsScope<T> 的 owner 面一致
35
+ * (get/watch/update),但不依赖包级类型导出,跨 DSH 版本稳定。
36
+ */
37
+ export interface SettingsScope<T> {
38
+ get(): T;
39
+ watch(callback: (next: T, prev: T) => void | Promise<void>): () => void;
40
+ update(patch: object): Promise<void>;
41
+ }
42
+ export interface LiveSettingsHandle {
43
+ /** settings 服务是否可用(不可用时 UI 侧隐藏开关面板) */
44
+ supported: boolean;
45
+ get(): MemoryLiveSettings;
46
+ /** UI 写入入口;不支持时抛错由 RPC 层转成业务错误 */
47
+ update(patch: Partial<MemoryLiveSettings>): Promise<void>;
48
+ }
49
+ export declare function liveSettingsSchema(): Schema<MemoryLiveSettings>;
50
+ export declare function registerLiveSettings(ctx: Context, logger: MemoryLogger): LiveSettingsHandle;
@@ -0,0 +1,355 @@
1
+ import Schema from '@deepseek-ai/schemastery';
2
+ import { EFFORT_CHOICES } from './config.js';
3
+ /** 运行时路由链上限(写入门与 UI 同限,防误粘贴巨数组撑爆 settings 存储)。 */
4
+ export const DISTILL_CHAIN_MAX = 8;
5
+ /**
6
+ * 运行时统一路由链的展示投影(llm-providers 的 chain.current 数据源):
7
+ * distillChain 非空即原样返回;为空时投影旧运行时键(distillProvider/distillModel
8
+ * 成对 → 单行主路由,旧档位 reasoningEffort 作为该主路由的档位)。注意:生效逻辑
9
+ * (effectiveCfg)只认显式 distillChain、不走本投影——旧键路径在未配链时按旧语义
10
+ * 原样生效。
11
+ */
12
+ export function projectDistillChain(s) {
13
+ if (s?.distillChain?.length)
14
+ return s.distillChain;
15
+ if (s?.distillProvider && s?.distillModel) {
16
+ return [{ provider: s.distillProvider, model: s.distillModel, reasoningEffort: s.reasoningEffort || '' }];
17
+ }
18
+ return [];
19
+ }
20
+ /**
21
+ * settings-set 写入门校验:返回错误文案(null = 通过)。
22
+ * opts.requireExplicitHead(层链用):头行必须 provider+model 双显式——层键出现
23
+ * 即意图覆盖;"双空 = 跟随默认模型"是全局链独有语义,层链禁掉双空头,消除
24
+ * "层链头跟随哪套全局解析"的歧义。
25
+ */
26
+ export function validateDistillChain(chain, opts) {
27
+ if (!Array.isArray(chain))
28
+ return 'distillChain 须为数组';
29
+ if (chain.length > DISTILL_CHAIN_MAX)
30
+ return `路由链最多 ${DISTILL_CHAIN_MAX} 条`;
31
+ const seen = new Set();
32
+ for (let i = 0; i < chain.length; i++) {
33
+ if (!chain[i] || typeof chain[i] !== 'object')
34
+ return `第 ${i + 1} 行须为对象`;
35
+ const e = chain[i];
36
+ const p = typeof e.provider === 'string' ? e.provider : '';
37
+ const m = typeof e.model === 'string' ? e.model : '';
38
+ const eff = typeof e.reasoningEffort === 'string' ? e.reasoningEffort : '';
39
+ if (p.length > 200 || m.length > 200)
40
+ return `第 ${i + 1} 行 provider/model 过长(≤200 字符)`;
41
+ if (!EFFORT_CHOICES.includes(eff))
42
+ return `第 ${i + 1} 行思考档位非法: ${eff || '(空)'}`;
43
+ if (i === 0) {
44
+ if (opts?.requireExplicitHead && (!p || !m))
45
+ return '主路由行必须显式选择供应商与模型(层链不支持跟随默认模型)';
46
+ if ((p && !m) || (!p && m))
47
+ return '主路由行 provider 与 model 须成对(双空 = 跟随默认模型)';
48
+ }
49
+ else if (!p || !m) {
50
+ return `第 ${i + 1} 行回退路由必须显式选择供应商与模型`;
51
+ }
52
+ if (p && m) {
53
+ const key = `${p}::${m}`;
54
+ if (seen.has(key))
55
+ return `第 ${i + 1} 行与前面的路由重复(${p}/${m})`;
56
+ seen.add(key);
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ const NS = 'dsh-memory';
62
+ const ALWAYS_ON = {
63
+ enabled: true,
64
+ capture: true,
65
+ distill: true,
66
+ recall: true,
67
+ reasoningEffort: '',
68
+ distillProvider: '',
69
+ distillModel: '',
70
+ distillChain: [],
71
+ distillBudgets: { extract: 0, dedup: 0, l2: 0, l3: 0, graph: 0 },
72
+ distillMaxInputChars: 0,
73
+ distillLayerChains: { l1: [], l2: [], l3: [] },
74
+ distillMode: '',
75
+ directBaseURL: '',
76
+ directApiKey: '',
77
+ embedRemoteBaseURL: '',
78
+ embedRemoteApiKey: '',
79
+ embedRemoteModel: '',
80
+ embedRemoteDimensions: 0,
81
+ memoryMutate: false,
82
+ };
83
+ /**
84
+ * 进程内 scope 复用(fiber 重启重挂)。
85
+ * dsh-settings 的 register 把注册挂在其服务自身 ctx 的 effect 上,不随本插件
86
+ * fiber 销毁——fiber 重启后二次 register 会抛 already registered。模块级状态在
87
+ * fiber 重启间存活:复用上次注册的 scope 并重挂 watcher,否则开关读写停在 stub。
88
+ *
89
+ * 同时按服务实例(cachedSvc)判活:settings 服务自身重启时其注册随服务 ctx 销毁,
90
+ * 旧 scope 变死引用——新实例与缓存不符时作废缓存、向新实例重新注册
91
+ * (用户层由服务从磁盘重解析,已存开关不丢)。
92
+ */
93
+ let cachedScope;
94
+ let cachedUnwatch;
95
+ let cachedSvc;
96
+ export function liveSettingsSchema() {
97
+ const budget = () => Schema.number().min(0).max(1_000_000).default(0);
98
+ // 层链条目形状与 distillChain 相同(档位必填、'' = 跟随);写入校验另在
99
+ // settings-set 门做逐层 requireExplicitHead(schema 层只管形状默认,语义门在 host)
100
+ const chainEntry = () => Schema.object({
101
+ provider: Schema.string().default(''),
102
+ model: Schema.string().default(''),
103
+ reasoningEffort: Schema.union([...EFFORT_CHOICES]).default(''),
104
+ });
105
+ return Schema.object({
106
+ enabled: Schema.boolean().default(true),
107
+ capture: Schema.boolean().default(true),
108
+ distill: Schema.boolean().default(true),
109
+ recall: Schema.boolean().default(true),
110
+ reasoningEffort: Schema.union([...EFFORT_CHOICES]).default(''),
111
+ distillProvider: Schema.string().default(''),
112
+ distillModel: Schema.string().default(''),
113
+ distillChain: Schema.array(chainEntry()).default([]),
114
+ distillLayerChains: Schema.object({
115
+ l1: Schema.array(chainEntry()).default([]),
116
+ l2: Schema.array(chainEntry()).default([]),
117
+ l3: Schema.array(chainEntry()).default([]),
118
+ }).default({ l1: [], l2: [], l3: [] }),
119
+ distillBudgets: Schema.object({
120
+ extract: budget(),
121
+ dedup: budget(),
122
+ l2: budget(),
123
+ l3: budget(),
124
+ // 图谱投影输出预算(投影 job 单批 ≤8 条记录,默认 8000)
125
+ graph: budget(),
126
+ }).default({ extract: 0, dedup: 0, l2: 0, l3: 0, graph: 0 }),
127
+ distillMaxInputChars: Schema.number().min(0).max(1_000_000).default(0),
128
+ // 蒸馏通道运行时覆盖:'' = 跟随部署 config / 'host' = 复用宿主 / 'direct' = 原生直连
129
+ distillMode: Schema.union(['', 'host', 'direct']).default(''),
130
+ directBaseURL: Schema.string().default(''),
131
+ // 直连 apiKey 属机密:schema 只接受字符串,不回读到 UI、不落日志
132
+ directApiKey: Schema.string().default(''),
133
+ // 远程嵌入连接运行时覆盖(设置 UI 可编辑,替代部署 YAML;dimension 上限与部署 schema 一致)
134
+ embedRemoteBaseURL: Schema.string().default(''),
135
+ embedRemoteApiKey: Schema.string().default(''),
136
+ embedRemoteModel: Schema.string().default(''),
137
+ embedRemoteDimensions: Schema.number().min(0).max(8192).default(0),
138
+ // 记忆写删权限门:默认 false(模型写删风险高,须显式在面板开启高权限模式)
139
+ memoryMutate: Schema.boolean().default(false),
140
+ });
141
+ }
142
+ export function registerLiveSettings(ctx, logger) {
143
+ // settings 服务可能晚于插件就绪(provider 先读盘再发布):先探测,未上线则监听补挂
144
+ let inner = {
145
+ supported: false,
146
+ get: () => ALWAYS_ON,
147
+ update: () => Promise.reject(new Error('settings 服务不可用')),
148
+ };
149
+ /** 挂接一个(新注册或复用的)scope:重挂前先摘旧 watcher,防跨重启累积。 */
150
+ const wireScope = (scope) => {
151
+ cachedUnwatch?.();
152
+ let current = resolveSettings(scope.get());
153
+ cachedUnwatch = scope.watch((next) => {
154
+ const prev = current;
155
+ current = resolveSettings(next);
156
+ const b = current.distillBudgets;
157
+ const budgetNote = (b.extract || b.dedup || b.l2 || b.l3 || b.graph)
158
+ ? `,输出预算=抽取 ${b.extract || '默认'}/去重 ${b.dedup || '默认'}/L2 ${b.l2 || '默认'}/L3 ${b.l3 || '默认'}/图谱 ${b.graph || '默认'}`
159
+ : '';
160
+ const inputNote = current.distillMaxInputChars > 0 ? `,输入预算=${current.distillMaxInputChars}` : '';
161
+ logger.info(`[memory] 记忆模式开关更新:总=${current.enabled} 捕获=${current.capture} 蒸馏=${current.distill} 召回=${current.recall}` +
162
+ `,蒸馏思考=${current.reasoningEffort || '跟随配置'}(此前 总=${prev.enabled})` +
163
+ (current.distillProvider && current.distillModel
164
+ ? `,蒸馏模型=${current.distillProvider}/${current.distillModel}`
165
+ : '') + budgetNote + inputNote);
166
+ });
167
+ return {
168
+ supported: true,
169
+ get: () => current,
170
+ update: async (patch) => {
171
+ await scope.update(patch);
172
+ },
173
+ };
174
+ };
175
+ /** 作废进程内缓存(服务下线/实例替换时旧注册已随服务销毁)。 */
176
+ const invalidateCache = () => {
177
+ cachedScope = undefined;
178
+ cachedSvc = undefined;
179
+ cachedUnwatch?.();
180
+ cachedUnwatch = undefined;
181
+ };
182
+ const tryAttach = () => {
183
+ const settings = ctx.get('settings');
184
+ if (!settings)
185
+ return false;
186
+ // 仅当缓存来自同一服务实例时才可复用——换了实例(服务重启/替换)就重新注册
187
+ if (cachedScope && cachedSvc === settings) {
188
+ try {
189
+ inner = wireScope(cachedScope);
190
+ const c = inner.get();
191
+ logger.info(`[memory] 记忆模式开关重挂(复用进程内注册,当前:总=${c.enabled} 捕获=${c.capture} 蒸馏=${c.distill} 召回=${c.recall}` +
192
+ `,蒸馏思考=${c.reasoningEffort || '跟随配置'})`);
193
+ return true;
194
+ }
195
+ catch (err) {
196
+ logger.warn(`[memory] 记忆模式开关缓存复用失败,改为重新注册: ${err instanceof Error ? err.message : String(err)}`);
197
+ invalidateCache();
198
+ }
199
+ }
200
+ // 分支 1(全版本存在):register 返回 owner 面的 SettingsScope(get/watch/update),
201
+ // 本插件的 live 开关读写/UI 写入全走它——优先级高于 installSection:
202
+ // 后者返回 void 只走 hooks(setSource/onChange),桥接后 update 写路径不可用,
203
+ // 仅当宿主只暴露 installSection 时才作降级回退(见分支 2)。
204
+ if (typeof settings.register === 'function') {
205
+ try {
206
+ const scope = settings
207
+ .register(NS, liveSettingsSchema(), { applies: 'live' });
208
+ cachedScope = scope;
209
+ cachedSvc = settings;
210
+ inner = wireScope(scope);
211
+ logger.info(`[memory] 记忆模式开关就绪(settings.register,命名空间 dsh-memory,当前:总=${inner.get().enabled} 捕获=${inner.get().capture} 蒸馏=${inner.get().distill} 召回=${inner.get().recall}` +
212
+ `,蒸馏思考=${inner.get().reasoningEffort || '跟随配置'})`);
213
+ return true;
214
+ }
215
+ catch (err) {
216
+ logger.warn(`[memory] 记忆模式开关注册失败(保持全开): ${err instanceof Error ? err.message : String(err)}`);
217
+ return true; // 已拿到服务但注册失败,不再重试
218
+ }
219
+ }
220
+ // 分支 2(v0.1.2+ 服务面,仅 register 缺失时):installSection(owner, ns, schema,
221
+ // entry, hooks) 只回 hooks——get 走 setSource 注入的 thunk,变更通知走 onChange,
222
+ // update 桥接为显式拒绝(UI 写入报业务错误;宿主未删 register 时不会走到这)。
223
+ if (typeof settings.installSection === 'function') {
224
+ try {
225
+ let source = () => ({ ...ALWAYS_ON });
226
+ let notify;
227
+ const bridge = {
228
+ get: () => resolveSettings(source()),
229
+ watch: (callback) => {
230
+ notify = () => void callback(bridge.get(), bridge.get());
231
+ return () => {
232
+ notify = undefined;
233
+ };
234
+ },
235
+ update: () => Promise.reject(new Error('installSection 桥接模式不支持运行时写入')),
236
+ };
237
+ settings.installSection(ctx, NS, liveSettingsSchema(), { ...ALWAYS_ON }, {
238
+ setSource: (current) => {
239
+ source = current;
240
+ },
241
+ onChange: () => notify?.(),
242
+ });
243
+ cachedScope = bridge;
244
+ cachedSvc = settings;
245
+ inner = wireScope(bridge);
246
+ logger.info('[memory] 记忆模式开关就绪(settings.installSection 桥接,运行时写入不可用)');
247
+ return true;
248
+ }
249
+ catch (err) {
250
+ logger.warn(`[memory] 记忆模式开关 installSection 桥接失败(保持全开): ${err instanceof Error ? err.message : String(err)}`);
251
+ return true;
252
+ }
253
+ }
254
+ // 分支 3(双 API 皆无):settings 服务在但没有可用注册面——保持全开降级,
255
+ // 不再重试(服务面不会凭空长出新 API)。
256
+ logger.warn('[memory] settings 服务无可用的 register/installSection API,记忆模式开关降级为恒开');
257
+ return true;
258
+ };
259
+ if (!tryAttach()) {
260
+ logger.warn('[memory] settings 服务未就绪,记忆模式开关暂不可用(保持全开,等待服务上线)');
261
+ }
262
+ // 无论初始是否成功都监听服务迁移:下线 → 作废缓存;换实例 → 作废后立即重挂
263
+ ctx.on('internal/service', (name, impl) => {
264
+ if (name !== 'settings')
265
+ return;
266
+ if (!impl) {
267
+ if (cachedSvc !== undefined) {
268
+ invalidateCache();
269
+ logger.warn('[memory] settings 服务下线,开关缓存已作废(期间读数为冻结值,恢复后自动重挂)');
270
+ }
271
+ return;
272
+ }
273
+ // 实例变了才作废缓存;但 tryAttach 无条件执行(幂等)——同一事件会广播到
274
+ // 所有存活 fiber 的监听器,后跑的那个也必须修好自己闭包里的 inner
275
+ if (impl !== cachedSvc)
276
+ invalidateCache();
277
+ tryAttach();
278
+ });
279
+ return {
280
+ get supported() {
281
+ return inner.supported;
282
+ },
283
+ get: () => inner.get(),
284
+ update: (patch) => inner.update(patch),
285
+ };
286
+ }
287
+ /** scope.get() 的防御性解析:异常值回退默认(宁可多记不可静默停摆)。 */
288
+ function resolveSettings(value) {
289
+ if (!value || typeof value !== 'object')
290
+ return { ...ALWAYS_ON, distillChain: [] };
291
+ const v = value;
292
+ const num = (x) => (typeof x === 'number' && Number.isFinite(x) && x >= 0 ? Math.floor(x) : 0);
293
+ const rawBudgets = (v.distillBudgets ?? {});
294
+ // 路由链逐条防御:非对象条目剔除、超长截断、非法档位归空、超限截断到上限
295
+ const defuseChain = (raw) => {
296
+ const out = [];
297
+ if (!Array.isArray(raw))
298
+ return out;
299
+ for (const item of raw) {
300
+ if (out.length >= DISTILL_CHAIN_MAX)
301
+ break;
302
+ if (!item || typeof item !== 'object')
303
+ continue;
304
+ const e = item;
305
+ const eff = typeof e.reasoningEffort === 'string' && EFFORT_CHOICES.includes(e.reasoningEffort)
306
+ ? e.reasoningEffort
307
+ : '';
308
+ out.push({
309
+ provider: typeof e.provider === 'string' ? e.provider.slice(0, 200) : '',
310
+ model: typeof e.model === 'string' ? e.model.slice(0, 200) : '',
311
+ reasoningEffort: eff,
312
+ });
313
+ }
314
+ return out;
315
+ };
316
+ const rawLayer = (v.distillLayerChains ?? {});
317
+ return {
318
+ enabled: v.enabled !== false,
319
+ capture: v.capture !== false,
320
+ distill: v.distill !== false,
321
+ recall: v.recall !== false,
322
+ reasoningEffort: typeof v.reasoningEffort === 'string' && EFFORT_CHOICES.includes(v.reasoningEffort)
323
+ ? v.reasoningEffort
324
+ : '',
325
+ distillProvider: typeof v.distillProvider === 'string' ? v.distillProvider : '',
326
+ distillModel: typeof v.distillModel === 'string' ? v.distillModel : '',
327
+ distillChain: defuseChain(v.distillChain),
328
+ distillLayerChains: {
329
+ l1: defuseChain(rawLayer.l1),
330
+ l2: defuseChain(rawLayer.l2),
331
+ l3: defuseChain(rawLayer.l3),
332
+ },
333
+ distillBudgets: {
334
+ extract: num(rawBudgets.extract),
335
+ dedup: num(rawBudgets.dedup),
336
+ l2: num(rawBudgets.l2),
337
+ l3: num(rawBudgets.l3),
338
+ graph: num(rawBudgets.graph),
339
+ },
340
+ distillMaxInputChars: num(v.distillMaxInputChars),
341
+ // 蒸馏通道:mode 白名单(非法归 '' = 跟随部署);端点与密钥截断防御但保留原始内容
342
+ distillMode: v.distillMode === 'host' || v.distillMode === 'direct' ? v.distillMode : '',
343
+ directBaseURL: typeof v.directBaseURL === 'string' ? v.directBaseURL.slice(0, 2000) : '',
344
+ directApiKey: typeof v.directApiKey === 'string' ? v.directApiKey.slice(0, 2000) : '',
345
+ // 远程嵌入覆盖:端点/密钥截 2000,模型名截 200,维度钳 0~8192(与部署 schema 上限一致)
346
+ embedRemoteBaseURL: typeof v.embedRemoteBaseURL === 'string' ? v.embedRemoteBaseURL.slice(0, 2000) : '',
347
+ embedRemoteApiKey: typeof v.embedRemoteApiKey === 'string' ? v.embedRemoteApiKey.slice(0, 2000) : '',
348
+ embedRemoteModel: typeof v.embedRemoteModel === 'string' ? v.embedRemoteModel.slice(0, 200) : '',
349
+ embedRemoteDimensions: typeof v.embedRemoteDimensions === 'number' && Number.isFinite(v.embedRemoteDimensions)
350
+ ? Math.min(Math.max(Math.floor(v.embedRemoteDimensions), 0), 8192)
351
+ : 0,
352
+ // 写删门:严格 === true(任何异常值都视为关,模型写删风险宁紧勿松)
353
+ memoryMutate: v.memoryMutate === true,
354
+ };
355
+ }
@@ -0,0 +1,109 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import { type MemoryConfig } from './config.js';
3
+ import { type RecallSessionStats } from './hooks/recall.js';
4
+ import type { RebuildController } from './pipeline/rebuild.js';
5
+ import type { RuminateController } from './pipeline/ruminate.js';
6
+ import { type LiveSettingsHandle } from './settings.js';
7
+ import type { GraphStore } from './store/graph-store.js';
8
+ import type { L0Store } from './store/l0.js';
9
+ import type { L1Store } from './store/l1.js';
10
+ import type { PersonaStore } from './store/persona.js';
11
+ import type { SceneStore } from './store/scenes.js';
12
+ import type { SessionModeStore } from './store/session-modes.js';
13
+ import type { EmbeddingManager } from './store/embedding-source.js';
14
+ import type { StateStore } from './store/state.js';
15
+ import type { MemoryFamily, MemoryLogger } from './types.js';
16
+ export declare const PLUGIN_VERSION: string;
17
+ /** 运行态来源(index.ts 注入):避免 stats 撒谎字段。 */
18
+ export interface MemoryStatusSource {
19
+ /** 存储是否处于降级态(数据目录/检索库不可用)。 */
20
+ degraded(): boolean;
21
+ /** L1 抽取待重试的消息条数。 */
22
+ pending(): number;
23
+ }
24
+ /**
25
+ * 会话级统计数据源(悬浮卡信息区;index.ts 注入)。
26
+ * 硬规则:本端点按"打开期间 2~5s 轮询"设计,实现只允许内存注册表读取与
27
+ * 索引化 SQL 点查——禁止任何文件读/目录扫描(scenes.list()/persona.read()
28
+ * 级别的 I/O 会把每次轮询变成数十毫秒的全量读,见 slider-spec 数据策略节)。
29
+ */
30
+ export interface SessionInfoSource {
31
+ /** 召回统计(recall.ts 注册表;未发生检索的会话返回 undefined)。 */
32
+ recallStats(sessionId: string): RecallSessionStats | undefined;
33
+ /** 记忆上下文占用账本(context-occupancy 唯一权威实例;未注入过的会话返回 null)。 */
34
+ memoryOccupancy(sessionId: string): MemoryOccupancy | null;
35
+ /** 稳定区份额估算(旧会话回填用;缺省 = 装配未提供,回填隐藏)。 */
36
+ profileEstimate?(sessionId: string): number;
37
+ /** 召回份额回填(live surface 现扫,miss 读盘上日志;缺省 = null)。 */
38
+ recallEstimate?(sessionId: string): Promise<number | null> | number | null;
39
+ /** 蒸馏管线会话视图(runner:攒批进度/挂起切片/会话产出)。 */
40
+ runnerView(sessionId: string, mode: string): {
41
+ pendingSlice: number;
42
+ parkedSlices: number;
43
+ threshold: number | null;
44
+ producedRecords: number;
45
+ lastDistillAt: number | null;
46
+ };
47
+ /** L0 该会话已捕获消息数(索引 COUNT)。 */
48
+ l0Count(sessionId: string): Promise<number>;
49
+ /** 检索能力位(hybrid / keyword 降级判定)。 */
50
+ capabilities(): {
51
+ ftsSearch: boolean;
52
+ vectorSearch: boolean;
53
+ };
54
+ }
55
+ import type { MemoryOccupancy } from './contract.js';
56
+ export type { MemoryStats } from './contract.js';
57
+ /** 注册状态 RPC(web 侧 connection 服务可选,缺失时跳过,不影响插件主体)。 */
58
+ /** registerMemoryRpc 形参中需要落入端点 deps 的部分。 */
59
+ interface MemoryRpcSources {
60
+ status?: MemoryStatusSource;
61
+ live?: LiveSettingsHandle;
62
+ modes?: SessionModeStore;
63
+ dataDir?: string;
64
+ rebuild?: RebuildController;
65
+ embedManager?: EmbeddingManager;
66
+ sessionInfo?: SessionInfoSource;
67
+ }
68
+ /** 端点 deps 的注入面(ctx/cfg/stores/logger 由调用方绑定,其余由此处决定)。 */
69
+ export type EndpointDepsInput = Omit<EndpointDeps, 'ctx' | 'cfg' | 'stores' | 'logger'>;
70
+ /**
71
+ * 组装端点 deps。抽成函数是为了让"哪个控制器落入哪个字段"成为可测接缝:
72
+ * 反刍端点读 deps.ruminate,若此处漏注入,端点会静默恒返 {supported:false}(面板整块不渲染)。
73
+ * @param controller - 反刍控制器;由 index.ts 在存储可用时装配,降级时为 undefined。
74
+ */
75
+ export declare function buildEndpointDeps(base: Pick<EndpointDeps, 'ctx' | 'cfg' | 'stores' | 'logger'>, sources: MemoryRpcSources, controller: RuminateController | undefined): EndpointDeps;
76
+ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, stores: {
77
+ l0: L0Store;
78
+ l1: L1Store;
79
+ scenes: Record<MemoryFamily, SceneStore>;
80
+ persona: Record<MemoryFamily, PersonaStore>;
81
+ state: StateStore;
82
+ /** 图谱存储(可选:未装配时图谱端点返空,不报错)。 */
83
+ graph?: GraphStore;
84
+ }, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager, sessionInfo?: SessionInfoSource,
85
+ /** 反刍控制器(存储降级时为 undefined):经 buildEndpointDeps 落入 deps.ruminate。 */
86
+ ruminate?: RuminateController): void;
87
+ export interface EndpointDeps {
88
+ ctx: Context;
89
+ cfg: MemoryConfig;
90
+ stores: {
91
+ l0: L0Store;
92
+ l1: L1Store;
93
+ scenes: Record<MemoryFamily, SceneStore>;
94
+ persona: Record<MemoryFamily, PersonaStore>;
95
+ state: StateStore;
96
+ graph?: GraphStore;
97
+ };
98
+ status?: MemoryStatusSource;
99
+ live?: LiveSettingsHandle;
100
+ modes?: SessionModeStore;
101
+ dataDir: string;
102
+ logger: MemoryLogger;
103
+ rebuild?: RebuildController;
104
+ ruminate?: RuminateController;
105
+ embedManager?: EmbeddingManager;
106
+ sessionInfo?: SessionInfoSource;
107
+ }
108
+ /** 端点分发表(导出供测试直调:可精确注入 rebuild/ruminate 等可选控制器,验证 deps 接线)。 */
109
+ export declare function handleEndpoint(endpoint: string, payload: unknown, deps: EndpointDeps): Promise<unknown>;