dsh-memoir 0.5.2 → 0.5.3
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/README.en.md +11 -6
- package/README.md +11 -6
- package/cordis.patch.yml +2 -0
- package/lib/autodistill.d.ts +6 -0
- package/lib/autodistill.js +6 -5
- package/lib/client.js +207 -2
- package/lib/client.js.map +2 -2
- package/lib/index.d.ts +4 -0
- package/lib/index.js +25 -13
- package/lib/routes.d.ts +10 -1
- package/lib/routes.js +37 -2
- package/lib/settings.d.ts +40 -0
- package/lib/settings.js +122 -0
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -81,10 +81,11 @@ need long-tail history? memoir_read (local relevance-ranked recall)
|
|
|
81
81
|
|
|
82
82
|
**Session Snapshot freezing semantics**: one session's injected text is built once and frozen (stable prompt prefix, maximizing prompt-prefix cache hits); the current session does not re-consume memory it just wrote, and a new session rebuilds and sees the latest memory. Since v0.4.2, when there is no unique session identity (session.id / agent.id), freezing is skipped — a cache miss beats wrongly reusing another session's snapshot.
|
|
83
83
|
|
|
84
|
-
## v0.5.
|
|
84
|
+
## v0.5.3 Web-configurable auto-distill and rc2 compatibility
|
|
85
85
|
|
|
86
86
|
- The development and peer-dependency baseline is `@deepseek-ai/dsh-* 0.1.1-rc.2`.
|
|
87
87
|
- Auto-distill now supports per-agent worked-turn intervals, time cooldowns, and tool-call thresholds; defaults `1 / 0 / 1` preserve prior behavior.
|
|
88
|
+
- The Web panel now includes Auto-distill Settings for enabling/disabling and editing those three parameters. Saves apply to subsequent turns immediately and persist in `~/.dsh/dsh-memoir.settings.json`; one action removes the Web override and restores the profile values captured at startup.
|
|
88
89
|
- Store format v3 migrates v2 entries without changing their `id`, content, or timestamp. The first mutation materializes `importance`, `pinned`, `status`, `supersedes`, and `tags`; startup reads do not rewrite old files.
|
|
89
90
|
- Retrieval defaults to `active`. Archived and superseded history is retained and can be inspected from the Web panel. Explicit `supersedes` marks its targets as superseded; history is never deleted automatically.
|
|
90
91
|
- Agents can use `memoir_update` to edit an entry's section, title, content, and lifecycle in place; the Web panel also supports editing, pinning, marking superseded, archiving, and restoring.
|
|
@@ -117,11 +118,12 @@ Curated-query Top-5 hit rate: 100% (quality gate ≥ 90%, see `test/recall-quali
|
|
|
117
118
|
|
|
118
119
|
## GUI
|
|
119
120
|
|
|
120
|
-
The v0.4 Project / Global / Search / Add / Delete / Diagnostics architecture is kept
|
|
121
|
+
The v0.4 Project / Global / Search / Add / Delete / Diagnostics architecture is kept and extended:
|
|
121
122
|
|
|
122
123
|
- **Search unified on RetrievalEngine**: a non-empty query calls `GET /api/dsh-memoir/search` — the same BM25 ranking as the agent's `memoir_read` — results ordered by relevance with scores shown
|
|
123
124
|
- **Hot Memory Inspector**: expand to see the Hot Memory that will actually be injected for the current workspace (Actions / Lessons / Recent state) — i.e. "what exactly the next session inherits"
|
|
124
125
|
- **Retrieval Diagnostics**: Retrieval Index (docs/terms/epoch), Query Cache (hits/misses/evictions/hit rate/size/capacity), Last Query (latency/returned), Session Snapshot (hash/createdAt/storeRevision)
|
|
126
|
+
- **Auto-distill Settings (v0.5.3)**: enable or disable auto-distill and edit the worked-turn interval, cooldown minutes, and minimum tool calls in the panel; validated saves persist and apply immediately without restarting DSH
|
|
125
127
|
|
|
126
128
|
## Screenshots
|
|
127
129
|
|
|
@@ -149,6 +151,7 @@ The v0.4 Project / Global / Search / Add / Delete / Diagnostics architecture is
|
|
|
149
151
|
|
|
150
152
|
```text
|
|
151
153
|
~/.dsh/dsh-memoir.json ← structured JSON (single source of truth / SSOT)
|
|
154
|
+
~/.dsh/dsh-memoir.settings.json ← auto-distill overrides saved by the Web panel
|
|
152
155
|
<workspace>/PROJECT_MEMORY.md ← human-readable projection regenerated from the JSON (git-friendly)
|
|
153
156
|
|
|
154
157
|
No cloud memory DB · No embedding API · No vector DB
|
|
@@ -181,6 +184,8 @@ Add a `config` block on the plugin row in `cordis.patch.yml` (all optional; defa
|
|
|
181
184
|
|
|
182
185
|
The three auto-distill frequency conditions are combined with AND and isolated per agent. Idle, aborted, subagent, and prior-`memoir_record` turns do not advance the interval. A worked turn below `autoDistillMinTools` advances the interval but cannot trigger by itself. Cooldown changes only after a successful steer.
|
|
183
186
|
|
|
187
|
+
The auto-distill fields in `cordis.patch.yml` are startup defaults. Since v0.5.3, the Auto-distill Settings section at the bottom of the Memory panel can override them: saving atomically writes `~/.dsh/dsh-memoir.settings.json`, and subsequent turns read the new policy immediately. Restore Startup Config removes that override and returns to the profile values resolved when the plugin mounted. Other Hot Memory, recall, and cache settings remain profile-managed.
|
|
188
|
+
|
|
184
189
|
## Design Trade-offs
|
|
185
190
|
|
|
186
191
|
- **Bounded vs full injection**: v0.3 injected the full history into the prompt and it kept growing; v0.4+ injects only budgeted Hot Memory, with long-tail history recalled on demand. Token benchmarks below.
|
|
@@ -189,7 +194,7 @@ The three auto-distill frequency conditions are combined with AND and isolated p
|
|
|
189
194
|
- **Multi-process safety**: store record/remove runs inside a cross-process critical section on `~/.dsh/dsh-memoir.lock` (exclusive O_EXCL creation with timeout); the section force-reloads from disk before mutating, so two interleaved DSH processes lose no updates (v0.4.2).
|
|
190
195
|
- **Windows paths**: canonical keys are fully lowercased (`C:\A` / `c:\a\` / `C:/A` share one bucket) while display paths keep the original casing (v0.4.2).
|
|
191
196
|
- **GUI and Agent share one engine**: panel search and `memoir_read` use the same RetrievalEngine instead of separate filter logic (v0.4.2).
|
|
192
|
-
- **Auto-distill cadence**: the default still reminds after every worked turn; research-heavy sessions can combine interval, cooldown, and activity thresholds
|
|
197
|
+
- **Auto-distill cadence**: the default still reminds after every worked turn; research-heavy sessions can combine interval, cooldown, and activity thresholds and tune them immediately from the Web panel (v0.5.3).
|
|
193
198
|
|
|
194
199
|
## Use Cases
|
|
195
200
|
|
|
@@ -220,7 +225,7 @@ Each plugin has its own focus — pick per need; no "which is stronger" narrativ
|
|
|
220
225
|
pnpm install # install devDeps (typescript, esbuild, @deepseek-ai/* type packages)
|
|
221
226
|
pnpm run build # tsc builds the host + esbuild builds the client bundle
|
|
222
227
|
pnpm run typecheck # full type check (src + test)
|
|
223
|
-
pnpm test #
|
|
228
|
+
pnpm test # 154 tests: store (incl. multi-process lock) / settings / snapshot / selector / retrieval / tools / routes / auto-distill / integration / client pure logic / bundle protocol & purity / release notes
|
|
224
229
|
npm run bench # benchmark (100/1k/10k/100k entries); results written to bench/report.md
|
|
225
230
|
```
|
|
226
231
|
|
|
@@ -237,7 +242,7 @@ v0.4.2 benchmark summary (node v22.23.2, budget 900/1200 tokens; full report in
|
|
|
237
242
|
|
|
238
243
|
## Implementation
|
|
239
244
|
|
|
240
|
-
- **Full-stack TypeScript**: `src/host/*.ts` (store / tools / retrieval / selector / snapshot / routes / autodistill / index — tsc emits `lib/*.js`) + `src/client/*.ts(x)` (esbuild emits the `lib/client.js` closure-factory bundle).
|
|
245
|
+
- **Full-stack TypeScript**: `src/host/*.ts` (store / settings / tools / retrieval / selector / snapshot / routes / autodistill / index — tsc emits `lib/*.js`) + `src/client/*.ts(x)` (esbuild emits the `lib/client.js` closure-factory bundle).
|
|
241
246
|
- **Two-sided plugin**: the host half registers the agent tools, `/api/dsh-memoir` routes, the `agent/turn-stopping` auto-distill listener, and the per-project system-prompt injection section; the client half renders the panel. Runtime deps are official NPM SDK packages only.
|
|
242
247
|
- Mounted via the `dsh.bundle.patch` manifest (`insert` row in `cordis.patch.yml`); no DSH source changes.
|
|
243
248
|
- Auto-distill safety boundaries: top-level sessions only (subagents / nested delegations skipped), turns with tool activity that haven't recorded yet, aborted turns skipped, at most one steer per turn.
|
|
@@ -254,7 +259,7 @@ Bug reports must include screenshot / log evidence, a smoke test, code reference
|
|
|
254
259
|
|
|
255
260
|
## Release
|
|
256
261
|
|
|
257
|
-
Current stable release: **v0.5.
|
|
262
|
+
Current stable release: **v0.5.3** (2026-08-22) · [GitHub Release](https://github.com/Qinling-Melon-Farmers/dsh-memoir/releases/tag/v0.5.3) · [npm](https://www.npmjs.com/package/dsh-memoir/v/0.5.3). Full history is in [CHANGELOG.md](./CHANGELOG.md).
|
|
258
263
|
|
|
259
264
|
Every version keeps Chinese and English release notes in sync. GitHub Releases show Chinese by default and place the English notes in a collapsible `English` section.
|
|
260
265
|
|
package/README.md
CHANGED
|
@@ -81,10 +81,11 @@ memoir_record 沉淀工作 / 教训 / 下一步
|
|
|
81
81
|
|
|
82
82
|
**Session Snapshot 冻结语义**:同一 session 的注入文本只构建一次并冻结(prompt 前缀稳定,最大化 prompt-prefix cache 命中);当前 session 不重新消费自己刚写的记忆,新 session 重建并看到最新记忆。v0.4.2 起,没有唯一会话身份(session.id / agent.id)时**不做冻结**——宁可 cache miss,不可跨 session 错复用旧快照。
|
|
83
83
|
|
|
84
|
-
## v0.5.
|
|
84
|
+
## v0.5.3 Web 可调自动收尾与 rc2 兼容性
|
|
85
85
|
|
|
86
86
|
- 当前开发基线为 `@deepseek-ai/dsh-* 0.1.1-rc.2`;peer dependency 与开发依赖已统一到 rc2。
|
|
87
87
|
- 自动收尾支持按 agent 配置 worked-turn 间隔、时间冷却和工具调用阈值;默认值 `1 / 0 / 1` 与旧版行为一致。
|
|
88
|
+
- Web 面板新增「自动蒸馏设置」,可启停并修改上述三个参数;保存后立即作用于后续回合,并持久化到 `~/.dsh/dsh-memoir.settings.json`。可一键删除 Web 覆盖并恢复本次启动时的 profile 配置。
|
|
88
89
|
- 存储格式从 v2 迁移到 v3:旧条目保持原有 `id`、内容和时间,首次变更时补齐 `importance`、`pinned`、`status`、`supersedes` 与 `tags`;启动读取不会重写旧文件。
|
|
89
90
|
- 默认只召回 `active` 条目;归档和被替代条目保留在历史中,可在 Web 面板切换状态查看。显式 `supersedes` 会把目标条目标记为 `superseded`,不会自动删除历史。
|
|
90
91
|
- Agent 可用 `memoir_update` 原地编辑条目的分类、标题、正文和生命周期;Web 面板也支持编辑、置顶、标记过时、归档与恢复。
|
|
@@ -117,11 +118,12 @@ curated 查询 Top-5 命中率 100%(质量门禁 ≥90%,见 `test/recall-qua
|
|
|
117
118
|
|
|
118
119
|
## GUI
|
|
119
120
|
|
|
120
|
-
保留 v0.4 的 Project / Global / Search / Add / Delete / Diagnostics
|
|
121
|
+
保留 v0.4 的 Project / Global / Search / Add / Delete / Diagnostics 架构,并持续扩展:
|
|
121
122
|
|
|
122
123
|
- **搜索统一走 RetrievalEngine**:query 非空时面板调用 `GET /api/dsh-memoir/search`,与 agent 的 `memoir_read` 共用同一套 BM25 排序,结果按相关性排列并显示分数
|
|
123
124
|
- **Hot Memory Inspector**:展开查看当前工作区实际会被注入的 Hot Memory(Actions / Lessons / Recent state),即「下一会话到底自动继承什么」
|
|
124
125
|
- **Retrieval Diagnostics**:Retrieval Index(docs/terms/epoch)、Query Cache(hits/misses/evictions/hit rate/size/capacity)、Last Query(latency/returned)、Session Snapshot(hash/createdAt/storeRevision)
|
|
126
|
+
- **自动蒸馏设置(v0.5.3)**:面板内启停 auto-distill,编辑 worked-turn 间隔、冷却分钟数和最低工具调用数;严格校验后持久化并即时应用,无需重启 DSH
|
|
125
127
|
|
|
126
128
|
## 界面预览
|
|
127
129
|
|
|
@@ -149,6 +151,7 @@ curated 查询 Top-5 命中率 100%(质量门禁 ≥90%,见 `test/recall-qua
|
|
|
149
151
|
|
|
150
152
|
```text
|
|
151
153
|
~/.dsh/dsh-memoir.json ← 结构化 JSON(唯一事实源 / SSOT)
|
|
154
|
+
~/.dsh/dsh-memoir.settings.json ← Web 面板保存的自动蒸馏覆盖设置
|
|
152
155
|
<工作区>/PROJECT_MEMORY.md ← 由 JSON 重新生成的人类可读投影(git 友好)
|
|
153
156
|
|
|
154
157
|
No cloud memory DB · No embedding API · No vector DB
|
|
@@ -181,6 +184,8 @@ JSON 是 source of truth,Markdown 是 generated projection:面板、工具
|
|
|
181
184
|
|
|
182
185
|
三个 auto-distill 频率条件按 AND 关系判定并按 agent 隔离。idle、aborted、subagent、已调用 `memoir_record` 的 turn 不推进间隔计数;低于 `autoDistillMinTools` 的 worked turn 会推进间隔,但自身不能触发提醒。冷却只在 steer 成功后更新。
|
|
183
186
|
|
|
187
|
+
`cordis.patch.yml` 中的 auto-distill 字段是启动默认值。v0.5.3 起,可在记忆面板底部展开「自动蒸馏设置」进行修改:保存会原子写入 `~/.dsh/dsh-memoir.settings.json`,后续 turn 立即读取新策略;「恢复启动配置」会删除该覆盖文件并回到本次插件挂载时解析出的 profile 值。其余 Hot Memory、召回和缓存参数仍由 profile 配置。
|
|
188
|
+
|
|
184
189
|
## Design Trade-offs
|
|
185
190
|
|
|
186
191
|
- **有界注入 vs 全量注入**:v0.3 把完整历史注入 prompt,越用越膨胀;v0.4+ 只注入预算内的 Hot Memory,长尾历史按需召回。token 基准见下方 Benchmark。
|
|
@@ -189,7 +194,7 @@ JSON 是 source of truth,Markdown 是 generated projection:面板、工具
|
|
|
189
194
|
- **多进程安全**:store 的 record/remove 走 `~/.dsh/dsh-memoir.lock` 跨进程临界区(O_EXCL 独占创建 + 超时),临界区内强制从磁盘重读再改,两个 DSH 进程交错写入不丢更新(v0.4.2)。
|
|
190
195
|
- **Windows 路径**:canonical key 全小写(`C:\A` / `c:\a\` / `C:/A` 一个桶),display path 保留原始大小写(v0.4.2)。
|
|
191
196
|
- **GUI 与 Agent 同源**:面板搜索与 `memoir_read` 共用 RetrievalEngine,不再各写一套过滤逻辑(v0.4.2)。
|
|
192
|
-
- **自动收尾节奏**:默认仍逐 worked turn
|
|
197
|
+
- **自动收尾节奏**:默认仍逐 worked turn 提醒;研究型会话可组合轮次间隔、冷却与活动阈值降低打扰,并从 Web 面板即时调节(v0.5.3)。
|
|
193
198
|
|
|
194
199
|
## Use Cases
|
|
195
200
|
|
|
@@ -220,7 +225,7 @@ JSON 是 source of truth,Markdown 是 generated projection:面板、工具
|
|
|
220
225
|
pnpm install # 安装 devDeps(typescript、esbuild、@deepseek-ai/* 类型包)
|
|
221
226
|
pnpm run build # tsc 构建 host + esbuild 构建 client bundle
|
|
222
227
|
pnpm run typecheck # 全量类型检查(src + test)
|
|
223
|
-
pnpm test #
|
|
228
|
+
pnpm test # 154 项测试:store(含多进程锁) / settings / snapshot / selector / retrieval / tools / routes / 自动收尾 / 集成 / client 纯逻辑 / bundle 协议与纯净性 / 发布说明
|
|
224
229
|
npm run bench # benchmark(100/1k/10k/100k 条目),结果写入 bench/report.md
|
|
225
230
|
```
|
|
226
231
|
|
|
@@ -237,7 +242,7 @@ v0.4.2 benchmark 摘要(node v22.23.2,budget 900/1200 tokens;完整报告
|
|
|
237
242
|
|
|
238
243
|
## 实现说明
|
|
239
244
|
|
|
240
|
-
- **TypeScript 全栈**:`src/host/*.ts`(store / tools / retrieval / selector / snapshot / routes / autodistill / index,tsc 构建出 `lib/*.js`)+ `src/client/*.ts(x)`(esbuild 打出 `lib/client.js` 闭包工厂 bundle)。
|
|
245
|
+
- **TypeScript 全栈**:`src/host/*.ts`(store / settings / tools / retrieval / selector / snapshot / routes / autodistill / index,tsc 构建出 `lib/*.js`)+ `src/client/*.ts(x)`(esbuild 打出 `lib/client.js` 闭包工厂 bundle)。
|
|
241
246
|
- **双面插件**:host 半注册 agent 工具、`/api/dsh-memoir` 路由、`agent/turn-stopping` 自动收尾监听与按项目求值的 system prompt 注入段;client 半提供面板。运行时仅依赖官方 NPM SDK。
|
|
242
247
|
- 通过 `dsh.bundle.patch` manifest(`cordis.patch.yml` 的 `insert` 行)挂载,不改 DSH 源码。
|
|
243
248
|
- 自动收尾安全边界:仅顶级会话(跳过 subagent / 嵌套委托)、仅「有工具调用且未记录过」的回合、已中止回合不打扰、每回合至多一次。
|
|
@@ -255,7 +260,7 @@ PR 请先提 Issue 讨论。
|
|
|
255
260
|
|
|
256
261
|
## Release
|
|
257
262
|
|
|
258
|
-
当前稳定版:**v0.5.
|
|
263
|
+
当前稳定版:**v0.5.3**(2026-08-22) · [GitHub Release](https://github.com/Qinling-Melon-Farmers/dsh-memoir/releases/tag/v0.5.3) · [npm](https://www.npmjs.com/package/dsh-memoir/v/0.5.3)。完整历史见 [CHANGELOG.md](./CHANGELOG.md)。
|
|
259
264
|
|
|
260
265
|
每个版本的更新日志均同步维护中英文;GitHub Release 默认展开中文,英文说明收纳在可折叠的 `English` 区域。
|
|
261
266
|
|
package/cordis.patch.yml
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
# autoDistillEvery: 1 # remind at most once per N worked turns
|
|
20
20
|
# autoDistillCooldownMin: 0 # minimum minutes between successful reminders
|
|
21
21
|
# autoDistillMinTools: 1 # minimum tool calls on the triggering turn
|
|
22
|
+
# These auto-distill values are startup defaults. v0.5.3+ can persist a live
|
|
23
|
+
# Web-panel override in ~/.dsh/dsh-memoir.settings.json and restore defaults.
|
|
22
24
|
- insert:
|
|
23
25
|
- id: memoir
|
|
24
26
|
name: dsh-memoir
|
package/lib/autodistill.d.ts
CHANGED
|
@@ -77,5 +77,11 @@ export declare function installAutoDistill(wire: AutoDistillWire, options: {
|
|
|
77
77
|
every?: number;
|
|
78
78
|
cooldownMin?: number;
|
|
79
79
|
minTools?: number;
|
|
80
|
+
/** Optional live policy source used by the Web settings panel. */
|
|
81
|
+
policy?: () => {
|
|
82
|
+
every?: number;
|
|
83
|
+
cooldownMin?: number;
|
|
84
|
+
minTools?: number;
|
|
85
|
+
};
|
|
80
86
|
now?: () => number;
|
|
81
87
|
}): () => void;
|
package/lib/autodistill.js
CHANGED
|
@@ -90,11 +90,6 @@ export function installAutoDistill(wire, options) {
|
|
|
90
90
|
const gate = new AutoDistillGate();
|
|
91
91
|
const integerAtLeast = (value, fallback, minimum) => typeof value === 'number' && Number.isFinite(value) ? Math.max(minimum, Math.floor(value)) : fallback;
|
|
92
92
|
const numberAtLeast = (value, fallback, minimum) => typeof value === 'number' && Number.isFinite(value) ? Math.max(minimum, value) : fallback;
|
|
93
|
-
const policy = {
|
|
94
|
-
every: integerAtLeast(options.every, 1, 1),
|
|
95
|
-
cooldownMs: numberAtLeast(options.cooldownMin, 0, 0) * 60_000,
|
|
96
|
-
minTools: integerAtLeast(options.minTools, 1, 1),
|
|
97
|
-
};
|
|
98
93
|
return wire.on('agent/turn-stopping', (payload) => {
|
|
99
94
|
if (!options.enabled())
|
|
100
95
|
return;
|
|
@@ -106,6 +101,12 @@ export function installAutoDistill(wire, options) {
|
|
|
106
101
|
const { worked, recorded, toolCalls } = turnActivity(agent.session.events, turn);
|
|
107
102
|
if (!worked || recorded)
|
|
108
103
|
return;
|
|
104
|
+
const live = options.policy?.();
|
|
105
|
+
const policy = {
|
|
106
|
+
every: integerAtLeast(live?.every ?? options.every, 1, 1),
|
|
107
|
+
cooldownMs: numberAtLeast(live?.cooldownMin ?? options.cooldownMin, 0, 0) * 60_000,
|
|
108
|
+
minTools: integerAtLeast(live?.minTools ?? options.minTools, 1, 1),
|
|
109
|
+
};
|
|
109
110
|
const now = options.now?.() ?? Date.now();
|
|
110
111
|
if (!gate.consume(agent.id, turn, toolCalls, policy, now))
|
|
111
112
|
return;
|
package/lib/client.js
CHANGED
|
@@ -114,6 +114,29 @@ var MemoirApi = class {
|
|
|
114
114
|
const response = await this.fetchImpl("/api/dsh-memoir/diagnostics" + query({ path }));
|
|
115
115
|
return readEnvelope(response);
|
|
116
116
|
}
|
|
117
|
+
/** Read the live auto-distill policy and whether it comes from Web overrides. */
|
|
118
|
+
async settings() {
|
|
119
|
+
const response = await this.fetchImpl("/api/dsh-memoir/settings");
|
|
120
|
+
return readEnvelope(response);
|
|
121
|
+
}
|
|
122
|
+
/** Persist and immediately apply the auto-distill policy. */
|
|
123
|
+
async updateSettings(settings) {
|
|
124
|
+
const response = await this.fetchImpl("/api/dsh-memoir/settings", {
|
|
125
|
+
method: "PUT",
|
|
126
|
+
headers: { "content-type": "application/json" },
|
|
127
|
+
body: JSON.stringify(settings)
|
|
128
|
+
});
|
|
129
|
+
return readEnvelope(response);
|
|
130
|
+
}
|
|
131
|
+
/** Remove the Web override and restore the profile defaults captured at boot. */
|
|
132
|
+
async resetSettings() {
|
|
133
|
+
const response = await this.fetchImpl("/api/dsh-memoir/settings", {
|
|
134
|
+
method: "DELETE",
|
|
135
|
+
headers: { "content-type": "application/json" },
|
|
136
|
+
body: "{}"
|
|
137
|
+
});
|
|
138
|
+
return readEnvelope(response);
|
|
139
|
+
}
|
|
117
140
|
/**
|
|
118
141
|
* Ranked search over the host RetrievalEngine (v0.4.2) — the same ranking
|
|
119
142
|
* memoir_read uses, so the GUI search and the agent recall never diverge.
|
|
@@ -254,7 +277,28 @@ var dictionaries = {
|
|
|
254
277
|
"lifecycle.supersede": "\u6807\u8BB0\u5DF2\u66FF\u4EE3",
|
|
255
278
|
"lifecycle.unsupersede": "\u6062\u590D\u6D3B\u8DC3",
|
|
256
279
|
"lifecycle.archive": "\u5F52\u6863",
|
|
257
|
-
"lifecycle.restore": "\u6062\u590D"
|
|
280
|
+
"lifecycle.restore": "\u6062\u590D",
|
|
281
|
+
"settings.title": "\u81EA\u52A8\u84B8\u998F\u8BBE\u7F6E",
|
|
282
|
+
"settings.description": "\u4FEE\u6539\u540E\u7ACB\u5373\u4F5C\u7528\u4E8E\u540E\u7EED\u56DE\u5408\uFF0C\u5E76\u6301\u4E45\u5316\u5230\u672C\u673A DSH \u7528\u6237\u76EE\u5F55\u3002",
|
|
283
|
+
"settings.enabled": "\u542F\u7528\u81EA\u52A8\u84B8\u998F",
|
|
284
|
+
"settings.enabledHint": "\u5728\u9876\u5C42 agent \u7684\u6709\u6548\u5DE5\u4F5C\u56DE\u5408\u7ED3\u675F\u65F6\u63D0\u9192\u5F52\u7EB3\u8BB0\u5FC6\u3002",
|
|
285
|
+
"settings.every": "\u5DE5\u4F5C\u56DE\u5408\u95F4\u9694",
|
|
286
|
+
"settings.everyHint": "\u6BCF\u7D2F\u8BA1 N \u4E2A worked turn \u6700\u591A\u63D0\u9192\u4E00\u6B21\uFF08\u6700\u5C0F 1\uFF09\u3002",
|
|
287
|
+
"settings.cooldown": "\u51B7\u5374\u65F6\u95F4\uFF08\u5206\u949F\uFF09",
|
|
288
|
+
"settings.cooldownHint": "\u4E24\u6B21\u6210\u529F\u63D0\u9192\u81F3\u5C11\u95F4\u9694 M \u5206\u949F\uFF08\u6700\u5C0F 0\uFF09\u3002",
|
|
289
|
+
"settings.minTools": "\u6700\u4F4E\u5DE5\u5177\u8C03\u7528\u6570",
|
|
290
|
+
"settings.minToolsHint": "\u89E6\u53D1\u56DE\u5408\u81F3\u5C11\u5305\u542B K \u6B21\u5DE5\u5177\u8C03\u7528\uFF08\u6700\u5C0F 1\uFF09\u3002",
|
|
291
|
+
"settings.andHint": "\u4E09\u4E2A\u9891\u7387\u6761\u4EF6\u6309 AND \u5224\u5B9A\uFF1Bidle\u3001aborted\u3001subagent \u548C\u5DF2\u8BB0\u5F55\u8BB0\u5FC6\u7684\u56DE\u5408\u4E0D\u4F1A\u89E6\u53D1\u3002",
|
|
292
|
+
"settings.source": "\u5F53\u524D\u6765\u6E90",
|
|
293
|
+
"settings.source.profile": "\u542F\u52A8\u914D\u7F6E",
|
|
294
|
+
"settings.source.web": "Web \u6301\u4E45\u5316\u8986\u76D6",
|
|
295
|
+
"settings.save": "\u4FDD\u5B58\u5E76\u7ACB\u5373\u5E94\u7528",
|
|
296
|
+
"settings.reset": "\u6062\u590D\u542F\u52A8\u914D\u7F6E",
|
|
297
|
+
"settings.saved": "\u5DF2\u4FDD\u5B58\uFF0C\u540E\u7EED\u56DE\u5408\u7ACB\u5373\u751F\u6548\u3002",
|
|
298
|
+
"settings.resetDone": "\u5DF2\u6062\u590D\u672C\u6B21\u542F\u52A8\u65F6\u7684\u914D\u7F6E\u3002",
|
|
299
|
+
"settings.invalid": "\u8BF7\u8F93\u5165\u6709\u6548\u6570\u503C\uFF1A\u95F4\u9694\u548C\u5DE5\u5177\u6570\u4E3A\u4E0D\u5C0F\u4E8E 1 \u7684\u6574\u6570\uFF0C\u51B7\u5374\u65F6\u95F4\u4E0D\u5C0F\u4E8E 0\u3002",
|
|
300
|
+
"settings.loadFailed": "\u8BBE\u7F6E\u52A0\u8F7D\u5931\u8D25",
|
|
301
|
+
"settings.saveFailed": "\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25"
|
|
258
302
|
},
|
|
259
303
|
en: {
|
|
260
304
|
"entry.label": "Memory",
|
|
@@ -316,7 +360,28 @@ var dictionaries = {
|
|
|
316
360
|
"lifecycle.supersede": "Mark superseded",
|
|
317
361
|
"lifecycle.unsupersede": "Restore active",
|
|
318
362
|
"lifecycle.archive": "Archive",
|
|
319
|
-
"lifecycle.restore": "Restore"
|
|
363
|
+
"lifecycle.restore": "Restore",
|
|
364
|
+
"settings.title": "Auto-distill settings",
|
|
365
|
+
"settings.description": "Changes apply to subsequent turns immediately and persist in the local DSH user directory.",
|
|
366
|
+
"settings.enabled": "Enable auto-distill",
|
|
367
|
+
"settings.enabledHint": "Prompt top-level agents to distill memory after eligible worked turns.",
|
|
368
|
+
"settings.every": "Worked-turn interval",
|
|
369
|
+
"settings.everyHint": "Remind at most once per N accumulated worked turns (minimum 1).",
|
|
370
|
+
"settings.cooldown": "Cooldown (minutes)",
|
|
371
|
+
"settings.cooldownHint": "Require at least M minutes between successful reminders (minimum 0).",
|
|
372
|
+
"settings.minTools": "Minimum tool calls",
|
|
373
|
+
"settings.minToolsHint": "The triggering turn must contain at least K tool calls (minimum 1).",
|
|
374
|
+
"settings.andHint": "All three frequency conditions use AND semantics; idle, aborted, subagent, and already-recorded turns do not trigger.",
|
|
375
|
+
"settings.source": "Current source",
|
|
376
|
+
"settings.source.profile": "startup profile config",
|
|
377
|
+
"settings.source.web": "persistent Web override",
|
|
378
|
+
"settings.save": "Save and apply now",
|
|
379
|
+
"settings.reset": "Restore startup config",
|
|
380
|
+
"settings.saved": "Saved; subsequent turns use the new policy immediately.",
|
|
381
|
+
"settings.resetDone": "Restored the configuration captured at startup.",
|
|
382
|
+
"settings.invalid": "Enter valid values: interval and tool count are integers of at least 1; cooldown is at least 0.",
|
|
383
|
+
"settings.loadFailed": "Failed to load settings",
|
|
384
|
+
"settings.saveFailed": "Failed to save settings"
|
|
320
385
|
}
|
|
321
386
|
};
|
|
322
387
|
var SECTION_KEYS = ["work", "lessons", "actions", "note"];
|
|
@@ -385,6 +450,7 @@ html[data-dsh-memoir-active] [class*="centerCol"] > *:not([data-dsh-memoir-view]
|
|
|
385
450
|
border-radius: 6px; padding: 6px 12px; cursor: pointer; font-size: 13px;
|
|
386
451
|
}
|
|
387
452
|
.memoir-primary:hover { filter: brightness(1.05); }
|
|
453
|
+
.memoir-primary:disabled, .memoir-iconbtn:disabled { cursor: not-allowed; opacity: .45; }
|
|
388
454
|
|
|
389
455
|
.memoir-body { flex: 1; overflow-y: auto; padding: 4px 14px 16px; }
|
|
390
456
|
.memoir-section-title {
|
|
@@ -434,6 +500,27 @@ html[data-dsh-memoir-active] [class*="centerCol"] > *:not([data-dsh-memoir-view]
|
|
|
434
500
|
.memoir-field textarea { min-height: 72px; resize: vertical; }
|
|
435
501
|
.memoir-form-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
436
502
|
|
|
503
|
+
/* v0.5.3 live auto-distill settings */
|
|
504
|
+
.memoir-settings { margin: 0 14px; border-top: 1px solid var(--border, rgba(0,0,0,.1)); padding-top: 8px; }
|
|
505
|
+
.memoir-settings-body {
|
|
506
|
+
display: flex; flex-direction: column; gap: 10px; margin-top: 7px; padding: 10px;
|
|
507
|
+
border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 8px;
|
|
508
|
+
background: var(--bg-card, rgba(0,0,0,.02)); max-height: min(50vh, 430px); overflow-y: auto;
|
|
509
|
+
}
|
|
510
|
+
.memoir-settings-description, .memoir-settings-note, .memoir-settings-source { font-size: 11px; opacity: .72; line-height: 1.45; }
|
|
511
|
+
.memoir-settings-switch { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
|
|
512
|
+
.memoir-settings-switch input { margin-top: 3px; }
|
|
513
|
+
.memoir-settings-switch span { display: flex; flex-direction: column; gap: 2px; }
|
|
514
|
+
.memoir-settings-switch small, .memoir-settings-grid small { font-size: 10px; opacity: .65; line-height: 1.35; }
|
|
515
|
+
.memoir-settings-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
516
|
+
.memoir-settings-grid .memoir-field { min-width: 0; }
|
|
517
|
+
.memoir-settings-grid .memoir-field > span { font-size: 11px; opacity: .8; }
|
|
518
|
+
.memoir-settings-feedback { padding: 0; }
|
|
519
|
+
.memoir-settings-success { color: var(--success, #15803d); font-size: 12px; }
|
|
520
|
+
@media (max-width: 760px) {
|
|
521
|
+
.memoir-settings-grid { grid-template-columns: 1fr; }
|
|
522
|
+
}
|
|
523
|
+
|
|
437
524
|
/* Sidebar entry row (plain DOM, matches the shell's nav rows). */
|
|
438
525
|
.memoir-entry-row {
|
|
439
526
|
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
@@ -723,6 +810,123 @@ function AddForm({ t, onSubmit, onCancel }) {
|
|
|
723
810
|
] })
|
|
724
811
|
] });
|
|
725
812
|
}
|
|
813
|
+
function AutoDistillSettingsPanel({ api, t, refreshKey, onChanged }) {
|
|
814
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
815
|
+
const [settings, setSettings] = (0, import_react.useState)(null);
|
|
816
|
+
const [source, setSource] = (0, import_react.useState)("profile");
|
|
817
|
+
const [every, setEvery] = (0, import_react.useState)("1");
|
|
818
|
+
const [cooldown, setCooldown] = (0, import_react.useState)("0");
|
|
819
|
+
const [minTools, setMinTools] = (0, import_react.useState)("1");
|
|
820
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
821
|
+
const [message, setMessage] = (0, import_react.useState)(null);
|
|
822
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
823
|
+
const applySnapshot = (snapshot) => {
|
|
824
|
+
setError(null);
|
|
825
|
+
setSettings(snapshot.settings);
|
|
826
|
+
setSource(snapshot.source);
|
|
827
|
+
setEvery(String(snapshot.settings.autoDistillEvery));
|
|
828
|
+
setCooldown(String(snapshot.settings.autoDistillCooldownMin));
|
|
829
|
+
setMinTools(String(snapshot.settings.autoDistillMinTools));
|
|
830
|
+
};
|
|
831
|
+
(0, import_react.useEffect)(() => {
|
|
832
|
+
let cancelled = false;
|
|
833
|
+
api.settings().then((value) => {
|
|
834
|
+
if (!cancelled) applySnapshot(value);
|
|
835
|
+
}).catch((e) => {
|
|
836
|
+
if (!cancelled) setError(`${t("settings.loadFailed")}: ${e.message}`);
|
|
837
|
+
});
|
|
838
|
+
return () => {
|
|
839
|
+
cancelled = true;
|
|
840
|
+
};
|
|
841
|
+
}, [api, refreshKey]);
|
|
842
|
+
const save = () => {
|
|
843
|
+
if (settings === null) return;
|
|
844
|
+
const parsedEvery = Number(every);
|
|
845
|
+
const parsedCooldown = Number(cooldown);
|
|
846
|
+
const parsedMinTools = Number(minTools);
|
|
847
|
+
if (!Number.isSafeInteger(parsedEvery) || parsedEvery < 1 || !Number.isFinite(parsedCooldown) || parsedCooldown < 0 || !Number.isSafeInteger(parsedMinTools) || parsedMinTools < 1) {
|
|
848
|
+
setMessage(null);
|
|
849
|
+
setError(t("settings.invalid"));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
setBusy(true);
|
|
853
|
+
setError(null);
|
|
854
|
+
setMessage(null);
|
|
855
|
+
api.updateSettings({
|
|
856
|
+
autoDistill: settings.autoDistill,
|
|
857
|
+
autoDistillEvery: parsedEvery,
|
|
858
|
+
autoDistillCooldownMin: parsedCooldown,
|
|
859
|
+
autoDistillMinTools: parsedMinTools
|
|
860
|
+
}).then((value) => {
|
|
861
|
+
applySnapshot(value);
|
|
862
|
+
setMessage(t("settings.saved"));
|
|
863
|
+
onChanged();
|
|
864
|
+
}).catch((e) => setError(`${t("settings.saveFailed")}: ${e.message}`)).finally(() => setBusy(false));
|
|
865
|
+
};
|
|
866
|
+
const reset = () => {
|
|
867
|
+
setBusy(true);
|
|
868
|
+
setError(null);
|
|
869
|
+
setMessage(null);
|
|
870
|
+
api.resetSettings().then((value) => {
|
|
871
|
+
applySnapshot(value);
|
|
872
|
+
setMessage(t("settings.resetDone"));
|
|
873
|
+
onChanged();
|
|
874
|
+
}).catch((e) => setError(`${t("settings.saveFailed")}: ${e.message}`)).finally(() => setBusy(false));
|
|
875
|
+
};
|
|
876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-settings", children: [
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "memoir-diagnostics-toggle", onClick: () => setOpen((value) => !value), children: [
|
|
878
|
+
t("settings.title"),
|
|
879
|
+
" ",
|
|
880
|
+
open ? "\u25BE" : "\u25B8"
|
|
881
|
+
] }),
|
|
882
|
+
open ? settings === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-settings-body", children: error ?? "\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-settings-body", children: [
|
|
883
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-settings-description", children: t("settings.description") }),
|
|
884
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "memoir-settings-switch", children: [
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
886
|
+
"input",
|
|
887
|
+
{
|
|
888
|
+
type: "checkbox",
|
|
889
|
+
checked: settings.autoDistill,
|
|
890
|
+
onChange: (event) => setSettings({ ...settings, autoDistill: event.target.checked })
|
|
891
|
+
}
|
|
892
|
+
),
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("settings.enabled") }),
|
|
895
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("settings.enabledHint") })
|
|
896
|
+
] })
|
|
897
|
+
] }),
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-settings-grid", children: [
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "memoir-field", children: [
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("settings.every") }),
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", min: "1", step: "1", value: every, onChange: (event) => setEvery(event.target.value) }),
|
|
902
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("settings.everyHint") })
|
|
903
|
+
] }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "memoir-field", children: [
|
|
905
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("settings.cooldown") }),
|
|
906
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", min: "0", step: "0.1", value: cooldown, onChange: (event) => setCooldown(event.target.value) }),
|
|
907
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("settings.cooldownHint") })
|
|
908
|
+
] }),
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "memoir-field", children: [
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("settings.minTools") }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", min: "1", step: "1", value: minTools, onChange: (event) => setMinTools(event.target.value) }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("settings.minToolsHint") })
|
|
913
|
+
] })
|
|
914
|
+
] }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-settings-note", children: t("settings.andHint") }),
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-settings-source", children: [
|
|
917
|
+
t("settings.source"),
|
|
918
|
+
": ",
|
|
919
|
+
t(`settings.source.${source}`)
|
|
920
|
+
] }),
|
|
921
|
+
error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-error memoir-settings-feedback", children: error }) : null,
|
|
922
|
+
message !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-settings-success", children: message }) : null,
|
|
923
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-form-actions", children: [
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-iconbtn", disabled: busy || source === "profile", onClick: reset, children: t("settings.reset") }),
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-primary", disabled: busy, onClick: save, children: t("settings.save") })
|
|
926
|
+
] })
|
|
927
|
+
] }) : null
|
|
928
|
+
] });
|
|
929
|
+
}
|
|
726
930
|
function MemoirPanel({ controller, api, cwdTracker, t }) {
|
|
727
931
|
const cwd = (0, import_react.useSyncExternalStore)(cwdTracker.subscribe, cwdTracker.getSnapshot);
|
|
728
932
|
const [tab, setTab] = (0, import_react.useState)("project");
|
|
@@ -890,6 +1094,7 @@ function MemoirPanel({ controller, api, cwdTracker, t }) {
|
|
|
890
1094
|
] }, p.key);
|
|
891
1095
|
}) }),
|
|
892
1096
|
busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: "\u2026" }) : null,
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoDistillSettingsPanel, { api, t, refreshKey, onChanged: reload }),
|
|
893
1098
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-inspector", children: [
|
|
894
1099
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "memoir-diagnostics-toggle", onClick: () => setInspectorOpen((v) => !v), children: [
|
|
895
1100
|
t("inspector.title"),
|