dsh-memoir 0.6.1 → 0.7.0
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 +18 -9
- package/README.md +18 -9
- package/cordis.patch.yml +2 -2
- package/lib/autodistill.d.ts +39 -0
- package/lib/autodistill.js +63 -20
- package/lib/client.js +60 -0
- package/lib/client.js.map +2 -2
- package/lib/i18n.js +2 -0
- package/lib/index.js +5 -1
- package/package.json +18 -18
package/README.en.md
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
No embeddings, vector database, or cloud memory service. The npm package has zero bundled runtime dependencies; DSH peers are supplied by the host.
|
|
13
13
|
|
|
14
14
|
> [!IMPORTANT]
|
|
15
|
-
>
|
|
15
|
+
> `dsh-memoir@0.7.0` is a compatibility-only release for DSH **0.1.5-rc.1**, with no new features. It requires `>=0.1.5-rc.1 <0.1.6-0`; upgrade DSH first. Keep `dsh-memoir@0.6.2` on DSH 0.1.2, or `0.5.6` on DSH 0.1.1-rc.2.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install --global @deepseek-ai/dsh@
|
|
18
|
+
npm install --global @deepseek-ai/dsh@0.1.5-rc.1
|
|
19
19
|
dsh plugin --profile web add dsh-memoir@latest
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -77,6 +77,10 @@ Similar-memory governance starts with BM25 candidates, then combines title simil
|
|
|
77
77
|
|
|
78
78
|
## Automatic distillation
|
|
79
79
|
|
|
80
|
+
v0.6.2 diagnostics show the latest decision and process-local counters. Turns calling `memoir_record` or `memoir_update` skip reminders; submitting a reminder does not confirm persistence. Agent disposal clears gate state, with a fallback cap of 1024 recently active agents (eviction also forgets their turn watermark and cooldown). Manual recording remains available when automatic distillation is disabled.
|
|
81
|
+
|
|
82
|
+
The validated baseline is DSH `0.1.5-rc.1` (npm has not published an unsuffixed 0.1.5). BM25 is lexical retrieval and does not guarantee cross-language semantic matches without shared terms. Distillation guidance does not replace fact checking.
|
|
83
|
+
|
|
80
84
|
Automatic distillation is an observable agent turn-end reminder, not silent background scraping of every chat. The default `1 / 0 / 1` means every eligible worked turn, no extra cooldown, and at least one tool call.
|
|
81
85
|
|
|
82
86
|
`autoDistillEvery`, `autoDistillCooldownMin`, and `autoDistillMinTools` are AND conditions isolated per agent. Idle, aborted, subagent, and already-recorded turns do not trigger. Cooldown advances only after a successful reminder. All cadence parameters are live-editable in the GUI.
|
|
@@ -113,6 +117,10 @@ Installing into a DSH-alpha `web` profile registers a native Memory Conversation
|
|
|
113
117
|
<details>
|
|
114
118
|
<summary>More GUI screenshots</summary>
|
|
115
119
|
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+

|
|
123
|
+
|
|
116
124
|

|
|
117
125
|
|
|
118
126
|

|
|
@@ -131,23 +139,24 @@ Installing into a DSH-alpha `web` profile registers a native Memory Conversation
|
|
|
131
139
|
|
|
132
140
|
| Channel | DSH baseline | Installation | Status |
|
|
133
141
|
| --- | --- | --- | --- |
|
|
134
|
-
| npm `latest` (`0.
|
|
142
|
+
| npm `latest` (`0.7.0`) | `>=0.1.5-rc.1 <0.1.6-0` | `dsh plugin --profile web add dsh-memoir@latest` | Compatibility release validated on 0.1.5-rc.1 |
|
|
143
|
+
| pinned npm `0.6.2` | `>=0.1.2-alpha.2 <0.1.3` | `dsh plugin --profile web add dsh-memoir@0.6.2` | Legacy 0.1.2 line |
|
|
135
144
|
| pinned npm `0.5.6` | `0.1.1-rc.2` | `dsh plugin --profile web add dsh-memoir@0.5.6` | rc2 compatibility line |
|
|
136
|
-
| GitHub `main` (`0.
|
|
145
|
+
| GitHub `main` (`0.7.0`) | `>=0.1.5-rc.1 <0.1.6-0` | source clone + `link:` | Development and debugging |
|
|
137
146
|
|
|
138
|
-
Node.js `^22.19.0 || >=24.0.0` is required.
|
|
147
|
+
Node.js `^22.19.0 || >=24.0.0` is required. 0.7.0 keeps the native `conversation.view` / `settings.section` slots and `snapshotEvents()`. DSH 0.1.5 uses Session log V3, independent of Memoir store v4 / settings v3. Back up DSH_HOME before upgrading DSH; migrated sessions are not guaranteed readable by older hosts. This Memoir release neither migrates nor resets memory and retains frozen session snapshots without enabling new dynamic-prompt behavior.
|
|
139
148
|
|
|
140
149
|
<details>
|
|
141
150
|
<summary>Install from source</summary>
|
|
142
151
|
|
|
143
|
-
0.
|
|
152
|
+
0.7.x source:
|
|
144
153
|
|
|
145
154
|
```bash
|
|
146
155
|
git clone https://github.com/Qinling-Melon-Farmers/dsh-memoir.git
|
|
147
156
|
cd dsh-memoir
|
|
148
157
|
pnpm install --frozen-lockfile
|
|
149
158
|
pnpm run build
|
|
150
|
-
npm install --global @deepseek-ai/dsh@
|
|
159
|
+
npm install --global @deepseek-ai/dsh@0.1.5-rc.1
|
|
151
160
|
dsh plugin --profile web add "link:/absolute/path/dsh-memoir"
|
|
152
161
|
```
|
|
153
162
|
|
|
@@ -204,7 +213,7 @@ v0.5.6 benchmark (Node 24.19, 900/1200-token budget; full data in [`bench/report
|
|
|
204
213
|
|
|
205
214
|
Numbers vary by machine and corpus. The important properties are that injection remains bounded and the cache-hit path is independent of total memory size.
|
|
206
215
|
|
|
207
|
-
|
|
216
|
+
0.7.0 passes 193 tests and type checking, retaining all existing regressions and adding an official DSH Session V3 object test for provenance, distillation, tool writes, and frozen injection. An isolated DSH 0.1.5-rc.1 profile passes Web Settings, bilingual diagnostics, and API checks. Historical alpha/rc validation is recorded in the changelog and does not imply 0.7.0 can be installed on older hosts.
|
|
208
217
|
|
|
209
218
|
## FAQ
|
|
210
219
|
|
|
@@ -233,6 +242,6 @@ pnpm test
|
|
|
233
242
|
npm run bench
|
|
234
243
|
```
|
|
235
244
|
|
|
236
|
-
Read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting changes. See [CHANGELOG.md](./CHANGELOG.md) for version history. Formal packages are published by the tag workflow through npm OIDC. The current npm release is [v0.
|
|
245
|
+
Read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting changes. See [CHANGELOG.md](./CHANGELOG.md) for version history. Formal packages are published by the tag workflow through npm OIDC. The current npm release is [v0.7.0](https://github.com/Qinling-Melon-Farmers/dsh-memoir/releases/tag/v0.7.0), and `main` is synchronized with it.
|
|
237
246
|
|
|
238
247
|
Apache-2.0
|
package/README.md
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
无需 embedding、向量数据库或云端记忆服务;npm 包零捆绑运行时依赖,DSH peer 由宿主提供。
|
|
12
12
|
|
|
13
13
|
> [!IMPORTANT]
|
|
14
|
-
>
|
|
14
|
+
> `dsh-memoir@0.7.0` 是 DSH **0.1.5-rc.1** 的兼容性发布,不增加新功能。要求 `>=0.1.5-rc.1 <0.1.6-0`;请先升级 DSH。旧 DSH 0.1.2 用户固定使用 `dsh-memoir@0.6.2`,0.1.1-rc.2 用户固定使用 `0.5.6`。
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install --global @deepseek-ai/dsh@
|
|
17
|
+
npm install --global @deepseek-ai/dsh@0.1.5-rc.1
|
|
18
18
|
dsh plugin --profile web add dsh-memoir@latest
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -76,6 +76,10 @@ memoir_record / memoir_update
|
|
|
76
76
|
|
|
77
77
|
## 自动蒸馏
|
|
78
78
|
|
|
79
|
+
v0.6.2 的诊断页显示最近触发或跳过原因及本次进程计数。已经调用 `memoir_record` 或 `memoir_update` 的回合不再提醒;提交提醒不代表写入已完成。Agent 销毁会清理门控状态,最多保留 1024 个最近活动 Agent(淘汰后不再保留其回合水位和冷却)。关闭自动蒸馏后仍可手动记录。
|
|
80
|
+
|
|
81
|
+
当前验证基线为 DSH `0.1.5-rc.1`(npm 尚无不带后缀的 0.1.5)。BM25 是词项召回,不能保证无共同词项的跨语言语义匹配;提炼质量提示也不能替代事实核验。
|
|
82
|
+
|
|
79
83
|
自动蒸馏是可观察的 Agent 收尾提醒,不是后台静默抓取聊天内容。默认 `1 / 0 / 1` 表示:每个有效 worked turn、无额外冷却、至少一次工具调用即可提醒。
|
|
80
84
|
|
|
81
85
|
`autoDistillEvery`、`autoDistillCooldownMin`、`autoDistillMinTools` 三个条件按 AND 判定并按 Agent 隔离。idle、aborted、subagent 和已调用 `memoir_record` 的回合不会触发;冷却只在提醒成功后更新。所有频率参数都可在 GUI 中即时修改。
|
|
@@ -112,6 +116,10 @@ memoir_record / memoir_update
|
|
|
112
116
|
<details>
|
|
113
117
|
<summary>查看更多 GUI 截图</summary>
|
|
114
118
|
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
115
123
|

|
|
116
124
|
|
|
117
125
|

|
|
@@ -130,23 +138,24 @@ memoir_record / memoir_update
|
|
|
130
138
|
|
|
131
139
|
| 渠道 | DSH 基线 | 安装方式 | 状态 |
|
|
132
140
|
| --- | --- | --- | --- |
|
|
133
|
-
| npm `latest`(`0.
|
|
141
|
+
| npm `latest`(`0.7.0`) | `>=0.1.5-rc.1 <0.1.6-0` | `dsh plugin --profile web add dsh-memoir@latest` | 当前兼容性发布;验证基线 0.1.5-rc.1 |
|
|
142
|
+
| npm 固定版 `0.6.2` | `>=0.1.2-alpha.2 <0.1.3` | `dsh plugin --profile web add dsh-memoir@0.6.2` | 旧 0.1.2 兼容线 |
|
|
134
143
|
| npm 固定版 `0.5.6` | `0.1.1-rc.2` | `dsh plugin --profile web add dsh-memoir@0.5.6` | rc2 兼容线 |
|
|
135
|
-
| GitHub `main`(`0.
|
|
144
|
+
| GitHub `main`(`0.7.0`) | `>=0.1.5-rc.1 <0.1.6-0` | 源码 clone + `link:` | 开发和调试使用 |
|
|
136
145
|
|
|
137
|
-
需要 Node.js `^22.19.0 || >=24.0.0
|
|
146
|
+
需要 Node.js `^22.19.0 || >=24.0.0`。0.7.0 继续使用原生 `conversation.view` / `settings.section` 与 `snapshotEvents()`。DSH 0.1.5 的会话日志升级至 V3;其迁移与 Memoir 的 store v4 / settings v3 是独立格式。升级 DSH 前备份 DSH_HOME,迁移后的 DSH 会话不能承诺被旧宿主读取。Memoir 本次不迁移或清空记忆,也不启用新动态提示词行为;既有会话快照语义保持不变。
|
|
138
147
|
|
|
139
148
|
<details>
|
|
140
149
|
<summary>从源码安装</summary>
|
|
141
150
|
|
|
142
|
-
0.
|
|
151
|
+
0.7.x 源码:
|
|
143
152
|
|
|
144
153
|
```bash
|
|
145
154
|
git clone https://github.com/Qinling-Melon-Farmers/dsh-memoir.git
|
|
146
155
|
cd dsh-memoir
|
|
147
156
|
pnpm install --frozen-lockfile
|
|
148
157
|
pnpm run build
|
|
149
|
-
npm install --global @deepseek-ai/dsh@
|
|
158
|
+
npm install --global @deepseek-ai/dsh@0.1.5-rc.1
|
|
150
159
|
dsh plugin --profile web add "link:/absolute/path/dsh-memoir"
|
|
151
160
|
```
|
|
152
161
|
|
|
@@ -203,7 +212,7 @@ v0.5.6 基准(Node 24.19,900/1200 token;完整数据见 [`bench/report.md`
|
|
|
203
212
|
|
|
204
213
|
基准值取决于机器和语料;它证明的重点是注入预算保持有界、缓存命中路径与记忆总量解耦。
|
|
205
214
|
|
|
206
|
-
|
|
215
|
+
0.7.0 的 193 项测试与类型检查通过,保留全部既有回归,并新增使用官方 DSH Session V3 对象的来源、蒸馏、工具写入与冻结注入测试。隔离 DSH 0.1.5-rc.1 的 Web 设置、双语诊断与 API 回归通过。旧版 alpha/rc 的历史验证见各版本更新日志;不据此宣称 0.7.0 可安装在旧宿主。
|
|
207
216
|
|
|
208
217
|
## 常见问题
|
|
209
218
|
|
|
@@ -232,6 +241,6 @@ pnpm test
|
|
|
232
241
|
npm run bench
|
|
233
242
|
```
|
|
234
243
|
|
|
235
|
-
提交前请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。版本变化见 [CHANGELOG.md](./CHANGELOG.md),正式包由 tag 工作流通过 npm OIDC 发布。当前 npm 正式版是 [v0.
|
|
244
|
+
提交前请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。版本变化见 [CHANGELOG.md](./CHANGELOG.md),正式包由 tag 工作流通过 npm OIDC 发布。当前 npm 正式版是 [v0.7.0](https://github.com/Qinling-Melon-Farmers/dsh-memoir/releases/tag/v0.7.0),`main` 与该版本同步。
|
|
236
245
|
|
|
237
246
|
Apache-2.0
|
package/cordis.patch.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-memoir bundle patch: inserts the host-half plugin row into the web
|
|
2
2
|
# profile roster. Applied as a profile bundle layer (the `dsh.bundle.patch`
|
|
3
|
-
# manifest field) over dsh-base. dsh-memoir v0.
|
|
4
|
-
#
|
|
3
|
+
# manifest field) over dsh-base. dsh-memoir v0.7.0 targets the published DSH
|
|
4
|
+
# line >=0.1.5-rc.1 <0.1.6-0 and installs from npm with
|
|
5
5
|
# dsh plugin --profile web add dsh-memoir@latest
|
|
6
6
|
#
|
|
7
7
|
# The row is a bare plugin by package name: the node half (exports ".")
|
package/lib/autodistill.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ export interface AutoDistillPolicy {
|
|
|
62
62
|
/** Per-agent frequency, cooldown, and duplicate-turn state (with pruning). */
|
|
63
63
|
export declare class AutoDistillGate {
|
|
64
64
|
private states;
|
|
65
|
+
readonly capacity = 1024;
|
|
66
|
+
reason: 'duplicate' | 'interval' | 'tools' | 'cooldown' | 'ready';
|
|
67
|
+
get size(): number;
|
|
68
|
+
clear(): void;
|
|
65
69
|
/**
|
|
66
70
|
* Consume one eligible worked turn and decide whether all policy conditions
|
|
67
71
|
* are ready. Duplicate events never advance the worked-turn counter.
|
|
@@ -72,6 +76,39 @@ export declare class AutoDistillGate {
|
|
|
72
76
|
/** Drop all state for one agent (disposal hygiene). */
|
|
73
77
|
forget(agentId: string): void;
|
|
74
78
|
}
|
|
79
|
+
export type DistillOutcome = 'disabled' | 'subagent' | 'aborted' | 'idle' | 'recorded' | 'duplicate' | 'interval' | 'tools' | 'cooldown' | 'steered' | 'failed';
|
|
80
|
+
/** Process-local counters only; never retains message content or credentials. */
|
|
81
|
+
export declare class DistillDiagnostics {
|
|
82
|
+
private counts;
|
|
83
|
+
private last;
|
|
84
|
+
private workedTurns;
|
|
85
|
+
private agents;
|
|
86
|
+
record(outcome: DistillOutcome, at: number, turn: number, toolCalls: number, agents: number): void;
|
|
87
|
+
snapshot(): {
|
|
88
|
+
counts: {
|
|
89
|
+
subagent?: number | undefined;
|
|
90
|
+
duplicate?: number | undefined;
|
|
91
|
+
interval?: number | undefined;
|
|
92
|
+
tools?: number | undefined;
|
|
93
|
+
cooldown?: number | undefined;
|
|
94
|
+
disabled?: number | undefined;
|
|
95
|
+
aborted?: number | undefined;
|
|
96
|
+
idle?: number | undefined;
|
|
97
|
+
recorded?: number | undefined;
|
|
98
|
+
steered?: number | undefined;
|
|
99
|
+
failed?: number | undefined;
|
|
100
|
+
};
|
|
101
|
+
workedTurns: number;
|
|
102
|
+
agents: number;
|
|
103
|
+
last: {
|
|
104
|
+
outcome: DistillOutcome;
|
|
105
|
+
at: number;
|
|
106
|
+
turn: number;
|
|
107
|
+
toolCalls: number;
|
|
108
|
+
} | null;
|
|
109
|
+
};
|
|
110
|
+
setAgents(agents: number): void;
|
|
111
|
+
}
|
|
75
112
|
export interface TurnStoppingPayload {
|
|
76
113
|
agent: AutoDistillAgentLike;
|
|
77
114
|
turn: number;
|
|
@@ -80,6 +117,7 @@ export interface TurnStoppingPayload {
|
|
|
80
117
|
/** The event-wire surface the installer needs (satisfied by ctx.on). */
|
|
81
118
|
export interface AutoDistillWire {
|
|
82
119
|
on(name: 'agent/turn-stopping', listener: (payload: TurnStoppingPayload) => void): () => void;
|
|
120
|
+
onDisposed?(listener: (agentId: string) => void): () => void;
|
|
83
121
|
}
|
|
84
122
|
/**
|
|
85
123
|
* Install the turn-end listener. The returned disposer removes the listener.
|
|
@@ -100,4 +138,5 @@ export declare function installAutoDistill(wire: AutoDistillWire, options: {
|
|
|
100
138
|
/** Optional live language source for the steering instruction. */
|
|
101
139
|
language?: () => MemoirLanguage;
|
|
102
140
|
now?: () => number;
|
|
141
|
+
diagnostics?: DistillDiagnostics;
|
|
103
142
|
}): () => void;
|
package/lib/autodistill.js
CHANGED
|
@@ -40,7 +40,7 @@ export function turnActivity(events, turn) {
|
|
|
40
40
|
continue;
|
|
41
41
|
if (event.type === 'tool/call') {
|
|
42
42
|
toolCalls += 1;
|
|
43
|
-
if (data.name === 'memoir_record')
|
|
43
|
+
if (data.name === 'memoir_record' || data.name === 'memoir_update')
|
|
44
44
|
recorded = true;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -53,6 +53,10 @@ export function isSubagentSession(agent) {
|
|
|
53
53
|
/** Per-agent frequency, cooldown, and duplicate-turn state (with pruning). */
|
|
54
54
|
export class AutoDistillGate {
|
|
55
55
|
states = new Map();
|
|
56
|
+
capacity = 1024;
|
|
57
|
+
reason = 'ready';
|
|
58
|
+
get size() { return this.states.size; }
|
|
59
|
+
clear() { this.states.clear(); }
|
|
56
60
|
/**
|
|
57
61
|
* Consume one eligible worked turn and decide whether all policy conditions
|
|
58
62
|
* are ready. Duplicate events never advance the worked-turn counter.
|
|
@@ -60,21 +64,24 @@ export class AutoDistillGate {
|
|
|
60
64
|
consume(agentId, turn, toolCalls, policy, now) {
|
|
61
65
|
let state = this.states.get(agentId);
|
|
62
66
|
if (state === undefined) {
|
|
63
|
-
state = {
|
|
67
|
+
state = { lastTurn: -Infinity, workedSinceSteer: 0 };
|
|
64
68
|
this.states.set(agentId, state);
|
|
69
|
+
if (this.states.size > this.capacity)
|
|
70
|
+
this.states.delete(this.states.keys().next().value);
|
|
65
71
|
}
|
|
66
|
-
if (state.
|
|
72
|
+
if (turn <= state.lastTurn) {
|
|
73
|
+
this.reason = 'duplicate';
|
|
67
74
|
return false;
|
|
68
|
-
state.processedTurns.add(turn);
|
|
69
|
-
for (const value of [...state.processedTurns]) {
|
|
70
|
-
if (value < turn - 100)
|
|
71
|
-
state.processedTurns.delete(value);
|
|
72
75
|
}
|
|
76
|
+
state.lastTurn = turn;
|
|
77
|
+
this.states.delete(agentId);
|
|
78
|
+
this.states.set(agentId, state);
|
|
73
79
|
state.workedSinceSteer += 1;
|
|
74
80
|
const intervalReady = state.workedSinceSteer >= policy.every;
|
|
75
81
|
const activityReady = toolCalls >= policy.minTools;
|
|
76
82
|
const cooldownReady = state.lastSteeredAt === undefined || now - state.lastSteeredAt >= policy.cooldownMs;
|
|
77
|
-
|
|
83
|
+
this.reason = !intervalReady ? 'interval' : !activityReady ? 'tools' : !cooldownReady ? 'cooldown' : 'ready';
|
|
84
|
+
return this.reason === 'ready';
|
|
78
85
|
}
|
|
79
86
|
/** Record a successful steer; failed steer attempts do not start cooldown. */
|
|
80
87
|
recordSteer(agentId, now) {
|
|
@@ -89,6 +96,22 @@ export class AutoDistillGate {
|
|
|
89
96
|
this.states.delete(agentId);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
99
|
+
/** Process-local counters only; never retains message content or credentials. */
|
|
100
|
+
export class DistillDiagnostics {
|
|
101
|
+
counts = {};
|
|
102
|
+
last = null;
|
|
103
|
+
workedTurns = 0;
|
|
104
|
+
agents = 0;
|
|
105
|
+
record(outcome, at, turn, toolCalls, agents) {
|
|
106
|
+
this.counts[outcome] = (this.counts[outcome] ?? 0) + 1;
|
|
107
|
+
if (['interval', 'tools', 'cooldown', 'steered', 'failed'].includes(outcome))
|
|
108
|
+
this.workedTurns += 1;
|
|
109
|
+
this.last = { outcome, at, turn, toolCalls };
|
|
110
|
+
this.agents = agents;
|
|
111
|
+
}
|
|
112
|
+
snapshot() { return { counts: { ...this.counts }, workedTurns: this.workedTurns, agents: this.agents, last: this.last === null ? null : { ...this.last } }; }
|
|
113
|
+
setAgents(agents) { this.agents = agents; }
|
|
114
|
+
}
|
|
92
115
|
/**
|
|
93
116
|
* Install the turn-end listener. The returned disposer removes the listener.
|
|
94
117
|
* @param wire - the event wire (the cordis context).
|
|
@@ -98,30 +121,50 @@ export function installAutoDistill(wire, options) {
|
|
|
98
121
|
const gate = new AutoDistillGate();
|
|
99
122
|
const integerAtLeast = (value, fallback, minimum) => typeof value === 'number' && Number.isFinite(value) ? Math.max(minimum, Math.floor(value)) : fallback;
|
|
100
123
|
const numberAtLeast = (value, fallback, minimum) => typeof value === 'number' && Number.isFinite(value) ? Math.max(minimum, value) : fallback;
|
|
101
|
-
|
|
102
|
-
if (!options.enabled())
|
|
103
|
-
return;
|
|
124
|
+
const dispose = wire.on('agent/turn-stopping', (payload) => {
|
|
104
125
|
const { agent, turn, signal } = payload;
|
|
105
|
-
|
|
126
|
+
const now = options.now?.() ?? Date.now();
|
|
127
|
+
const report = (outcome, tools = 0) => options.diagnostics?.record(outcome, now, turn, tools, gate.size);
|
|
128
|
+
if (!options.enabled()) {
|
|
129
|
+
report('disabled');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (isSubagentSession(agent)) {
|
|
133
|
+
report('subagent');
|
|
106
134
|
return;
|
|
107
|
-
|
|
135
|
+
}
|
|
136
|
+
if (signal.aborted) {
|
|
137
|
+
report('aborted');
|
|
108
138
|
return;
|
|
139
|
+
}
|
|
109
140
|
const { worked, recorded, toolCalls } = turnActivity(sessionEventSnapshot(agent.session), turn);
|
|
110
|
-
if (!worked || recorded)
|
|
141
|
+
if (!worked || recorded) {
|
|
142
|
+
report(recorded ? 'recorded' : 'idle', toolCalls);
|
|
111
143
|
return;
|
|
144
|
+
}
|
|
112
145
|
const live = options.policy?.();
|
|
113
146
|
const policy = {
|
|
114
147
|
every: integerAtLeast(live?.every ?? options.every, 1, 1),
|
|
115
148
|
cooldownMs: numberAtLeast(live?.cooldownMin ?? options.cooldownMin, 0, 0) * 60_000,
|
|
116
149
|
minTools: integerAtLeast(live?.minTools ?? options.minTools, 1, 1),
|
|
117
150
|
};
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
if (!gate.consume(agent.id, turn, toolCalls, policy, now)) {
|
|
152
|
+
report(gate.reason === 'ready' ? 'duplicate' : gate.reason, toolCalls);
|
|
120
153
|
return;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
agent.steer(createUserMessage({
|
|
157
|
+
content: [{ type: 'text', text: distillPrompt(options.language?.()) }],
|
|
158
|
+
source: { kind: 'plugin', plugin: AUTO_DISTILL_PLUGIN },
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
report('failed', toolCalls);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
125
165
|
gate.recordSteer(agent.id, now);
|
|
166
|
+
report('steered', toolCalls);
|
|
126
167
|
});
|
|
168
|
+
const disposeAgent = wire.onDisposed?.((agentId) => { gate.forget(agentId); options.diagnostics?.setAgents(gate.size); });
|
|
169
|
+
return () => { dispose(); disposeAgent?.(); gate.clear(); options.diagnostics?.setAgents(0); };
|
|
127
170
|
}
|
package/lib/client.js
CHANGED
|
@@ -157,6 +157,22 @@ var MemoirApi = class {
|
|
|
157
157
|
// src/client/i18n.ts
|
|
158
158
|
var dictionaries = {
|
|
159
159
|
zh: {
|
|
160
|
+
"distill.title": "\u81EA\u52A8\u84B8\u998F\u751F\u547D\u5468\u671F",
|
|
161
|
+
"distill.worked": "\u7D2F\u8BA1\u6709\u6548\u5DE5\u4F5C\u56DE\u5408",
|
|
162
|
+
"distill.agents": "\u8DDF\u8E2A Agent",
|
|
163
|
+
"distill.note": "\u672C\u6B21\u8FDB\u7A0B\u5185\u7EDF\u8BA1\uFF1B\u63D0\u9192\u6210\u529F\u4E0D\u4EE3\u8868\u8BB0\u5FC6\u5DF2\u5199\u5165\u3002\u505C\u7528\u81EA\u52A8\u84B8\u998F\u540E\u4ECD\u53EF\u624B\u52A8\u8BB0\u5F55\u3002",
|
|
164
|
+
"distill.last": "\u6700\u8FD1\u51B3\u7B56",
|
|
165
|
+
"distill.disabled": "\u81EA\u52A8\u84B8\u998F\u5DF2\u5173\u95ED",
|
|
166
|
+
"distill.subagent": "\u5B50\u4EE3\u7406\u8DF3\u8FC7",
|
|
167
|
+
"distill.aborted": "\u4E2D\u6B62\u8DF3\u8FC7",
|
|
168
|
+
"distill.idle": "\u65E0\u5DE5\u5177\u6D3B\u52A8",
|
|
169
|
+
"distill.recorded": "\u5DF2\u8C03\u7528\u8BB0\u5FC6\u5199\u5165\u6216\u66F4\u65B0",
|
|
170
|
+
"distill.duplicate": "\u91CD\u590D\u6216\u8FC7\u671F\u56DE\u5408",
|
|
171
|
+
"distill.interval": "\u672A\u8FBE\u56DE\u5408\u95F4\u9694",
|
|
172
|
+
"distill.tools": "\u672A\u8FBE\u5DE5\u5177\u8C03\u7528\u9608\u503C",
|
|
173
|
+
"distill.cooldown": "\u51B7\u5374\u4E2D",
|
|
174
|
+
"distill.steered": "\u63D0\u9192\u5DF2\u63D0\u4EA4",
|
|
175
|
+
"distill.failed": "\u63D0\u9192\u63D0\u4EA4\u5931\u8D25",
|
|
160
176
|
"entry.label": "\u8BB0\u5FC6",
|
|
161
177
|
"entry.tooltip": "\u6253\u5F00\u8BB0\u5FC6\u9762\u677F\uFF08\u9879\u76EE\u8BB0\u5FC6 / \u5168\u5C40\u8BB0\u5FC6\uFF09",
|
|
162
178
|
"panel.title": "\u8BB0\u5FC6 Memoir",
|
|
@@ -319,6 +335,22 @@ var dictionaries = {
|
|
|
319
335
|
"settings.saveFailed": "\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25"
|
|
320
336
|
},
|
|
321
337
|
en: {
|
|
338
|
+
"distill.title": "Automatic distillation lifecycle",
|
|
339
|
+
"distill.worked": "Eligible worked turns",
|
|
340
|
+
"distill.agents": "Tracked agents",
|
|
341
|
+
"distill.note": "Process-local counters; a submitted reminder does not confirm a memory write. Manual recording remains available when disabled.",
|
|
342
|
+
"distill.last": "Latest decision",
|
|
343
|
+
"distill.disabled": "Automatic distillation disabled",
|
|
344
|
+
"distill.subagent": "Subagent skipped",
|
|
345
|
+
"distill.aborted": "Aborted turn skipped",
|
|
346
|
+
"distill.idle": "No tool activity",
|
|
347
|
+
"distill.recorded": "Memory write or update already called",
|
|
348
|
+
"distill.duplicate": "Duplicate or stale turn",
|
|
349
|
+
"distill.interval": "Turn interval not reached",
|
|
350
|
+
"distill.tools": "Tool threshold not reached",
|
|
351
|
+
"distill.cooldown": "Cooldown active",
|
|
352
|
+
"distill.steered": "Reminder submitted",
|
|
353
|
+
"distill.failed": "Reminder submission failed",
|
|
322
354
|
"entry.label": "Memory",
|
|
323
355
|
"entry.tooltip": "Open the memory panel (project / global)",
|
|
324
356
|
"panel.title": "Memory Memoir",
|
|
@@ -1426,6 +1458,34 @@ ${projectSignature}`;
|
|
|
1426
1458
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { id: `${panelId}-diagnostics-panel`, className: "memoir-surface", role: "tabpanel", "aria-labelledby": `${panelId}-diagnostics-tab`, hidden: surface !== "diagnostics", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-surface-scroll", "data-dsh-part": "diagnostics-scroll", tabIndex: 0, "aria-label": t("surface.diagnostics"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-surface-card", "data-dsh-part": "diagnostics", children: [
|
|
1427
1459
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("diag.title") }),
|
|
1428
1460
|
diag === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: t("diag.unavailable") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-diagnostics-body", children: [
|
|
1461
|
+
diag.autoDistill ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1462
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: t("distill.title") }),
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1464
|
+
t("distill.worked"),
|
|
1465
|
+
": ",
|
|
1466
|
+
diag.autoDistill.workedTurns,
|
|
1467
|
+
" \xB7 ",
|
|
1468
|
+
t("distill.agents"),
|
|
1469
|
+
": ",
|
|
1470
|
+
diag.autoDistill.agents,
|
|
1471
|
+
"/1024"
|
|
1472
|
+
] }),
|
|
1473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: t("distill.note") }),
|
|
1474
|
+
Object.entries(diag.autoDistill.counts).map(([reason, count]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1475
|
+
t(`distill.${reason}`),
|
|
1476
|
+
": ",
|
|
1477
|
+
count
|
|
1478
|
+
] }, reason)),
|
|
1479
|
+
diag.autoDistill.last ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1480
|
+
t("distill.last"),
|
|
1481
|
+
": ",
|
|
1482
|
+
t(`distill.${diag.autoDistill.last.outcome}`),
|
|
1483
|
+
" \xB7 ",
|
|
1484
|
+
new Date(diag.autoDistill.last.at).toLocaleString(),
|
|
1485
|
+
" \xB7 turn ",
|
|
1486
|
+
diag.autoDistill.last.turn
|
|
1487
|
+
] }) : null
|
|
1488
|
+
] }) : null,
|
|
1429
1489
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1430
1490
|
t("diag.revision"),
|
|
1431
1491
|
": ",
|