dsh-prime-memory 0.25.0 → 0.26.1

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/dist/config.d.ts CHANGED
@@ -123,12 +123,40 @@ export interface MemoryConfig {
123
123
  /** active 技能闲置多少天后自动归档(0 = 不做闲置归档)。 */
124
124
  idleArchiveDays: number;
125
125
  };
126
- /** wing 存量回填护栏(默认 300 条/120s;想一次多跑点可调大,代价是后台占用更久)。 */
126
+ /**
127
+ * 存量回填护栏(wing / keyword / redistill 共用,默认 300 条 / 30min 总预算 / 串行 / 单条请求 240s)。
128
+ *
129
+ * 形状约定(用户定):**按单元、串行、不合并**——一个请求只装一条记忆,跑完一条再发下一条。
130
+ * 实测单条 35B 只要 ~2.6s,而 20 条合并成一批要 200s+ 且一次空输出/截断就带走整批,
131
+ * 所以“批量”在这里不省时间只堆风险。`concurrency` 默认 1;拖高概览页滑块可临时多路在飞。
132
+ *
133
+ * 三个旋钮分工(别混淆):
134
+ * - `maxRecords` = **一轮最多处理多少条**(防一次点击拖过久;真实队列没跑完的计入 deferred,
135
+ * 再点一次继续)。注意它**不是**请求批大小:逐条形状下每个请求只装 1 条。
136
+ * - `timeBudgetMs` = **本轮总墙钟预算**(防跑飞)。与 maxRecords 谁先到谁生效:
137
+ * 默认 300 条 × ~2.6s ≈ 13min < 30min,正常由 maxRecords 先结算。
138
+ * - `callTimeoutMs` = **一次 `callLLM`(含整条回退链)**的超时。不能沿用 `llm.timeoutMs`(120s):
139
+ * 它是**整次调用**的预算而非每条路由各享一份——真机踩到主路由(9B)空输出吃 95s,
140
+ * 降到 35B 后只剩余量在线上被掐死(看似“35B 也跑不完”)。默认 240s 按「路由数 × 单路由耗时」留余量。
141
+ */
127
142
  backfill: {
128
143
  /** 单次回填处理条数上限。 */
129
144
  maxRecords: number;
130
- /** 单次回填墙钟预算(ms),超时剩留待再次触发。 */
145
+ /** 单次回填总墙钟预算(ms),超时剩留待再次触发。 */
131
146
  timeBudgetMs: number;
147
+ /** LLM 批调用的并行度(1 = 串行)。 */
148
+ concurrency: number;
149
+ /** 一次 LLM 调用(含整条回退链)的超时(ms),比对话蒸馏的 llm.timeoutMs 宽;按路由数留余量。 */
150
+ callTimeoutMs: number;
151
+ /**
152
+ * 中止宽限(ms):按下中止后先给在飞的那条这段时间自然收工(token 不白费),
153
+ * **到期强杀**在飞请求(abort),保证中止是**有界**的。
154
+ *
155
+ * 硬界(用户定):**中止必须 30s 内强停**。故运行时取值会被钳到 ≤30_000(上限),
156
+ * 与 schema 的 max 同口径;默认 15s——比 callTimeoutMs(240s) 小一个量级,
157
+ * 既不让“中止”变成“等四分钟”,又给快回路留出自然收工的机会。0 = 立即强杀。
158
+ */
159
+ cancelGraceMs: number;
132
160
  };
133
161
  /**
134
162
  * 内容路由(workspace 归属):按「这条记忆**谈到**哪个项目」给记录补 `workspace_id`。
@@ -205,6 +233,20 @@ export interface MemoryConfig {
205
233
  keyword: {
206
234
  enabled: boolean;
207
235
  };
236
+ /**
237
+ * 存量记忆重蒸馏整形(redistill):**独立回填批扫**,把活跃且 `scoreSkeleton` 判「骨架不齐备」
238
+ * 的 L1 正文按 G2 分类型骨架重排,并顺带产 `dsh_keywords` / `dsh_usage_scenario`。由
239
+ * `dsh-memory/redistill-backfill` 端点触发,镜像 keyword/wing 回填(单飞/后台/有界)。
240
+ *
241
+ * **默认关**:关时不整形、content/FTS/投射逐字不变(零漂移)。护栏复用 `backfill`。
242
+ * 与 keyword 回填的关键差异:本项**改 content**,写回走 `L1Store.appendNew`(追加 JSONL 事实源
243
+ * + 重嵌 + FTS + 投射点火),且入口要求 `vectorsReady()`(embed 未就绪时改正文会静默丢向量)。
244
+ * 只有整形后骨架分严格更优才写回,并盖整形版本戳(`dsh_reshape_ver`);未过闸的留原文**不打标记**,
245
+ * 下轮仍可再试——即只有「成功替换的」不再进入。
246
+ */
247
+ redistill: {
248
+ enabled: boolean;
249
+ };
208
250
  /**
209
251
  * 类型叙述模板(G2):抽取时按 7 类型各自的“前因后果”骨架写 content。
210
252
  *
package/dist/config.js CHANGED
@@ -80,6 +80,22 @@ export function liveSettingsSchema() {
80
80
  conflictFreeze: Schema.boolean().default(false),
81
81
  // 轨2 技能结晶总开关(看板可拨):默认 false。开=注册 memory_skill_* 工具 + 注入 active 技能 + skills-* 端点可用。
82
82
  skillsEnabled: Schema.boolean().default(false),
83
+ // 存量回填/进阶功能的启用开关(概览「存量回填与整形」区可拨):默认 false,
84
+ // 与部署 cfg.<x>.enabled 取或(经 effectiveCfg)——部署可强制开,live 只能追加开。
85
+ // 存在的理由:这些面都有对应按钮/端点,若开关只住 YAML,用户点了只会报“未开启”却没处翻。
86
+ keywordEnabled: Schema.boolean().default(false),
87
+ redistillEnabled: Schema.boolean().default(false),
88
+ mdSyncEnabled: Schema.boolean().default(false),
89
+ memportEnabled: Schema.boolean().default(false),
90
+ /**
91
+ * 存量回填(keyword / redistill)LLM 批调用的**运行时并行度**覆盖:
92
+ * `0` = 跟随部署 `cfg.backfill.concurrency`;1–8 = 直接覆盖。
93
+ *
94
+ * 为什么要是 live 而不是只住 YAML:真机出过“3 并发把每请求拖过超时线、13 批全 abort”,
95
+ * 排查时需要的恰好是“立刻降到 1 串行再看一遍”——为此改 YAML + 重启不现实。
96
+ * 概览页 0–8 滑块写的就是它(0 = 跟随部署,默认部署值是 1 串行)。
97
+ */
98
+ backfillConcurrency: Schema.number().min(0).max(8).default(0),
83
99
  // 工具分组封印(v0.21.0):false = 该组工具不注册,从模型工具列表消失。
84
100
  // 核心读(memory_search/read_scene/receipts/conversation_search)与槽位三件不受控。
85
101
  toolRoom: Schema.boolean().default(true),
@@ -87,6 +103,9 @@ export function liveSettingsSchema() {
87
103
  toolRuminate: Schema.boolean().default(true),
88
104
  toolConflict: Schema.boolean().default(true),
89
105
  toolMutate: Schema.boolean().default(true),
106
+ // 技能工具组封印:false = memory_skill_* 不注册。与功能总开关 skillsEnabled 取 AND——
107
+ // 功能关或本键 false 都不注册;两者都随 volatile 热更。
108
+ toolSkills: Schema.boolean().default(true),
90
109
  });
91
110
  // 探测后再标:`Schema.object(...).volatile()` 只在新版 schemastery 上有(格内实测:
92
111
  // cordis/schemastery 4.0.4 有、0.1.5-rc.3 解析到的 4.0.2 没有)。直接调用会让**整插件**
@@ -164,10 +183,17 @@ export const memorySchema = Schema.object({
164
183
  excerptChars: Schema.number().min(0).max(4000).default(240),
165
184
  idleArchiveDays: Schema.number().min(0).max(3650).default(90),
166
185
  }),
167
- // wing 回填护栏:默认与旧行为一致(300 条 / 120s),需一次多跑点时调大。
186
+ // wing/keyword/redistill 回填护栏。maxRecords 是“一轮处理多少条”,timeBudgetMs 是“本轮不跑飞”,
187
+ // callTimeoutMs 是“单次请求(含回退链)不撞线”;逐条形状下每请求只装 1 条(不是 20 条一批)。
188
+ // maxRecords 不得低于真实队列量级:真机活跃集里待整形真目标 251 条,砍到 40 就等于“点一次只动 40”。
168
189
  backfill: Schema.object({
169
190
  maxRecords: Schema.number().min(1).max(10_000).default(300),
170
- timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(120_000),
191
+ timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(1_800_000),
192
+ concurrency: Schema.number().min(1).max(8).default(1),
193
+ callTimeoutMs: Schema.number().min(1000).max(1_800_000).default(240_000),
194
+ // 中止宽限:到期强杀在飞请求——没有它,“中止”最坏要等满 callTimeoutMs(240s) 才生效。
195
+ // 硬界:中止必须 30s 内强停(运行时仍会再钳一次,见各批扫模块的 cancelGraceMs 钳位)。
196
+ cancelGraceMs: Schema.number().min(0).max(30_000).default(15_000),
171
197
  }),
172
198
  /**
173
199
  * 内容路由:默认**开**(用户口径:谈到本仓/dsh 生态的记忆要归到本仓 workspace,
@@ -230,6 +256,10 @@ export const memorySchema = Schema.object({
230
256
  keyword: Schema.object({
231
257
  enabled: Schema.boolean().default(false),
232
258
  }),
259
+ // 存量记忆重蒸馏整形:默认关(关时不整形、content/FTS/投射逐字不变);护栏复用 backfill。
260
+ redistill: Schema.object({
261
+ enabled: Schema.boolean().default(false),
262
+ }),
233
263
  // 类型叙述模板(G2):默认**开**;开时抽取 prompt 追加分类型 content 骨架(关时仍逐字不变)。
234
264
  // 默认值从关改开的理由写在上方接口注释:只给定义实测骨架齐备 0/23,
235
265
  // 而"没前因后果就不算可用"是用户定的判据,所以默认值要按可用性定而不是按保守定。
@@ -53,6 +53,8 @@ export interface MemoryLiveSettings {
53
53
  toolRuminate: boolean;
54
54
  toolConflict: boolean;
55
55
  toolMutate: boolean;
56
+ /** 技能工具组封印:false = memory_skill_* 从模型工具列表移除(技能功能开启时才相关;功能关时本就零表面积)。 */
57
+ toolSkills: boolean;
56
58
  /** L0 捕获(原始对话落盘) */
57
59
  capture: boolean;
58
60
  /** L1 抽取 + L2/L3 蒸馏 */
@@ -106,9 +108,20 @@ export interface MemoryLiveSettings {
106
108
  /** 轨2 技能结晶总开关(看板可拨):true = 注册 memory_skill_* 工具、注入 active 技能、skills-* 端点可用。
107
109
  * 默认 false(新功能默认关);与部署 cfg.skills.enabled 取或(任一开则生效)。 */
108
110
  skillsEnabled: boolean;
111
+ /** 关键词回填启用开关(概览区可拨):true = 放行 dsh-memory/keyword-backfill。与部署 cfg.keyword.enabled 取或。 */
112
+ keywordEnabled: boolean;
113
+ /** 重蒸馏整形启用开关(概览区可拨):true = 放行 dsh-memory/redistill-backfill。与部署 cfg.redistill.enabled 取或。 */
114
+ redistillEnabled: boolean;
115
+ /** md 反向同步启用开关(概览区可拨):true = 放行 md-sync 回灌。与部署 cfg.mdSync.enabled 取或。 */
116
+ mdSyncEnabled: boolean;
117
+ /** 外部记忆来源 Connect 启用开关(概览区可拨):true = 放行 memport-* 端点。与部署 cfg.memport.enabled 取或。 */
118
+ memportEnabled: boolean;
109
119
  /** md 投射/后缀收敛的单次动作护栏运行时覆盖:0 = 跟随部署 `cfg.memExport.maxRecords`(默认 2000)。
110
120
  * 存在的理由:一次点完多少条属于“回填历史时才改一下”的旋钮,不该要求改 YAML + 重启。 */
111
121
  mdExportMaxRecords: number;
122
+ /** 存量回填(keyword/redistill)LLM 批调用的运行时并行度覆盖:0 = 跟随部署
123
+ * `cfg.backfill.concurrency`(默认 2);1–8 = 直接覆盖。概览页 1–3 滑块写的就是它。 */
124
+ backfillConcurrency: number;
112
125
  }
113
126
  /** 召回停用原因(session-stats recall.enabled=false 时带出;短路序第一个为假的因子)。 */
114
127
  export type RecallDisabledReason = 'deploy' | 'global' | 'session' | 'mode';
@@ -641,12 +654,51 @@ export interface RoomAdminResponse {
641
654
  /** retire withRecords 实际退场的记录条数(软删,可恢复)。 */
642
655
  retiredRecords?: number;
643
656
  }
657
+ /**
658
+ * 回填端点(wing/keyword/redistill)的请求体。三者都同时是「触发器 + 状态 + 控制」:
659
+ * - 缺省/probe=false:空闲时**启动**一轮;已在跑则只回状态(单飞)。
660
+ * - `probe: true`:**只读快照**,绝不启动——供 UI 离开区域再回来时恢复在途任务的进度
661
+ * (否则计时器随组件卸载而清,回到页面只能看到卡住的最后一帧)。
662
+ * - `cancel: true`:**请求中止**本轮。不硬杀在飞请求:当前条跑完就收工,
663
+ * 已入账的写回全部保留,剩余计入 deferred 可再点一次。
664
+ */
665
+ export interface BackfillTriggerRequest {
666
+ probe?: boolean;
667
+ cancel?: boolean;
668
+ }
644
669
  /** dsh-memory/wing-backfill(一键回填,后台任务;端点立即返回,进度以 wing-overview 轮询)。 */
645
670
  export interface WingBackfillResponse {
646
671
  /** true = 本次触发启动了新任务;false = 已有任务在跑(单飞,不叠加)。 */
647
672
  started: boolean;
648
673
  running: boolean;
649
674
  }
675
+ /** 回填批扫的逐条进度(keyword/redistill 共用):后台任务一轮可达几分钟,没有它 UI 只能转圈圈。 */
676
+ export interface BackfillProgress {
677
+ /** 每个请求处理的条数(两个回填都已改成逐条 = 1;保留字段供未来批量形状复用)。 */
678
+ chunk: number;
679
+ /** 扫描阶段确定的**真目标**总数(扫描未完成时为已发现数)。 */
680
+ candidates: number;
681
+ /** 本次已完成(送过 LLM 且已提交结果)条数。 */
682
+ processed: number;
683
+ /** 仍在扫描阶段(此阶段报「扫描中」而不是「第 N 条」)。 */
684
+ scanning: boolean;
685
+ /** 已完成条数 / 本次要处理的总条数。 */
686
+ batchesDone: number;
687
+ batchTotal: number;
688
+ /** LLM 批调用并行度(cfg.backfill.concurrency)。 */
689
+ concurrency: number;
690
+ /** 预算/上限用尽未处理、留下次的条数(跟“跑完了”区分开的关键一个数)。 */
691
+ deferred: number;
692
+ /** 本轮被人工中止(cancel)——与“预算用尽”分开报,面板文案不同。 */
693
+ cancelled: boolean;
694
+ /**
695
+ * 扫描期已扫行数 / 总行数(`scanTotal=0` = 总数未知)。
696
+ * 为何要报:扫描段原本在面板上只是一个静态「正在扫描…」——存量上千行时
697
+ * 用户分不清“在跑”还是“卡死”(真机就因此把它当成“不开始”)。
698
+ */
699
+ scanned: number;
700
+ scanTotal: number;
701
+ }
650
702
  /** dsh-memory/keyword-backfill(关键词蒸馏回填,后台任务;门控关时 enabled=false 不启动)。 */
651
703
  export interface KeywordBackfillResponse {
652
704
  /** true = 本次触发启动了新任务;false = 已有任务在跑或门控关。 */
@@ -658,6 +710,24 @@ export interface KeywordBackfillResponse {
658
710
  updated: number;
659
711
  /** 失败/跳过数。 */
660
712
  failed: number;
713
+ /** 分批进度(运行期轮询显示用)。 */
714
+ progress: BackfillProgress;
715
+ }
716
+ /** dsh-memory/redistill-backfill(存量记忆重蒸馏整形,后台任务;门控关时 enabled=false 不启动)。 */
717
+ export interface RedistillBackfillResponse {
718
+ /** true = 本次触发启动了新任务;false = 已有任务在跑或门控关。 */
719
+ started: boolean;
720
+ running: boolean;
721
+ /** 门控 `cfg.redistill.enabled`;关时不启动。 */
722
+ enabled: boolean;
723
+ /** 本进程累计整形写回条数(重启归零;观测用)。 */
724
+ updated: number;
725
+ /** 质量闸判不写(留原文)条数。 */
726
+ skipped: number;
727
+ /** 失败/跳过数(LLM 失败、id 配不上、embed 掉线)。 */
728
+ failed: number;
729
+ /** 分批进度(运行期轮询显示用)。 */
730
+ progress: BackfillProgress;
661
731
  }
662
732
  /** dsh-memory/session-stats(悬浮卡信息区;热路径端点)。 */
663
733
  export interface SessionStatsRequest {
@@ -892,6 +962,38 @@ export interface UiRecord {
892
962
  */
893
963
  userRelevance?: number;
894
964
  }
965
+ /** dsh-memory/records-meta(按 ID 批量查元数据,供 .memory.md 查看器拼接 Qoder 风格条目)。 */
966
+ export interface RecordsMetaRequest {
967
+ /** L1 record_id 列表(上限 50)。 */
968
+ ids: string[];
969
+ }
970
+ /** 单条轻量元数据(含正文,供 .memory.md 查看器拼接头部条)。 */
971
+ export interface RecordMeta {
972
+ id: string;
973
+ /** 名称蒸馏产出的短标题;无 name 时为 null。 */
974
+ name: string | null;
975
+ type: string;
976
+ priority: number;
977
+ /** 正文全文(markdown)。 */
978
+ content: string;
979
+ maturity?: 'low' | 'mid' | 'high';
980
+ createdAt: string | null;
981
+ updatedAt: string | null;
982
+ scene: string;
983
+ scope: 'global' | 'workspace';
984
+ workspaceId: string | null;
985
+ /** 关键词(metadata.dsh_keywords)。 */
986
+ keywords?: string[];
987
+ /** 使用场景(metadata.dsh_usage_scenario)。 */
988
+ usageScenario?: string[];
989
+ /** 投射文件相对路径 `<type>/<stem>.memory.md`。 */
990
+ filePath: string;
991
+ /** 是否已退场(软删)。 */
992
+ retired: boolean;
993
+ }
994
+ export interface RecordsMetaResponse {
995
+ items: RecordMeta[];
996
+ }
895
997
  export interface ListRecordsResponse {
896
998
  items: UiRecord[];
897
999
  hasMore: boolean;
@@ -1661,12 +1763,14 @@ export interface DshMemoryRequestMap {
1661
1763
  'dsh-memory/rooms-export': RoomsExportRequest;
1662
1764
  'dsh-memory/room-register': RoomRegisterRequest;
1663
1765
  'dsh-memory/room-admin': RoomAdminRequest;
1664
- 'dsh-memory/wing-backfill': Record<string, never>;
1665
- 'dsh-memory/keyword-backfill': Record<string, never>;
1766
+ 'dsh-memory/wing-backfill': BackfillTriggerRequest;
1767
+ 'dsh-memory/keyword-backfill': BackfillTriggerRequest;
1768
+ 'dsh-memory/redistill-backfill': BackfillTriggerRequest;
1666
1769
  'dsh-memory/session-stats': SessionStatsRequest;
1667
1770
  'dsh-memory/settings-get': Record<string, never>;
1668
1771
  'dsh-memory/settings-set': SettingsSetRequest;
1669
1772
  'dsh-memory/list-records': ListRecordsRequest;
1773
+ 'dsh-memory/records-meta': RecordsMetaRequest;
1670
1774
  'dsh-memory/records-delete': RecordsDeleteRequest;
1671
1775
  'dsh-memory/receipts': ReceiptsRequest;
1672
1776
  'dsh-memory/conflicts': ConflictsRequest;
@@ -1726,10 +1830,12 @@ export interface DshMemoryResponseMap {
1726
1830
  'dsh-memory/room-admin': RoomAdminResponse;
1727
1831
  'dsh-memory/wing-backfill': WingBackfillResponse;
1728
1832
  'dsh-memory/keyword-backfill': KeywordBackfillResponse;
1833
+ 'dsh-memory/redistill-backfill': RedistillBackfillResponse;
1729
1834
  'dsh-memory/session-stats': SessionStatsResponse;
1730
1835
  'dsh-memory/settings-get': SettingsGetResponse;
1731
1836
  'dsh-memory/settings-set': SettingsSetResponse;
1732
1837
  'dsh-memory/list-records': ListRecordsResponse;
1838
+ 'dsh-memory/records-meta': RecordsMetaResponse;
1733
1839
  'dsh-memory/records-delete': RecordsDeleteResponse;
1734
1840
  'dsh-memory/receipts': ReceiptsResponse;
1735
1841
  'dsh-memory/conflicts': ConflictsResponse;
package/dist/index.js CHANGED
@@ -430,12 +430,12 @@ export async function apply(ctx, config) {
430
430
  stores.l1.setFileSyncHook(fileSync);
431
431
  const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir, recallUsage);
432
432
  runner.setAfterRun(recall.invalidateProfile);
433
- registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
433
+ const toolSeals = registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
434
434
  // 激活槽位(active slot):工具面 + 常驻注入 + 服务端投影(均走 ctx.effect,可撤销)
435
435
  registerSlotTools(ctx, config, stores.slots, logger, modes, live, stores.l1);
436
436
  const slotRecall = registerSlotRecall(ctx, config, stores.slots, logger, live);
437
- // 轨2 技能:工具面与常驻注入(均 cfg.skills.enabled 默认关时自 no-op,零表面积)。
438
- registerSkillTools(ctx, config, skillStore, logger, live);
437
+ // 轨2 技能:工具面经宿主封印机制(seals)登记,与常驻注入分离(默认关时自 no-op,零表面积)。
438
+ registerSkillTools(ctx, skillStore, logger, live, toolSeals);
439
439
  registerSkillRecall(ctx, config, skillStore, logger, live);
440
440
  // memorySlots 投影:常驻槽位(手动 pin 的约定)的展示契约,消费方是 dsh-brief-sidebar。
441
441
  // 宿主契约(dsh-session-projection/lib/index.js):插件不注册时 key 从快照消失、
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * 关键词蒸馏回填(keyword axis 产出方)。
3
3
  *
4
- * 职责:从 L1 记录中找「缺 dsh_keywords」的存量记录,按 chunk 20 条一批送 LLM 提炼关键词
4
+ * 职责:从 L1 记录中找「缺 dsh_keywords」的存量记录,**一条一个请求**送 LLM 提炼关键词
5
5
  * 与使用场景,归一后写 `metadata.dsh_keywords` + `metadata.dsh_usage_scenario`,
6
6
  * 并用 `patchMetadataAndReindex` 保证新关键词**立即进 FTS BM25 轴**。
7
7
  *
8
8
  * 安全边界(继承 wing-backfill):
9
9
  * - 单飞:同时最多一个回填任务,重复触发直接返回 running(不排队不叠加)
10
- * - 后台执行:端点立即返回,逐块推进(每块 20 条),不阻塞 RPC/管线
10
+ * - 后台执行:端点立即返回,**逐条推进、跑完一条再发下一条**,不阻塞 RPC/管线
11
11
  * - 失败可退:LLM 失败/输出不合法只跳过当前块,原记录零改动(标签只写成功的)
12
12
  * - 有界:单次任务最多处理 cfg.backfill.maxRecords 条,防止一次跑飞;其余可再次触发
13
13
  * - 门控关(`cfg.keyword.enabled = false`):run() 入口立即跳过——无回填、FTS 逐字不变
@@ -21,12 +21,48 @@ export interface KeywordBackfillDeps {
21
21
  cfg: MemoryConfig;
22
22
  backend: MemoryBackend;
23
23
  logger: MemoryLogger;
24
+ /**
25
+ * 运行时**生效并发**读取器(可选):给正在跑的任务即时改并发用——池子每完成一条重读一次。
26
+ * 缺省 = 启动时读一次 `cfg.backfill.concurrency`(旧行为)。
27
+ */
28
+ concurrency?: () => number | undefined;
24
29
  }
25
30
  export interface KeywordBackfillState {
26
31
  running: boolean;
27
32
  updated: number;
28
33
  failed: number;
34
+ /** 分批进度(UI 轮询据此显示「扫描中 / 第几条」,否则运行期只有一个「回填中…」): */
35
+ /** 每个请求处理的条数——已改逐条,永远为 1(与 redistill 同形状)。 */
36
+ chunk: number;
37
+ /** 扫描阶段确定的真目标总数(缺关键词;扫描未完成时为已发现数)。 */
38
+ candidates: number;
39
+ /** 本次已完成(送过 LLM 且已写回)**在请求完成时才自增**,否则“全失败”跟“全成功”看起来一样。 */
40
+ processed: number;
41
+ /** 仍在扫描阶段。 */
42
+ scanning: boolean;
43
+ /** 已完成条数(与 processed 同值)。 */
44
+ batchesDone: number;
45
+ /** 本次要处理的总条数。 */
46
+ batchTotal: number;
47
+ /** LLM 批调用并行度。 */
48
+ concurrency: number;
49
+ /** 预算/上限用尽未处理、留下次的条数。 */
50
+ deferred: number;
51
+ /** 本轮被人工中止(进端点 cancel)——与“预算用尽”分开报文案。 */
52
+ cancelled: boolean;
53
+ /**
54
+ * 扫描期已扫行数 / 总行数(`scanTotal=0` = 总数未知)。
55
+ * 为何要暴露:扫描段原本在面板上只是一个静态「正在扫描…」,存量上千行时
56
+ * 用户分不清“在跑”还是“卡死”——真机就是因为这个把它当成“不开始”。
57
+ */
58
+ scanned: number;
59
+ scanTotal: number;
29
60
  }
61
+ /** 请求中止本轮(空闲时无副作用);返回后中止是**有界**的:最多等 graceMs。 */
62
+ export declare function cancelKeywordBackfill(): {
63
+ cancelled: boolean;
64
+ running: boolean;
65
+ };
30
66
  export declare function keywordBackfillState(): KeywordBackfillState;
31
67
  /** 启动后台关键词回填;已在运行返回 false(单飞)。 */
32
68
  export declare function startKeywordBackfill(deps: KeywordBackfillDeps): {
@@ -38,9 +74,18 @@ export declare function startKeywordBackfill(deps: KeywordBackfillDeps): {
38
74
  * 门控关(`cfg.keyword.enabled = false`)时入口立即返回,不碰任何记录与 fts。
39
75
  */
40
76
  export declare function runKeywordBackfillOnce(deps: KeywordBackfillDeps): Promise<void>;
41
- /** 关键词标注器(导出供单测)。 */
42
- export declare function labelKeywordChunk(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, chunk: MemoryRecord[]): Promise<Array<{
43
- record: MemoryRecord;
77
+ /** 单条标注结果。 */
78
+ export interface KeywordOneResult {
44
79
  keywords: string[];
45
- usageScenario?: string[];
46
- }>>;
80
+ usageScenario: string[];
81
+ }
82
+ /**
83
+ * 标注**一条**记忆的关键词与情景(一个请求只装一条)。失败/无合法关键词返回 null。
84
+ *
85
+ * 旧名 `labelKeywordChunk`(多条一次)已废:合并请求下一次截断/空输出就整批丢弃,
86
+ * 而逐条只花秒级。记账(state.failed 等)全部归调用方——标注器不再动全局状态,
87
+ * 否则同一条会被两处各计一次。
88
+ */
89
+ export declare function labelKeywordOne(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, rec: MemoryRecord, callTimeoutMs?: number,
90
+ /** 本轮中止信号(可选):与单次超时**取或**,宽限到期后由它当场掐断请求。 */
91
+ externalSignal?: AbortSignal): Promise<KeywordOneResult | null>;