dsh-prime-memory 0.24.0 → 0.24.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 +31 -0
- package/CHANGELOG.ja.md +26 -0
- package/CHANGELOG.ko.md +26 -0
- package/CHANGELOG.md +31 -0
- package/dist/client.js +126 -7
- package/dist/config.d.ts +4 -1
- package/dist/config.js +10 -1
- package/dist/conflict-service.d.ts +3 -1
- package/dist/conflict-service.js +1 -0
- package/dist/contract.d.ts +37 -0
- package/dist/index.js +22 -0
- package/dist/pipeline/ruminate.d.ts +4 -1
- package/dist/pipeline/ruminate.js +19 -10
- package/dist/pipeline/runner.js +6 -1
- package/dist/settings.js +4 -0
- package/dist/stats.d.ts +6 -0
- package/dist/stats.js +40 -7
- package/dist/store/l1.d.ts +19 -0
- package/dist/store/l1.js +46 -2
- package/dist/store/memory-md-disable.d.ts +51 -0
- package/dist/store/memory-md-disable.js +207 -11
- package/dist/store/memory-md-export.js +28 -1
- package/dist/store/memory-md-run.d.ts +48 -0
- package/dist/store/memory-md-run.js +151 -0
- package/dist/store/memory-md-sync.d.ts +59 -0
- package/dist/store/memory-md-sync.js +152 -0
- package/dist/store/memory-md-types.d.ts +71 -2
- package/dist/store/memory-md.d.ts +9 -0
- package/dist/store/memory-md.js +11 -0
- package/dist/store/sqlite.js +24 -6
- package/dist/tools/index.js +3 -1
- package/dist/tools/slots.d.ts +2 -1
- package/dist/tools/slots.js +2 -1
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -7,6 +7,37 @@
|
|
|
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.24.2] — 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Projection identity fields in the records panel**: card headers now show "Name" and "Scope" badges, and the expanded detail adds three rows (Name / Keywords / Scope), so users can directly verify the "memory ↔ projection filename" mapping. The `UiRecord` contract (`contract.ts`) gains optional `name`/`scope`/`workspaceId`/`keywords`; `stats.ts::hitToUiRecord` reads them from the record and the `metadata.dsh_keywords` reserved key.
|
|
15
|
+
- **FTS keyword axis as a separate, reserved column (dormant)**: `l1_fts` is ordered `keywords → content`, so keywords form an independent BM25 axis rather than being diluted into body-text term frequency. No keyword-distillation producer exists yet ⇒ the column is always empty and retrieval behaves byte-for-byte as before (zero drift); when distillation ships, no schema change is needed (FTS5 cannot ALTER).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Hook-consolidation comment cleanup**: the retired factory name `makeSuffixSync` no longer appears anywhere in `src/` (acceptance grep = 0), leaving a single source of truth for the consolidation conclusion.
|
|
20
|
+
|
|
21
|
+
## [0.24.1] — 2026-10-14
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Suffix invariant for the projection (`convergeDisabledSuffix`)**: `.memory.md` vs `.memory.md.disable` is decided solely by "is this record retired in the library" — independent of `name` and of which path retired it. Every record is reconciled through three checks (file existence, current suffix, fingerprint match). A hand-made `.disable` is a file-side retirement proof (the library cannot see it while P2 is off), so it is only reported as `both`, never touched.
|
|
26
|
+
- **The library→file hook moved into `L1Store` (`store/memory-md-sync.ts`)**: `setFileSyncHook` fires at the four confluence points (`appendNew` / `retire` / `restore` / `rename`) instead of the eight call sites where event hooks used to hang — on a real machine 920 retired records had only 8 `.disable` files, all from the recall panel path. Machine-side retirements (merge supersede, conflict loss) and model-side `memory_add` now follow automatically, and new call sites cannot be forgotten.
|
|
27
|
+
- **Rename follows the file**: after `rename()` the projection moves to the new name (the `.disable` sibling travels with it) and the ledger key is rewritten; if the old path had no file the new name is written instead, and an occupied target is reported rather than overwritten.
|
|
28
|
+
- **Rolling projection with a progress bar (`store/memory-md-run.ts`, new endpoint `dsh-memory/mem-run-status`)**: one click pushes the export cursor to the end of the table and then the suffix cursor, with cross-round `maxPasses` / `totalBudgetMs` guards; if the budget runs out it says how many files remain instead of stopping silently. The panel reuses the ruminate/rebuild progress bar.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Per-click file-update budget**: `memExport.maxRecords` default 300 → **2000**, plus a runtime knob `live.mdExportMaxRecords` (0 = follow the deployment default, no restart). The panel's misleading "single-action guardrail" is renamed to **"per-click file update budget"**, counted in files, and echoed from the server's actually applied `appliedMaxRecords`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **The quota counted traversal, not disk actions** — unchanged entries consumed budget, so a second click rescanned the same first N; at 1210 records backfilling meant four clicks.
|
|
37
|
+
- **O(n²) ledger writes**: the converger flushed the ledger per rename (the ledger is one atomic JSON rewrite), so 300 records meant 300 full rewrites and the wall-clock budget split the job into several clicks. Now: mark dirty in the loop, flush once at the end.
|
|
38
|
+
- **Adjacent fires stepping on each other**: projection work is now serialized in the background. Previously each fire opened its own promise, so `appendNew` followed immediately by `retire` overlapped — the second task's converger saw "no such file" before the first one wrote it, and the retired record stayed on `.memory.md` until the next full export.
|
|
39
|
+
- **Permanent duplicates after revival**: the "both files exist" case for a live record only reported and never reconciled. The stale `.disable` is now removed only when all three hold: same ledger id, its bytes unchanged since we wrote it, and the `.memory.md` copy is exactly the current render.
|
|
40
|
+
|
|
10
41
|
## [0.24.0] — Unreleased (local line `0.24.0-local.0…3`, 2026-10-13)
|
|
11
42
|
|
|
12
43
|
### Added
|
package/CHANGELOG.ja.md
CHANGED
|
@@ -9,6 +9,32 @@
|
|
|
9
9
|
|
|
10
10
|
本ファイルは **0.12.0** リリースノートと現在の**未リリース**変更の日本語版です。全履歴は [CHANGELOG.md](./CHANGELOG.md)(中文)を参照してください。
|
|
11
11
|
|
|
12
|
+
## [0.24.2] — 2026-10-14
|
|
13
|
+
|
|
14
|
+
### 追加
|
|
15
|
+
|
|
16
|
+
- **レコードパネルの投射アイデンティティ項目**:カードヘッドに「名称」「スコープ」バッジを追加、展開詳細に3行(名称 / キーワード / スコープ)を追加し、「記憶↔投射ファイル名」の対応を直接確認できるようにした。`UiRecord` 契約(`contract.ts`)にオプションの `name`/`scope`/`workspaceId`/`keywords` を追加、`stats.ts::hitToUiRecord` がレコード本体と `metadata.dsh_keywords` 予約キーから読み出す。
|
|
17
|
+
- **FTS キーワード独立検索軸(予約・休止)**:`l1_fts` を「keywords → content」の2列インデックスに並べ、キーワードを本文の語頻と混ざらない独立 BM25 軸として予約。現状は蒸留の産出側がなく当該列は常に空 ⇒ 検索挙動は改修前と逐字同一(ゼロドリフト)、蒸留実装時は表構造の変更不要(FTS5 は ALTER 不可)。
|
|
18
|
+
|
|
19
|
+
### 変更
|
|
20
|
+
|
|
21
|
+
- **フック収束コメントの整理**:退役した旧ファクトリ名 `makeSuffixSync` のリテラルが `src/` から消え(検収 grep ゼロ)、収束結論は単一の事実源のみ。
|
|
22
|
+
|
|
23
|
+
## [0.24.1] — 2026-10-14
|
|
24
|
+
|
|
25
|
+
### 追加
|
|
26
|
+
- **接尾語の不変式収束(`convergeDisabledSuffix`)**:`.memory.md` / `.memory.md.disable` は「その記録がライブラリで退場済みか」だけで決まる。`name` とも、どの経路で退場させたかとも無関係。各件を「ファイル存在 + 現在の接尾語 + 指紋一致」の三条件で照合する。人が作った `.disable` はファイル側の退場証拠なので ``both`` と報告するだけ。
|
|
27
|
+
- **ライブラリ→ファイルの鈎子を `L1Store` に集約(`store/memory-md-sync.ts`)**:`appendNew` / `retire` / `restore` / `rename` の四つの合流点で `setFileSyncHook` を発火。これまで 8 か所の呼び出し点に散っていたイベント鈎子は実測 920 件中 8 件しか `.disable` を作っていなかった(リコールパネルの 1 経路のみ)。機械側の退場(統合置換・衝突敗北)もモデル側 `memory_add` も自動的に追随する。
|
|
28
|
+
- **リネーム追随**:`rename()` 後に投射ファイルを新名へ移動(`.disable` も一緒に)、台帳キーも差し替え。旧パスに無い場合は新名で書き出し、移動先が埋まっていれば上書きせず報告のみ。
|
|
29
|
+
- **長タスクの滚动実行と進捗バー(`store/memory-md-run.ts`、新エンドポイント `dsh-memory/mem-run-status`)**:1 クリックでエクスポート游標を表の末尾まで、続けて収束游標を表の末尾まで。総限度 `maxPasses` / `totalBudgetMs` を超えた分は `notice` に正直に残す。
|
|
30
|
+
### 変更
|
|
31
|
+
- **1 クリックあたりのファイル更新予算**:`memExport.maxRecords` 既定 300 → **2000**、実行時つまみ `live.mdExportMaxRecords` を追加(0 = デプロイ既定、再起動不要)。パネルの「単次動作护栏」は **「単次ファイル更新予算」** に改称、単位は「個ファイル/回」、表示はサーバー側の `appliedMaxRecords` を採用。
|
|
32
|
+
### 修正
|
|
33
|
+
- 予算が「走査件数」を数えていた(未変も枠を消費)→ 1210 件の復元は 4 クリック必要だった。
|
|
34
|
+
- 台帳の逐件 flush = O(n²)(300 件 = 全文 JSON 再書込 300 回)→ ループ中は dirty フラグのみ、末尾で 1 回 flush。
|
|
35
|
+
- 立て続けの 2 回の発火が互いに踏み合う → 投射作業を裏で**直列化**。従来は「`appendNew` 直後の `retire`」で第二タスクの収束器が「ファイル無し」と判定し、退場記録が `.memory.md` に残っていた。
|
|
36
|
+
- 復権後に二重化が恒久化 → 「同一 id の台帳 + `.disable` が書き込み後未編集 + `.memory.md` が現在のレンダリングそのもの」の 3 条件が揃った時だけ古い証憑を削除。
|
|
37
|
+
|
|
12
38
|
## [0.24.0] — 未リリース(ローカルライン `0.24.0-local.0…3`、2026-10-13)
|
|
13
39
|
|
|
14
40
|
### 追加
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -9,6 +9,32 @@
|
|
|
9
9
|
|
|
10
10
|
이 파일은 **0.12.0** 릴리스 노트와 현재 **미출시** 변경 사항의 한국어판입니다. 전체 이력은 [CHANGELOG.md](./CHANGELOG.md)(中文)를 참조하세요.
|
|
11
11
|
|
|
12
|
+
## [0.24.2] — 2026-10-14
|
|
13
|
+
|
|
14
|
+
### 신규
|
|
15
|
+
|
|
16
|
+
- **레코드 패널의 투사 정체성 필드**: 카드 헤더에 "이름" "범위" 배지를, 전개 상세에 3행(이름 / 키워드 / 범위)을 추가해 "기억↔투사 파일명" 대응을 직접 확인할 수 있게 했습니다. `UiRecord` 계약(`contract.ts`)에 선택 필드 `name`/`scope`/`workspaceId`/`keywords`를 추가하고, `stats.ts::hitToUiRecord`가 레코드 본체와 `metadata.dsh_keywords` 예약 키에서 읽어냅니다.
|
|
17
|
+
- **FTS 키워드 독립 검색 축(예약·휴면)**: `l1_fts`를 "keywords → content" 2열 인덱스로 배치해 키워드를 본문 어휘 빈도와 섞이지 않는 독립 BM25 축으로 예약했습니다. 현재 증류 생산자가 없어 해당 열은 항상 비어 ⇒ 검색 동작은 변경 전과 글자 그대로 동일(영 드리프트), 증류 도입 시 테이블 구조 변경 불필요(FTS5는 ALTER 불가).
|
|
18
|
+
|
|
19
|
+
### 변경
|
|
20
|
+
|
|
21
|
+
- **훅 수렴 주석 정리**: 퇴역한 구 팩토리명 `makeSuffixSync` 리터럴이 `src/`에서 사라져(검수 grep 0), 수렴 결론은 단일 사실 소스만 남습니다.
|
|
22
|
+
|
|
23
|
+
## [0.24.1] — 2026-10-14
|
|
24
|
+
|
|
25
|
+
### 추가
|
|
26
|
+
- **투영 접미사 불변식 수렴(`convergeDisabledSuffix`)**: `.memory.md` / `.memory.md.disable` 는 "이 기록이 라이브러리에서 퇴장했는지"로만 결정된다. `name` 과도, 어떤 경로로 퇴장시켰 과도 무관. 각 항목을 "파일 존재 + 현재 접미사 + 지문 일치" 세 조건으로 맞춘다. 사람이 만든 `.disable` 는 파일 측 퇴장 증표이므로 ``both`` 로 보고만 하고 손대지 않는다.
|
|
27
|
+
- **라이브러리→파일 고리를 `L1Store` 에 수구(`store/memory-md-sync.ts`)**: `appendNew` / `retire` / `restore` / `rename` 네 합류점에서 `setFileSyncHook` 을 점화한다. 이전처럼 8 개 호출점에 흩어진 이벤트 고리는 실측 920 건 퇴장 중 8 건만 `.disable` 를 만들었다(리콜 패널 한 경로). 기계 측 퇴장(병합 치환, 충돌 패배)과 모델 `memory_add` 도 자동으로-follow 한다.
|
|
28
|
+
- **이름 바꾸기 추적**: `rename()` 이후 투영 파일을 새 이름으로 옮기고(`.disable` 도 함께) 대장 키를 갈아치운다. 옛 경로에 파일이 없으면 새 이름으로 쓰고, 목적지가 이미 쓰여 있으면 덮어쓰지 않고 보고만 한다.
|
|
29
|
+
- **장시간 작업의 굴림 실행과 진행 표시(`store/memory-md-run.ts`, 새 엔드포인트 `dsh-memory/mem-run-status`)**: 한 번의 클릭으로 내보내기 커서를 표 끝까지, 이어서 수렴 커서를 표 끝까지 밀고, 가로막는 종합 한도(`maxPasses` / `totalBudgetMs`) 때문에 남은 분량은 `notice` 에 그대로 남긴다.
|
|
30
|
+
### 변경
|
|
31
|
+
- **1 회 클릭 파일 갱신 예산**: `memExport.maxRecords` 기본값 300 → **2000**, 실행 중 돌리기 `live.mdExportMaxRecords` 추가(0 = 배포 기본값, 재시작 불요). 패널의 이름 맞지 않던 "단차 동작 가드레일" 을 **"단차 파일 갱신 예산"** 으로, 단위를 "개 파일/회" 로, 표시는 서버가 실제로 쓴 `appliedMaxRecords` 를 쓴다.
|
|
32
|
+
### 수정
|
|
33
|
+
- 예산이 "순회 건수"를 셌다(변화 없음도 할당 소비) → 1210 건 복원에 클릭 4 회가 필요했다.
|
|
34
|
+
- 항목별 대장 flush = O(n²)(300 건 = 통째 JSON 재작성 300 회) → 루프에서는 더티 표시만, 마무리에서 1 회 flush.
|
|
35
|
+
- 겹쳐 점화한 두 작업이 서로 밟았다 → 투영 작업을 뒷에서 **직렬화**. 종래에는 `appendNew` 직후 `retire` 에서 두 번째 수렴기가 "파일이 없다" 고 판해 퇴장 기록이 `.memory.md` 에 남았다.
|
|
36
|
+
- 복권 후 영구 이중화 → "대장 동일 id + `.disable` 가 기록 후 손대지 않음 + `.memory.md` 가 현재 렌더와 동일" 세 조건이 갖춰질 때만 오래된 증표를 지운다.
|
|
37
|
+
|
|
12
38
|
## [0.24.0] — 미출시(로컬 라인 `0.24.0-local.0…3`, 2026-10-13)
|
|
13
39
|
|
|
14
40
|
### 새로 추가
|
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,37 @@
|
|
|
17
17
|
- **建议状态(`longtask-hint-get`)以纯端点提供**:判定 = 上下文占用达 `contextThresholdPct` 或 todo 漂移达 `driftThreshold`(部署配置可调);配合 `longtask-compress-tail` 作手动压缩入口。
|
|
18
18
|
- **撤下浮动球 UI(`client/src/longtask-fab.ts`;beta.1 起曾随包发布)**:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——2026-10-02 用户裁定撤下。撤下不违座位契约(settings.section 恰一个 + 输入栏 pill,body 单例不占座位),但全应用级 overlay 会与其他插件的悬浮 UI 抢占屏幕角落;长任务开关与手动压缩入口待长任务定版时落记忆设置分节。
|
|
19
19
|
- **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
|
|
20
|
+
## [0.24.2] — 2026-10-14
|
|
21
|
+
|
|
22
|
+
### 新增
|
|
23
|
+
|
|
24
|
+
- **检索面板投射身份字段**:RecordsTab 卡头新增「名称」「作用范围」徽标,展开详情新增三行(名称 / 关键词 / 作用范围),帮助用户直观核对「记忆↔投射文件名」的对应关系。UiRecord 契约(`contract.ts`)加 `name`/`scope`/`workspaceId`/`keywords` 四个可选字段,`stats.ts::hitToUiRecord` 从记录本体和 `metadata.dsh_keywords` 保留键读出。
|
|
25
|
+
- **FTS 关键词独立检索轴(预留、休眠)**:`l1_fts` 排列改为「keywords → content」两列索引,关键词作为独立 BM25 命中轴、不与正文词频混淆;当前无关键词蒸馏产出方 ⇒ 该列恒空、检索行为与改动前逐字相同(零漂移),将来蒸馏上线无需再改表(FTS5 不能 ALTER)。
|
|
26
|
+
|
|
27
|
+
### 变更
|
|
28
|
+
|
|
29
|
+
- **挂钩收口注释清理**:`src/` 内不再残留旧工厂名 `makeSuffixSync` 字面量(验收 grep 归零),收口结论只留一处事实源。
|
|
30
|
+
|
|
31
|
+
## [0.24.1] — 2026-10-14
|
|
32
|
+
|
|
33
|
+
### 新增
|
|
34
|
+
|
|
35
|
+
- **投射后缀不变式收敛(`convergeDisabledSuffix`)**:`.memory.md` / `.memory.md.disable` 只由「这条记录在库里是否退场」决定,与 `name` 无关、与「是哪条路径把它退场的」也无关。逐条按「文件存在性 + 当前后缀 + 指纹一致性」三项检查对齐;人手造的 `.disable` 是文件侧退场凭证(P2 关着时库里不知道),只报 `both` 不动盘。
|
|
36
|
+
- **库→文件挂钩收口到 `L1Store`(`store/memory-md-sync.ts`)**:`appendNew` / `retire` / `restore` / `rename` 四个汇流口注入 `setFileSyncHook`,取代此前散布在 8 个调用点的事件挂钩——真机 920 条退场记录只有 8 个 `.disable`,全部来自召回面板那一条路径。机器侧自动退场(合并取代、冲突判负)与模型 `memory_add` 从此自动跟随,新增调用点不会漏。
|
|
37
|
+
- **改名跟随**:`rename()` 之后投射文件搬到新名(`.disable` 兄弟一起走)、账本跟着换键;旧路径本就没这份时按新名补写,目标已被占用时只报不覆盖。
|
|
38
|
+
- **投影长任务滚动执行 + 进度条(`store/memory-md-run.ts`、新端点 `dsh-memory/mem-run-status`)**:一次点击自己把导出游标推到表尾、再把后缀收敛游标推到表尾(跨轮另有 `maxPasses` / `totalBudgetMs` 两道总闸),滚不完如实写进 `notice` 而不是停在半路;面板复用反刍/重建那根进度条。
|
|
39
|
+
|
|
40
|
+
### 变更
|
|
41
|
+
|
|
42
|
+
- **单次文件更新预算**:`memExport.maxRecords` 默认 300 → **2000**,并新增运行时旋钮 `live.mdExportMaxRecords`(0 = 跟随部署默认,改完不必重启)。面板上名不副实的「单次动作护栏」改成 **「单次文件更新预算」**,单位说成「个文件/次」,结果行回显取自服务端**实际生效**的 `appliedMaxRecords`。
|
|
43
|
+
|
|
44
|
+
### 修复
|
|
45
|
+
|
|
46
|
+
- **护栏数的是「遍历」而不是「碰盘动作」**:判为「未变」的条也吃配额,第二次点击扫的还是同一批前 N 条——装机量级 1210 条意味着回填要点 4 次。
|
|
47
|
+
- **批量落盘 O(n²)**:收敛器原本每条改名都 `ledger.flush()`(账本是整本 JSON 的原子重写),300 条 = 300 次整本重写,被墙钟 `timeBudgetMs` 拦成好几次点击。现统一「循环里只记脏、收尾落一次」。
|
|
48
|
+
- **紧邻两次点火互踩**:投射联动改为**后台串行**。旧写法每次点火自己开一个 promise,于是「先 `appendNew` 再 `retire`」会重叠——第二个任务的收敛器在第一个还没写出文件时判「盘上没这份」(`absent`),退场记录就永久停在 `.memory.md` 上,直到下次全量导出才被抬回来。
|
|
49
|
+
- **恢复活跃后永久双份**:有效记录的「两份都在」格子原先只报账不动盘。现只在「账本同 id + `.disable` 自写下后未被手改 + `.memory.md` 那份正是当前渲染」三条同时成立时删掉那份陈年凭证,任一不成立仍交人裁决。
|
|
50
|
+
|
|
20
51
|
## [0.24.0] — 未发布(本地线 `0.24.0-local.0…3`,2026-10-13)
|
|
21
52
|
|
|
22
53
|
### 新增
|
package/dist/client.js
CHANGED
|
@@ -1675,8 +1675,13 @@ var __defProp = Object.defineProperty;
|
|
|
1675
1675
|
const head = `已写 ${rep.written} · 未变 ${rep.unchanged} · 跳过 ${rep.skipped}`;
|
|
1676
1676
|
const named = rep.named > 0 ? ` · 新起名 ${rep.named} 条` : "";
|
|
1677
1677
|
const nameBad = rep.nameFailed > 0 ? ` · 起名失败 ${rep.nameFailed} 条(下轮重试)` : "";
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1678
|
+
const s = rep.suffix;
|
|
1679
|
+
const suffix = s && s.renamed + s.revived + s.both + s.edited > 0 ? ` · 后缀对齐 改名 ${s.renamed}${s.edited > 0 ? `(人改过 ${s.edited})` : ""} 改回 ${s.revived}${s.both > 0 ? ` · 冲突 ${s.both}` : ""}` : "";
|
|
1680
|
+
const suffixMore = s && s.capped && s.remaining > 0 ? ` · 后缀还有 ${s.remaining} 个文件待对齐,再点一次继续` : "";
|
|
1681
|
+
const tail = rep.remaining > 0 ? ` · 剩余 ${rep.remaining} 个文件,再点一次继续` : "";
|
|
1682
|
+
const budget = typeof rep.appliedMaxRecords === "number" && rep.appliedMaxRecords > 0 ? ` · 本次预算 ${rep.appliedMaxRecords} 个文件` : "";
|
|
1683
|
+
const passes = typeof rep.passes === "number" && rep.passes > 1 ? ` · 共滚 ${rep.passes} 轮` : "";
|
|
1684
|
+
return head + named + nameBad + suffix + suffixMore + budget + passes + tail;
|
|
1680
1685
|
}
|
|
1681
1686
|
function lineOfReconcile(rep) {
|
|
1682
1687
|
const parts = [
|
|
@@ -1723,6 +1728,54 @@ var __defProp = Object.defineProperty;
|
|
|
1723
1728
|
const [orphanArmed, setOrphanArmed] = (0, import_react7.useState)(false);
|
|
1724
1729
|
const [armed, setArmed] = (0, import_react7.useState)(false);
|
|
1725
1730
|
const [error, setError] = (0, import_react7.useState)(void 0);
|
|
1731
|
+
const [budgetDraft, setBudgetDraft] = (0, import_react7.useState)("");
|
|
1732
|
+
const [budgetNote, setBudgetNote] = (0, import_react7.useState)("");
|
|
1733
|
+
const [prog, setProg] = (0, import_react7.useState)(void 0);
|
|
1734
|
+
const pullProgress = () => {
|
|
1735
|
+
void rpc("dsh-memory/mem-run-status", {}).then((r) => {
|
|
1736
|
+
const v = r && r.ok ? r.value : void 0;
|
|
1737
|
+
if (v !== void 0) setProg(v);
|
|
1738
|
+
}).catch(() => {
|
|
1739
|
+
});
|
|
1740
|
+
};
|
|
1741
|
+
const runExport = () => {
|
|
1742
|
+
if (busy) return;
|
|
1743
|
+
setBusy(true);
|
|
1744
|
+
setError(void 0);
|
|
1745
|
+
const timer = setInterval(pullProgress, 500);
|
|
1746
|
+
pullProgress();
|
|
1747
|
+
void call(rpc, "dsh-memory/mem-export", {}).then((out) => {
|
|
1748
|
+
clearInterval(timer);
|
|
1749
|
+
setBusy(false);
|
|
1750
|
+
if (out.report !== void 0) {
|
|
1751
|
+
setExported(out.report);
|
|
1752
|
+
pullProgress();
|
|
1753
|
+
}
|
|
1754
|
+
if (out.error !== void 0) setError(out.error);
|
|
1755
|
+
});
|
|
1756
|
+
};
|
|
1757
|
+
(0, import_react7.useEffect)(() => {
|
|
1758
|
+
void rpc("dsh-memory/settings-get", {}).then((r) => {
|
|
1759
|
+
const v = r && r.ok ? r.value : void 0;
|
|
1760
|
+
const n = v?.settings?.mdExportMaxRecords;
|
|
1761
|
+
if (typeof n === "number") setBudgetDraft(String(n));
|
|
1762
|
+
}).catch(() => {
|
|
1763
|
+
});
|
|
1764
|
+
}, [rpc]);
|
|
1765
|
+
const commitBudget = () => {
|
|
1766
|
+
const raw = budgetDraft.trim();
|
|
1767
|
+
if (raw === "") return;
|
|
1768
|
+
const n = Math.floor(Number(raw));
|
|
1769
|
+
if (!Number.isFinite(n) || n < 0 || n > 1e4) {
|
|
1770
|
+
setBudgetNote("预算要 0~10000 的整数(0 = 跟随部署默认)");
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
void rpc("dsh-memory/settings-set", { mdExportMaxRecords: n }).then((r) => {
|
|
1774
|
+
setBudgetNote(r && r.ok ? n === 0 ? "已改为跟随部署默认" : `已设为每次最多 ${n} 个文件,下一次点击生效` : "写入失败:预算未改");
|
|
1775
|
+
}).catch(() => {
|
|
1776
|
+
setBudgetNote("写入失败:预算未改");
|
|
1777
|
+
});
|
|
1778
|
+
};
|
|
1726
1779
|
const run = (method, sink, payload) => {
|
|
1727
1780
|
if (busy) return;
|
|
1728
1781
|
setBusy(true);
|
|
@@ -1734,6 +1787,7 @@ var __defProp = Object.defineProperty;
|
|
|
1734
1787
|
});
|
|
1735
1788
|
};
|
|
1736
1789
|
const root = exported?.root ?? recon?.root;
|
|
1790
|
+
const pct = prog !== void 0 && prog.total > 0 ? Math.min(100, Math.round(prog.done / (prog.total * 2) * 100)) : 0;
|
|
1737
1791
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { marginTop: 12, paddingTop: 10, borderTop: "1px solid var(--dsh-mem-border)" }, children: [
|
|
1738
1792
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: 12, fontWeight: 600, color: "var(--dsh-mem-text-2)", marginBottom: 6 }, children: "外部存档(markdown 投射)" }),
|
|
1739
1793
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: S.flexRow, children: [
|
|
@@ -1741,10 +1795,8 @@ var __defProp = Object.defineProperty;
|
|
|
1741
1795
|
NButton,
|
|
1742
1796
|
{
|
|
1743
1797
|
disabled: busy,
|
|
1744
|
-
title: "把当前全部记忆渲染为 <根>/<类型>/<名称>.memory.md
|
|
1745
|
-
onClick:
|
|
1746
|
-
run("dsh-memory/mem-export", setExported, {});
|
|
1747
|
-
},
|
|
1798
|
+
title: "把当前全部记忆渲染为 <根>/<类型>/<名称>.memory.md,并记下内容指纹(只写文件,不改库)。一次点完:一轮预算不够时服务端自己续跑下几轮,进度看下方进度条",
|
|
1799
|
+
onClick: runExport,
|
|
1748
1800
|
children: "导出为可携存档"
|
|
1749
1801
|
}
|
|
1750
1802
|
),
|
|
@@ -1827,6 +1879,40 @@ var __defProp = Object.defineProperty;
|
|
|
1827
1879
|
}
|
|
1828
1880
|
)
|
|
1829
1881
|
] }),
|
|
1882
|
+
prog !== void 0 && prog.total > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { marginTop: 8 }, children: [
|
|
1883
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }) }),
|
|
1884
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...HINT_STYLE, marginTop: 3 }, children: [
|
|
1885
|
+
prog.detail,
|
|
1886
|
+
prog.notice !== void 0 && prog.notice !== "" ? ` · ${prog.notice}` : ""
|
|
1887
|
+
] })
|
|
1888
|
+
] }) : null,
|
|
1889
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...S.flexRow, marginTop: 8, alignItems: "center" }, children: [
|
|
1890
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: "单次文件更新预算" }),
|
|
1891
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1892
|
+
NInput,
|
|
1893
|
+
{
|
|
1894
|
+
type: "number",
|
|
1895
|
+
min: 0,
|
|
1896
|
+
max: 1e4,
|
|
1897
|
+
style: { width: 96 },
|
|
1898
|
+
title: "一次点击最多更新多少个投射文件(导出写入 + 后缀改名都占额度)。“没变过”的不占额度。0 = 跟随部署默认(2000);上限 10000。",
|
|
1899
|
+
placeholder: "0",
|
|
1900
|
+
value: budgetDraft,
|
|
1901
|
+
onChange: (e) => {
|
|
1902
|
+
setBudgetDraft(e.target.value);
|
|
1903
|
+
setBudgetNote("");
|
|
1904
|
+
},
|
|
1905
|
+
onBlur: () => {
|
|
1906
|
+
commitBudget();
|
|
1907
|
+
},
|
|
1908
|
+
onKeyDown: (e) => {
|
|
1909
|
+
if (e.key === "Enter") commitBudget();
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
),
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: "个文件/次(0 = 默认 2000)" }),
|
|
1914
|
+
budgetNote !== "" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: budgetNote }) : null
|
|
1915
|
+
] }),
|
|
1830
1916
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { ...HINT_STYLE, marginTop: 6 }, children: error !== void 0 ? `失败:${error}` : cleanup !== void 0 ? lineOfCleanup(cleanup) : orphans !== void 0 ? lineOfOrphans(orphans) : synced !== void 0 ? lineOfSync(synced) : exported !== void 0 ? lineOf(exported) : recon !== void 0 ? lineOfReconcile(recon) : "导出=库→文件;对账=只读比较;同步=文件→库;孤儿=搬走没人认领的残片;清理=物理删除(不可逆,需高权限模式)。" }),
|
|
1831
1917
|
recon !== void 0 && error === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: HINT_STYLE, children: [
|
|
1832
1918
|
"最近差异样本:",
|
|
@@ -4409,6 +4495,39 @@ var __defProp = Object.defineProperty;
|
|
|
4409
4495
|
children: "用 " + m.usage.used + "/" + m.usage.attempts
|
|
4410
4496
|
}
|
|
4411
4497
|
) : null,
|
|
4498
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4499
|
+
"span",
|
|
4500
|
+
{
|
|
4501
|
+
title: m.name && m.name.length > 0 ? "名称(名称蒸馏产物;投射文件的命名取它):" + m.name : "尚未命名(名称蒸馏未产出);投射回落 mem-" + m.id.slice(-8),
|
|
4502
|
+
style: {
|
|
4503
|
+
fontSize: 11,
|
|
4504
|
+
padding: "1px 6px",
|
|
4505
|
+
borderRadius: 999,
|
|
4506
|
+
whiteSpace: "nowrap",
|
|
4507
|
+
maxWidth: 220,
|
|
4508
|
+
overflow: "hidden",
|
|
4509
|
+
textOverflow: "ellipsis",
|
|
4510
|
+
border: "1px solid " + (m.name && m.name.length > 0 ? "var(--dsh-mem-border)" : "var(--dsh-mem-danger)"),
|
|
4511
|
+
color: m.name && m.name.length > 0 ? "var(--dsh-mem-text-2)" : "var(--dsh-mem-danger)"
|
|
4512
|
+
},
|
|
4513
|
+
children: "名称 " + (m.name && m.name.length > 0 ? m.name : "未命名")
|
|
4514
|
+
}
|
|
4515
|
+
),
|
|
4516
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
4517
|
+
"span",
|
|
4518
|
+
{
|
|
4519
|
+
title: m.scope === "workspace" ? "作用范围:指定工作区(仅归属工作区可见)" + (m.workspaceId ? ":" + m.workspaceId : "") : "作用范围:用户默认(global,任何工作区可见)",
|
|
4520
|
+
style: {
|
|
4521
|
+
fontSize: 11,
|
|
4522
|
+
padding: "1px 6px",
|
|
4523
|
+
borderRadius: 999,
|
|
4524
|
+
whiteSpace: "nowrap",
|
|
4525
|
+
border: "1px solid var(--dsh-mem-border)",
|
|
4526
|
+
color: "var(--dsh-mem-text-2)"
|
|
4527
|
+
},
|
|
4528
|
+
children: m.scope === "workspace" ? "工作区" : "用户默认"
|
|
4529
|
+
}
|
|
4530
|
+
),
|
|
4412
4531
|
m.score !== null && m.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: "相关度 " + Number(m.score).toFixed(2) }) : null,
|
|
4413
4532
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.grow }),
|
|
4414
4533
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: fmtTime(m.updatedAt) }),
|
|
@@ -4437,7 +4556,7 @@ var __defProp = Object.defineProperty;
|
|
|
4437
4556
|
) : null
|
|
4438
4557
|
] }),
|
|
4439
4558
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.content, children: m.content }),
|
|
4440
|
-
open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.detail, children: "id: " + m.id + "\n情境: " + (m.scene || "-") + "\n版本: v" + m.version + "(去重合并次数 " + m.version + ")\n创建: " + fmtTime(m.createdAt) + "\n活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "-") + "\n" + (m.sourceAnchors && m.sourceAnchors.length > 0 ? "来源锚点: " + m.sourceAnchors.join(", ") : "来源锚点: -") }) : null
|
|
4559
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.detail, children: "id: " + m.id + "\n名称: " + (m.name && m.name.length > 0 ? m.name : "(未命名,投射回落 mem-" + m.id.slice(-8) + ")") + "\n情境: " + (m.scene || "-") + "\n关键词: " + (m.keywords && m.keywords.length > 0 ? m.keywords.join(",") : "(暂无 · 预留检索轴)") + "\n作用范围: " + (m.scope === "workspace" ? "指定工作区" + (m.workspaceId ? ":" + m.workspaceId : "(未记录路径)") : "用户默认(global,任何工作区可见)") + "\n版本: v" + m.version + "(去重合并次数 " + m.version + ")\n创建: " + fmtTime(m.createdAt) + "\n活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "-") + "\n" + (m.sourceAnchors && m.sourceAnchors.length > 0 ? "来源锚点: " + m.sourceAnchors.join(", ") : "来源锚点: -") }) : null
|
|
4441
4560
|
]
|
|
4442
4561
|
},
|
|
4443
4562
|
m.id
|
package/dist/config.d.ts
CHANGED
|
@@ -169,7 +169,10 @@ export interface MemoryConfig {
|
|
|
169
169
|
root: string;
|
|
170
170
|
/** 单次导出的**动作数**护栏(照 wing-backfill / workspace-relink 口径):只数碰了磁盘的
|
|
171
171
|
* 条(写入 / 补账 / 失败)。“未变且已记账”的不吃配额,否则第二次点击在同一批前 N 条上
|
|
172
|
-
* 打转、`remaining`
|
|
172
|
+
* 打转、`remaining` 永不递减;遍历总量由 `timeBudgetMs` 兜底。
|
|
173
|
+
*
|
|
174
|
+
* 默认 2000:本库量级就是 1000+ 条(真机 1210),旧的 300 意味着一次回填要点 4 次,
|
|
175
|
+
* 而“再点一次继续”对人是隐形成本。面板上可经 `live.mdExportMaxRecords` 运行时改。 */
|
|
173
176
|
maxRecords: number;
|
|
174
177
|
/** 单次导出的墙钟预算(ms)。 */
|
|
175
178
|
timeBudgetMs: number;
|
package/dist/config.js
CHANGED
|
@@ -67,6 +67,15 @@ export function liveSettingsSchema() {
|
|
|
67
67
|
embedRemoteDimensions: Schema.number().min(0).max(8192).default(0),
|
|
68
68
|
// 记忆写删权限门:默认 false(模型写删风险高,须显式在面板开启高权限模式)
|
|
69
69
|
memoryMutate: Schema.boolean().default(false),
|
|
70
|
+
/**
|
|
71
|
+
* md 投射/后缀收敛的**单次文件更新预算**运行时覆盖(`0` = 跟随启动配置 `memExport.maxRecords`)。
|
|
72
|
+
* 面板上就叫这个名字(设置页入口在 `client/src/tabs/ArchiveSection.tsx`)。
|
|
73
|
+
*
|
|
74
|
+
* 为什么要有这个键:投射面的量级随库增长(真机 1210 条),而它住在启动 YAML 里
|
|
75
|
+
* 就要人改 `cordis.patch.yml` + 重启才能调大——一个只在“回填历史”时用一下的旋钮
|
|
76
|
+
* 不该要求重启。生效路径在 `effectiveCfg`(与其他 live 键同一覆盖链)。
|
|
77
|
+
*/
|
|
78
|
+
mdExportMaxRecords: Schema.number().min(0).max(10_000).default(0),
|
|
70
79
|
// §C 人工冲突裁决总开关:默认 false(冻结消耗注意力,不可默认全开)
|
|
71
80
|
conflictFreeze: Schema.boolean().default(false),
|
|
72
81
|
// 轨2 技能结晶总开关(看板可拨):默认 false。开=注册 memory_skill_* 工具 + 注入 active 技能 + skills-* 端点可用。
|
|
@@ -203,7 +212,7 @@ export const memorySchema = Schema.object({
|
|
|
203
212
|
memExport: Schema.object({
|
|
204
213
|
enabled: Schema.boolean().default(true),
|
|
205
214
|
root: Schema.string().default(''),
|
|
206
|
-
maxRecords: Schema.number().min(1).max(10_000).default(
|
|
215
|
+
maxRecords: Schema.number().min(1).max(10_000).default(2000),
|
|
207
216
|
timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(120_000),
|
|
208
217
|
}),
|
|
209
218
|
// P2 反向同步:默认关。文件数护栏比导出的条数护栏宽一些——一次目录扫描的代价
|
|
@@ -13,7 +13,9 @@ import type { ConflictPairView, ConflictsResponse as ConflictsView, ConflictReso
|
|
|
13
13
|
export type { ConflictPairView, ConflictsView, ConflictResolutionView };
|
|
14
14
|
export interface ConflictResolveDeps {
|
|
15
15
|
l1: Pick<L1Store, 'listConflictPending' | 'resolveConflictPending' | 'retire' | 'syncGraphDisputed' | 'markConflictReviewed'>;
|
|
16
|
-
/** `conflictFreeze.enabled`。未开启时队列恒空,直接给出提示而非静默无操作。
|
|
16
|
+
/** `conflictFreeze.enabled`。未开启时队列恒空,直接给出提示而非静默无操作。
|
|
17
|
+
* 退场/恢复的文件跟随不在本模块点火:裁决败方走 `l1.retire()`,投射挂钩已收口在
|
|
18
|
+
* `L1Store` 的汇流口(`store/memory-md-sync.ts`),这里再挂一刀就是第二事实源。 */
|
|
17
19
|
conflictFreezeEnabled: boolean;
|
|
18
20
|
}
|
|
19
21
|
/**
|
package/dist/conflict-service.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function resolveConflictPair(deps, pairId, outcome) {
|
|
|
79
79
|
pairId,
|
|
80
80
|
...(pair.conflictType ? { conflictType: pair.conflictType } : {}),
|
|
81
81
|
});
|
|
82
|
+
// 退场即上盘:`l1.retire()` 里已点火(机器侧与本条同一个入口),所以这里不重发。
|
|
82
83
|
}
|
|
83
84
|
// 图谱 disputed 重算:此刻仍未裁决的对才是争议集,已了结的节点自动复原 active
|
|
84
85
|
const ids = new Set();
|
package/dist/contract.d.ts
CHANGED
|
@@ -106,6 +106,9 @@ export interface MemoryLiveSettings {
|
|
|
106
106
|
/** 轨2 技能结晶总开关(看板可拨):true = 注册 memory_skill_* 工具、注入 active 技能、skills-* 端点可用。
|
|
107
107
|
* 默认 false(新功能默认关);与部署 cfg.skills.enabled 取或(任一开则生效)。 */
|
|
108
108
|
skillsEnabled: boolean;
|
|
109
|
+
/** md 投射/后缀收敛的单次动作护栏运行时覆盖:0 = 跟随部署 `cfg.memExport.maxRecords`(默认 2000)。
|
|
110
|
+
* 存在的理由:一次点完多少条属于“回填历史时才改一下”的旋钮,不该要求改 YAML + 重启。 */
|
|
111
|
+
mdExportMaxRecords: number;
|
|
109
112
|
}
|
|
110
113
|
/** 召回停用原因(session-stats recall.enabled=false 时带出;短路序第一个为假的因子)。 */
|
|
111
114
|
export type RecallDisabledReason = 'deploy' | 'global' | 'session' | 'mode';
|
|
@@ -739,6 +742,8 @@ export interface SettingsSetRequest {
|
|
|
739
742
|
graph: number;
|
|
740
743
|
};
|
|
741
744
|
distillMaxInputChars?: number;
|
|
745
|
+
/** md 投射/后缀收敛的单次动作护栏覆盖:0 = 跟随部署 `cfg.memExport.maxRecords`。 */
|
|
746
|
+
mdExportMaxRecords?: number;
|
|
742
747
|
/** 蒸馏通道运行时覆盖:'' = 跟随部署 config;'host' = 复用宿主;'direct' = 插件原生直连。 */
|
|
743
748
|
distillMode?: '' | 'host' | 'direct';
|
|
744
749
|
/** 运行时直连端点覆盖('direct' 用;非机密,可回显)。 */
|
|
@@ -847,6 +852,28 @@ export interface UiRecord {
|
|
|
847
852
|
maturity?: 'low' | 'mid' | 'high';
|
|
848
853
|
/** 使用场景(G1):从 metadata.dsh_usage_scenario 读出;功能关/无则 undefined。 */
|
|
849
854
|
usageScenario?: string[];
|
|
855
|
+
/**
|
|
856
|
+
* 名称(md 投射相,N1):名称蒸馏产出的短标题,**投射文件名就取它**
|
|
857
|
+
* (`<type>/<name>.memory.md` 的 stem)。`undefined`/`null` = 还没名字(存量记录),
|
|
858
|
+
* 投射器回落 `mem-<id8>`。面板显示它是为了核对“这条记忆对应哪个投射文件”。
|
|
859
|
+
* 纯投影字段:读时从 `l1_records.name` 带出,不进存储改动。
|
|
860
|
+
*/
|
|
861
|
+
name?: string | null;
|
|
862
|
+
/**
|
|
863
|
+
* 可见范围(§E):`global` = 用户默认(任何工作区可见);`workspace` = 仅归属工作区可见。
|
|
864
|
+
* 展示用(读时从 `l1_records.scope` 带出,纯投影)。
|
|
865
|
+
*/
|
|
866
|
+
scope?: 'global' | 'workspace';
|
|
867
|
+
/** 归属工作区路径(仅 `scope='workspace'` 时非空;`global` 恒为 `null`)。展示用。 */
|
|
868
|
+
workspaceId?: string | null;
|
|
869
|
+
/**
|
|
870
|
+
* 关键词(保留字段,**用于 BM25 匹配**):从 `metadata.dsh_keywords` 读出。
|
|
871
|
+
*
|
|
872
|
+
* 与 `usageScenario` 同范式(保留键、随 metadata 落库、不进 `l1_records` DDL)。
|
|
873
|
+
* **当前无产出方**——抽取/命名尚未产出该键,故实机上恒为空;先预留字段与检索轴,
|
|
874
|
+
* 让 UI 与 BM25 都就位。无键 → `undefined`(缺席 ≠ 空数组)。
|
|
875
|
+
*/
|
|
876
|
+
keywords?: string[];
|
|
850
877
|
/**
|
|
851
878
|
* 人给的相关度均值(0..5);从未被 0..5 打过分为 undefined(**缺席 ≠ 0 分**)。
|
|
852
879
|
* 读时从 recall-feedback sidecar 派生,不进存储/快照。
|
|
@@ -1554,6 +1581,14 @@ export type MemExportRequest = Record<string, never>;
|
|
|
1554
1581
|
export type MemExportResponse = import('./store/memory-md-types.js').MemExportResponse;
|
|
1555
1582
|
export type MemReconcileRequest = Record<string, never>;
|
|
1556
1583
|
export type MemReconcileResponse = import('./store/memory-md-types.js').ReconcileReport;
|
|
1584
|
+
/**
|
|
1585
|
+
* dsh-memory/mem-run-status:投影长任务的进度快照(只读、无入参)。
|
|
1586
|
+
*
|
|
1587
|
+
* 为什么要有它:`mem-export` 现在自己滚到完(多轮),一次点击可能跑十几秒—
|
|
1588
|
+
* 没有进度面就退化成人靠“再点一下”猜任务还在不在跑(真机反馈正是“点三下”)。
|
|
1589
|
+
*/
|
|
1590
|
+
export type MemRunStatusRequest = Record<string, never>;
|
|
1591
|
+
export type MemRunStatusResponse = import('./store/memory-md-types.js').MdRunStatus;
|
|
1557
1592
|
/**
|
|
1558
1593
|
* dsh-memory/mem-sync:P2 反向同步(md 文件 → 索引)。
|
|
1559
1594
|
*
|
|
@@ -1660,6 +1695,7 @@ export interface DshMemoryRequestMap {
|
|
|
1660
1695
|
'dsh-memory/recall-feedback': RecallFeedbackRequest;
|
|
1661
1696
|
'dsh-memory/workspace-relink': WorkspaceRelinkRequest;
|
|
1662
1697
|
'dsh-memory/mem-export': MemExportRequest;
|
|
1698
|
+
'dsh-memory/mem-run-status': MemRunStatusRequest;
|
|
1663
1699
|
'dsh-memory/mem-reconcile': MemReconcileRequest;
|
|
1664
1700
|
'dsh-memory/mem-sync': MemSyncRequest;
|
|
1665
1701
|
'dsh-memory/mem-orphans': MemOrphansRequest;
|
|
@@ -1721,6 +1757,7 @@ export interface DshMemoryResponseMap {
|
|
|
1721
1757
|
'dsh-memory/recall-feedback': RecallFeedbackResponse;
|
|
1722
1758
|
'dsh-memory/workspace-relink': WorkspaceRelinkResponse;
|
|
1723
1759
|
'dsh-memory/mem-export': MemExportResponse;
|
|
1760
|
+
'dsh-memory/mem-run-status': MemRunStatusResponse;
|
|
1724
1761
|
'dsh-memory/mem-reconcile': MemReconcileResponse;
|
|
1725
1762
|
'dsh-memory/mem-sync': MemSyncResponse;
|
|
1726
1763
|
'dsh-memory/mem-orphans': MemOrphansResponse;
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ import { registerSkillTools } from './tools/skills.js';
|
|
|
46
46
|
import { RecallUsageStore } from './store/recall-usage.js';
|
|
47
47
|
import { RecallFeedbackStore } from './store/recall-feedback.js';
|
|
48
48
|
import { MdLedgerStore } from './store/memory-md-ledger.js';
|
|
49
|
+
import { makeFileSyncHook } from './store/memory-md-sync.js';
|
|
49
50
|
import { ObsoleteNoticeStore } from './store/obsolete-notices.js';
|
|
50
51
|
import { registerSlotsProjection } from './projection/slots.js';
|
|
51
52
|
import { registerRecallProjection } from './projection/recall.js';
|
|
@@ -406,6 +407,27 @@ export async function apply(ctx, config) {
|
|
|
406
407
|
const mdLedger = new MdLedgerStore(dataDir, logger);
|
|
407
408
|
await mdLedger.init();
|
|
408
409
|
stores.mdLedger = mdLedger;
|
|
410
|
+
// 库→文件投射的跟随钩子:**收口在 `L1Store` 的四个汇流口**(appendNew / retire / restore / rename),
|
|
411
|
+
// 不在这里往调用方分发。不变式本体在 `store/memory-md-sync.ts`。
|
|
412
|
+
// 为什么只能装配层建:工具面/冲突裁决各自看不到 cfg + ledger + dataDir 的完整视野。
|
|
413
|
+
const fileSync = makeFileSyncHook({
|
|
414
|
+
getByIds: (list) => stores.l1.getByIds([...list]),
|
|
415
|
+
// 现读生效配置(volatile 热更后不必重启):与 RPC 分发用同一个 `effectiveCfg` 口径。
|
|
416
|
+
exportCfg: () => {
|
|
417
|
+
const exp = effectiveCfg(config, live).memExport;
|
|
418
|
+
return {
|
|
419
|
+
enabled: exp?.enabled === true,
|
|
420
|
+
root: exp?.root,
|
|
421
|
+
maxRecords: exp?.maxRecords ?? 2000,
|
|
422
|
+
timeBudgetMs: exp?.timeBudgetMs ?? 120_000,
|
|
423
|
+
};
|
|
424
|
+
},
|
|
425
|
+
dataDir,
|
|
426
|
+
ledger: () => stores.mdLedger,
|
|
427
|
+
logger,
|
|
428
|
+
});
|
|
429
|
+
// 晚绑定:`stores.l1` 在 md 账本之前就建好了,这里只把钩子接上去。
|
|
430
|
+
stores.l1.setFileSyncHook(fileSync);
|
|
409
431
|
const recall = registerRecall(ctx, config, stores, logger, live, modes, dataDir, recallUsage);
|
|
410
432
|
runner.setAfterRun(recall.invalidateProfile);
|
|
411
433
|
registerMemoryTools(ctx, config, stores, logger, modes, live, ruminate, storageOk ? roomRegistry : undefined);
|
|
@@ -142,7 +142,7 @@ export declare class RuminateController {
|
|
|
142
142
|
*/
|
|
143
143
|
private snapshotMdBaseline;
|
|
144
144
|
/**
|
|
145
|
-
* 反刍收尾的增量 md 投射(task_2.8):库 → 文件 +
|
|
145
|
+
* 反刍收尾的增量 md 投射(task_2.8):库 → 文件 + 账本 + 后缀对齐,**只碰本轮新增/变更的那批**。
|
|
146
146
|
*
|
|
147
147
|
* 与手动的 `dsh-memory/mem-export` 端点的区别:
|
|
148
148
|
* - 不跑命名回填(`assignMissingNames` 会写库,反刍是只读旁路,不能在那里改库);
|
|
@@ -150,6 +150,9 @@ export declare class RuminateController {
|
|
|
150
150
|
* - 不建类型目录骨架(`ensureTypeSkeleton`),那是面板显式触发的动作。
|
|
151
151
|
* 护栏全部复用 `cfg.memExport` 既有值(`maxRecords`/`timeBudgetMs`),**不新增配置项**。
|
|
152
152
|
*
|
|
153
|
+
* 一轮投射走 `syncFiles`(与 `L1Store` 钩子同一函数):导出之后紧跟后缀收敛,
|
|
154
|
+
* 所以本轮被合并取代/冲突判负的增量记录不会停在未改名的 `.memory.md` 上。
|
|
155
|
+
*
|
|
153
156
|
* 失败面:整个方法自吞异常,只 warn + 计数,绝不抛给调用方、绝不改变反刍结果。
|
|
154
157
|
* @param stage - 日志里的路径名(区分 finalize 与轻量刷新两条挂载点)。
|
|
155
158
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveDataDir } from '../config.js';
|
|
2
2
|
import { InProcMemoryBackend } from '../store/memory-backend.js';
|
|
3
|
-
import {
|
|
3
|
+
import { syncFiles } from '../store/memory-md-sync.js';
|
|
4
4
|
import { groupPendingBySession, loadPending, } from '../store/pending.js';
|
|
5
5
|
import { errDetail } from '../util/filelog.js';
|
|
6
6
|
import { runSceneConsolidation } from './l2.js';
|
|
@@ -469,7 +469,7 @@ export class RuminateController {
|
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
/**
|
|
472
|
-
* 反刍收尾的增量 md 投射(task_2.8):库 → 文件 +
|
|
472
|
+
* 反刍收尾的增量 md 投射(task_2.8):库 → 文件 + 账本 + 后缀对齐,**只碰本轮新增/变更的那批**。
|
|
473
473
|
*
|
|
474
474
|
* 与手动的 `dsh-memory/mem-export` 端点的区别:
|
|
475
475
|
* - 不跑命名回填(`assignMissingNames` 会写库,反刍是只读旁路,不能在那里改库);
|
|
@@ -477,6 +477,9 @@ export class RuminateController {
|
|
|
477
477
|
* - 不建类型目录骨架(`ensureTypeSkeleton`),那是面板显式触发的动作。
|
|
478
478
|
* 护栏全部复用 `cfg.memExport` 既有值(`maxRecords`/`timeBudgetMs`),**不新增配置项**。
|
|
479
479
|
*
|
|
480
|
+
* 一轮投射走 `syncFiles`(与 `L1Store` 钩子同一函数):导出之后紧跟后缀收敛,
|
|
481
|
+
* 所以本轮被合并取代/冲突判负的增量记录不会停在未改名的 `.memory.md` 上。
|
|
482
|
+
*
|
|
480
483
|
* 失败面:整个方法自吞异常,只 warn + 计数,绝不抛给调用方、绝不改变反刍结果。
|
|
481
484
|
* @param stage - 日志里的路径名(区分 finalize 与轻量刷新两条挂载点)。
|
|
482
485
|
*/
|
|
@@ -498,16 +501,22 @@ export class RuminateController {
|
|
|
498
501
|
}
|
|
499
502
|
if (changed.length === 0)
|
|
500
503
|
return;
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
+
const deps = {
|
|
505
|
+
exportCfg: () => ({
|
|
506
|
+
enabled: exp.enabled === true,
|
|
507
|
+
root: exp.root,
|
|
508
|
+
maxRecords: exp.maxRecords,
|
|
509
|
+
timeBudgetMs: exp.timeBudgetMs,
|
|
510
|
+
}),
|
|
504
511
|
dataDir: resolveDataDir(this.cfg),
|
|
505
|
-
ledger,
|
|
506
|
-
maxRecords: exp.maxRecords,
|
|
507
|
-
timeBudgetMs: exp.timeBudgetMs,
|
|
512
|
+
ledger: () => this.stores.mdLedger,
|
|
508
513
|
logger: this.logger,
|
|
509
|
-
}
|
|
510
|
-
|
|
514
|
+
};
|
|
515
|
+
const out = await syncFiles(deps, changed);
|
|
516
|
+
if (out === undefined)
|
|
517
|
+
return;
|
|
518
|
+
const { exported: rep, suffix } = out;
|
|
519
|
+
this.logger.info(`[memory] 反刍 md 投射(${stage}):增量 ${changed.length} 条,写 ${rep.written},未变 ${rep.unchanged},跳过 ${rep.skipped},改名 ${suffix.renamed},落点 ${rep.root}${rep.capped ? `,预算用尽剩 ${rep.remaining} 个文件(下轮反刍继续)` : ''}`);
|
|
511
520
|
}
|
|
512
521
|
catch (err) {
|
|
513
522
|
this.mdFailures++;
|
package/dist/pipeline/runner.js
CHANGED
|
@@ -121,12 +121,17 @@ export function effectiveCfg(cfg, live) {
|
|
|
121
121
|
const cfOverride = s?.conflictFreeze !== undefined
|
|
122
122
|
? { conflictFreeze: { ...(cfg.conflictFreeze ?? {}), enabled: s.conflictFreeze } }
|
|
123
123
|
: null;
|
|
124
|
-
|
|
124
|
+
// md 投射/后缀收敛的单次动作护栏运行时覆盖(0 = 跟随部署 cfg.memExport.maxRecords)。
|
|
125
|
+
// 只覆盖条数护栏,不动 enabled/root——开关与根路径仍属部署配置。
|
|
126
|
+
const mdx = s?.mdExportMaxRecords ?? 0;
|
|
127
|
+
const memExportOverride = mdx > 0 ? { memExport: { ...(cfg.memExport ?? {}), maxRecords: mdx } } : null;
|
|
128
|
+
if (!override && !budgets && !maxInput && !fallbacksTakeover && !chainEffort && !chainFallbacks && !effortInject && !layerChains && !hasChannel && !embedOverride && !cfOverride && !memExportOverride)
|
|
125
129
|
return cfg;
|
|
126
130
|
return {
|
|
127
131
|
...cfg,
|
|
128
132
|
...(embedOverride ?? {}),
|
|
129
133
|
...(cfOverride ?? {}),
|
|
134
|
+
...(memExportOverride ?? {}),
|
|
130
135
|
llm: {
|
|
131
136
|
...cfg.llm,
|
|
132
137
|
...(effortInject ?? {}),
|