dsh-prime-memory 0.11.0 → 0.12.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/CHANGELOG.en.md +36 -1
- package/CHANGELOG.ja.md +36 -1
- package/CHANGELOG.ko.md +36 -1
- package/CHANGELOG.md +40 -0
- package/README.en.md +28 -0
- package/README.ja.md +25 -0
- package/README.ko.md +25 -0
- package/README.md +22 -0
- package/dist/client.js +54 -30
- package/dist/config.d.ts +39 -1
- package/dist/config.js +16 -0
- package/dist/conflict-service.d.ts +38 -0
- package/dist/conflict-service.js +65 -0
- package/dist/graph/search.d.ts +15 -0
- package/dist/graph/search.js +54 -1
- package/dist/hooks/recall.js +4 -0
- package/dist/index.d.ts +28 -1
- package/dist/index.js +12 -3
- package/dist/pipeline/l1.d.ts +7 -1
- package/dist/pipeline/l1.js +165 -20
- package/dist/pipeline/runner.js +6 -1
- package/dist/prompts/l1-dedup.d.ts +22 -1
- package/dist/prompts/l1-dedup.js +61 -4
- package/dist/stats.d.ts +12 -0
- package/dist/stats.js +222 -33
- package/dist/store/conflicts.d.ts +62 -0
- package/dist/store/conflicts.js +69 -0
- package/dist/store/graph-store.d.ts +72 -1
- package/dist/store/graph-store.js +165 -1
- package/dist/store/l1.d.ts +97 -4
- package/dist/store/l1.js +163 -21
- package/dist/store/receipts.d.ts +157 -0
- package/dist/store/receipts.js +139 -0
- package/dist/store/search-utils.d.ts +15 -0
- package/dist/store/search-utils.js +20 -0
- package/dist/store/session-modes.d.ts +7 -0
- package/dist/store/session-modes.js +9 -0
- package/dist/store/sqlite.d.ts +116 -8
- package/dist/store/sqlite.js +366 -46
- package/dist/store/vec-utils.d.ts +16 -0
- package/dist/store/vec-utils.js +24 -0
- package/dist/tools/index.js +204 -31
- package/dist/types.d.ts +48 -0
- package/dist/types.js +40 -0
- package/dist/workspace.d.ts +46 -0
- package/dist/workspace.js +105 -0
- package/package.json +9 -3
package/CHANGELOG.en.md
CHANGED
|
@@ -5,7 +5,42 @@
|
|
|
5
5
|
- [日本語 changelog](./CHANGELOG.ja.md)
|
|
6
6
|
- [한국어 changelog](./CHANGELOG.ko.md)
|
|
7
7
|
|
|
8
|
-
This file covers the **0.11.0** release notes in English. For the full history, see [CHANGELOG.md](./CHANGELOG.md) (Chinese).
|
|
8
|
+
This file covers the **0.11.0** release notes and the current **unreleased** changes in English. For the full history, see [CHANGELOG.md](./CHANGELOG.md) (Chinese).
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **§E Storage scope `scope` (visibility range, orthogonal to `family`)**. Until now every project shared one memory store: `work`-family memories distilled in project A were recalled in project B. New `scope` setting (`global` default / `workspace`), **orthogonal** to `family` — `family` answers "what kind of content is this", `scope` answers "how far should it be visible". "In `workspace` mode the `work` family is isolated per workspace while `chat` stays global" is a **default, not a derivation rule** (personal memories are meant to cross projects; contamination lives between projects).
|
|
15
|
+
- **Zero drift is constructed, not compared**: when `cfg.scope` is not `workspace`, the shared `scopeFilterOf` always returns `undefined` (= no filtering), so no call site can accidentally pass a workspace id. Existing deployments (key absent) behave **byte-identically**.
|
|
16
|
+
- **Isolation sits on the same layer as family isolation**: not just the retrieval exit — **dedup candidate recall** is filtered too. The candidate pool decides the next dedup decision; leaving it cross-workspace produces memories that are "invisible in project B yet already decided project A's fate", which is worse than no isolation ([`ADR-0008`](./docs/adr/0008-storage-scope-vs-family.md)). The graph lane filters on the **source record's** ownership at the same layer; all write paths share one `resolveRecordScope`.
|
|
17
|
+
- **The workspace identifier is the canonical cwd, not the host's `dsh-workspace` `WorkspaceId` (uuid)**: same header channel as §A's `parentSession`, synchronously available; declaring `inject` would fail the whole plugin tree on hosts lacking the service; the host's own membership rule is "session header's canonical cwd == workspace path"; a uuid requires a *registered* workspace, so unregistered directories would silently lose isolation. Known boundary: **symlinks are not resolved**, which over-isolates (the safe direction) rather than leaking ([`ADR-0009`](./docs/adr/0009-workspace-identity-source.md)).
|
|
18
|
+
- **Migration labels ownership only, never moves or deletes**: `l1_records` / `l1_fts` each gain `scope` + `workspace_id`; existing data is graded `global` by the `ALTER` default — row count, id, content and created_time stay byte-identical.
|
|
19
|
+
- **Two real defects caught during execution**: ① an FTS rebuild backfill that dropped scope would **silently erase isolation** (its per-row `catch` swallows the column-count mismatch, leaving `count=0` and an empty index); ② a missing **shape normalization** on the write side meant memories could be written and then be unreachable (retrieval passes a normalized lower-case path while the store kept the caller's upper-case string). Both were caught by end-to-end tests and mutation probes.
|
|
20
|
+
- **§F Graph node vector column `graph_node_vec` (storage and degradation groundwork)**. The graph only had lexical weighted scoring, so semantically equivalent but literally different entities ("云深处" vs "DeepRobotics") could not resolve to each other. Adds a vec0 virtual table **same-pattern** as `l1_vec` (one shared encoding, one `float[N] distance_metric=cosine` declaration); dimensions **reuse** the existing capability probe rather than adding a second one.
|
|
21
|
+
- **Degradation is absorbed in an inner `try/catch`**: a missing vec0 makes table creation throw, and if that reached `GraphStore.init`'s outer catch the graph would degrade from "vector lane unavailable" to "**the entire graph domain unavailable**", taking lexical retrieval, projection and adjudication with it ([`ADR-0011`](./docs/adr/0011-graph-node-vector-storage-and-degradation.md)).
|
|
22
|
+
- **This wave ships a door, not a producer or consumer**: projection does not yet compute embeddings. Recorded as incomplete — it is §F's starting point, not its end.
|
|
23
|
+
|
|
24
|
+
- **§B L1 decision-receipt chain (traceability infrastructure)**. Every L1 record is the *result* of a dedup decision (store / update / merge / skip), but the decision itself left no trace — you could see the outcome, never what it was based on. New `l1_receipts` table records one receipt per decision (`run_id` / `record_id` / `kind` / **a sha256 digest of the ordered candidate pool** / `decided_at`), with the new **`memory_receipts`** tool and **`dsh-memory/receipts`** RPC endpoint for **two-dimensional backtracking** (by record or by run; both = AND). Receipts must exist *before* the event — input snapshots **cannot be backfilled**, so this shipped as a prerequisite rather than waiting for a symptom ([ADR-0006](./docs/adr/0006-l1-decision-receipts.md)).
|
|
25
|
+
- Retention is **by run count** (`RECEIPTS_MAX_RUNS = 1000`) — a time window **cannot bound the row count**. Trimming is **per run, not per row**: cutting rows yields *half batches*, which produce conclusions that look complete but silently omit entries — worse than finding nothing.
|
|
26
|
+
- Hard line: trimming **touches `l1_receipts` only, never `l1_records`** (discardable observation data vs. the user's source of truth).
|
|
27
|
+
- Failure isolation: receipts are a side channel — a write failure logs a warning and **never interrupts L1 distillation**.
|
|
28
|
+
|
|
29
|
+
- **§C conflict freeze (opt-in, off by default)**. The dedup **action vocabulary** was `store` / `update` / `merge` / `skip` — so the "conflict detector" adjudicated contradictions itself (`update` overwrite or `merge`), with **no "stop and ask a human" option**. With `conflictFreeze.enabled`, the vocabulary gains `conflict`: when the model judges that both sides look right and it cannot tell, the pair is **parked** in `conflict_pending` — **the new memory is still stored and neither side is rewritten** — and a human adjudicates via the new **`memory_resolve_conflict`** tool (RPC: `dsh-memory/conflict-resolve`) with `winner` / `loser` / `both`.
|
|
30
|
+
- **Freeze is not "block the write", it is "do not auto-adjudicate"** — the former loses information, which is worse than the problem it solves.
|
|
31
|
+
- **Safety valves**: `maxPending` (queue cap) and `timeoutDays` (timeout fallback). The semantics are "**stop taking new ones**", not "quietly delete old ones" — auto-settled pairs **are still written to the queue** with `resolution = auto`, distinguishable from a human verdict.
|
|
32
|
+
- **Graph side**: nodes whose sources hit a frozen record are marked `disputed` (an existing status; still a retrieval candidate, i.e. "recalled as usual but visibly contested"). The mark is a **derived sync**, not a one-way flag: adjudication **cancels** the dispute, and a one-way flag would leave resolved nodes stuck at `disputed` — a derived graph lying about the facts.
|
|
33
|
+
- **Zero drift**: while off, the dedup prompt is **byte-identical** to before. This is **structural** (the off path returns the base constant directly), not a manual diff ([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **New settings** `conflictFreeze.enabled` (off) / `conflictFreeze.maxPending` (100) / `conflictFreeze.timeoutDays` (30; `0` = no timeout fallback). **New endpoints** `dsh-memory/receipts` and `dsh-memory/conflict-resolve` (endpoint surface 26 → 28).
|
|
38
|
+
- **`L1ReceiptKind` gains `conflict`**. Extending an action vocabulary requires checking **every consumer of that vocabulary** (receipt normalisation, stats logs, rendered text, schema descriptions) — measured during this work: a missed registration recorded "the model **explicitly** said it cannot tell" as `skip_missing` ("the model **did not answer**"), and since §C's auditability depends **entirely** on the receipt chain, the audit conclusion was the exact opposite of the facts.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **Unrecognised actions silently absorbed by the fallback branch.** The apply loop in `pipeline/l1.ts` branches explicitly only on `store` / `skip`; **every other action falls through to the update/merge branch**. A `conflict` decision carries no `target_ids` by design, so `targets=[]` and the record was appended as a "merged product that replaced 0 rows" with `version = 1`. **No error, no data loss — the only trace was a version number**, i.e. `conflict` had been silently downgraded to merge/update: exactly the behaviour §C exists to remove. **Fix**: an explicit `conflict` branch; when validation fails or the switch is off it falls back to `store`, never to the fallback branch.
|
|
43
|
+
- **The endpoint gate was a hand-copied list.** `tests/contract-keys.test.ts` duplicated the real registry instead of importing it, so both assertions were self-consistent with the copy. When §B added `receipts` the real registry went to 27 while the copy stayed at 26 and the count assertion stayed at `toBe(26)` — it went green regardless. Adding `conflict-resolve` this round **still** went green. **Fix**: assert the local list equals `MEMORY_ENDPOINTS` item by item.
|
|
9
44
|
|
|
10
45
|
## [0.11.0] — 2026-09-13
|
|
11
46
|
|
package/CHANGELOG.ja.md
CHANGED
|
@@ -7,7 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
> **互換性の注意**:本プラグインは日本語ドキュメントを提供しますが、公式 DSH の `LocaleRuntime` が登録する言語は `zh` / `en` のみです。`ja` を選択すると `locale "ja" is not registered` となります。DSH を fork して `LOCALE_IDS` と `LOCALES` ラベルを更新し再ビルドすることで利用可能になります。
|
|
9
9
|
|
|
10
|
-
本ファイルは **0.11.0**
|
|
10
|
+
本ファイルは **0.11.0** リリースノートと現在の**未リリース**変更の日本語版です。全履歴は [CHANGELOG.md](./CHANGELOG.md)(中文)を参照してください。
|
|
11
|
+
|
|
12
|
+
## [未リリース]
|
|
13
|
+
|
|
14
|
+
### 追加
|
|
15
|
+
|
|
16
|
+
- **§E ストレージスコープ `scope`(可視範囲、`family` と直交)**。これまで全プロジェクトが 1 つの記憶庫を共有しており、プロジェクト A で蒸留した `work` 族の記憶がプロジェクト B の会話でも検索されていました。新しい `scope` 設定(`global` 既定 / `workspace`)を追加し、既存の `family` と**直交**させます——`family` は「これはどんな内容か」、`scope` は「どこまで可視であるべきか」に答えます。「`workspace` モードでは `work` 族をワークスペース単位で隔離し、`chat` 族は既定でグローバルのまま」は**既定値であり導出規則ではありません**(個人的な記憶はプロジェクトをまたぐべきで、汚染面はプロジェクト間にこそあります)。
|
|
17
|
+
- **ゼロドリフトは比較ではなく構造で保証**:`cfg.scope` が `workspace` でなければ共通の `scopeFilterOf` が常に `undefined`(=フィルタなし)を返すため、どの呼び出し点も誤ってワークスペース識別子を渡せません。既存デプロイ(キー未指定)の挙動は**バイト単位で同一**です。
|
|
18
|
+
- **隔離は族隔離と同一層**に置きます:検索の出口だけでなく**重複排除の候補召回**もフィルタします。候補プールは次の重複排除判断を決めるため、ここがワークスペースをまたぐと「プロジェクト B では見えないのに、すでにプロジェクト A の記憶の行き先を決めてしまった」記憶が生まれ、隔離しないより悪くなります([`ADR-0008`](./docs/adr/0008-storage-scope-vs-family.md))。グラフ路は**来源レコード**の帰属で同層フィルタし、書き込み経路(抽取パイプライン / `memory_add` / `memory_import`)は同一の `resolveRecordScope` を共有します。
|
|
19
|
+
- **ワークスペース識別子は canonical cwd を取り、宿主 `dsh-workspace` の `WorkspaceId`(uuid)は取りません**:§A の `parentSession` と同じ header 経路で同期取得でき、`inject` を宣言すると当該サービスを持たない宿主でツリー全体のロードが失敗し、宿主自身の成員判定も「session header の canonical cwd == workspace path」であり、uuid は「登録済みワークスペース」を要求するため未登録ディレクトリでは隔離が静かに失效します。既知の境界:**シンボリックリンクは解決しません**——結果は過剰隔離(安全側)であり漏洩ではありません([`ADR-0009`](./docs/adr/0009-workspace-identity-source.md))。
|
|
20
|
+
- **移行は帰属を記すだけで移動も削除もしません**:`l1_records` / `l1_fts` に `scope` + `workspace_id` を追加し、既存データは `ALTER` の既定値で `global` に分類されます——件数・id・content・created_time はバイト単位で不変です。
|
|
21
|
+
- **実行中に見つかった 2 件の実不具合**:① FTS 再構築の回灌が scope を落とすと**隔離が静かに消える**(列数の不一致が行ごとの `catch` に飲まれ、`count=0` の空インデックスになる)。② 書き込み側の**形状正規化漏れ**により「書き込めたのに二度と検索できない」状態が生じる(検索側は正規化済みの小文字パスを渡し、保存側は呼び出し元の大文字文字列をそのまま保持するため、文字列一致が必ず外れる)。いずれも端到端テストと変異プローブが捕捉しました。
|
|
22
|
+
- **§F グラフ節点ベクトル列 `graph_node_vec`(記憶と降格の土台)**。グラフは従来語彙加重スコアのみで、意味的に等価でも字面が異なる実体(「云深处」と「DeepRobotics」)を相互解決できませんでした。`l1_vec` と**同パターン**の vec0 仮想表を追加し(同一の符号化、同一の `float[N] distance_metric=cosine` 宣言)、次元は**既存の能力探測結果を再利用**して二重の探測を作りません。
|
|
23
|
+
- **降格は内側の `try/catch` で消化**:vec0 が無いとテーブル作成が例外を投げ、それが `GraphStore.init` の外側の catch に届くとグラフは「ベクトル路が使えない」から「**グラフ領域全体が使えない**」へと降格し、語彙検索・投影・裁定を道連れにします([`ADR-0011`](./docs/adr/0011-graph-node-vector-storage-and-degradation.md))。
|
|
24
|
+
- **本波は扉のみで、生産者と消費者は未接続**:投影パイプラインはまだ埋め込みを計算しません。未完了として記録します——§F の起点であり終点ではありません。
|
|
25
|
+
|
|
26
|
+
- **§B L1 決定証跡チェーン(追跡可能性の基盤)**。L1 レコードはすべて重複排除判断の**結果**ですが、判断自体は痕跡を残していませんでした(結果は見えるが、何に基づいたかは見えない)。新テーブル `l1_receipts` が判断ごとに証跡を残します(`run_id` / `record_id` / `kind` / **候補プールの順序付き sha256 ダイジェスト** / `decided_at`)。新ツール **`memory_receipts`** と RPC 端点 **`dsh-memory/receipts`** で、レコード単位・バッチ単位の**二次元遡及**ができます。証跡はイベントの**前に**存在しなければなりません——入力スナップショットは**後から補填できない**ためです([ADR-0006](./docs/adr/0006-l1-decision-receipts.md))。
|
|
27
|
+
- 保持方針は **run 数**単位(`RECEIPTS_MAX_RUNS = 1000`)——時間窓では行数の上界を与えられません。またトリミングは **run 単位であり行単位ではありません**(行単位では「半端なバッチ」が生まれ、完全に見えて実は欠落した結論を出してしまいます——「見つからない」より有害)。
|
|
28
|
+
- レッドライン:トリミングは **`l1_receipts` のみに触れ、`l1_records` には絶対に触れない**。
|
|
29
|
+
- 失敗隔離:証跡は側路インフラであり、書き込み失敗は `warn` のみで **L1 蒸留を絶対に中断しません**。
|
|
30
|
+
|
|
31
|
+
- **§C 矛盾凍結(オプション、既定オフ)**。従来の重複排除**決定語彙**は `store` / `update` / `merge` / `skip` のみ——「衝突検出器」が矛盾を検出しても **LLM が直接裁定して書き込み**、**「止めて人に聞く」という選択肢がありませんでした**。`conflictFreeze.enabled` を有効にすると語彙に `conflict` が加わり、どちらも正しそうで機械には判定できない場合にペアを `conflict_pending` に**待機**させます——**新しい記憶は通常どおり保存され、双方の内容は書き換えられません**。裁定は新ツール **`memory_resolve_conflict`**(RPC: `dsh-memory/conflict-resolve`)で行い、結論は `winner` / `loser` / `both` です。
|
|
32
|
+
- **凍結は「書き込みを止める」ではなく「自動裁定しない」**——前者は情報を失い、解こうとした問題より悪くなります。
|
|
33
|
+
- **安全弁**:`maxPending`(キュー上限)/ `timeoutDays`(タイムアウト降格)。意味は「**新しいものを受け取らない**」であり「古いものを黙って消す」ではありません——自動決着したペアも**キューの行として残り**、`resolution = auto` で人の結論と区別されます。
|
|
34
|
+
- **グラフ側**:凍結された記録をソースに含むノードは `disputed` になります(既存状態。検索候補には残るため「通常どおり召回されるが状態が見える」中間状態)。この付与は**派生同期**であり片方向フラグではありません——裁定は争いを**取り消す**ため、片方向だと解決済みノードが永久に `disputed` のままになり、**派生グラフが事実と食い違います**。
|
|
35
|
+
- **ゼロドリフト**:無効時、重複排除プロンプトは変更前と**バイト単位で同一**。これは**構造的**な保証(無効パスは base 定数をそのまま返す)であり、人手の比較ではありません([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md))。
|
|
36
|
+
|
|
37
|
+
### 変更
|
|
38
|
+
|
|
39
|
+
- **新設定** `conflictFreeze.enabled`(既定オフ)/ `conflictFreeze.maxPending`(100)/ `conflictFreeze.timeoutDays`(30、`0` = タイムアウト降格なし)。**新端点** `dsh-memory/receipts` と `dsh-memory/conflict-resolve`(端点面 26 → 28)。
|
|
40
|
+
- **`L1ReceiptKind` に `conflict` を追加**。動作語彙を拡張する際は**その語彙を消費するすべての箇所**(証跡の正規化・統計ログ・描画文言・schema 記述)を同時に確認する必要があります——実行時に実測:登録漏れがあると「モデルが**明確に**判定不能と言った」が `skip_missing`(=「モデルが**答えなかった**」)として記録され、§C の監査可能性は**完全に**証跡チェーンに依存するため、監査結論が事実と**正反対**になります。
|
|
41
|
+
|
|
42
|
+
### 修正
|
|
43
|
+
|
|
44
|
+
- **未認識のアクションがフォールバック分岐に静かに吸収される**。`pipeline/l1.ts` の適用ループは `store` / `skip` だけを明示分岐し、**他のアクションはすべて update/merge 分岐に落ちます**。conflict 判断は設計上 `target_ids` を持たないため `targets=[]` となり、「0 件を置換した」マージ産物として追記され `version` が **1** になっていました。**エラーもなくデータ損失もなく、痕跡は version という数字だけ**——すなわち conflict が黙って merge/update に降格していた、§C がまさに排除したい挙動です。**修正**:明示的な `conflict` 分岐を追加し、検証失敗またはスイッチ無効時は **`store` にフォールバック**、フォールバック分岐には絶対に落としません。
|
|
45
|
+
- **端点ゲートが手書きの写しだった**。`tests/contract-keys.test.ts` は真の登録表を import せず複製していたため、両方の断言が写し自身としか整合していませんでした。§B が `receipts` を追加して真の登録表が 27 になっても写しは 26 のままで、件数断言も `toBe(26)` のまま——**緑のままでした**。今回 `conflict-resolve` を追加しても**依然として緑**でした。**修正**:ローカル一覧が `MEMORY_ENDPOINTS` と項目単位で一致することを断言します。
|
|
11
46
|
|
|
12
47
|
## [0.11.0] — 2026-09-13
|
|
13
48
|
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -7,7 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
> **호환성 참고**: 본 플러그인은 한국어 문서를 제공하지만, 공식 DSH의 `LocaleRuntime`이 등록하는 언어는 `zh` / `en`뿐입니다. `ko`를 선택하면 `locale "ko" is not registered` 오류가 납니다. DSH를 fork하여 `LOCALE_IDS`와 `LOCALES` 라벨을 갱신하고 재빌드하면 사용 가능해집니다.
|
|
9
9
|
|
|
10
|
-
이 파일은 **0.11.0** 릴리스
|
|
10
|
+
이 파일은 **0.11.0** 릴리스 노트와 현재 **미출시** 변경 사항의 한국어판입니다. 전체 이력은 [CHANGELOG.md](./CHANGELOG.md)(中文)를 참조하세요.
|
|
11
|
+
|
|
12
|
+
## [미출시]
|
|
13
|
+
|
|
14
|
+
### 추가
|
|
15
|
+
|
|
16
|
+
- **§E 스토리지 스코프 `scope`(가시 범위, `family`와 직교)**. 지금까지 모든 프로젝트가 하나의 기억 저장소를 공유하여 프로젝트 A에서 증류한 `work` 패밀리 기억이 프로젝트 B 대화에서도 검색되었습니다. 새 `scope` 설정(`global` 기본 / `workspace`)을 추가하고 기존 `family`와 **직교**시킵니다——`family`는 "이것이 어떤 내용인가", `scope`는 "어디까지 보여야 하는가"에 답합니다. "`workspace` 모드에서 `work` 패밀리를 워크스페이스 단위로 격리하고 `chat` 패밀리는 기본적으로 전역 유지"는 **기본값이며 유도 규칙이 아닙니다**(개인 기억은 프로젝트를 넘어야 하고, 오염 면은 프로젝트 사이에 있습니다).
|
|
17
|
+
- **제로 드리프트는 비교가 아니라 구조로 보장**: `cfg.scope`가 `workspace`가 아니면 공용 `scopeFilterOf`가 항상 `undefined`(= 필터 없음)를 반환하므로 어떤 호출 지점도 실수로 워크스페이스 식별자를 넘길 수 없습니다. 기존 배포(키 미지정)의 동작은 **바이트 단위로 동일**합니다.
|
|
18
|
+
- **격리는 패밀리 격리와 같은 층**에 놓입니다: 검색 출구뿐 아니라 **중복 제거 후보 회수**도 필터합니다. 후보 풀이 다음 중복 제거 판단을 결정하므로 여기가 워크스페이스를 넘으면 "프로젝트 B에서는 보이지 않는데 이미 프로젝트 A 기억의 향방을 결정한" 기억이 생겨 격리하지 않는 것보다 나쁩니다([`ADR-0008`](./docs/adr/0008-storage-scope-vs-family.md)). 그래프 경로는 **출처 레코드**의 귀속으로 같은 층에서 필터하고, 쓰기 경로(추출 파이프라인 / `memory_add` / `memory_import`)는 동일한 `resolveRecordScope`를 공유합니다.
|
|
19
|
+
- **워크스페이스 식별자는 canonical cwd를 쓰고 호스트 `dsh-workspace`의 `WorkspaceId`(uuid)는 쓰지 않습니다**: §A의 `parentSession`과 같은 header 경로로 동기 획득되며, `inject`를 선언하면 해당 서비스가 없는 호스트에서 트리 전체 로드가 실패하고, 호스트 자신의 구성원 판정도 "session header의 canonical cwd == workspace path"이며, uuid는 "등록된 워크스페이스"를 요구하므로 미등록 디렉터리에서는 격리가 조용히 무효화됩니다. 알려진 경계: **심볼릭 링크는 해석하지 않습니다**——결과는 과잉 격리(안전한 방향)이며 누출이 아닙니다([`ADR-0009`](./docs/adr/0009-workspace-identity-source.md)).
|
|
20
|
+
- **마이그레이션은 귀속만 표시하고 옮기지도 삭제하지도 않습니다**: `l1_records` / `l1_fts`에 `scope` + `workspace_id`를 추가하고 기존 데이터는 `ALTER` 기본값으로 `global`로 분류됩니다——행 수, id, content, created_time이 바이트 단위로 불변입니다.
|
|
21
|
+
- **실행 중 발견한 실제 결함 2건**: ① FTS 재구축 재주입이 scope를 빠뜨리면 **격리가 조용히 지워집니다**(컬럼 수 불일치가 행별 `catch`에 삼켜져 `count=0`의 빈 인덱스가 됩니다). ② 쓰기 측의 **형태 정규화 누락**으로 "기억은 써졌는데 다시는 검색할 수 없는" 상태가 생깁니다(검색 측은 정규화된 소문자 경로를 넘기고 저장 측은 호출자의 대문자 문자열을 그대로 보관하여 문자열 일치가 반드시 어긋납니다). 둘 다 엔드투엔드 테스트와 변이 프로브가 잡아냈습니다.
|
|
22
|
+
- **§F 그래프 노드 벡터 열 `graph_node_vec`(저장과 강등의 토대)**. 그래프는 기존에 어휘 가중 점수만 있어 의미상 동등하지만 자구가 다른 실체("云深处"와 "DeepRobotics")를 상호 해소할 수 없었습니다. `l1_vec`과 **동일 패턴**의 vec0 가상 테이블을 추가하고(동일한 인코딩, 동일한 `float[N] distance_metric=cosine` 선언) 차원은 **기존 능력 탐지 결과를 재사용**하여 두 번째 탐지를 만들지 않습니다.
|
|
23
|
+
- **강등은 내부 `try/catch`에서 소화**: vec0가 없으면 테이블 생성이 예외를 던지고, 그것이 `GraphStore.init`의 바깥 catch에 도달하면 그래프는 "벡터 경로 사용 불가"에서 "**그래프 영역 전체 사용 불가**"로 강등되어 어휘 검색·투영·재정을 끌고 갑니다([`ADR-0011`](./docs/adr/0011-graph-node-vector-storage-and-degradation.md)).
|
|
24
|
+
- **이번 웨이브는 문만 놓았고 생산자와 소비자는 미연결**: 투영 파이프라인은 아직 임베딩을 계산하지 않습니다. 미완료로 기록합니다——§F의 출발점이며 종착점이 아닙니다.
|
|
25
|
+
|
|
26
|
+
- **§B L1 결정 증거 체인(추적 가능성 기반)**. 모든 L1 레코드는 중복 제거 판단의 **결과**지만, 판단 자체는 흔적을 남기지 않았습니다(결과는 보이지만 무엇에 근거했는지는 보이지 않음). 새 테이블 `l1_receipts`가 판단마다 증거를 남깁니다(`run_id` / `record_id` / `kind` / **후보 풀의 순서 있는 sha256 다이제스트** / `decided_at`). 새 도구 **`memory_receipts`** 와 RPC 엔드포인트 **`dsh-memory/receipts`** 로 레코드 단위·배치 단위 **2차원 소급**이 가능합니다. 증거는 이벤트 **이전**에 존재해야 합니다——입력 스냅샷은 **사후에 채워 넣을 수 없기** 때문입니다([ADR-0006](./docs/adr/0006-l1-decision-receipts.md)).
|
|
27
|
+
- 보존 정책은 **run 수** 기준(`RECEIPTS_MAX_RUNS = 1000`)——시간 창으로는 행 수의 상한을 줄 수 없습니다. 또한 트리밍은 **run 단위이며 행 단위가 아닙니다**(행 단위로 자르면 "반쪽 배치"가 생겨 완전해 보이지만 실제로는 누락된 결론을 내놓습니다——"찾을 수 없음"보다 해롭습니다).
|
|
28
|
+
- 레드라인: 트리밍은 **`l1_receipts`만 건드리고 `l1_records`는 절대 건드리지 않습니다**.
|
|
29
|
+
- 실패 격리: 증거는 측면 인프라이므로 쓰기 실패는 `warn`만 남기고 **L1 증류를 절대 중단하지 않습니다**.
|
|
30
|
+
|
|
31
|
+
- **§C 모순 동결(선택, 기본 꺼짐)**. 기존 중복 제거 **결정 어휘**는 `store` / `update` / `merge` / `skip`뿐이어서, "충돌 감지기"가 모순을 감지해도 **LLM이 직접 재정해 기록**했고 **"멈추고 사람에게 묻는" 선택지가 없었습니다**. `conflictFreeze.enabled`를 켜면 어휘에 `conflict`가 추가되어, 양쪽 다 맞아 보여 기계가 판단할 수 없을 때 그 쌍을 `conflict_pending`에 **대기**시킵니다——**새 기억은 평소대로 저장되고 양쪽 내용 모두 바뀌지 않습니다**. 재정은 새 도구 **`memory_resolve_conflict`**(RPC: `dsh-memory/conflict-resolve`)로 하며 결론은 `winner` / `loser` / `both`입니다.
|
|
32
|
+
- **동결은 "쓰기를 막는 것"이 아니라 "자동 재정하지 않는 것"**——전자는 정보를 잃어, 해결하려던 문제보다 나빠집니다.
|
|
33
|
+
- **안전밸브**: `maxPending`(큐 상한) / `timeoutDays`(타임아웃 강등). 의미는 "**새것을 받지 않는다**"이지 "오래된 것을 조용히 지운다"가 아닙니다——자동 결착된 쌍도 **큐의 행으로 남고** `resolution = auto`로 사람의 결론과 구분됩니다.
|
|
34
|
+
- **그래프 측**: 동결된 레코드를 소스로 가진 노드는 `disputed`가 됩니다(기존 상태. 검색 후보에는 남으므로 "평소대로 회수되지만 상태가 보이는" 중간 상태). 이 부여는 **파생 동기화**이며 단방향 플래그가 아닙니다——재정은 분쟁을 **취소**하므로, 단방향이면 해결된 노드가 영원히 `disputed`로 남아 **파생 그래프가 사실과 어긋납니다**.
|
|
35
|
+
- **제로 드리프트**: 꺼져 있을 때 중복 제거 프롬프트는 변경 전과 **바이트 단위로 동일**합니다. 이는 **구조적** 보증(꺼진 경로는 base 상수를 그대로 반환)이며 수동 비교가 아닙니다([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)).
|
|
36
|
+
|
|
37
|
+
### 변경
|
|
38
|
+
|
|
39
|
+
- **새 설정** `conflictFreeze.enabled`(기본 꺼짐) / `conflictFreeze.maxPending`(100) / `conflictFreeze.timeoutDays`(30, `0` = 타임아웃 강등 없음). **새 엔드포인트** `dsh-memory/receipts`와 `dsh-memory/conflict-resolve`(엔드포인트 면 26 → 28).
|
|
40
|
+
- **`L1ReceiptKind`에 `conflict` 추가**. 동작 어휘를 확장할 때는 **그 어휘를 소비하는 모든 지점**(증거 정규화·통계 로그·렌더 문구·schema 설명)을 함께 확인해야 합니다——실행 중 실측: 등록 누락이 있으면 "모델이 **명확히** 판단 불가라고 말한" 것이 `skip_missing`(= "모델이 **답하지 않음**")으로 기록되고, §C의 감사 가능성은 **전적으로** 증거 체인에 의존하므로 감사 결론이 사실과 **정반대**가 됩니다.
|
|
41
|
+
|
|
42
|
+
### 수정
|
|
43
|
+
|
|
44
|
+
- **미인식 액션이 폴백 분기에 조용히 흡수됨**. `pipeline/l1.ts`의 적용 루프는 `store` / `skip`만 명시 분기하고 **나머지 액션은 모두 update/merge 분기로 떨어집니다**. conflict 판단은 설계상 `target_ids`가 없어 `targets=[]`가 되고, "0건을 교체한" 병합 산물로 추가되어 `version`이 **1**로 계산되었습니다. **오류도 없고 데이터 손실도 없으며 흔적은 version 숫자 하나뿐**——즉 conflict가 조용히 merge/update로 강등되고 있었고, 이는 §C가 바로 제거하려던 동작입니다. **수정**: 명시적 `conflict` 분기를 추가하고, 검증 실패 또는 스위치 꺼짐일 때는 **`store`로 폴백**하며 폴백 분기로는 절대 떨어지지 않습니다.
|
|
45
|
+
- **엔드포인트 게이트가 손으로 베낀 목록이었음**. `tests/contract-keys.test.ts`는 실제 등록표를 import하지 않고 복제했기 때문에 두 단언이 복제본 자체와만 일치했습니다. §B가 `receipts`를 추가해 실제 등록표가 27이 되어도 복제본은 26이었고 개수 단언도 `toBe(26)` 그대로여서——**계속 초록**이었습니다. 이번에 `conflict-resolve`를 추가해도 **여전히 초록**이었습니다. **수정**: 로컬 목록이 `MEMORY_ENDPOINTS`와 항목 단위로 일치함을 단언합니다.
|
|
11
46
|
|
|
12
47
|
## [0.11.0] — 2026-09-13
|
|
13
48
|
|
package/CHANGELOG.md
CHANGED
|
@@ -36,8 +36,48 @@
|
|
|
36
36
|
> 沙箱 `spawn EPERM`(含 `ESBUILD_BINARY_PATH` 为何无效)、PowerShell 管道捕获让 `tsc` 错误数变 0、
|
|
37
37
|
> 编码 BOM/乱码/行号漂移,以及 `git amend -m` 清空提交正文等 Git 陷阱。
|
|
38
38
|
|
|
39
|
+
### 新增
|
|
40
|
+
|
|
41
|
+
- **§E 存储作用域 `scope`(可见范围,与 `family` 正交)**。此前所有项目共用一份记忆库:项目 A 沉淀的 `work` 族记忆在项目 B 的会话里同样被召回。新增 `scope` 配置(`global` 默认 / `workspace`),与既有 `family`(内容类型)**正交**——`family` 问"这是什么内容",`scope` 问"它该在多大范围内可见",四象限都存在。「`workspace` 模式下按工作区隔离 `work` 族、`chat` 族默认仍全局」是**默认值不是推导规则**(个人记忆本应跨项目,而污染面恰在项目之间)。
|
|
42
|
+
- **零漂移是构造性的,不是比对出来的**:`cfg.scope` 非 `workspace` 时统一的 `scopeFilterOf` 恒返回 `undefined`(= 不过滤),任何调用点都不可能意外传进一个工作区标识。既有部署(不传该键)行为与改动前**逐字一致**。
|
|
43
|
+
- **隔离落在与族隔离同一层**:不只是检索出口——**去重候选召回**同样过滤。候选池决定新的去重决策,此处跨工作区会产出「项目 B 里看不见、却已经决定了项目 A 记忆去向」的记忆,比不隔离更糟([`ADR-0008`](./docs/adr/0008-storage-scope-vs-family.md))。图谱路按**来源记录**的归属在同层过滤;写入路径(抽取管线 / `memory_add` / `memory_import`)共用同一个 `resolveRecordScope`。
|
|
44
|
+
- **工作区标识取 canonical cwd,而非宿主 `dsh-workspace` 的 `WorkspaceId`(uuid)**:与 §A 的 `parentSession` 同一条 header 通路、同步可得;声明 `inject` 会让缺该服务的宿主整树加载失败;宿主自己的成员判据本就是「session header 的 canonical cwd == workspace path」;uuid 需要"已注册的工作区",未注册目录下隔离会静默失效。已知边界:**不解析符号链接**,后果是过度隔离(安全方向)而非泄漏([`ADR-0009`](./docs/adr/0009-workspace-identity-source.md))。
|
|
45
|
+
- **迁移只标注归属,不搬运不删除**:`l1_records` / `l1_fts` 各补 `scope` + `workspace_id` 两列,存量数据由 `ALTER` 的 `DEFAULT` 直接标为 `global`——条数、id、content、created_time 逐字不变。
|
|
46
|
+
- **执行期抓到两处真问题**:① **FTS 重建回灌漏带 scope 会静默抹平隔离**(`backfillL1Fts` 参数与 insert 语句不对齐时会被它自己的逐行 `catch` 吞掉,症状是 `count=0` 的空索引);② **写入侧漏做形态归一 → 记忆写进去却再也查不出来**(检索侧传归一后的小写路径,写入侧原样存调用方的大写字符串,字符串相等判定必然落空)。两者均由端到端测试与变异探针抓出并修复。
|
|
47
|
+
- **§F 图谱节点向量列 `graph_node_vec`(存储与降级地基)**。图谱此前只有词法加权打分,无向量列——"云深处"与"DeepRobotics"这类**语义等价但字面不同**的实体无法互相消解。新增与 `l1_vec` **同模式**的 vec0 虚拟表(同一份编码、同一种 `float[N] distance_metric=cosine` 声明),维度**复用**既有能力探测结果,不新增一套。
|
|
48
|
+
- **降级内层消化**:vec0 缺失时建表会抛,若冒到 `GraphStore.init` 外层那层 `catch`,图谱会从"向量路不可用"退化成"**整个图谱域不可用**"——词法检索、投影、裁决全部陪葬。故向量列自带窄 `try/catch`。接口在停用态一律 no-op 返回,调用方无需判断能力位([`ADR-0011`](./docs/adr/0011-graph-node-vector-storage-and-degradation.md))。
|
|
49
|
+
- **本波只有门,没有生产者与消费者**:投影管线尚未接线去算嵌入。如实登记为未完成——它是 §F 的起点而非终点。
|
|
50
|
+
- **§B L1 决策凭证链(可追溯性基础设施)**。`memory.db` 里每条 L1 记录都是**去重决策的结果**(store / update / merge / skip),但决策本身不留痕——事后只能看到"结果长这样",看不到"当时基于什么做的判断"。新增 `l1_receipts` 表:每次去重决策留一条凭证(`run_id` / `record_id` / `kind` / **候选池有序序列的 sha256 摘要** `input_digest` / `decided_at`),配套新工具 **`memory_receipts`** 与 RPC 端点 **`dsh-memory/receipts`**,按记录或按批次**双维回溯**(同给为 AND)。凭证必须在事件**之前**存在——输入快照**无法事后补录**,故本能力前置落地、不设症状触发条件([`ADR-0006`](./docs/adr/0006-l1-decision-receipts.md))。
|
|
51
|
+
- **`input_digest` 三项刻意设计**:**顺序敏感**(候选池是有序的,"当时看到哪些候选、按什么序"正是要复原的输入)、**重复不折叠**(候选池里的重复本身是事实)、**长度前缀编码**(否则 `['a|b']` 与 `['a','b']` 会碰撞——序列化不是单射,摘要就失去指纹意义)。
|
|
52
|
+
- **保留策略**:按 **run 数**裁剪(`RECEIPTS_MAX_RUNS = 1000`)——时间窗口**给不出行的上界**(阈值与写入速率无关,高频用户 90 天能写进任意多行,无界增长只是被**推迟**);裁剪**粒度是 run 而非行**——按行裁剪会切出"半截批次",让"这一轮都判了什么"给出**看似完整、实则遗漏**的结论,危害**高于查不到**。
|
|
53
|
+
- **红线**:裁剪**只碰 `l1_receipts`,绝不碰 `l1_records`**——前者是可丢弃的观测数据,后者是用户的事实源,为省几 MB 而波及记忆本体是把容量优化做成了数据丢失。
|
|
54
|
+
- **失败隔离**:凭证是旁路设施,写失败只记 `warn`、**绝不中断 L1 蒸馏**。
|
|
55
|
+
|
|
56
|
+
- **§C 矛盾冻结(可选,默认关)**。此前去重**决策词表**只有 `store` / `update` / `merge` / `skip`——"冲突检测器"(`CONFLICT_DETECTION_SYSTEM_PROMPT`)检测到矛盾后**由 LLM 直接裁决落盘**(`update` 覆盖或 `merge` 合并),**没有"停下来等人裁决"这个选项**。开启 `conflictFreeze.enabled` 后词表新增 `conflict` 动作:LLM 判定"两边都像是对的、机器判不了"时,把冲突对**停放**到 `conflict_pending` 队列——**新记忆照常入库,双方内容都不被改写**,由新工具 **`memory_resolve_conflict`**(RPC:`dsh-memory/conflict-resolve`)交人裁决,结论 `winner` / `loser` / `both`。
|
|
57
|
+
- **冻结不是"拦住写入",而是"不自动裁决"**——实现成前者会丢信息,比它想解决的问题更糟。
|
|
58
|
+
- **安全阀**:`maxPending`(队列上限)/ `timeoutDays`(超时降级)。语义是"**不收新的**"而非"偷偷删旧的":被自动了结的对**仍写入队列留痕**,`resolution` 记为 `auto` 以区别于人工结论。没有安全阀的两个后果都是确定的:队列无界增长;"两条互相矛盾的记忆长期并列召回"永久留在库里。
|
|
59
|
+
- **图谱侧**:被冻结的记录,其**来源命中的图谱节点**标为 `disputed`(复用既有状态;该状态仍在检索候选内,是"照常召回、但状态可见"的中间态)。该标记是**派生同步**而非单向标记——裁决会**撤销**争议,单向标记会让已裁决的节点永远停在 `disputed`,那是**派生图谱在说谎**。
|
|
60
|
+
- **零漂移**:关闭时去重 prompt 与改动前**逐字节相同**——这是**构造性**保证(关闭态直接 `return base`),不是人工比对出来的([`ADR-0010`](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md))。
|
|
61
|
+
|
|
62
|
+
### 变更
|
|
63
|
+
|
|
64
|
+
- **新增配置** `conflictFreeze.enabled`(默认关)/ `conflictFreeze.maxPending`(100)/ `conflictFreeze.timeoutDays`(30,`0` = 不做超时降级);**新增端点** `dsh-memory/receipts` 与 `dsh-memory/conflict-resolve`(端点面 26 → 28)。
|
|
65
|
+
- **`L1ReceiptKind` 新增 `conflict`**。扩词表时必须同步检查**所有消费该词表的地方**(凭证归一、统计日志、渲染文案、schema 描述)——执行期实测:漏登记会把"模型**明确**说判不了"记成 `skip_missing`(= "模型**没答**"),而 §C 的裁决可审计性**完全**依赖凭证链,审计结论与事实**正好相反**(比缺一条凭证更糟:缺了是"查不到",错了是"查到了错的")。
|
|
66
|
+
- **`GraphStore.markSourcesDisputed`(单向标记)→ `syncDisputed`(派生同步)**:`active` 且来源命中 → `disputed`;`disputed` 且来源不再命中 → 复原 `active`;`archived` 墓碑不动。管线侧传的是**当前全部未裁决对的记录集**,而非本轮新增那一对。
|
|
67
|
+
- **裁决顺序刻意为之**:先打 `resolved_at` 再退场败方——反过来的话,"记录已消失、队列里那条仍在待裁决"要等人再点一次才发现无据可依;打标用 `WHERE resolved_at = ''`,**二次裁决不覆盖第一次结论**。
|
|
68
|
+
|
|
69
|
+
### 测试
|
|
70
|
+
|
|
71
|
+
- **§B 新增 5 个测试文件**(凭证建表与摘要、写入点与失败隔离、保留策略、双维查询、端到端回溯),含**变异探针**:把裁剪表换成 `l1_records` 后红线用例确实失败。
|
|
72
|
+
- **§C 新增 7 个测试文件**(词表 / 建表 / 开关 / 冻结语义 / 安全阀 / 裁决 / 闭环),均按 TDD 先观察 RED。三条**区分性**判据值得一提:`version===0`(区分 store 语义与 merge/update 语义)、端到端抓真管线实际送出的 prompt(静态函数全绿**证明不了**管线把开关传了下去)、`both` 分支变异后用例确实变红。
|
|
73
|
+
- 全量 **34 文件 / 341 用例**;CI 七步链(`typecheck` / `test` / `lint` / `build` / `build:smoke` / `smoke` / `verify-catalog`)全绿。
|
|
74
|
+
- 闭环实测留档:真 `runExtraction` × 2(仅 LLM 传输层打桩)→ 真裁决端点,原始 JSON 存档于计划域 `evidence/`。
|
|
75
|
+
|
|
39
76
|
### 修复
|
|
40
77
|
|
|
78
|
+
- **未识别动作被兜底分支静默承接**。`pipeline/l1.ts` 的应用循环只对 `store` / `skip` 显式分支,**其余动作一律落到 update/merge 分支**。conflict 决策按设计不带 `target_ids`,于是 `targets=[]` → 记录被当成"替换了 0 条"的合并产物追加,`version` 被算成 `1`。**不报错、不丢数据,唯一痕迹是一个 version 数字**——即"conflict 被静默降级为 merge/update",正是 §C 要消灭的行为。**修复**:显式 `conflict` 分支;校验不过或开关关闭时**回落 `store`**,绝不落到兜底分支。
|
|
79
|
+
- **端点门禁是手抄副本**。`tests/contract-keys.test.ts` 的 `ENDPOINTS` 是真实注册表(`src/stats.ts` 的 `MEMORY_ENDPOINTS`)的**手抄副本**,两条断言只在该副本内部自洽(`ENDPOINTS.length === 26`)。§B 新增 `dsh-memory/receipts` 使真实注册表升到 27 时,副本**没有跟着更新**,而数量断言仍停在 26——于是"断言与副本一致、副本与事实不符"让它**一路绿灯**;本次新增 `conflict-resolve` 后**依然全绿**。**门禁测的是自己的影子**:被测系统换成什么都不会变红。**修复**:新增 `expect([...ENDPOINTS].sort()).toEqual([...MEMORY_ENDPOINTS].sort())`——本地清单必须与唯一事实源**逐项一致**,数量断言保留作为变更时的显式路标。
|
|
80
|
+
|
|
41
81
|
- **插件树加载失败:工具输出 schema 使用了 DSL 不支持的 `nullable` 关键字**(回归修复,会导致 DSH 完全无法启动)。`memory_ruminate` 与 `memory_ruminate_status` 的输出 schema 在 `startedAt`/`finishedAt`/`error` 上声明了 `nullable: true`,而 DSH 的 value schema DSL 只接受一组白名单作者键(`description`/`title`/`default`/`examples`/`required`/`enum`/`const` 及各类型的 `type`/`properties`/`additionalProperties`/`items`/`oneOf`)。`defineTool()` 编译 schema 时抛 `JsonSchemaError: schema.properties.startedAt.nullable is not supported by the value schema DSL`,loader 随之判定 `dsh-memory (dsh-prime-memory)` 条目加载失败,整个插件树 apply 中止,进程以未捕获异常退出。
|
|
42
82
|
**修复**:删除 4 处 `nullable: true`。语义不变——该 DSL 中属性默认即为可选,仅显式 `required: true` 才必填;且运行时校验对 `undefined` 做跳过处理,`execute` 返回的 `startedAt: undefined` 依旧合法。校验步骤:`tsc` 编译通过 + dist 产物已无该关键字 + 新增工具注册回归用例。
|
|
43
83
|
- **反刍(ruminate)RPC 端点全部不可达:控制器从未注入端点 deps**。`dsh-memory/ruminate-status` 恒返 `{supported:false,running:false,phase:'idle'}`,`ruminate-start`/`ruminate-cancel` 恒抛「反刍控制器未初始化」。根因是 `EndpointDeps.ruminate` 虽已声明、三个端点实现也已写好,但 `registerMemoryRpc` 组装 deps 实参时未把控制器注入,`deps.ruminate` 恒为 `undefined`,端点被永久钉在降级分支上。
|
package/README.en.md
CHANGED
|
@@ -325,6 +325,10 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
|
|
|
325
325
|
| `tokenCost.retentionDays` | `365` | Retention (days) for distillation cost details (the `token_cost` table); rows older than this are rolled away on write. `0` = keep forever. Also the upper bound of the cost dashboard's "last N days" window |
|
|
326
326
|
| `tools` | `true` | Whether to register model-callable memory tools |
|
|
327
327
|
| `benchControl` | `false` | Register the in-process bench control service (rebuild trigger / session-mode setting / distillation usage snapshot — used by the benchmark's lifecycle track). Off by default — zero surface in production deployments; do not enable casually |
|
|
328
|
+
| `scope` | `global` | **Storage scope** (visibility range): `global` (default) = visible across workspaces; `workspace` = isolate the **`work` family** per workspace. **Orthogonal** to `family` (content type) — the two axes answer different questions, and all four quadrants exist (`chat×global` / `chat×workspace` / `work×global` / `work×workspace`). The `chat` family stays global by default: personal memories are meant to cross projects. **With the default `global`, behavior is byte-identical to before this setting existed** — existing single-root data is all graded `global`, and migration only labels ownership, never moves or deletes ([ADR-0008](./docs/adr/0008-storage-scope-vs-family.md) / [ADR-0009](./docs/adr/0009-workspace-identity-source.md)). When no workspace can be resolved, it falls back to `global` (no throw, no startup block) |
|
|
329
|
+
| `conflictFreeze.enabled` | `false` | Master switch for **conflict freeze**. When on, the dedup action vocabulary gains `conflict`: if the model judges that "both sides look right and it cannot tell", it **no longer auto-`update`s or `merge`s** — the pair is **parked** in a pending queue instead. The new memory is still stored, and **neither side is rewritten**; a human adjudicates via the `memory_resolve_conflict` tool. While off, the dedup prompt is **byte-identical** to before the feature existed (zero drift). Off by default: freezing spends human attention, so it must not be on by default ([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)) |
|
|
330
|
+
| `conflictFreeze.maxPending` | `100` | Pending-queue cap. Once the number of unresolved pairs reaches it, new conflicts are **no longer parked** and are settled on the spot using the LLM's winner/loser (the pair is **still written to the queue** for the audit trail, with `resolution` = `auto` to distinguish it from a human verdict). The semantics are "**stop taking new ones**", not "quietly delete old ones" — that is what makes the bound structural rather than a promise |
|
|
331
|
+
| `conflictFreeze.timeoutDays` | `30` | Timeout fallback (days): pairs left unresolved longer than this are settled automatically at the start of the **next distillation run** (again recorded as `resolution=auto`). `0` = **no** timeout fallback (explicitly off, not "everything expires immediately"). Without a safety valve, "two contradictory memories recalled side by side forever" stays in the database permanently |
|
|
328
332
|
|
|
329
333
|
### Distillation fallback chain & slow-TTFT models
|
|
330
334
|
|
|
@@ -440,6 +444,30 @@ fully re-derived from the fact source via "Rebuild memories".
|
|
|
440
444
|
|
|
441
445
|
## Credits
|
|
442
446
|
|
|
447
|
+
The direct upstream of this repository is
|
|
448
|
+
[JunNanLYS/dsh-layered-memory](https://github.com/JunNanLYS/dsh-layered-memory) — the layered
|
|
449
|
+
distillation memory plugin for DSH. Thanks to **JunNanLYS** for open-sourcing it: this repository
|
|
450
|
+
rewrites the implementation layer on top of it (the first commit `0b506b8` is
|
|
451
|
+
"净室重写清场 — remove the old implementation and build artifacts"), while the documentation,
|
|
452
|
+
images and module layout are carried over from upstream. Compared with upstream, this repository
|
|
453
|
+
adds 12 agent-facing memory tools (high-privilege writes `memory_add` / `memory_delete` /
|
|
454
|
+
`memory_import`, ruminate controls `memory_ruminate`, the memory graph
|
|
455
|
+
`memory_search_graph` / `memory_expand_graph_node`, decision-receipt backtracking
|
|
456
|
+
`memory_receipts`, and conflict adjudication `memory_resolve_conflict`), the `skills/memport`
|
|
457
|
+
cross-tool memory transfer, and storefront screenshot declarations.
|
|
458
|
+
|
|
459
|
+
Two of those tools exist for **traceability**:
|
|
460
|
+
|
|
461
|
+
- `memory_receipts` — trace **where a memory came from**. Every L1 dedup decision leaves a
|
|
462
|
+
receipt (a digest of the candidate pool it saw + the verdict), so you can ask "which run did
|
|
463
|
+
this record come from, and what candidates did it see?" per record, or "what did that batch
|
|
464
|
+
decide?" per run. Receipts must exist *before* the event — input snapshots cannot be
|
|
465
|
+
backfilled ([ADR-0006](./docs/adr/0006-l1-decision-receipts.md)).
|
|
466
|
+
- `memory_resolve_conflict` — adjudicate conflict pairs parked by **conflict freeze** (see the
|
|
467
|
+
`conflictFreeze.*` settings). The verdict is `winner` / `loser` / `both`: picking a side
|
|
468
|
+
retires the other from retrieval, while `both` means the two records are really independent
|
|
469
|
+
facts and both are kept.
|
|
470
|
+
|
|
443
471
|
The core memory capabilities (layered distillation pipeline, prompt design, and the
|
|
444
472
|
dual-write storage architecture) are modeled after **MemoryCore** from
|
|
445
473
|
[TencentCloud/TencentDB-Agent-Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory).
|
package/README.ja.md
CHANGED
|
@@ -269,6 +269,10 @@ node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600
|
|
|
269
269
|
| `tokenCost.retentionDays` | `365` | 蒸留コスト明細保持日数。`0` = 永久 |
|
|
270
270
|
| `tools` | `true` | モデル呼び出し可能な記憶ツールを登録するか |
|
|
271
271
|
| `benchControl` | `false` | ベンチ制御サービス登録(既定オフ) |
|
|
272
|
+
| `scope` | `global` | **ストレージスコープ**(可視範囲):`global`(既定)= ワークスペースをまたいで可視;`workspace` = **`work` 族**をワークスペース単位で隔離。`family`(内容タイプ)と**直交**——二軸は別の問いに答えるもので、四象限すべてが存在します(`chat×global` / `chat×workspace` / `work×global` / `work×workspace`)。`chat` 族は既定でグローバルのまま:個人的な記憶はプロジェクトをまたぐべきものです。**既定の `global` では、本設定が存在しなかったときと挙動がバイト単位で同一**——既存の単一根データはすべて `global` に分類され、移行は帰属を記すだけで移動も削除もしません([ADR-0008](./docs/adr/0008-storage-scope-vs-family.md) / [ADR-0009](./docs/adr/0009-workspace-identity-source.md))。ワークスペースを解決できない場合は `global` にフォールバックします(例外を投げず、起動も妨げません) |
|
|
273
|
+
| `conflictFreeze.enabled` | `false` | **矛盾凍結**の総スイッチ。有効にすると重複排除の決定語彙に `conflict` が加わります——LLM が「どちらも正しそうで機械には判定できない」と判断したとき、**自動で `update` 上書きや `merge` 統合を行わず**、そのペアを**待機キューに停める**。新しい記憶は通常どおり保存され、**双方の内容は書き換えられません**。裁定は `memory_resolve_conflict` ツールで人間が行います。無効時、重複排除プロンプトは機能追加前と**バイト単位で同一**(ゼロドリフト)。既定オフ:凍結は人の注意力を消費するため既定で全開にはできません([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)) |
|
|
274
|
+
| `conflictFreeze.maxPending` | `100` | 待機キューの上限。未裁定数が上限に達すると、新しい衝突は**もう停めず**、その場で LLM の winner/loser により自動決着します(そのペアも**キューの行として残ります**。`resolution` は `auto` となり人間の結論と区別されます)。意味は「**新しいものを受け取らない**」であり「古いものを黙って消す」ではありません——これにより上限が構造的な保証になります |
|
|
275
|
+
| `conflictFreeze.timeoutDays` | `30` | タイムアウト降格(日)。これを超えて停まったままのペアは**次回の蒸留の冒頭**で自動決着します(同様に `resolution=auto`)。`0` = タイムアウト降格を**行わない**(明示的な無効化であり「即座に全部期限切れ」ではありません)。安全弁が無いと「互いに矛盾する 2 件が永久に並んで検索される」状態が庫内に残り続けます |
|
|
272
276
|
|
|
273
277
|
### 蒸留フォールバックチェーンと遅い TTFT モデル
|
|
274
278
|
|
|
@@ -299,6 +303,27 @@ dsh ホストはプラグインのログをコンソールへ出力します。
|
|
|
299
303
|
|
|
300
304
|
## 謝辞
|
|
301
305
|
|
|
306
|
+
本リポジトリの直接の上流は [JunNanLYS/dsh-layered-memory](https://github.com/JunNanLYS/dsh-layered-memory)
|
|
307
|
+
——DSH 向けの階層的蒸留記憶プラグインです。原作者 **JunNanLYS** が公開してくださったことに感謝します。
|
|
308
|
+
本リポジトリはその上で実装層を書き直しました(最初のコミット `0b506b8` は
|
|
309
|
+
「净室重写清场 — 旧実装とビルド成果物の削除」)。ドキュメント・画像・モジュール構成は上流から引き継いでいます。
|
|
310
|
+
上流と比べて本リポジトリが追加したのは、Agent 向けの 12 個の記憶ツール(高権限書き込み
|
|
311
|
+
`memory_add` / `memory_delete` / `memory_import`、反芻制御 `memory_ruminate` シリーズ、記憶グラフ
|
|
312
|
+
`memory_search_graph` / `memory_expand_graph_node`、決定証跡の遡及 `memory_receipts`、矛盾の裁定
|
|
313
|
+
`memory_resolve_conflict`)、`skills/memport` によるツール横断の記憶移行、
|
|
314
|
+
およびストア用スクリーンショット宣言です。
|
|
315
|
+
|
|
316
|
+
このうち 2 つは**追跡可能性**のためのツールです:
|
|
317
|
+
|
|
318
|
+
- `memory_receipts` ——「この記憶が**どこから来たか**」を遡ります。L1 の重複排除判断ごとに
|
|
319
|
+
証跡(判断時に見ていた**候補プールのダイジェスト** + 結論)を残すので、記録単位で
|
|
320
|
+
「どのバッチ由来か、どんな候補を見ていたか」を、バッチ単位で「その回は何を判断したか」を問えます。
|
|
321
|
+
証跡はイベントの**前に**存在しなければなりません——入力スナップショットは後から補填できないためです
|
|
322
|
+
([ADR-0006](./docs/adr/0006-l1-decision-receipts.md))。
|
|
323
|
+
- `memory_resolve_conflict` —— **矛盾凍結**が待機させた衝突ペアを裁定します(`conflictFreeze.*` 設定)。
|
|
324
|
+
結論は `winner` / `loser` / `both`:どちらかを真と判定すれば他方は検索から退場し、
|
|
325
|
+
`both` は両者が実は独立した事実であるとして両方残します。
|
|
326
|
+
|
|
302
327
|
核心記憶能力(階層的蒸留パイプライン、プロンプト設計、二重書き込みストレージ)は [TencentCloud/TencentDB-Agent-Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory) の **MemoryCore** を参考にしています。
|
|
303
328
|
|
|
304
329
|
## License
|
package/README.ko.md
CHANGED
|
@@ -269,6 +269,10 @@ node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600
|
|
|
269
269
|
| `tokenCost.retentionDays` | `365` | 증류 비용 명세 보존 일수. `0` = 영구 |
|
|
270
270
|
| `tools` | `true` | 모델 호출 가능한 기억 도구 등록 여부 |
|
|
271
271
|
| `benchControl` | `false` | 벤치 제어 서비스 등록(기본 꺼짐) |
|
|
272
|
+
| `scope` | `global` | **스토리지 스코프**(가시 범위): `global`(기본) = 워크스페이스를 넘어 가시; `workspace` = **`work` 패밀리**를 워크스페이스 단위로 격리. `family`(내용 유형)와 **직교**——두 축은 서로 다른 질문에 답하며 네 사분면이 모두 존재합니다(`chat×global` / `chat×workspace` / `work×global` / `work×workspace`). `chat` 패밀리는 기본적으로 전역 유지: 개인 기억은 프로젝트를 넘어야 합니다. **기본 `global`에서는 이 설정이 없던 때와 동작이 바이트 단위로 동일**——기존 단일 루트 데이터는 모두 `global`로 분류되고, 마이그레이션은 귀속만 표시할 뿐 옮기지도 삭제하지도 않습니다([ADR-0008](./docs/adr/0008-storage-scope-vs-family.md) / [ADR-0009](./docs/adr/0009-workspace-identity-source.md)). 워크스페이스를 해석할 수 없으면 `global`로 폴백합니다(예외를 던지지 않고 시작을 막지도 않습니다) |
|
|
273
|
+
| `conflictFreeze.enabled` | `false` | **모순 동결** 총 스위치. 켜면 중복 제거 결정 어휘에 `conflict`가 추가됩니다——LLM이 "양쪽 다 맞아 보여 기계가 판단할 수 없다"고 판단하면 **자동으로 `update` 덮어쓰기나 `merge` 병합을 하지 않고** 그 쌍을 **대기 큐에 세워 둡니다**. 새 기억은 평소대로 저장되고 **양쪽 내용 모두 바뀌지 않습니다**. 재정은 `memory_resolve_conflict` 도구로 사람이 합니다. 꺼져 있을 때 중복 제거 프롬프트는 기능 추가 전과 **바이트 단위로 동일**합니다(제로 드리프트). 기본 꺼짐: 동결은 사람의 주의를 소모하므로 기본으로 전면 개방할 수 없습니다([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)) |
|
|
274
|
+
| `conflictFreeze.maxPending` | `100` | 대기 큐 상한. 미재정 수가 상한에 도달하면 새 충돌은 **더 이상 세워 두지 않고** 그 자리에서 LLM의 winner/loser로 자동 결착합니다(그 쌍도 **큐의 행으로 남습니다**. `resolution`은 `auto`가 되어 사람의 결론과 구분됩니다). 의미는 "**새것을 받지 않는다**"이지 "오래된 것을 조용히 지운다"가 아닙니다——이로써 상한이 구조적 보증이 됩니다 |
|
|
275
|
+
| `conflictFreeze.timeoutDays` | `30` | 타임아웃 강등(일). 이보다 오래 미재정으로 남은 쌍은 **다음 증류 회차의 맨 앞**에서 자동 결착합니다(역시 `resolution=auto`). `0` = 타임아웃 강등을 **하지 않음**(명시적 비활성이며 "즉시 전부 만료"가 아닙니다). 안전밸브가 없으면 "서로 모순되는 두 기억이 영구히 나란히 검색되는" 상태가 저장소에 남습니다 |
|
|
272
276
|
|
|
273
277
|
### 증류 폴백 체인과 느린 TTFT 모델
|
|
274
278
|
|
|
@@ -299,6 +303,27 @@ dsh 호스트는 플러그인 로그를 콘솔로 출력합니다. 플러그인
|
|
|
299
303
|
|
|
300
304
|
## 감사
|
|
301
305
|
|
|
306
|
+
이 저장소의 직접적인 업스트림은 [JunNanLYS/dsh-layered-memory](https://github.com/JunNanLYS/dsh-layered-memory)
|
|
307
|
+
——DSH용 계층적 증류 기억 플러그인입니다. 원작자 **JunNanLYS**께 공개해 주신 데 감사드립니다.
|
|
308
|
+
이 저장소는 그 위에서 구현 계층을 다시 작성했습니다(첫 커밋 `0b506b8`은
|
|
309
|
+
「净室重写清场 — 기존 구현과 빌드 산출물 제거」). 문서·이미지·모듈 구조는 업스트림에서 이어받았습니다.
|
|
310
|
+
업스트림과 비교해 이 저장소가 추가한 것은 Agent용 기억 도구 12개(고권한 쓰기
|
|
311
|
+
`memory_add` / `memory_delete` / `memory_import`, 반추 제어 `memory_ruminate` 시리즈, 기억 그래프
|
|
312
|
+
`memory_search_graph` / `memory_expand_graph_node`, 결정 증거 추적 `memory_receipts`, 모순 재정
|
|
313
|
+
`memory_resolve_conflict`), `skills/memport` 도구 간 기억 이전,
|
|
314
|
+
그리고 스토어 스크린샷 선언입니다.
|
|
315
|
+
|
|
316
|
+
이 중 두 도구는 **추적 가능성**을 위한 것입니다:
|
|
317
|
+
|
|
318
|
+
- `memory_receipts` —— "이 기억이 **어디서 왔는지**"를 추적합니다. L1 중복 제거 판단마다
|
|
319
|
+
증거(판단 당시 본 **후보 풀 다이제스트** + 결론)를 남기므로, 레코드 단위로는
|
|
320
|
+
"어느 배치에서 나왔고 어떤 후보를 봤는지", 배치 단위로는 "그 회차가 무엇을 판단했는지"를 물을 수 있습니다.
|
|
321
|
+
증거는 이벤트 **이전**에 존재해야 합니다——입력 스냅샷은 사후에 채워 넣을 수 없기 때문입니다
|
|
322
|
+
([ADR-0006](./docs/adr/0006-l1-decision-receipts.md)).
|
|
323
|
+
- `memory_resolve_conflict` —— **모순 동결**이 대기시킨 충돌 쌍을 재정합니다(`conflictFreeze.*` 설정).
|
|
324
|
+
결론은 `winner` / `loser` / `both`: 한쪽을 참으로 판정하면 다른 쪽은 검색에서 퇴장하고,
|
|
325
|
+
`both`는 둘이 실은 독립된 사실임을 뜻해 둘 다 남깁니다.
|
|
326
|
+
|
|
302
327
|
핵심 기억 능력(계층적 증류 파이프라인, 프롬프트 설계, 이중 기록 저장소)은 [TencentCloud/TencentDB-Agent-Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory)의 **MemoryCore**를 참고했습니다.
|
|
303
328
|
|
|
304
329
|
## License
|
package/README.md
CHANGED
|
@@ -337,6 +337,10 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
|
|
|
337
337
|
| `tokenCost.retentionDays` | `365` | 蒸馏成本明细(token\_cost 表)保留天数,写入时滚动清理更早行;`0` = 永久保留。成本看板「近 N 天」窗口上限同此值 |
|
|
338
338
|
| `tools` | `true` | 是否注册模型可调用的记忆工具 |
|
|
339
339
|
| `benchControl` | `false` | 注册 bench 控制服务(进程内 rebuild 触发/会话档位设置/蒸馏用量快照,供基准 lifecycle 赛道)。默认关——生产部署零表面积,勿随意开启 |
|
|
340
|
+
| `scope` | `global` | **存储作用域**(可见范围):`global`(默认)= 跨工作区可见;`workspace` = 按工作区隔离 **`work` 族**。与 `family`(内容类型)**正交**——两轴问的是不同问题,四象限都存在(`chat×global` / `chat×workspace` / `work×global` / `work×workspace`)。`chat` 族默认仍全局:个人记忆本应跨项目。**默认 `global` 时行为与未引入该配置逐字一致**——既有单根数据全部归 `global`,迁移只标注归属、不搬不删([ADR-0008](./docs/adr/0008-storage-scope-vs-family.md) / [ADR-0009](./docs/adr/0009-workspace-identity-source.md))。拿不到会话工作区时回落 `global`(不抛、不阻断) |
|
|
341
|
+
| `conflictFreeze.enabled` | `false` | **矛盾冻结**总开关。开启后去重决策词表多出 `conflict` 动作:LLM 判定"两边都像是对的、机器判不了"时**不再自动 `update` 覆盖或 `merge` 合并**,而是把这一对**停放**到待裁决队列——新记忆照常入库,**双方内容都不被改写**,由新增的 `memory_resolve_conflict` 工具交人裁决。关闭时去重 prompt 与未开启该功能时**逐字一致**(零漂移)。默认关:冻结消耗人的注意力,不可默认全开([ADR-0010](./docs/adr/0010-conflict-freeze-default-off-and-timeout.md)) |
|
|
342
|
+
| `conflictFreeze.maxPending` | `100` | 待裁决队列上限。未裁决数达上限时新的冲突**不再停放**,当场按 LLM 给出的 winner/loser 自动了结(该对**仍写入队列留痕**,`resolution` 记为 `auto` 以区别于人工结论)。语义是"**不收新的**",不是"偷偷删旧的"——有界性由此成立,而不会丢掉人还没看过的裁决请求 |
|
|
343
|
+
| `conflictFreeze.timeoutDays` | `30` | 超时降级(天):停放超过该天数的待裁决对,在**下一轮蒸馏开头**被自动了结(同上,留痕为 `resolution=auto`)。`0` = **不做**超时降级(显式关闭,而非"立刻全部超时")。没有安全阀时,"两条互相矛盾的记忆长期并列召回"会永久留在库里 |
|
|
340
344
|
|
|
341
345
|
### 蒸馏回退链与慢 TTFT 模型
|
|
342
346
|
|
|
@@ -416,6 +420,24 @@ fsync),断电等极端崩溃最多丢最后一小段尾部,检索库可用
|
|
|
416
420
|
|
|
417
421
|
## 致谢
|
|
418
422
|
|
|
423
|
+
本仓库的直接上游是 [JunNanLYS/dsh-layered-memory](https://github.com/JunNanLYS/dsh-layered-memory)
|
|
424
|
+
——DSH 侧的分层蒸馏记忆插件。感谢原作者 **JunNanLYS** 开放该项目:本仓库在其基础上重写了实现层
|
|
425
|
+
(首个提交 `0b506b8` 即「净室重写清场——移除旧实现与构建产物」),文档、图片与模块架构沿用上游。
|
|
426
|
+
相对上游,本仓库新增了面向 Agent 的 12 个记忆工具(含高权限写入 `memory_add` / `memory_delete` /
|
|
427
|
+
`memory_import`、反刍控制 `memory_ruminate` 系列、记忆图谱 `memory_search_graph` /
|
|
428
|
+
`memory_expand_graph_node`、决策凭证回溯 `memory_receipts`、矛盾裁决 `memory_resolve_conflict`)、
|
|
429
|
+
`skills/memport` 跨工具记忆搬运,以及商店截图声明。
|
|
430
|
+
|
|
431
|
+
其中两个工具服务于**可追溯性**:
|
|
432
|
+
|
|
433
|
+
- `memory_receipts` —— 回溯"这条记忆**怎么来的**"。每次 L1 去重决策都留一条凭证
|
|
434
|
+
(决策当时的**候选池输入摘要** + 结论),可按记录问"出自哪一轮、当时看到什么候选池",
|
|
435
|
+
或按批次问"那一轮都判了什么"。凭证必须在事件**之前**存在——输入快照无法事后补录
|
|
436
|
+
([ADR-0006](./docs/adr/0006-l1-decision-receipts.md))。
|
|
437
|
+
- `memory_resolve_conflict` —— 裁决**矛盾冻结**停放的冲突对(见 `conflictFreeze.*` 配置)。
|
|
438
|
+
结论 `winner` / `loser` / `both`:判某一方为真则另一方从检索中退场,判 `both` 表示
|
|
439
|
+
两者其实是各自独立的事实、都保留。
|
|
440
|
+
|
|
419
441
|
记忆核心能力(分层蒸馏管线、Prompt 设计、双写存储架构)参考自
|
|
420
442
|
[TencentCloud/TencentDB-Agent-Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory)
|
|
421
443
|
项目中的 **MemoryCore**,感谢原项目开放的设计与实现。
|
package/dist/client.js
CHANGED
|
@@ -470,6 +470,8 @@ var __defProp = Object.defineProperty;
|
|
|
470
470
|
".dsh-mem-rb-card {",
|
|
471
471
|
" border: 1px solid var(--dsh-mem-border); border-radius: 10px; background: var(--dsh-mem-bg-card);",
|
|
472
472
|
" box-shadow: var(--dsh-mem-shadow-card); padding: 12px 14px; margin-bottom: 14px; font-size: 13px;",
|
|
473
|
+
// 剪掉行内溢出的固定宽控件(如维度输入框),防其顶破卡片圆角边界
|
|
474
|
+
" overflow: hidden;",
|
|
473
475
|
"}",
|
|
474
476
|
".dsh-mem-rb-bar { height: 8px; border-radius: 4px; overflow: hidden; flex: 1; background: var(--dsh-mem-track); }",
|
|
475
477
|
".dsh-mem-rb-fill { height: 100%; border-radius: 4px; background: var(--dsh-mem-accent-fill); transition: width .4s ease; }",
|
|
@@ -2037,11 +2039,24 @@ var __defProp = Object.defineProperty;
|
|
|
2037
2039
|
var import_react9 = require("react");
|
|
2038
2040
|
|
|
2039
2041
|
// client/src/rpc.ts
|
|
2040
|
-
function
|
|
2041
|
-
return (endpoint
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2042
|
+
function shortMethod(endpoint) {
|
|
2043
|
+
return endpoint.startsWith("dsh-memory/") ? endpoint.slice("dsh-memory/".length) : endpoint;
|
|
2044
|
+
}
|
|
2045
|
+
function makeRpc(_ctx) {
|
|
2046
|
+
return (async (endpoint, payload) => {
|
|
2047
|
+
let response;
|
|
2048
|
+
try {
|
|
2049
|
+
response = await fetch(`/dsh-memory/rpc/${shortMethod(endpoint)}`, {
|
|
2050
|
+
method: "POST",
|
|
2051
|
+
headers: { "content-type": "application/json" },
|
|
2052
|
+
body: JSON.stringify(payload ?? {})
|
|
2053
|
+
});
|
|
2054
|
+
} catch (err) {
|
|
2055
|
+
throw new Error(`transport failure for ${endpoint}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (!response.ok) throw new Error(`transport failure for ${endpoint}: HTTP ${response.status}`);
|
|
2058
|
+
return await response.json();
|
|
2059
|
+
});
|
|
2045
2060
|
}
|
|
2046
2061
|
function asLoose(rpc) {
|
|
2047
2062
|
return rpc;
|
|
@@ -2394,7 +2409,7 @@ var __defProp = Object.defineProperty;
|
|
|
2394
2409
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2395
2410
|
NInput,
|
|
2396
2411
|
{
|
|
2397
|
-
style: { width: 90,
|
|
2412
|
+
style: { width: 90, minWidth: 0, flex: "0 1 90px" },
|
|
2398
2413
|
type: "number",
|
|
2399
2414
|
min: 0,
|
|
2400
2415
|
placeholder: "跟随部署",
|
|
@@ -4255,32 +4270,41 @@ var __defProp = Object.defineProperty;
|
|
|
4255
4270
|
}
|
|
4256
4271
|
|
|
4257
4272
|
// client/src/entry.tsx
|
|
4258
|
-
var inject = ["slots"
|
|
4273
|
+
var inject = ["slots"];
|
|
4259
4274
|
function apply(ctx) {
|
|
4260
4275
|
const rpc = makeRpc(ctx);
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4276
|
+
console.info("[dsh-prime-memory] client apply: slots 注入就绪,注册 UI 槽位");
|
|
4277
|
+
try {
|
|
4278
|
+
ctx.slots.inject("settings.section", () => {
|
|
4279
|
+
return ctx.slots.register(
|
|
4280
|
+
{
|
|
4281
|
+
name: "settings.section",
|
|
4282
|
+
id: "dsh-memory",
|
|
4283
|
+
order: 200,
|
|
4284
|
+
label: "记忆",
|
|
4285
|
+
inject: () => ({ rpc })
|
|
4286
|
+
},
|
|
4287
|
+
MemoryPanel
|
|
4288
|
+
);
|
|
4289
|
+
});
|
|
4290
|
+
} catch (err) {
|
|
4291
|
+
console.warn("[dsh-prime-memory] settings.section 注册失败(新宿主已收编):", err);
|
|
4292
|
+
}
|
|
4293
|
+
try {
|
|
4294
|
+
ctx.slots.inject("conversation.input.left", () => {
|
|
4295
|
+
return ctx.slots.register(
|
|
4296
|
+
{
|
|
4297
|
+
name: "conversation.input.left",
|
|
4298
|
+
id: "dsh-memory-mode",
|
|
4299
|
+
order: 100,
|
|
4300
|
+
inject: (sessionId) => ({ sessionId, rpc })
|
|
4301
|
+
},
|
|
4302
|
+
MemoryModePill
|
|
4303
|
+
);
|
|
4304
|
+
});
|
|
4305
|
+
} catch (err) {
|
|
4306
|
+
console.warn("[dsh-prime-memory] conversation.input.left 注册失败:", err);
|
|
4307
|
+
}
|
|
4284
4308
|
}
|
|
4285
4309
|
|
|
4286
4310
|
// esbuild 对具名导出会整体替换 module.exports(__toCommonJS:getter + __esModule);
|