dsh-prime-memory 0.26.0 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.en.md CHANGED
@@ -7,6 +7,26 @@
7
7
 
8
8
  This file covers the **0.12.0** release notes and the current **unreleased** changes in English. For the full history, see [CHANGELOG.md](./CHANGELOG.md) (Chinese).
9
9
 
10
+ ## [0.26.1] — 2026-10-15
11
+
12
+ ### Added
13
+
14
+ - **Backfill panel: auto-resumed progress + scan progress + honest summary.** The three backfill endpoints are trigger-plus-status hybrids, so a bare call while idle **starts a run**. New request body `BackfillTriggerRequest { probe?: boolean }` — `probe:true` returns the current snapshot and never starts anything; `BackfillSection` probes all three on mount and re-attaches polling if one is running (leaving and returning to the overview no longer needs a manual re-click). `BackfillProgress` gains `scanned/scanTotal` ("scanning 1252/1252 rows…"), and a 0-candidate scan now reports "no records missing keywords, nothing to backfill" instead of "0 updated", which read as a failure.
15
+ - **Cancel switch for the three batch actions (bounded hard-stop).** The contract gains `cancel?: boolean` and `BackfillProgress.cancelled`; each module exports `cancelXBackfill()`. Cancellation is **bounded**: after the signal a one-shot grace timer fires `runAbort.abort()` to cut in-flight requests. The hard bound is clamped by `backfill.cancelGraceMs` (schema max 30_000, default 15s) — no YAML value can exceed it.
16
+ - **Skill tool-group seal `toolSkills`.** The four `memory_skill_*` tools now go through the host seal mechanism (`reg('skills', …)`, ANDed with the `skillsEnabled` feature switch); both hot-apply on `loader/volatile-update` (toggling in the panel registers/unregisters immediately, no restart). Previously they gated once at registration, sat unconditionally in the model tool list once enabled, and did not hot-update on toggle.
17
+
18
+ ### Changed
19
+
20
+ - **All LLM batch sites unified to one-record-at-a-time serial.** The remaining three merged batches — wing (`labelWingOne`/`tagOne`), `pipeline/relabel.ts`, `room-review.ts` (`roomCandidateOne`) — were converted; all five batch sites now send exactly one memory per request and await it before the next (`concurrency` defaults to 1). Rationale: a single 35B record measures ~2.6s, while a multi-record batch loses the whole batch to one truncation or empty output.
21
+ - **Reshape exclusion now keyed by a version stamp.** `dsh_reshape_ver` (=2) with `isReshapedAtCurrentVersion(meta, content)` — the stamp is written **only on a successful write-back**; a record left unchanged gets **no marker** and stays retryable next run (the legacy boolean `dsh_reshape_tried` is read only for legacy compatibility). Bumping the schema version re-runs earlier outputs automatically.
22
+ - **`backfill.maxRecords` defaults to 300** (was wrongly set to 40, so a real 251-record target only advanced 40 per run).
23
+
24
+ ### Fixed
25
+
26
+ - **Bare-call polling hot loop (the real cause of "it doesn't start").** The UI tick called the trigger+status hybrid endpoints bare, so while idle it started a new run every 3s and the panel stayed on "scanning". The tick now sends `{ probe: true }`.
27
+ - **Batch scans returning empty output: thinking models used as direct-answer models.** `LlmCallOptions` gains a **call-level `reasoningEffort` override** and all five batch labelers pass `'off'`. With the route effort at `""` (auto) a thinking model defaulted to thinking on, the budget went to reasoning and the body came back at 0 characters (156 of 162 real failures were `local-9b` empty output).
28
+ - **`client/src/ui/MarkdownBody.tsx` typecheck failure.** The four `string | undefined` regex captures brought in with 0.26.0 failed under `noUncheckedIndexedAccess`; added non-null assertions.
29
+
10
30
  ## [0.26.0] — 2026-10-14
11
31
 
12
32
  ### Added
package/CHANGELOG.ja.md CHANGED
@@ -9,6 +9,26 @@
9
9
 
10
10
  本ファイルは **0.12.0** リリースノートと現在の**未リリース**変更の日本語版です。全履歴は [CHANGELOG.md](./CHANGELOG.md)(中文)を参照してください。
11
11
 
12
+ ## [0.26.1] — 2026-10-15
13
+
14
+ ### 追加
15
+
16
+ - **回填パネル「進行の自動復帰 + スキャン進捗 + 正直な要約」**:3 つの回填エンドポイントは「トリガー + 状態照会」の二合一で、アイドル時の裸呼び出しは**本当に走り出す**。新リクエストボディ `BackfillTriggerRequest { probe?: boolean }`——`probe:true` は現スナップショットのみ返し絶対に起動しない。`BackfillSection` はマウント時に resume で 3 つを偵察し、走っていればポーリングを再接続(概要区を離れて戻っても手動クリック不要)。`BackfillProgress` に `scanned/scanTotal` を追加(「ストック 1252/1252 行をスキャン中…」);候補 0 のときは「キーワード欠落の記録なし、回填不要」のような**正直な要約**を返す(「成功 0 件」が失敗と読まれる問題を解消)。
17
+ - **3 つのバッチ操作に「中止」スイッチ(有界強制停止)**:契約に `cancel?: boolean` と `BackfillProgress.cancelled` を追加;各モジュールが `cancelXBackfill()`。中止は**有界**:信号後に一度きりの猶予タイマーが発火し `runAbort.abort()` で在飛リクエストを即断。ハード境界は `backfill.cancelGraceMs`(schema max 30_000、既定 15s)でクランプ——YAML に何を書いても越えられない。
18
+ - **スキルツール群の封印 `toolSkills`**:`memory_skill_*` の四件を宿主封印機構(`reg('skills', …)`)へ統合し、機能スイッチ `skillsEnabled` と AND。両方とも `loader/volatile-update` で**ホット反映**(パネルの切り替えが即時登録/解除、再起動不要)。従来は登録時に一度だけ判定し、有効化後は無条件にモデルのツール一覧へ常駐、切り替えてもホット更新されなかった。
19
+
20
+ ### 変更
21
+
22
+ - **全 LLM バッチを「逐条直列」に統一**:残る 3 つの合併バッチ——wing(`labelWingOne`/`tagOne`)、`pipeline/relabel.ts`、`room-review.ts`(`roomCandidateOne`)——を変換;5 つのバッチ地点すべてが「1 リクエストに 1 件のみ、完了してから次」(`concurrency` 既定 1)。根拠:単条 35B は実測 ~2.6s、一方で複数条バッチは一度の截断/空出力でバッチ全体を失う。
23
+ - **整形の除外判定を「バージョン刻印」へ**:`dsh_reshape_ver`(=2)+ `isReshapedAtCurrentVersion(meta, content)`——刻印は**書き戻し成功時のみ**打つ;閘を通らなかった記録は原文のまま**マークなし**で次回も再試行可能(旧ブール `dsh_reshape_tried` はストック互換の読み取りのみ)。整形ルール更新時にバージョンを上げれば旧産物を自動再実行。
24
+ - **`backfill.maxRecords` 既定 300**(以前誤って 40 にしており、実機 251 件の対象が一回 40 件しか進まなかった)。
25
+
26
+ ### 修正
27
+
28
+ - **ポーリング裸呼び出しの死循環(「開始しない」の真因)**:UI の tick が「トリガー + 状態照会」二合一エンドポイントを裸で呼び、アイドル時に 3 秒ごとに新一轮を開始、パネルが「スキャン中」のまま。tick を `{ probe: true }` に変更。
29
+ - **バッチスキャンの全空出力の真因:思考モデルを直答モデルとして使用**:`LlmCallOptions` に**呼び出し級 `reasoningEffort` オーバーライド**を追加し、5 つのバッチラベラが `'off'` を明示。従来は経路の effort が `""`(自動)だと思考モデルが既定で思考オン、予算を reasoning に食われ本文 0 文字(実機 162 回の失敗中 156 回が `local-9b` の空出力)。
30
+ - **`client/src/ui/MarkdownBody.tsx` の typecheck 赤**:0.26.0 が持ち込んだ 4 箇所の `string | undefined`(正規表現キャプチャ)が `noUncheckedIndexedAccess` でエラー、非 null アサーションを追加。
31
+
12
32
  ## [0.26.0] — 2026-10-14
13
33
 
14
34
  ### 追加
package/CHANGELOG.ko.md CHANGED
@@ -9,6 +9,26 @@
9
9
 
10
10
  이 파일은 **0.12.0** 릴리스 노트와 현재 **미출시** 변경 사항의 한국어판입니다. 전체 이력은 [CHANGELOG.md](./CHANGELOG.md)(中文)를 참조하세요.
11
11
 
12
+ ## [0.26.1] — 2026-10-15
13
+
14
+ ### 추가
15
+
16
+ - **백필 패널 「진행 자동 복원 + 스캔 진행 + 정직한 요약」**: 3개 백필 엔드포인트는 「트리거 + 상태 조회」 이합일(二合一)이라, 유휴 시 베어 호출은 **실제로 한 회차를 시작**한다. 새 요청 바디 `BackfillTriggerRequest { probe?: boolean }`——`probe:true`는 현재 스냅샷만 반환하고 절대 시작하지 않는다. `BackfillSection`은 마운트 시 resume로 3개를 탐지해 돌고 있으면 폴링을 재연결(개요 구역을 떠났다 돌아와도 수동 재클릭 불필요). `BackfillProgress`에 `scanned/scanTotal` 추가(「스톡 1252/1252 행 스캔 중…」); 후보 0이면 「키워드가 없는 기록이 없어 백필 불필요」 같은 **정직한 요약**을 반환(「성공 0건」이 실패로 읽히던 문제 해소).
17
+ - **3개 배치 동작에 「중지」 스위치(유계 강제 정지)**: 계약에 `cancel?: boolean`과 `BackfillProgress.cancelled` 추가; 각 모듈이 `cancelXBackfill()` 제공. 중지는 **유계**: 신호 후 일회성 유예 타이머가 `runAbort.abort()`로 비행 중 요청을 즉시 절단. 하드 경계는 `backfill.cancelGraceMs`(schema max 30_000, 기본 15s)로 클램프——YAML에 무엇을 써도 넘을 수 없다.
18
+ - **스킬 도구군 봉인 `toolSkills`**: `memory_skill_*` 네 개를 호스트 봉인 메커니즘(`reg('skills', …)`)에 통합하고 기능 스위치 `skillsEnabled`와 AND. 둘 다 `loader/volatile-update`에서 **핫 반영**(패널 토글이 즉시 등록/해제, 재시작 불필요). 이전에는 등록 시 한 번만 판정했고, 활성화 후에는 무조건 모델 도구 목록에 상주했으며 토글해도 핫 갱신되지 않았다.
19
+
20
+ ### 변경
21
+
22
+ - **모든 LLM 배치를 「건별 직렬」로 통일**: 남은 3개 병합 배치——wing(`labelWingOne`/`tagOne`), `pipeline/relabel.ts`, `room-review.ts`(`roomCandidateOne`)——를 변환; 5개 배치 지점 모두 「요청당 정확히 1건, 완료 후 다음」(`concurrency` 기본 1). 근거: 단건 35B 실측 ~2.6s, 반면 다건 배치는 한 번의 절단/빈 출력으로 배치 전체를 잃는다.
23
+ - **재정형 제외 판정을 「버전 각인」으로**: `dsh_reshape_ver`(=2) + `isReshapedAtCurrentVersion(meta, content)`——각인은 **쓰기 성공 시에만** 찍는다; 게이트를 못 넘은 기록은 원문 그대로 **마커 없음**으로 다음 회차에도 재시도 가능(구 불리언 `dsh_reshape_tried`는 레거시 호환 읽기 전용). 재정형 규칙 갱신 시 버전을 올리면 이전 산출물을 자동 재실행.
24
+ - **`backfill.maxRecords` 기본 300**(이전에 잘못 40으로 설정되어 실기 251건 대상이 한 회차에 40건만 진행).
25
+
26
+ ### 수정
27
+
28
+ - **폴링 베어 호출 무한 루프(「시작하지 않음」의 진짜 원인)**: UI tick이 「트리거 + 상태 조회」 이합일 엔드포인트를 베어로 호출해 유휴 시 3초마다 새 회차를 시작, 패널이 「스캔 중」에 머묾. tick을 `{ probe: true }`로 변경.
29
+ - **배치 스캔 전부 빈 출력의 진짜 원인: 사고 모델을 직답 모델로 사용**: `LlmCallOptions`에 **호출급 `reasoningEffort` 오버라이드** 추가, 5개 배치 라벨러가 `'off'`를 명시. 이전에는 경로 effort가 `""`(자동)이면 사고 모델이 기본으로 사고 ON, 예산을 reasoning이 먹어 본문 0자(실기 162회 실패 중 156회가 `local-9b` 빈 출력).
30
+ - **`client/src/ui/MarkdownBody.tsx` typecheck 적색**: 0.26.0이 들여온 4곳의 `string | undefined`(정규식 캡처)가 `noUncheckedIndexedAccess`에서 오류, 비-null 단언 추가.
31
+
12
32
  ## [0.26.0] — 2026-10-14
13
33
 
14
34
  ### 신규
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # 更新日志(Changelog)
2
2
 
3
+ ## 0.26.2 (2026-10-18)
4
+
5
+ - hosts: 追加 0.2.1-rc.2 声明面枚举(34/34 @deepseek-ai/* 宿主 peer 逐包 npm 实测 rc.2 在版;本插件不触 ctx.web provider 与 conversation.input.model.label 槽,代码零改动,纯元数据发版;农场 rc.2 格挂账待命)
6
+
3
7
  本文件记录 dsh-prime-memory(0.5.0 前名为 dsh-memory-plugin)的显著变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
8
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
5
9
 
@@ -18,6 +22,26 @@
18
22
  - **撤下浮动球 UI(`client/src/longtask-fab.ts`;beta.1 起曾随包发布)**:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——2026-10-02 用户裁定撤下。撤下不违座位契约(settings.section 恰一个 + 输入栏 pill,body 单例不占座位),但全应用级 overlay 会与其他插件的悬浮 UI 抢占屏幕角落;长任务开关与手动压缩入口待长任务定版时落记忆设置分节。
19
23
  - **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
20
24
 
25
+ ## [0.26.1] — 2026-10-15
26
+
27
+ ### 新增
28
+
29
+ - **回填面板「自动恢复进度 + 扫描进度 + 诚实摘要」**:三个回填端点同时是「触发器 + 状态查询」,空闲时裸调用会**真的开跑**。新增请求体 `BackfillTriggerRequest { probe?: boolean }`——`probe:true` 只回当前快照、绝不启动;`BackfillSection` 挂载时以 resume 探测三个端点,在跑就接回轮询(离开概览区再回来不再需要手动重点)。`BackfillProgress` 加 `scanned/scanTotal`,扫描段显示「正在扫描存量 1252/1252 行…」;候选 0 时报「没有缺关键词的记录,无需回填」等**诚实摘要**(不再报“成功 0 条”被读成失败)。
30
+ - **三个批量动作「中止」开关(有界强杀)**:契约加 `cancel?: boolean`、`BackfillProgress.cancelled`;三个模块各带 `cancelXBackfill()`。中止**有界**:置信号后起一次性宽限表,到期 `runAbort.abort()` 当场捻断在飞请求;硬界由 `backfill.cancelGraceMs`(schema max 30_000、默认 15s)钳住——无论 YAML 怎么写都越不过。
31
+ - **技能工具组封印 `toolSkills`**:`memory_skill_*` 四件并入宿主封印机制(`reg('skills', …)`),与功能总开关 `skillsEnabled` 取 AND;两者都随 `loader/volatile-update` **热更**(面板拨开关即时注册/反注册,无需重启)。此前技能工具只在注册期判一次,开启后无条件常驻模型工具列表,且拨开关不热更。
32
+
33
+ ### 变更
34
+
35
+ - **全部 LLM 批处理统一「逐条串行」**:补齐 wing(`labelWingOne`/`tagOne`)、`pipeline/relabel.ts`、`room-review.ts`(`roomCandidateOne`)三处合并批——至此 5 个批处理站点全部「一个请求只装一条记忆、跑完一条再发下一条」(`concurrency` 默认 1)。依据:单条 35B 实测 ~2.6s,而一批多条一旦截断/空输出就带走整批。
36
+ - **整形排除判据改为「版本戳」**:`dsh_reshape_ver`(=2)+ `isReshapedAtCurrentVersion(meta, content)`——**仅成功写回**时盖戳;未过闸留原文**不打标记**、下轮仍可重试(旧布尔 `dsh_reshape_tried` 只作存量兼容读取)。整形规则升级时递增版本号即自动重跑旧产物。
37
+ - **`backfill.maxRecords` 默认 300**(此前误设 40,导致真机 251 条目标一次只动 40)。
38
+
39
+ ### 修复
40
+
41
+ - **轮询裸调用死循环(“不开始”真因)**:UI 的 tick 对「触发器 + 状态查询」二合一端点用裸调用 → 空闲时每 3s 又开一轮,面板永远停在“正在扫描”。tick 改 `{ probe: true }`。
42
+ - **批扫全部空输出真因:把思考模型当直答模型用**:`LlmCallOptions` 加**调用级 `reasoningEffort` 覆盖**,五个批扫标注器显式传 `'off'`——此前路由档位为 `""`(自动)时思考模型默认开思考,预算被 reasoning 吃光、正文 0 字符(真机 162 次失败中 156 次是 `local-9b` 空输出)。
43
+ - **`client/src/ui/MarkdownBody.tsx` typecheck 红**:0.26.0 带入的 4 处 `string | undefined`(正则捕获组)在 `noUncheckedIndexedAccess` 下报错,加非空断言。
44
+
21
45
  ## [0.26.0] — 2026-10-14
22
46
 
23
47
  ### 新增
package/README.de.md CHANGED
@@ -61,7 +61,7 @@ alt="DeepSeek Harness Hero-Banner: Unterhaltungen werden automatisch zu mehrschi
61
61
  > Kompatibilitätsmechanismus: Die Settings-Registrierung durchläuft zur Laufzeit drei Zweige
62
62
  > (`register` → `installSection`-Brücke → Dauerein-Notbetrieb), siehe Eintrag 0.11.0 im
63
63
  > [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` deklariert
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Schnellstart
67
67
 
package/README.en.md CHANGED
@@ -61,7 +61,7 @@
61
61
  > Compatibility mechanism: settings registration uses a three-way runtime branch
62
62
  > (`register` → `installSection` bridge → always-on degradation); see the 0.11.0 entry
63
63
  > in [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` declares
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Getting Started
67
67
 
package/README.es.md CHANGED
@@ -61,7 +61,7 @@ alt="Banner hero de DeepSeek Harness: las conversaciones se destilan automática
61
61
  > Mecanismo de compatibilidad: el registro de ajustes pasa en tiempo de ejecución por tres ramas
62
62
  > (`register` → puente `installSection` → degradación a siempre activado),
63
63
  > ver la entrada 0.11.0 del [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` declara
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Inicio rápido
67
67
 
package/README.fr.md CHANGED
@@ -61,7 +61,7 @@ alt="Bannière hero de DeepSeek Harness : les conversations sont automatiquement
61
61
  > Mécanisme de compatibilité : l'enregistrement settings passe par trois branches à l'exécution
62
62
  > (`register` → pont `installSection` → dégradation en mode toujours actif),
63
63
  > voir l'entrée 0.11.0 du [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` déclare
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Démarrage rapide
67
67
 
package/README.it.md CHANGED
@@ -61,7 +61,7 @@ alt="Banner hero di DeepSeek Harness: le conversazioni vengono distillate automa
61
61
  > Meccanismo di compatibilità: la registrazione delle impostazioni passa per tre rami a runtime
62
62
  > (`register` → ponte `installSection` → degrado a sempre-attivo),
63
63
  > vedi la voce 0.11.0 del [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` dichiara
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Avvio rapido
67
67
 
package/README.ja.md CHANGED
@@ -62,7 +62,7 @@ alt="DeepSeek Harness ヒーロー画像:会話がバックグラウンドで
62
62
 
63
63
  > 互換メカニズム:settings 登録は 3 分岐のランタイム判定(`register` → `installSection` ブリッジ → 常時オン縮退)。
64
64
  > 詳細は [CHANGELOG.md](./CHANGELOG.md) の 0.11.0 エントリ参照。`dsh.plugin.json` は
65
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"` を宣言。
65
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc) を宣言。
66
66
 
67
67
  ## クイックスタート
68
68
 
package/README.ko.md CHANGED
@@ -62,7 +62,7 @@ alt="DeepSeek Harness 히어로 이미지: 대화가 백그라운드에서 계
62
62
 
63
63
  > 호환 메커니즘: settings 등록은 3분기 런타임 분기(`register` → `installSection` 브리지 → 상시 온 강등).
64
64
  > 자세한 내용은 [CHANGELOG.md](./CHANGELOG.md)의 0.11.0 항목 참조. `dsh.plugin.json`은
65
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"` 선언.
65
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc) 선언.
66
66
 
67
67
  ## 빠른 시작
68
68
 
package/README.md CHANGED
@@ -60,7 +60,7 @@ alt="DeepSeek Harness hero 横幅:对话自动分层蒸馏成记忆,模型
60
60
 
61
61
  > 兼容机制:settings 注册走运行时三分支(`register` → `installSection` 桥接 → 恒开降级),
62
62
  > 详见 [CHANGELOG.md](./CHANGELOG.md) 的 0.11.0 条目。`dsh.plugin.json` 声明
63
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`。
63
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc)。
64
64
 
65
65
  ## 快速开始
66
66
 
package/README.ru.md CHANGED
@@ -61,7 +61,7 @@ alt="Баннер hero DeepSeek Harness: диалоги автоматическ
61
61
  > Механизм совместимости: регистрация настроек на этапе исполнения идёт по трём ветвям
62
62
  > (`register` → мост `installSection` → деградация в режим «всегда включено»),
63
63
  > подробнее — запись 0.11.0 в [CHANGELOG.md](./CHANGELOG.md). `dsh.plugin.json` объявляет
64
- > `engines.dsh: ">=0.2.0-rc.1 <0.2.1-0"`.
64
+ > `engines.dsh` 为逐 rc 精确枚举串(0.1.0-rc.2 → 0.2.1-rc.2,17 rc).
65
65
 
66
66
  ## Быстрый старт
67
67
 
package/dist/client.js CHANGED
@@ -3708,6 +3708,10 @@ var __defProp = Object.defineProperty;
3708
3708
  const [rdProgress, setRdProgress] = (0, import_react16.useState)(null);
3709
3709
  const [rdCounts, setRdCounts] = (0, import_react16.useState)(null);
3710
3710
  const [err, setErr] = (0, import_react16.useState)(null);
3711
+ const [wingAborting, setWingAborting] = (0, import_react16.useState)(false);
3712
+ const [kwAborting, setKwAborting] = (0, import_react16.useState)(false);
3713
+ const [rdAborting, setRdAborting] = (0, import_react16.useState)(false);
3714
+ const wingStopRef = (0, import_react16.useRef)(false);
3711
3715
  const timersRef = (0, import_react16.useRef)([]);
3712
3716
  (0, import_react16.useEffect)(
3713
3717
  () => () => {
@@ -3726,9 +3730,13 @@ var __defProp = Object.defineProperty;
3726
3730
  }, [refreshOverview]);
3727
3731
  ensureThemeStyle();
3728
3732
  const POLL_CAP = 400;
3729
- const fmtProgress = (p, verb, counts) => {
3733
+ const fmtProgress = (p, verb, counts, aborting = false) => {
3734
+ if (aborting) return `${verb}:正在中止——先等在跑的那条收工,超时即强行中断(已写回的保留)…`;
3730
3735
  if (!p) return `${verb}中…`;
3731
- if (p.scanning || p.candidates === 0) return `${verb}中:正在扫描存量、筛出需${verb}的记录…`;
3736
+ if (p.scanning) {
3737
+ return p.scanTotal > 0 ? `${verb}中:正在扫描存量 ${p.scanned}/${p.scanTotal} 行,筛出需${verb}的记录…` : `${verb}中:正在扫描活跃存量(已扫 ${p.scanned} 行),筛出需${verb}的记录…`;
3738
+ }
3739
+ if (p.candidates === 0) return `${verb}中:扫描完成,没有需要${verb}的记录…`;
3732
3740
  const parts = [
3733
3741
  `${verb}中:真目标 ${p.candidates} 条`,
3734
3742
  `已完成 ${p.processed} 条`,
@@ -3739,15 +3747,18 @@ var __defProp = Object.defineProperty;
3739
3747
  const outcome = counts ? `(写回 ${counts.updated ?? 0}${counts.skipped !== void 0 ? ` / 留原文 ${counts.skipped}` : ""})` : "";
3740
3748
  return parts.join(" · ") + outcome;
3741
3749
  };
3742
- const tail = (p) => p && p.deferred > 0 ? `,本次未跑完、剩 ${p.deferred} 条待再点一次` : "";
3750
+ const tail = (p) => p && p.cancelled ? `,已中止、剩 ${p.deferred} 条待再点一次` : p && p.deferred > 0 ? `,本次未跑完、剩 ${p.deferred} 条待再点一次` : "";
3743
3751
  const wingBackfill = (mode = "start") => {
3744
3752
  if (wingBusy) return;
3745
3753
  if (mode === "start" && (!overview || overview.unlabeled === 0)) return;
3746
3754
  if (mode === "start") setWingBusy(true);
3747
3755
  setErr(null);
3756
+ wingStopRef.current = false;
3757
+ setWingAborting(false);
3748
3758
  let polls = 0;
3749
3759
  const finish = () => setWingBusy(false);
3750
3760
  const tick = () => {
3761
+ if (wingStopRef.current) return;
3751
3762
  polls++;
3752
3763
  rpc("dsh-memory/wing-overview", {}).then((r) => {
3753
3764
  const v = r && r.ok ? r.value : null;
@@ -3781,6 +3792,7 @@ var __defProp = Object.defineProperty;
3781
3792
  setKwSummary(null);
3782
3793
  setKwProgress(null);
3783
3794
  setKwCounts(null);
3795
+ setKwAborting(false);
3784
3796
  }
3785
3797
  setErr(null);
3786
3798
  let polls = 0;
@@ -3788,7 +3800,12 @@ var __defProp = Object.defineProperty;
3788
3800
  const finish = () => setKwBusy(false);
3789
3801
  const showResult = (v) => {
3790
3802
  const fail = v.failed ?? 0;
3791
- setKwSummary(`关键词回填完成:成功 ${v.updated ?? 0} 条${fail > 0 ? `,失败 ${fail} 条` : ""}${tail(last)}`);
3803
+ if (last && !last.cancelled && last.candidates === 0) {
3804
+ setKwSummary("关键词回填:没有缺关键词的记录,无需回填");
3805
+ return;
3806
+ }
3807
+ const head = last?.cancelled ? "关键词回填已中止" : "关键词回填完成";
3808
+ setKwSummary(`${head}:成功 ${v.updated ?? 0} 条${fail > 0 ? `,失败 ${fail} 条` : ""}${tail(last)}`);
3792
3809
  };
3793
3810
  const failMsg = (m) => {
3794
3811
  setErr(m);
@@ -3796,7 +3813,7 @@ var __defProp = Object.defineProperty;
3796
3813
  };
3797
3814
  const tick = () => {
3798
3815
  polls++;
3799
- rpc("dsh-memory/keyword-backfill", {}).then((r) => {
3816
+ rpc("dsh-memory/keyword-backfill", { probe: true }).then((r) => {
3800
3817
  const v = r && r.ok ? r.value : null;
3801
3818
  if (!v) return failMsg("关键词回填失败");
3802
3819
  if (v.progress) {
@@ -3856,13 +3873,19 @@ var __defProp = Object.defineProperty;
3856
3873
  setRdSummary(null);
3857
3874
  setRdProgress(null);
3858
3875
  setRdCounts(null);
3876
+ setRdAborting(false);
3859
3877
  }
3860
3878
  setErr(null);
3861
3879
  let polls = 0;
3862
3880
  let last = null;
3863
3881
  const finish = () => setRdBusy(false);
3864
3882
  const showResult = (v) => {
3865
- setRdSummary(`整形完成:写回 ${v.updated ?? 0} 条,留原文 ${v.skipped ?? 0} 条${(v.failed ?? 0) > 0 ? `,失败 ${v.failed} 条` : ""}${tail(last)}`);
3883
+ if (last && !last.cancelled && last.candidates === 0) {
3884
+ setRdSummary("重蒸馏整形:没有需要整形的记录(骨架都已齐备),无需处理");
3885
+ return;
3886
+ }
3887
+ const head = last?.cancelled ? "整形已中止" : "整形完成";
3888
+ setRdSummary(`${head}:写回 ${v.updated ?? 0} 条,留原文 ${v.skipped ?? 0} 条${(v.failed ?? 0) > 0 ? `,失败 ${v.failed} 条` : ""}${tail(last)}`);
3866
3889
  };
3867
3890
  const failMsg = (m) => {
3868
3891
  setErr(m);
@@ -3870,7 +3893,7 @@ var __defProp = Object.defineProperty;
3870
3893
  };
3871
3894
  const tick = () => {
3872
3895
  polls++;
3873
- rpc("dsh-memory/redistill-backfill", {}).then((r) => {
3896
+ rpc("dsh-memory/redistill-backfill", { probe: true }).then((r) => {
3874
3897
  const v = r && r.ok ? r.value : null;
3875
3898
  if (!v) return failMsg("重蒸馏整形失败");
3876
3899
  if (v.progress) {
@@ -3923,6 +3946,28 @@ var __defProp = Object.defineProperty;
3923
3946
  failMsg("重蒸馏整形失败:" + String(e && e.message || e));
3924
3947
  });
3925
3948
  };
3949
+ const cancelWing = () => {
3950
+ wingStopRef.current = true;
3951
+ setWingAborting(true);
3952
+ setWingBusy(false);
3953
+ rpc("dsh-memory/wing-backfill", { cancel: true }).catch((e) => {
3954
+ setErr("中止请求失败:" + String(e && e.message || e));
3955
+ });
3956
+ };
3957
+ const cancelKw = () => {
3958
+ setKwAborting(true);
3959
+ rpc("dsh-memory/keyword-backfill", { cancel: true }).catch((e) => {
3960
+ setKwAborting(false);
3961
+ setErr("中止请求失败:" + String(e && e.message || e));
3962
+ });
3963
+ };
3964
+ const cancelRd = () => {
3965
+ setRdAborting(true);
3966
+ rpc("dsh-memory/redistill-backfill", { cancel: true }).catch((e) => {
3967
+ setRdAborting(false);
3968
+ setErr("中止请求失败:" + String(e && e.message || e));
3969
+ });
3970
+ };
3926
3971
  (0, import_react16.useEffect)(() => {
3927
3972
  keywordBackfill("resume");
3928
3973
  redistillBackfill("resume");
@@ -3939,24 +3984,24 @@ var __defProp = Object.defineProperty;
3939
3984
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "dsh-mem-rb-card", style: { padding: "4px 14px", marginTop: 12 }, children: [
3940
3985
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { fontWeight: 600, padding: "8px 0 2px" }, children: "存量回填与整形" }),
3941
3986
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: rowStyle, children: [
3942
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: overview ? `对存量未打标记忆批量补打 wing 标签 · 未打标 ${overview.unlabeled} 条` : "未打标计数加载中…" }),
3987
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: wingAborting ? "回填:正在中止…" : overview ? `对存量未打标记忆批量补打 wing 标签 · 未打标 ${overview.unlabeled} 条` : "未打标计数加载中…" }),
3943
3988
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3944
3989
  NButton,
3945
3990
  {
3946
- disabled: !overview || overview.unlabeled === 0 || wingBusy,
3991
+ disabled: !wingBusy && (!overview || overview.unlabeled === 0),
3947
3992
  style: { color: "var(--dsh-mem-accent)" },
3948
- onClick: () => wingBackfill(),
3949
- children: wingBusy ? "回填中…" : "wing 回填"
3993
+ onClick: () => wingBusy ? cancelWing() : wingBackfill(),
3994
+ children: wingBusy ? "中止" : "wing 回填"
3950
3995
  }
3951
3996
  )
3952
3997
  ] }),
3953
3998
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: rowStyle, children: [
3954
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: kwBusy ? fmtProgress(kwProgress, "回填", kwCounts ?? void 0) : kwSummary || "为缺 dsh_keywords 的存量记忆补检索关键词,喂 FTS 关键词轴(需先开关键词回填开关)" }),
3955
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NButton, { disabled: kwBusy, style: { color: "var(--dsh-mem-accent)" }, onClick: () => keywordBackfill(), children: kwBusy ? "回填中…" : "关键词回填" })
3999
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: kwBusy ? fmtProgress(kwProgress, "回填", kwCounts ?? void 0, kwAborting) : kwSummary || "为缺 dsh_keywords 的存量记忆补检索关键词,喂 FTS 关键词轴(需先开关键词回填开关)" }),
4000
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NButton, { disabled: kwAborting, style: { color: "var(--dsh-mem-accent)" }, onClick: () => kwBusy ? cancelKw() : keywordBackfill(), children: kwBusy ? kwAborting ? "中止中…" : "中止" : "关键词回填" })
3956
4001
  ] }),
3957
4002
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: rowStyle, children: [
3958
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: rdBusy ? fmtProgress(rdProgress, "整形", rdCounts ?? void 0) : rdSummary || "把“平”的存量记忆按类型重排成分节骨架,顺带产关键词/情景;只有严格更优才写回(需先开重蒸馏整形开关)" }),
3959
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NButton, { disabled: rdBusy, style: { color: "var(--dsh-mem-accent)" }, onClick: () => redistillBackfill(), children: rdBusy ? "整形中…" : "重蒸馏整形" })
4003
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: hintStyle, children: rdBusy ? fmtProgress(rdProgress, "整形", rdCounts ?? void 0, rdAborting) : rdSummary || "把“平”的存量记忆按类型重排成分节骨架,顺带产关键词/情景;只有严格更优才写回(需先开重蒸馏整形开关)" }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NButton, { disabled: rdAborting, style: { color: "var(--dsh-mem-accent)" }, onClick: () => rdBusy ? cancelRd() : redistillBackfill(), children: rdBusy ? rdAborting ? "中止中…" : "中止" : "重蒸馏整形" })
3960
4005
  ] }),
3961
4006
  err ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "6px 0", fontSize: 12, color: "var(--dsh-mem-danger)" }, children: err }) : null
3962
4007
  ] });
@@ -4059,6 +4104,7 @@ var __defProp = Object.defineProperty;
4059
4104
  const toolRuminate = settingsData && settingsData.settings ? settingsData.settings.toolRuminate !== false : true;
4060
4105
  const toolConflict = settingsData && settingsData.settings ? settingsData.settings.toolConflict !== false : true;
4061
4106
  const toolMutate = settingsData && settingsData.settings ? settingsData.settings.toolMutate !== false : true;
4107
+ const toolSkills = settingsData && settingsData.settings ? settingsData.settings.toolSkills !== false : true;
4062
4108
  const skillsEnabled = settingsData && settingsData.settings ? settingsData.settings.skillsEnabled === true : false;
4063
4109
  const keywordEnabled = settingsData && settingsData.settings ? settingsData.settings.keywordEnabled === true : false;
4064
4110
  const redistillEnabled = settingsData && settingsData.settings ? settingsData.settings.redistillEnabled === true : false;
@@ -4132,7 +4178,7 @@ var __defProp = Object.defineProperty;
4132
4178
  SwitchRow,
4133
4179
  {
4134
4180
  label: "技能(程序性记忆)",
4135
- desc: skillsEnabled ? "已开启:重复工作方法会结晶为技能候选,注入活跃技能,可在「技能」页审阅晋升" : "默认关闭;开启后注册 memory_skill_* 工具与注入(新装工具需重启后完全生效)",
4181
+ desc: skillsEnabled ? "已开启:重复工作方法会结晶为技能候选,注入活跃技能,可在「技能」页审阅晋升" : "默认关闭;开启后注册 memory_skill_* 工具与注入(开关即时生效)",
4136
4182
  checked: skillsEnabled,
4137
4183
  onChange: (v) => {
4138
4184
  toggle("skillsEnabled", v);
@@ -4195,6 +4241,17 @@ var __defProp = Object.defineProperty;
4195
4241
  }
4196
4242
  }
4197
4243
  ),
4244
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4245
+ SwitchRow,
4246
+ {
4247
+ label: "技能工具",
4248
+ desc: toolSkills ? "已注册:memory_skill_list / _read / _pin / _archive(需先开启「技能结晶」)" : "已封印:技能四件从模型工具列表移除(技能功能关闭时本就零表面积)",
4249
+ checked: toolSkills,
4250
+ onChange: (v) => {
4251
+ toggle("toolSkills", v);
4252
+ }
4253
+ }
4254
+ ),
4198
4255
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.panelLabel, children: "人工冲突裁决" }),
4199
4256
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4200
4257
  SwitchRow,
package/dist/config.d.ts CHANGED
@@ -148,6 +148,15 @@ export interface MemoryConfig {
148
148
  concurrency: number;
149
149
  /** 一次 LLM 调用(含整条回退链)的超时(ms),比对话蒸馏的 llm.timeoutMs 宽;按路由数留余量。 */
150
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;
151
160
  };
152
161
  /**
153
162
  * 内容路由(workspace 归属):按「这条记忆**谈到**哪个项目」给记录补 `workspace_id`。
package/dist/config.js CHANGED
@@ -103,6 +103,9 @@ export function liveSettingsSchema() {
103
103
  toolRuminate: Schema.boolean().default(true),
104
104
  toolConflict: Schema.boolean().default(true),
105
105
  toolMutate: Schema.boolean().default(true),
106
+ // 技能工具组封印:false = memory_skill_* 不注册。与功能总开关 skillsEnabled 取 AND——
107
+ // 功能关或本键 false 都不注册;两者都随 volatile 热更。
108
+ toolSkills: Schema.boolean().default(true),
106
109
  });
107
110
  // 探测后再标:`Schema.object(...).volatile()` 只在新版 schemastery 上有(格内实测:
108
111
  // cordis/schemastery 4.0.4 有、0.1.5-rc.3 解析到的 4.0.2 没有)。直接调用会让**整插件**
@@ -188,6 +191,9 @@ export const memorySchema = Schema.object({
188
191
  timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(1_800_000),
189
192
  concurrency: Schema.number().min(1).max(8).default(1),
190
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),
191
197
  }),
192
198
  /**
193
199
  * 内容路由:默认**开**(用户口径:谈到本仓/dsh 生态的记忆要归到本仓 workspace,
@@ -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 蒸馏 */
@@ -653,13 +655,16 @@ export interface RoomAdminResponse {
653
655
  retiredRecords?: number;
654
656
  }
655
657
  /**
656
- * 回填端点(wing/keyword/redistill)的请求体。三者都同时是「触发器 + 状态查询」:
657
- * - `probe` 缺省/false:空闲时**启动**一轮;已在跑则只回状态(单飞)。
658
+ * 回填端点(wing/keyword/redistill)的请求体。三者都同时是「触发器 + 状态 + 控制」:
659
+ * - 缺省/probe=false:空闲时**启动**一轮;已在跑则只回状态(单飞)。
658
660
  * - `probe: true`:**只读快照**,绝不启动——供 UI 离开区域再回来时恢复在途任务的进度
659
661
  * (否则计时器随组件卸载而清,回到页面只能看到卡住的最后一帧)。
662
+ * - `cancel: true`:**请求中止**本轮。不硬杀在飞请求:当前条跑完就收工,
663
+ * 已入账的写回全部保留,剩余计入 deferred 可再点一次。
660
664
  */
661
665
  export interface BackfillTriggerRequest {
662
666
  probe?: boolean;
667
+ cancel?: boolean;
663
668
  }
664
669
  /** dsh-memory/wing-backfill(一键回填,后台任务;端点立即返回,进度以 wing-overview 轮询)。 */
665
670
  export interface WingBackfillResponse {
@@ -684,6 +689,15 @@ export interface BackfillProgress {
684
689
  concurrency: number;
685
690
  /** 预算/上限用尽未处理、留下次的条数(跟“跑完了”区分开的关键一个数)。 */
686
691
  deferred: number;
692
+ /** 本轮被人工中止(cancel)——与“预算用尽”分开报,面板文案不同。 */
693
+ cancelled: boolean;
694
+ /**
695
+ * 扫描期已扫行数 / 总行数(`scanTotal=0` = 总数未知)。
696
+ * 为何要报:扫描段原本在面板上只是一个静态「正在扫描…」——存量上千行时
697
+ * 用户分不清“在跑”还是“卡死”(真机就因此把它当成“不开始”)。
698
+ */
699
+ scanned: number;
700
+ scanTotal: number;
687
701
  }
688
702
  /** dsh-memory/keyword-backfill(关键词蒸馏回填,后台任务;门控关时 enabled=false 不启动)。 */
689
703
  export interface KeywordBackfillResponse {
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 从快照消失、
@@ -48,7 +48,21 @@ export interface KeywordBackfillState {
48
48
  concurrency: number;
49
49
  /** 预算/上限用尽未处理、留下次的条数。 */
50
50
  deferred: number;
51
+ /** 本轮被人工中止(进端点 cancel)——与“预算用尽”分开报文案。 */
52
+ cancelled: boolean;
53
+ /**
54
+ * 扫描期已扫行数 / 总行数(`scanTotal=0` = 总数未知)。
55
+ * 为何要暴露:扫描段原本在面板上只是一个静态「正在扫描…」,存量上千行时
56
+ * 用户分不清“在跑”还是“卡死”——真机就是因为这个把它当成“不开始”。
57
+ */
58
+ scanned: number;
59
+ scanTotal: number;
51
60
  }
61
+ /** 请求中止本轮(空闲时无副作用);返回后中止是**有界**的:最多等 graceMs。 */
62
+ export declare function cancelKeywordBackfill(): {
63
+ cancelled: boolean;
64
+ running: boolean;
65
+ };
52
66
  export declare function keywordBackfillState(): KeywordBackfillState;
53
67
  /** 启动后台关键词回填;已在运行返回 false(单飞)。 */
54
68
  export declare function startKeywordBackfill(deps: KeywordBackfillDeps): {
@@ -72,4 +86,6 @@ export interface KeywordOneResult {
72
86
  * 而逐条只花秒级。记账(state.failed 等)全部归调用方——标注器不再动全局状态,
73
87
  * 否则同一条会被两处各计一次。
74
88
  */
75
- export declare function labelKeywordOne(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, rec: MemoryRecord, callTimeoutMs?: number): Promise<KeywordOneResult | null>;
89
+ export declare function labelKeywordOne(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, rec: MemoryRecord, callTimeoutMs?: number,
90
+ /** 本轮中止信号(可选):与单次超时**取或**,宽限到期后由它当场掐断请求。 */
91
+ externalSignal?: AbortSignal): Promise<KeywordOneResult | null>;