Skip to content

Releases: kenz1117/dsh-engram

v0.10.0 · 多语言与数据驱动批次

Choose a tag to compare

@kenz1117 kenz1117 released this 24 Oct 06:46

本版把「默认值」从代码属性改为数据属性——模型、语言绑定、服务端词典都不再写死在代码里。0.9.x 整批是「稳定性与可审计」,0.10.x 整批是「多语言与数据驱动」。

⚠️ 升级前请先读「升级说明」一节:默认嵌入模型变了,存量库需要重建向量,否则写入会被拒。

多语言层与数据注册表(合并 #36)

  • 默认嵌入模型改为注册表数据:registry/models.json(模型目录)、registry/bindings.json(语言 → 模型绑定)、registry/languages.json、registry/schema.json 全部落盘。src/embedder/local.ts 不再硬编码模型标识,src/embedder/select.ts 按语言解析;embedder.localModel 保留为显式覆盖。中文用 bge-small-zh(512 维),其余语言用 minilm-l12(384 维)。
  • 服务端词典外置:i18n/ru.txt(498 条)与 i18n/panel.ru.txt(48 条)以 TAB 分隔的「左原文 / 右译文」格式落盘,服务端消息与面板文案从文件读取。

新增

  • engram_language_guide 工具(工具总数 22 → 23):输出 <包根>/docs/agent-language-guide.ru.md,兑现首启窗口对未内置语言用户的「agent 会输出一份说明」承诺。
  • 面板「维护」视图:库状态、模型标签、向量重算、宫殿语言切换、词典查看与维护。
  • /api/engram/locales:提供「非内置语言 + 面板词典」,客户端拉取后注册到宿主;路由不可用时回落到内置语言。
  • docs/author/:中英俄三语的作者向设计文档与插图(留在仓库,不进包)。

修正

  • 语言切换的联锁移到服务端:此前窗口的状态在客户端——palace-change 校验的是面板送来的 from 与 startedAt,palace-language 则只校验语言码就写。被检查的东西来自被检查者,任何客户端只要送出当前时间戳就能让条件按构造成立。现在服务端自己记住第一个事件与当时库所在的语言,按自己的状态计算窗口;客户端不再发送这两个字段,重复开窗也不延长窗口;两条写入路径共用同一个联锁。关闭 docs/tech-debt.md 的 3.1。
  • 打包漏文件:i18n/、registry/、docs/agent-language-guide.ru.md 三条路径补进 package.json 的 files——缺了它们,装上 npm 的插件既没有俄语词典,也没有四个数据注册表,「按语言选模型」整条主线失效。
  • engram_language_guide 读的说明文件没进仓库:工具注册在模型可见的工具表里,每次调用都抛错。已把文件补进仓库并加进 files。
  • scripts/check-hook-order.mjs:新增静态钩子顺序检查(React 只在运行时报 #310,构建与 tsc 不会)。
  • 工具数 22 → 23 同步到 README(中英)、落地页与 EXPECTED_TOOLS。

⚠️ 升级说明:存量库需要重建向量

默认嵌入模型从 512 维的 bge-small-zh 变为 384 维的 minilm-l12(多语言)。 宫殿语言为中文的库不受影响(zh 仍绑定 bge-small-zh 512 维);其余语言的库——包括语言未设、回落默认(ru)的库——会落到 384 维。

两套向量不可比,维度也不一致。 仍在旧模型上的库写入时会被拒绝(store.embedderDimensionMismatch:比较与写入都拒绝,不做静默截断)——不重建的话,升级看起来就像「坏了」。二选一:

  • 用新模型重建:pnpm backfill --force(不带 --force 时脚本在写入前就发现维度不符并报错退出,绝不会写出混合维度的库);
  • 或把模型改回去:配置里写 embedder.localModel: 'Xenova/bge-small-zh-v1.5'(与旧库同维,可继续用)。

想按语言分别用不同模型,改 registry/bindings.json 即可,无需动代码。

已知边界

docs/tech-debt.md 记着三条不阻塞使用的边界:远程嵌入器不参与目标模型选择、未知维度的库可能被盖上目标标签、向量转换只校验条数不校验每条长度。已逐条写明原因,不是遗忘的待办。

v0.9.9 · 文档与市场曝光批次

Choose a tag to compare

@kenz1117 kenz1117 released this 23 Oct 14:45

本版本只动文档与市场元数据,无运行时行为变更。

README 首屏重写(中英双语同步)

  • 首屏改为「一句话英文定位句 + 事实型能力句」,记忆宫殿隐喻下沉到「宫殿结构」段
  • 新增可折叠目录
  • 新增「为什么是它」对照表:与服务端型(Postgres / pgvector / Milvus / Python 服务)、文件型(纯 Markdown)两条路线逐项对比
  • 新增记忆流动流程图(纯 ASCII,任何等宽字体都不断线)
  • 新增「让 agent 自己装」的安装 prompt 段(可复制文本)
  • 新增 CI 徽章

修正

  • 文档中的工具数量:实际注册 22 个 engram_* 工具(原文档写作 21,英文版另一处写作 20)

新增

  • CHANGELOG.md:按 0.x 批次约定整理全部版本
  • docs/index.html:GitHub Pages 落地页(对比表 / 流程图 / 面板截图 / 特性卡片)
  • docs/store/icon-180.png:商店卡片位图图标(含可复现的 icon-180.html 源)
  • docs/store/social-preview.png:GitHub 社交预览图(含可复现的 social-preview.html 源)

package.json

  • description 改为功能型表述并带关键词;补 homepage / bugs;keywords 扩到 24 个(agent-memory / rag / local-first / spaced-repetition 等);纳入 CHANGELOG.md

商店条目

  • awesome-dsh-plugin 条目描述更新到 v0.9.8 现状并补 icon 字段;category 保持 agi 不变(PR:awesome-dsh-plugin#8970)

自检

pnpm typecheck 通过 · check-messages / check-patterns 通过 · vitest 529 passed / 1 skipped · 干净环境安装冒烟通过

v0.9.8

Choose a tag to compare

@kenz1117 kenz1117 released this 22 Oct 03:29

This is the same code as v0.9.7, re-published. The 0.9.7 upload was interrupted by a network reset, and although npm accepted the version, its tarball took roughly 15 minutes to become downloadable — long enough that I assumed the publish had failed and published 0.9.8 from the identical tree. 0.9.7 turned out to be intact (shasum verified against the registry), so both versions carry the same code; 0.9.8 is what latest points at. If you prefer a clean version list, 0.9.7 can be unpublished with a 2FA-enabled npm session.

Embedder as a deployment property (#21, by @dsv180) — the model and the dimension are now configuration, not folklore:

  • The embedder is configurable: a local model with its pooling, or an OpenAI-compatible remote endpoint. The old model name is still a valid value, and nothing is inferred from a model's name — if the model wants cls pooling, the config says so.
  • A dimension mismatch is refused, never truncated. Writing a 384-dim vector into a 512-dim store fails loudly instead of silently comparing nonsense; the same goes for querying a store with the wrong model. The decision is made by error code, not by message text (the text is translated, the code is not).
  • A mismatch no longer costs you the keyword search. The vector lane in search() is wrapped: on a dimension mismatch the keyword lane still returns hits, the result carries degraded: true, and the warning prints once per process. Writes still refuse — an old store plus a new model is a state to convert, not a state to keep mixing.
  • backfill-embeddings.mjs --force rebuilds existing vectors (nodes and observations — rebuilding only one side would have left them incomparable), with --model / --pooling / --cache-dir. The dimension is validated before COMMIT, with a full rollback on mismatch, and the scan without --force refuses up front. The scan now covers shared.db and project-<hash>.db too — they had never been processed.
  • Taken along: a non-JSON response body yields EMBEDDER_REMOTE_FAILED instead of a bare SyntaxError; apiKeyEnv is read per request, so rotating a key needs no plugin restart; unknown keys inside the embedder block are an error instead of being silently ignored (a typo used to look exactly like "the setting did nothing").

The default model is unchanged in this release. Switching it to a multilingual 384-dim model is a migration event — existing 512-dim vectors are not comparable and the store would refuse writes until re-indexed — so it is being decided on its own (#35, draft) with its own migration notes. The database schema is unchanged, so upgrading this release needs no migration of your own.

Also in this release: the toolchain moves to TypeScript 7.0.2 and Vitest 5.0.4. Vitest 5 turned vite into a peer dependency, which this repo's auto-install-peers=false would not have installed — vite is now an explicit dev dependency, so the suite still runs.

Full Changelog: v0.9.6...v0.9.8

v0.9.7

Choose a tag to compare

@kenz1117 kenz1117 released this 22 Oct 03:17

Note: this version's npm tarball took ~15 minutes to become downloadable after an interrupted upload. It is intact and verified (shasum matches the registry) — the same code was also published as v0.9.8, which is what latest points at. Both carry identical code.

Embedder as a deployment property (#21, by @dsv180) — the model and the dimension are now configuration, not folklore:

  • The embedder is configurable: a local model with its pooling, or an OpenAI-compatible remote endpoint. The old model name is still a valid value, and nothing is inferred from a model's name — if the model wants cls pooling, the config says so.
  • A dimension mismatch is refused, never truncated. Writing a 384-dim vector into a 512-dim store fails loudly instead of silently comparing nonsense; the same goes for querying a store with the wrong model. The decision is made by error code, not by message text (the text is translated, the code is not).
  • A mismatch no longer costs you the keyword search. The vector lane in search() is wrapped: on a dimension mismatch the keyword lane still returns hits, the result carries degraded: true, and the warning prints once per process. Writes still refuse — an old store plus a new model is a state to convert, not a state to keep mixing.
  • backfill-embeddings.mjs --force rebuilds existing vectors (nodes and observations — rebuilding only one side would have left them incomparable), with --model / --pooling / --cache-dir. The dimension is validated before COMMIT, with a full rollback on mismatch, and the scan without --force refuses up front. The scan now covers shared.db and project-<hash>.db too — they had never been processed.
  • Taken along: a non-JSON response body yields EMBEDDER_REMOTE_FAILED instead of a bare SyntaxError; apiKeyEnv is read per request, so rotating a key needs no plugin restart; unknown keys inside the embedder block are an error instead of being silently ignored (a typo used to look exactly like "the setting did nothing").

The default model is unchanged in this release. Switching it to a multilingual 384-dim model is a migration event — existing 512-dim vectors are not comparable and the store would refuse writes until re-indexed — so it is being decided on its own (#35, draft) with its own migration notes. The database schema is unchanged, so upgrading this release needs no migration of your own.

Also in this release: the toolchain moves to TypeScript 7.0.2 and Vitest 5.0.4. Vitest 5 turned vite into a peer dependency, which this repo's auto-install-peers=false would not have installed — vite is now an explicit dev dependency, so the suite still runs.

Full Changelog: v0.9.6...v0.9.7

v0.9.6

Choose a tag to compare

@kenz1117 kenz1117 released this 21 Oct 15:01

Follow-up to v0.9.5 — the remaining two findings from the audit of the import fix:

  • The other nine write routes now tell the truth too. They had been given a "tolerant" reader that turned any bad body into an empty object, so an oversized body fell through to field validation and was reported as id required / archivePath required — the same "blame the caller's file for a server-side limit" pattern as #34, just on a different route. Now: too large → 413 with the limit, malformed JSON → 400 saying it is not valid JSON, not an object → 400 saying the request body must be a JSON object. The tolerant reader is gone — leaving it around is how the next person reintroduces the same misleading message. Behaviour change: an oversized body now answers 413 instead of 400, and a malformed body is no longer silently treated as an empty one. Field validation itself is unchanged (a small body without an id still answers id required).
  • A duplicate review-answer handler is gone. v0.7.2 (commit a28ccdc) added the same route twice with identical conditions — the second was unreachable, since the first returns unconditionally. The two also disagreed: the live one answers {id, review} and requires a numeric grade; the dead one answered {record} and used Number() loosely (Number(null) → 0, so grade: null slipped through validation as a failing grade). The live one — the one the test suite pins — stays; the dead one is deleted.

New tests pin the three failure classes on the ordinary write routes and assert that the 64 KiB cap still applies there. That cap was previously only asserted at the reader level, so a future refactor handing one of those routes the 16 MiB import budget would not have been caught.

Full Changelog: v0.9.5...v0.9.6

v0.9.5

Choose a tag to compare

@kenz1117 kenz1117 released this 21 Oct 14:47

Memory import, fixed (#34) — a valid export file was reported as "not a JSON object":

  • The panel's import route reused the generic 64 KiB JSON-body cap. An export bigger than that (the reporter's were 117 KB and 165 KB) was silently dropped server-side, the request body was then treated as an empty object, and the validator said "import file is not a JSON object" — blaming the user's file for a server-side limit. Those files were perfectly valid JSON objects.
  • The import route now gets its own 16 MiB budget; every other loopback write route keeps the 64 KiB cap (it is a deliberate abuse-prevention control).
  • The limit is counted in bytes now, not characters — Chinese is 3 bytes per character, so the old character count let the byte limit slip by up to 3×.
  • The body is decoded as one buffer instead of chunk by chunk. A multi-byte character split across a chunk boundary used to be torn into U+FFFD replacement characters — guaranteed to hit on large exports, which are mostly Chinese text.
  • The three failure classes are told apart now: too large → 413 with the limit and the alternative path (engram_import by file path); malformed JSON → 400 saying it is not valid JSON; not an object → 400 saying the request body must be a JSON object. None of them says "your file is not a JSON object" any more.
  • The panel checks the payload size before sending it and says so immediately, instead of uploading first and failing after.
  • If the file's top level is an array, the error says how to fix it (wrap it as {"records": [...]}).

Verified against the reported case: a ~165 KB export goes through preview and commit over real HTTP, 1200 Chinese records land intact, and the new tests fail as expected if the 64 KiB cap is put back.

Full Changelog: v0.9.4...v0.9.5

v0.9.4

Choose a tag to compare

@kenz1117 kenz1117 released this 21 Oct 00:30

Jev fix (PR #20, reported and fixed by @dsv180):

  • The memory judge is now assembled per operation instead of once at plugin load, and only a successful connection resolution is cached. Previously the tools-path judge cached its first resolution — failures included — for the life of the process: one transient host hiccup silently degraded Jev to rules-only for engram_save / engram_ingest_history / engram_reflect until restart, and provider/model/key changes in the panel never reached the tools path.
  • Now a failed resolution retries on the next ask (the caller falls back to rules for that single operation), provider and model changes apply immediately, and mid-session route inheritance works as documented. Parallel questions within one operation share a single resolution attempt.
  • Non-blocking items from the review also landed: the provider list shows only providers configured in DSH, legacy jev.apiKey/endpoint fields warn exactly once (src/jev/stale.ts), and unnamed provider/model inherit the session route. The key still never touches disk or logs.

Worktree-verified on the rebased base: full suite 493 passed / 1 skipped.

What's Changed

  • Jev takes the provider and the model from the host — the plugin stores no key or endpoint (#14) by @dsv180 in #20

Full Changelog: v0.9.3...v0.9.4

v0.9.3

Choose a tag to compare

@kenz1117 kenz1117 released this 20 Oct 06:06

Memory import (#33) — the round trip is complete:

  • A dedicated import dropdown in the panel (left of export, same style), with two explicit entries: Import JSON (lossless round trip of engram_export format=json) and Import Markdown (re-parses exported MD; lossy — missing fields are filled with defaults). The engram_import tool ships alongside for agent-driven migrations (dryRun=true previews first; format auto-detected from the .md extension).
  • Two-stage flow: pick a file → a preview drawer classifies every entry (new / duplicate / conflict) with per-entry checkboxes → commit only what you selected.
  • Dedup: content-identical entries are skipped by default; an id occupied by different content is reported as a conflict and never overwrites the store — re-importing your own backup is a no-op. Dangling edges are dropped and counted; entity aliases merge by id.
  • Exported files carry no vectors, so imported entries are embedded in place after the write (batched; if the embedder is unavailable they are left empty for a later backfill, never blocking the import).
  • Redacted-view MD exports are detected and flagged in the preview (importing stores the truncated preview text, not the originals).

Fix: clicking 镜像目录 (mirror tree) in the export menu could take the DSH panel down — the item was a top-level <a> navigation to a raw JSON response. It now runs via fetch and reports the export path, file and memory counts as a toast; the route also sends a content-disposition: attachment header as a fallback. Menu items shared by links and buttons gained a button-style reset so both render identically.

Full Changelog: v0.9.2...v0.9.3

v0.9.2

Choose a tag to compare

@kenz1117 kenz1117 released this 19 Oct 23:06

Two fixes, both reported from real-world use:

  • Grand Hall profile is re-injected after compaction (#32) — the standing profile was injected only once per session (step-1 gate + content-hash de-dup assumed the previous injection was still in context), so after the summarizer pruned the snapshot it never came back. The plugin now counts compaction/end events in the session's own event snapshot: whenever a compaction happened since the last injection, any turn re-injects — including a content-identical packet, bypassing the hash de-dup. Without a new compaction, de-dup behaves as before. Known boundary: a compaction that happened before an app restart is not detected (conservative no-inject). Ships with a five-stage regression test over the real Loader. Exemplary report by @dsv180.
  • Stats overview includes the shared palace (#19 follow-up) — the /stats route was missing the shared segment, so the shared-palace overview always displayed 0.

Plus README updates (JPEG hero, panel screenshots, acknowledgements).

Full Changelog: v0.9.1...v0.9.2

v0.9.1

Choose a tag to compare

@kenz1117 kenz1117 released this 18 Oct 11:48

Issue #19 follow-up (plan A — expand in place):

  • The refurb list moves out of the narrow bottom-right column into the full-width main column of the Today tab: decision rows, full memory texts and merge candidates now wrap and display completely, with actions applied in place — no popup detour needed to act.
  • /refurb returns full memory texts (the 280-char server-side truncation is gone; loopback only).
  • The review drawer gains a background-token fallback so it can never render translucent on host builds that lack the newer theme tokens.

What's Changed

  • chore(deps): bump actions/checkout from 4 to 7 by @dependabot[bot] in #24
  • chore(deps): bump actions/setup-node from 4 to 7 by @dependabot[bot] in #25
  • chore(deps): bump the minor-and-patch group across 1 directory with 3 updates by @dependabot[bot] in #27
  • chore(deps): bump pnpm/action-setup from 4 to 6 by @dependabot[bot] in #26

New Contributors

Full Changelog: v0.9.0...v0.9.1