@aiwayds/dsh-topics-memory 0.16.1-next.1 → 0.18.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.md +4 -4
- package/README.zh.md +3 -3
- package/icon.svg +6 -0
- package/lib/commands.js +29 -0
- package/lib/config.d.ts +55 -55
- package/lib/config.js +26 -26
- package/lib/distill.d.ts +8 -0
- package/lib/distill.js +1 -1
- package/lib/index.d.ts +131 -32
- package/lib/index.js +295 -162
- package/lib/store.d.ts +13 -0
- package/lib/store.js +25 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +30 -27
- package/skills/dsh-topics-memory-config/SKILL.md +6 -6
- package/src/commands.ts +26 -0
- package/src/config.ts +26 -26
- package/src/distill.ts +15 -1
- package/src/index.ts +376 -182
- package/src/store.ts +25 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
|
|
4
4
|
|
|
5
5
|
A dsh plugin: maintains "working topic memory" as an [OKF (Open Knowledge Format v0.2)](https://github.com/GoogleCloudPlatform/open-knowledge-format) knowledge bundle, persisted in a local git repository (optionally synced to a private GitHub repo), with conclusions traceable through git history, sessions automatically observed and distilled into knowledge, and relevant topics injected to the model before every turn.
|
|
6
6
|
|
|
7
|
-
> **Requires dsh >= 0.1.
|
|
7
|
+
> **Requires dsh >= 0.1.7-rc.1** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.**
|
|
8
8
|
|
|
9
9
|
## Demo
|
|
10
10
|
|
|
@@ -90,14 +90,14 @@ The host reconciles the profile automatically: the `dsh.profile.bundles` entry i
|
|
|
90
90
|
What stays on disk (kept on purpose — this is your memory):
|
|
91
91
|
|
|
92
92
|
- `~/.dsh/topics/` — the whole topic bundle: topic markdown, `meta/`, and the embedded `.git` repo (the full history; it may carry an `origin` remote — GitHub sync stops with the plugin). To archive the bundle elsewhere, copy or clone this directory as-is.
|
|
93
|
-
-
|
|
94
|
-
-
|
|
93
|
+
- The settings-page `dsh-topics-memory` entry (profile patch) — user overrides. Reinstalling silently reactivates sync including any configured `repo`; clear the entry first if you want a clean start.
|
|
94
|
+
- A legacy `llmwiki:` section left inside the old `settings.yaml` (renamed `settings.yaml.imported` after the host's one-shot 0.1.7 import) is never touched again; mine it by hand if something still needs it.
|
|
95
95
|
|
|
96
96
|
Purge everything: back up `~/.dsh/topics` first, then `rm -rf ~/.dsh/topics`.
|
|
97
97
|
|
|
98
98
|
## Configuration
|
|
99
99
|
|
|
100
|
-
First-time setup belongs to `/topics onboard`; day-to-day tuning is `/topics set <key> <value>`
|
|
100
|
+
First-time setup belongs to `/topics onboard`; day-to-day tuning is `/topics set <key> <value>` — on dsh 0.1.7+ it writes the settings-page `dsh-topics-memory` entry (the profile patch) and every key is volatile: edits take effect immediately, no restart. All keys and defaults:
|
|
101
101
|
|
|
102
102
|
| Key | Default | Meaning |
|
|
103
103
|
|---|---|---|
|
package/README.zh.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
一个 dsh 插件:把「工作 topic 记忆」维护成 [OKF 标准(Open Knowledge Format v0.2)](https://github.com/GoogleCloudPlatform/open-knowledge-format)的知识 bundle,持久化在本地 git 仓库(可选同步到 GitHub 私有仓库),利用 git 历史提供结论可追溯性,自动观察会话沉淀知识,并在每轮对话前向模型注入相关 Topic。
|
|
6
6
|
|
|
7
|
-
> **要求 dsh >= 0.1.
|
|
7
|
+
> **要求 dsh >= 0.1.7-rc.1** — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。**不再支持 alpha 线。**
|
|
8
8
|
|
|
9
9
|
## 演示
|
|
10
10
|
|
|
@@ -90,14 +90,14 @@ dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory
|
|
|
90
90
|
以下内容有意保留在磁盘上(这是你的记忆本体):
|
|
91
91
|
|
|
92
92
|
- `~/.dsh/topics/` —— 整个话题包:话题 markdown、`meta/`、以及内嵌的 `.git` 仓库(完整历史;可能带有 `origin` 远端 —— GitHub 同步随插件停止)。要异地归档就原样拷贝/克隆这个目录。
|
|
93
|
-
-
|
|
93
|
+
- 设置页 `dsh-topics-memory` 条目(profile patch)—— 用户覆盖项。注意:重装会静默恢复同步(包括已配置的 `repo`);想干净重来就先清空该条目。
|
|
94
94
|
- 0.5.x → 0.6.x 迁移留下的 `llmwiki:` 旧设置段不会被自动删除;确认无用后手动移除。
|
|
95
95
|
|
|
96
96
|
彻底清除:先备份 `~/.dsh/topics`,再 `rm -rf ~/.dsh/topics`。
|
|
97
97
|
|
|
98
98
|
## 配置
|
|
99
99
|
|
|
100
|
-
首次配置交给 `/topics onboard`;日常微调用 `/topics set <key> <value
|
|
100
|
+
首次配置交给 `/topics onboard`;日常微调用 `/topics set <key> <value>`——0.1.7+ 宿主上写设置页 `dsh-topics-memory` 条目(profile patch),全部键 volatile 免重启即时生效。全部键与默认值:
|
|
101
101
|
|
|
102
102
|
| 键 | 默认 | 说明 |
|
|
103
103
|
|---|---|---|
|
package/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle cx="6.5" cy="6" r="2.2"/>
|
|
3
|
+
<circle cx="17.5" cy="7.5" r="2.2"/>
|
|
4
|
+
<circle cx="12" cy="17.5" r="2.2"/>
|
|
5
|
+
<path d="M8.7 6.5 15.3 7M16.3 9.6 13.5 15.6M9.5 8.1l1.7 7.3"/>
|
|
6
|
+
</svg>
|
package/lib/commands.js
CHANGED
|
@@ -112,6 +112,35 @@ async function renderStatus(service) {
|
|
|
112
112
|
const gc = typeof lastRun.gcDropped === 'number' && lastRun.gcDropped > 0 ? `,GC 回收 ${lastRun.gcDropped}` : '';
|
|
113
113
|
rows.push(['最近蒸馏', `${outcome}${gc} @ ${cell(String(lastRun.at ?? '').slice(0, 19).replace('T', ' '))}`]);
|
|
114
114
|
}
|
|
115
|
+
// Fast-lane heartbeat (0.17.0): pair "observer lane recorded recently"
|
|
116
|
+
// with "no injection round for >48h" — exactly the asymmetry a silent
|
|
117
|
+
// fast-lane death produces (the 0.1.5-rc.2 claim-order incident shape).
|
|
118
|
+
// With autoInject off there is no fast lane to be stale.
|
|
119
|
+
if (cfg.autoInject) {
|
|
120
|
+
const HOUR = 3_600_000;
|
|
121
|
+
const hb = await service.store.readLaneHeartbeat();
|
|
122
|
+
const stamp = (iso) => cell(iso.slice(0, 19).replace('T', ' '));
|
|
123
|
+
const ago = (ms) => {
|
|
124
|
+
const h = ms / HOUR;
|
|
125
|
+
if (!Number.isFinite(h))
|
|
126
|
+
return '';
|
|
127
|
+
return h < 1 ? `${Math.max(1, Math.round(h * 60))} 分钟前` : h < 48 ? `${Math.round(h)}h 前` : `${Math.round(h / 24)} 天前`;
|
|
128
|
+
};
|
|
129
|
+
const lastAt = hb.lastInjectionAt;
|
|
130
|
+
const fastIdleMs = lastAt !== undefined ? Date.now() - Date.parse(lastAt) : Number.POSITIVE_INFINITY;
|
|
131
|
+
const obsAgeMs = hb.lastObservationAt !== undefined ? Date.now() - Date.parse(hb.lastObservationAt) : Number.POSITIVE_INFINITY;
|
|
132
|
+
const obsRecent = obsAgeMs < 24 * HOUR;
|
|
133
|
+
if (lastAt === undefined) {
|
|
134
|
+
rows.push(['快道心跳', obsRecent ? `⚠️ 从未注入,但观察 lane ${ago(obsAgeMs)}仍在记录——快道疑似静默死亡` : '从未注入']);
|
|
135
|
+
}
|
|
136
|
+
else if (fastIdleMs > 48 * HOUR && obsRecent) {
|
|
137
|
+
rows.push(['快道心跳', `⚠️ ${stamp(lastAt)}(${ago(fastIdleMs)})无注入轮次,而观察 lane ${ago(obsAgeMs)}仍在记录——快道疑似静默死亡`]);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const since = ago(fastIdleMs);
|
|
141
|
+
rows.push(['快道心跳', `${stamp(lastAt)}${since === '' ? '' : `(${since})`}`]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
115
144
|
const lastConsolidate = await service.store.readConsolidateState();
|
|
116
145
|
if (lastConsolidate !== undefined) {
|
|
117
146
|
const count = (v) => (Array.isArray(v) ? v.length : 0);
|
package/lib/config.d.ts
CHANGED
|
@@ -5,141 +5,141 @@
|
|
|
5
5
|
* @module config
|
|
6
6
|
*/
|
|
7
7
|
import z from '@deepseek-ai/schemastery';
|
|
8
|
-
export declare const TopicsConfig: z<Schemastery.ObjectS<{
|
|
8
|
+
export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
9
9
|
/** GitHub repo `owner/name`; empty = local-only mode (ADR 0008). */
|
|
10
|
-
repo: z<string, string>;
|
|
10
|
+
repo: z<string, string, "volatile-defined">;
|
|
11
11
|
/** Master switch for per-turn injection. */
|
|
12
|
-
autoInject: z<boolean, boolean>;
|
|
12
|
+
autoInject: z<boolean, boolean, "volatile-defined">;
|
|
13
13
|
/** Skip re-injecting topics already injected earlier in the same session. */
|
|
14
|
-
injectDedup: z<boolean, boolean>;
|
|
14
|
+
injectDedup: z<boolean, boolean, "volatile-defined">;
|
|
15
15
|
/** Skip re-injecting topics distilled from the CURRENT session's own turns
|
|
16
16
|
* (蒸馏回声): the conversation already carries that knowledge, so a
|
|
17
17
|
* pointer is a stale echo at best (2026-09 audit: 3 of 6 useless rounds).
|
|
18
18
|
* Provenance rides the observations log (sessionId → distilledInto). */
|
|
19
|
-
suppressEcho: z<boolean, boolean>;
|
|
19
|
+
suppressEcho: z<boolean, boolean, "volatile-defined">;
|
|
20
20
|
/** Max topics injected per round (ADR 0006: ≤4). */
|
|
21
|
-
topK: z<number, number>;
|
|
21
|
+
topK: z<number, number, "volatile-defined">;
|
|
22
22
|
/** Per-topic digest budget in tokens. */
|
|
23
|
-
perTopicBudget: z<number, number>;
|
|
23
|
+
perTopicBudget: z<number, number, "volatile-defined">;
|
|
24
24
|
/** Total injection budget in tokens. */
|
|
25
|
-
totalBudget: z<number, number>;
|
|
25
|
+
totalBudget: z<number, number, "volatile-defined">;
|
|
26
26
|
/** Retrieval score threshold — tune via /topics stats near-miss evidence. */
|
|
27
|
-
matchThreshold: z<number, number>;
|
|
27
|
+
matchThreshold: z<number, number, "volatile-defined">;
|
|
28
28
|
/** Additive boost per tag hit (v4: total cap = this value, was ×3). */
|
|
29
|
-
tagBoost: z<number, number>;
|
|
29
|
+
tagBoost: z<number, number, "volatile-defined">;
|
|
30
30
|
/** Injection shape: pointer (default, ≤600 tok) keeps the legacy digest view. */
|
|
31
|
-
injectMode: z<string, string>;
|
|
31
|
+
injectMode: z<string, string, "volatile-defined">;
|
|
32
32
|
/** Slow quality lane (v4 §4.2): off | sampled (1/3 of turns) | always. */
|
|
33
|
-
qualityLane: z<string, string>;
|
|
33
|
+
qualityLane: z<string, string, "volatile-defined">;
|
|
34
34
|
/** depends-graph expansion depth (0 disables). */
|
|
35
|
-
graphDepth: z<number, number>;
|
|
35
|
+
graphDepth: z<number, number, "volatile-defined">;
|
|
36
36
|
/** Days within which a topic counts as recent (+0.2). */
|
|
37
|
-
recencyWindowDays: z<number, number>;
|
|
37
|
+
recencyWindowDays: z<number, number, "volatile-defined">;
|
|
38
38
|
/** Capture each turn's user/assistant text as raw observations (M2). */
|
|
39
|
-
autoObserve: z<boolean, boolean>;
|
|
39
|
+
autoObserve: z<boolean, boolean, "volatile-defined">;
|
|
40
40
|
/** Whether injection and observation also engage delegated subagent sessions (ADR 0011).
|
|
41
41
|
* v4 default flipped to false: one-shot subagent turns diluted the pool.
|
|
42
42
|
* The slow quality lane never runs for subagents either way (hard guard). */
|
|
43
|
-
includeSubagents: z<boolean, boolean>;
|
|
43
|
+
includeSubagents: z<boolean, boolean, "volatile-defined">;
|
|
44
44
|
/** Max auto-captured chars per side (user/assistant) per turn. */
|
|
45
|
-
observationMaxChars: z<number, number>;
|
|
45
|
+
observationMaxChars: z<number, number, "volatile-defined">;
|
|
46
46
|
/** Background distill cadence: every N turns of a long session. */
|
|
47
|
-
distillEveryTurns: z<number, number>;
|
|
47
|
+
distillEveryTurns: z<number, number, "volatile-defined">;
|
|
48
48
|
/** Distill once when a session ends. */
|
|
49
|
-
distillOnSessionEnd: z<boolean, boolean>;
|
|
49
|
+
distillOnSessionEnd: z<boolean, boolean, "volatile-defined">;
|
|
50
50
|
/** Distill lane model route; both must be set, else distill stays idle. */
|
|
51
|
-
distillProvider: z<string, string>;
|
|
52
|
-
distillModel: z<string, string>;
|
|
51
|
+
distillProvider: z<string, string, "volatile-defined">;
|
|
52
|
+
distillModel: z<string, string, "volatile-defined">;
|
|
53
53
|
/** Observations per distill model call; auto-halves on output-limit failures (floor 5). */
|
|
54
|
-
distillBatchSize: z<number, number>;
|
|
54
|
+
distillBatchSize: z<number, number, "volatile-defined">;
|
|
55
55
|
/** Max model calls per distill run — successful batches keep their marks (partial progress).
|
|
56
56
|
* Default 8: with the batch loop, one run then drains ~30-40 observations
|
|
57
57
|
* instead of ~10, which is what makes a real backlog actually shrink. */
|
|
58
|
-
distillMaxModelCalls: z<number, number>;
|
|
58
|
+
distillMaxModelCalls: z<number, number, "volatile-defined">;
|
|
59
59
|
/** Consolidation lane (整理) cadence: how often a session start may run the
|
|
60
60
|
* LLM gardener over the EXISTING pool (merge near-duplicates, promote
|
|
61
61
|
* settled drafts, deprecate superseded, refresh metadata). Reuses the
|
|
62
62
|
* distill model route; off disables the lane entirely. */
|
|
63
|
-
consolidateCadence: z<string, string>;
|
|
63
|
+
consolidateCadence: z<string, string, "volatile-defined">;
|
|
64
64
|
/** Deprecated topics older than this many days are dropped at session start
|
|
65
65
|
* (local TTL sweep, no model; each drop is its own git commit, so history
|
|
66
66
|
* stays recoverable on the remote). 0 disables the sweep. */
|
|
67
|
-
deprecatedTtlDays: z<number, number>;
|
|
67
|
+
deprecatedTtlDays: z<number, number, "volatile-defined">;
|
|
68
68
|
/** UsageBoost (ADR 0015): behavioral bonus for topics injected/opened in
|
|
69
69
|
* the last 30 days, folded into the gate score — capped at 0.2 and never
|
|
70
70
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
71
71
|
* 0 = off. */
|
|
72
|
-
usageBoost: z<number, number>;
|
|
72
|
+
usageBoost: z<number, number, "volatile-defined">;
|
|
73
73
|
/** Debounced push delay in GitHub mode. */
|
|
74
|
-
pushDebounceSeconds: z<number, number>;
|
|
75
|
-
}
|
|
74
|
+
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
75
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
76
76
|
/** GitHub repo `owner/name`; empty = local-only mode (ADR 0008). */
|
|
77
|
-
repo: z<string, string>;
|
|
77
|
+
repo: z<string, string, "volatile-defined">;
|
|
78
78
|
/** Master switch for per-turn injection. */
|
|
79
|
-
autoInject: z<boolean, boolean>;
|
|
79
|
+
autoInject: z<boolean, boolean, "volatile-defined">;
|
|
80
80
|
/** Skip re-injecting topics already injected earlier in the same session. */
|
|
81
|
-
injectDedup: z<boolean, boolean>;
|
|
81
|
+
injectDedup: z<boolean, boolean, "volatile-defined">;
|
|
82
82
|
/** Skip re-injecting topics distilled from the CURRENT session's own turns
|
|
83
83
|
* (蒸馏回声): the conversation already carries that knowledge, so a
|
|
84
84
|
* pointer is a stale echo at best (2026-09 audit: 3 of 6 useless rounds).
|
|
85
85
|
* Provenance rides the observations log (sessionId → distilledInto). */
|
|
86
|
-
suppressEcho: z<boolean, boolean>;
|
|
86
|
+
suppressEcho: z<boolean, boolean, "volatile-defined">;
|
|
87
87
|
/** Max topics injected per round (ADR 0006: ≤4). */
|
|
88
|
-
topK: z<number, number>;
|
|
88
|
+
topK: z<number, number, "volatile-defined">;
|
|
89
89
|
/** Per-topic digest budget in tokens. */
|
|
90
|
-
perTopicBudget: z<number, number>;
|
|
90
|
+
perTopicBudget: z<number, number, "volatile-defined">;
|
|
91
91
|
/** Total injection budget in tokens. */
|
|
92
|
-
totalBudget: z<number, number>;
|
|
92
|
+
totalBudget: z<number, number, "volatile-defined">;
|
|
93
93
|
/** Retrieval score threshold — tune via /topics stats near-miss evidence. */
|
|
94
|
-
matchThreshold: z<number, number>;
|
|
94
|
+
matchThreshold: z<number, number, "volatile-defined">;
|
|
95
95
|
/** Additive boost per tag hit (v4: total cap = this value, was ×3). */
|
|
96
|
-
tagBoost: z<number, number>;
|
|
96
|
+
tagBoost: z<number, number, "volatile-defined">;
|
|
97
97
|
/** Injection shape: pointer (default, ≤600 tok) keeps the legacy digest view. */
|
|
98
|
-
injectMode: z<string, string>;
|
|
98
|
+
injectMode: z<string, string, "volatile-defined">;
|
|
99
99
|
/** Slow quality lane (v4 §4.2): off | sampled (1/3 of turns) | always. */
|
|
100
|
-
qualityLane: z<string, string>;
|
|
100
|
+
qualityLane: z<string, string, "volatile-defined">;
|
|
101
101
|
/** depends-graph expansion depth (0 disables). */
|
|
102
|
-
graphDepth: z<number, number>;
|
|
102
|
+
graphDepth: z<number, number, "volatile-defined">;
|
|
103
103
|
/** Days within which a topic counts as recent (+0.2). */
|
|
104
|
-
recencyWindowDays: z<number, number>;
|
|
104
|
+
recencyWindowDays: z<number, number, "volatile-defined">;
|
|
105
105
|
/** Capture each turn's user/assistant text as raw observations (M2). */
|
|
106
|
-
autoObserve: z<boolean, boolean>;
|
|
106
|
+
autoObserve: z<boolean, boolean, "volatile-defined">;
|
|
107
107
|
/** Whether injection and observation also engage delegated subagent sessions (ADR 0011).
|
|
108
108
|
* v4 default flipped to false: one-shot subagent turns diluted the pool.
|
|
109
109
|
* The slow quality lane never runs for subagents either way (hard guard). */
|
|
110
|
-
includeSubagents: z<boolean, boolean>;
|
|
110
|
+
includeSubagents: z<boolean, boolean, "volatile-defined">;
|
|
111
111
|
/** Max auto-captured chars per side (user/assistant) per turn. */
|
|
112
|
-
observationMaxChars: z<number, number>;
|
|
112
|
+
observationMaxChars: z<number, number, "volatile-defined">;
|
|
113
113
|
/** Background distill cadence: every N turns of a long session. */
|
|
114
|
-
distillEveryTurns: z<number, number>;
|
|
114
|
+
distillEveryTurns: z<number, number, "volatile-defined">;
|
|
115
115
|
/** Distill once when a session ends. */
|
|
116
|
-
distillOnSessionEnd: z<boolean, boolean>;
|
|
116
|
+
distillOnSessionEnd: z<boolean, boolean, "volatile-defined">;
|
|
117
117
|
/** Distill lane model route; both must be set, else distill stays idle. */
|
|
118
|
-
distillProvider: z<string, string>;
|
|
119
|
-
distillModel: z<string, string>;
|
|
118
|
+
distillProvider: z<string, string, "volatile-defined">;
|
|
119
|
+
distillModel: z<string, string, "volatile-defined">;
|
|
120
120
|
/** Observations per distill model call; auto-halves on output-limit failures (floor 5). */
|
|
121
|
-
distillBatchSize: z<number, number>;
|
|
121
|
+
distillBatchSize: z<number, number, "volatile-defined">;
|
|
122
122
|
/** Max model calls per distill run — successful batches keep their marks (partial progress).
|
|
123
123
|
* Default 8: with the batch loop, one run then drains ~30-40 observations
|
|
124
124
|
* instead of ~10, which is what makes a real backlog actually shrink. */
|
|
125
|
-
distillMaxModelCalls: z<number, number>;
|
|
125
|
+
distillMaxModelCalls: z<number, number, "volatile-defined">;
|
|
126
126
|
/** Consolidation lane (整理) cadence: how often a session start may run the
|
|
127
127
|
* LLM gardener over the EXISTING pool (merge near-duplicates, promote
|
|
128
128
|
* settled drafts, deprecate superseded, refresh metadata). Reuses the
|
|
129
129
|
* distill model route; off disables the lane entirely. */
|
|
130
|
-
consolidateCadence: z<string, string>;
|
|
130
|
+
consolidateCadence: z<string, string, "volatile-defined">;
|
|
131
131
|
/** Deprecated topics older than this many days are dropped at session start
|
|
132
132
|
* (local TTL sweep, no model; each drop is its own git commit, so history
|
|
133
133
|
* stays recoverable on the remote). 0 disables the sweep. */
|
|
134
|
-
deprecatedTtlDays: z<number, number>;
|
|
134
|
+
deprecatedTtlDays: z<number, number, "volatile-defined">;
|
|
135
135
|
/** UsageBoost (ADR 0015): behavioral bonus for topics injected/opened in
|
|
136
136
|
* the last 30 days, folded into the gate score — capped at 0.2 and never
|
|
137
137
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
138
138
|
* 0 = off. */
|
|
139
|
-
usageBoost: z<number, number>;
|
|
139
|
+
usageBoost: z<number, number, "volatile-defined">;
|
|
140
140
|
/** Debounced push delay in GitHub mode. */
|
|
141
|
-
pushDebounceSeconds: z<number, number>;
|
|
142
|
-
}
|
|
141
|
+
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
142
|
+
}>>, "plain">;
|
|
143
143
|
export type TopicsConfigValue = {
|
|
144
144
|
repo: string;
|
|
145
145
|
autoInject: boolean;
|
package/lib/config.js
CHANGED
|
@@ -7,71 +7,71 @@
|
|
|
7
7
|
import z from '@deepseek-ai/schemastery';
|
|
8
8
|
export const TopicsConfig = z.object({
|
|
9
9
|
/** GitHub repo `owner/name`; empty = local-only mode (ADR 0008). */
|
|
10
|
-
repo: z.string().default(''),
|
|
10
|
+
repo: z.string().default('').volatile(),
|
|
11
11
|
/** Master switch for per-turn injection. */
|
|
12
|
-
autoInject: z.boolean().default(true),
|
|
12
|
+
autoInject: z.boolean().default(true).volatile(),
|
|
13
13
|
/** Skip re-injecting topics already injected earlier in the same session. */
|
|
14
|
-
injectDedup: z.boolean().default(true),
|
|
14
|
+
injectDedup: z.boolean().default(true).volatile(),
|
|
15
15
|
/** Skip re-injecting topics distilled from the CURRENT session's own turns
|
|
16
16
|
* (蒸馏回声): the conversation already carries that knowledge, so a
|
|
17
17
|
* pointer is a stale echo at best (2026-09 audit: 3 of 6 useless rounds).
|
|
18
18
|
* Provenance rides the observations log (sessionId → distilledInto). */
|
|
19
|
-
suppressEcho: z.boolean().default(true),
|
|
19
|
+
suppressEcho: z.boolean().default(true).volatile(),
|
|
20
20
|
/** Max topics injected per round (ADR 0006: ≤4). */
|
|
21
|
-
topK: z.number().default(4),
|
|
21
|
+
topK: z.number().default(4).volatile(),
|
|
22
22
|
/** Per-topic digest budget in tokens. */
|
|
23
|
-
perTopicBudget: z.number().default(300),
|
|
23
|
+
perTopicBudget: z.number().default(300).volatile(),
|
|
24
24
|
/** Total injection budget in tokens. */
|
|
25
|
-
totalBudget: z.number().default(1500),
|
|
25
|
+
totalBudget: z.number().default(1500).volatile(),
|
|
26
26
|
/** Retrieval score threshold — tune via /topics stats near-miss evidence. */
|
|
27
|
-
matchThreshold: z.number().default(0.3),
|
|
27
|
+
matchThreshold: z.number().default(0.3).volatile(),
|
|
28
28
|
/** Additive boost per tag hit (v4: total cap = this value, was ×3). */
|
|
29
|
-
tagBoost: z.number().default(0.15),
|
|
29
|
+
tagBoost: z.number().default(0.15).volatile(),
|
|
30
30
|
/** Injection shape: pointer (default, ≤600 tok) keeps the legacy digest view. */
|
|
31
|
-
injectMode: z.string().default('pointer'),
|
|
31
|
+
injectMode: z.string().default('pointer').volatile(),
|
|
32
32
|
/** Slow quality lane (v4 §4.2): off | sampled (1/3 of turns) | always. */
|
|
33
|
-
qualityLane: z.string().default('sampled'),
|
|
33
|
+
qualityLane: z.string().default('sampled').volatile(),
|
|
34
34
|
/** depends-graph expansion depth (0 disables). */
|
|
35
|
-
graphDepth: z.number().default(2),
|
|
35
|
+
graphDepth: z.number().default(2).volatile(),
|
|
36
36
|
/** Days within which a topic counts as recent (+0.2). */
|
|
37
|
-
recencyWindowDays: z.number().default(7),
|
|
37
|
+
recencyWindowDays: z.number().default(7).volatile(),
|
|
38
38
|
/** Capture each turn's user/assistant text as raw observations (M2). */
|
|
39
|
-
autoObserve: z.boolean().default(true),
|
|
39
|
+
autoObserve: z.boolean().default(true).volatile(),
|
|
40
40
|
/** Whether injection and observation also engage delegated subagent sessions (ADR 0011).
|
|
41
41
|
* v4 default flipped to false: one-shot subagent turns diluted the pool.
|
|
42
42
|
* The slow quality lane never runs for subagents either way (hard guard). */
|
|
43
|
-
includeSubagents: z.boolean().default(false),
|
|
43
|
+
includeSubagents: z.boolean().default(false).volatile(),
|
|
44
44
|
/** Max auto-captured chars per side (user/assistant) per turn. */
|
|
45
|
-
observationMaxChars: z.number().default(2000),
|
|
45
|
+
observationMaxChars: z.number().default(2000).volatile(),
|
|
46
46
|
/** Background distill cadence: every N turns of a long session. */
|
|
47
|
-
distillEveryTurns: z.number().default(5),
|
|
47
|
+
distillEveryTurns: z.number().default(5).volatile(),
|
|
48
48
|
/** Distill once when a session ends. */
|
|
49
|
-
distillOnSessionEnd: z.boolean().default(true),
|
|
49
|
+
distillOnSessionEnd: z.boolean().default(true).volatile(),
|
|
50
50
|
/** Distill lane model route; both must be set, else distill stays idle. */
|
|
51
|
-
distillProvider: z.string().default(''),
|
|
52
|
-
distillModel: z.string().default(''),
|
|
51
|
+
distillProvider: z.string().default('').volatile(),
|
|
52
|
+
distillModel: z.string().default('').volatile(),
|
|
53
53
|
/** Observations per distill model call; auto-halves on output-limit failures (floor 5). */
|
|
54
|
-
distillBatchSize: z.number().default(40),
|
|
54
|
+
distillBatchSize: z.number().default(40).volatile(),
|
|
55
55
|
/** Max model calls per distill run — successful batches keep their marks (partial progress).
|
|
56
56
|
* Default 8: with the batch loop, one run then drains ~30-40 observations
|
|
57
57
|
* instead of ~10, which is what makes a real backlog actually shrink. */
|
|
58
|
-
distillMaxModelCalls: z.number().default(8),
|
|
58
|
+
distillMaxModelCalls: z.number().default(8).volatile(),
|
|
59
59
|
/** Consolidation lane (整理) cadence: how often a session start may run the
|
|
60
60
|
* LLM gardener over the EXISTING pool (merge near-duplicates, promote
|
|
61
61
|
* settled drafts, deprecate superseded, refresh metadata). Reuses the
|
|
62
62
|
* distill model route; off disables the lane entirely. */
|
|
63
|
-
consolidateCadence: z.string().default('daily'),
|
|
63
|
+
consolidateCadence: z.string().default('daily').volatile(),
|
|
64
64
|
/** Deprecated topics older than this many days are dropped at session start
|
|
65
65
|
* (local TTL sweep, no model; each drop is its own git commit, so history
|
|
66
66
|
* stays recoverable on the remote). 0 disables the sweep. */
|
|
67
|
-
deprecatedTtlDays: z.number().default(15),
|
|
67
|
+
deprecatedTtlDays: z.number().default(15).volatile(),
|
|
68
68
|
/** UsageBoost (ADR 0015): behavioral bonus for topics injected/opened in
|
|
69
69
|
* the last 30 days, folded into the gate score — capped at 0.2 and never
|
|
70
70
|
* granted to zero-lexical candidates; the structural gate still applies.
|
|
71
71
|
* 0 = off. */
|
|
72
|
-
usageBoost: z.number().default(0.15),
|
|
72
|
+
usageBoost: z.number().default(0.15).volatile(),
|
|
73
73
|
/** Debounced push delay in GitHub mode. */
|
|
74
|
-
pushDebounceSeconds: z.number().default(45),
|
|
74
|
+
pushDebounceSeconds: z.number().default(45).volatile(),
|
|
75
75
|
});
|
|
76
76
|
export const CONFIG_KEYS = [
|
|
77
77
|
'repo',
|
package/lib/distill.d.ts
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* @module distill
|
|
11
11
|
*/
|
|
12
12
|
import type { TopicsService } from './service.ts';
|
|
13
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
14
|
+
interface MessageSourceMap {
|
|
15
|
+
/** Producer context contributed by the dsh-topics-memory plugin. */
|
|
16
|
+
'topics-memory': {
|
|
17
|
+
kind: 'topics-memory';
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
export interface ModelRequest {
|
|
14
22
|
system: string;
|
|
15
23
|
user: string;
|
package/lib/distill.js
CHANGED
|
@@ -710,7 +710,7 @@ export function defaultModelCaller(getCandidates, getRoute) {
|
|
|
710
710
|
const messages = [
|
|
711
711
|
createUserMessage({
|
|
712
712
|
content: [{ type: 'text', text: req.user }],
|
|
713
|
-
source: { kind: '
|
|
713
|
+
source: { kind: 'topics-memory' },
|
|
714
714
|
}),
|
|
715
715
|
];
|
|
716
716
|
const options = deepFreeze({
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type TopicsConfigValue } from './config.ts';
|
|
2
3
|
export declare const name = "dsh-topics-memory";
|
|
3
4
|
/**
|
|
4
5
|
* Bounded wait for the process-exit meta commit. The exit path is
|
|
@@ -17,42 +18,140 @@ export declare function settleBounded(p: Promise<unknown> | undefined, ms: numbe
|
|
|
17
18
|
/** Services consumed at apply time; llm joins via guarded ctx.inject, and the
|
|
18
19
|
* skills registry serves the bundled usage/config guide. */
|
|
19
20
|
export declare const inject: string[];
|
|
21
|
+
/** A firehose event as the replay path reads it: envelope fields only. */
|
|
22
|
+
export interface InboxEventLike {
|
|
23
|
+
seq?: unknown;
|
|
24
|
+
type?: unknown;
|
|
25
|
+
data?: unknown;
|
|
26
|
+
}
|
|
27
|
+
/** The pending-inbox projection replayed from a session-log prefix. */
|
|
28
|
+
export interface PendingInboxWindow {
|
|
29
|
+
'next-turn': readonly unknown[];
|
|
30
|
+
'next-step': readonly unknown[];
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
33
|
+
* Fold a session-log prefix into the pending-inbox projection state.
|
|
34
|
+
* Coordinates in the log are already normalize-at-append (dsh-agent-loop
|
|
35
|
+
* clamps start/deleteCount before appending), so each entry applies as a
|
|
36
|
+
* plain toSpliced. Events at or past `upToSeqExclusive` are ignored — pass
|
|
37
|
+
* the current splice's own seq to fold everything BEFORE it. Returns
|
|
38
|
+
* undefined when any entry is malformed enough to break the fold (the
|
|
39
|
+
* caller degrades to the projection read, as before).
|
|
25
40
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}[]): Promise<void>;
|
|
41
|
+
export declare function replayPendingInbox(events: readonly InboxEventLike[], upToSeqExclusive: number): PendingInboxWindow | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Claimed-text extraction, shared verbatim by the projection read and the
|
|
44
|
+
* log replay: user-kind messages only, text blocks joined per message —
|
|
45
|
+
* the pre-0.17.0 splice-window semantics.
|
|
46
|
+
*/
|
|
47
|
+
export declare function claimedUserText(messages: readonly unknown[]): string;
|
|
48
|
+
/** Where the resolved claimed text actually came from (see resolveClaimedText). */
|
|
49
|
+
export type ClaimedTextSource = 'log-replay' | 'projection';
|
|
50
|
+
export interface ClaimedTextResolution {
|
|
51
|
+
text: string;
|
|
52
|
+
source: ClaimedTextSource;
|
|
39
53
|
}
|
|
40
54
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
55
|
+
* Resolve the claimed text for one splice, ordering the two sources by
|
|
56
|
+
* authority (review CONCERN A, 0.17.1):
|
|
57
|
+
*
|
|
58
|
+
* 1. Session-log replay — preferred UNCONDITIONALLY whenever the event
|
|
59
|
+
* carries a seq and a log prefix is available. The fold reproduces the
|
|
60
|
+
* pre-splice window the claim coordinates (start/removedCount) are
|
|
61
|
+
* defined against, so it is immune to handler order AND to the backlog
|
|
62
|
+
* misalignment: with ≥2 pending messages the post-splice projection
|
|
63
|
+
* read is NON-empty but shifted by one (a claim of msg1 would read
|
|
64
|
+
* msg2's residual text), which the 0.17.0 empty-projection-only gating
|
|
65
|
+
* missed. The replay verdict is FINAL — when the fold succeeds the
|
|
66
|
+
* projection is not consulted, even if the replayed slice comes back
|
|
67
|
+
* empty (the pre-splice window genuinely held no user text).
|
|
68
|
+
* 2. Projection read — the fallback for hosts where the replay cannot arm
|
|
69
|
+
* (no event.seq on the firehose, or snapshotEvents unreachable/failed).
|
|
70
|
+
* Pre-0.1.5 live dispatch precedes the mutation, so this read yields
|
|
71
|
+
* the pre-splice window, as it always did.
|
|
72
|
+
*
|
|
73
|
+
* `source` names the source that ACTUALLY produced the returned text
|
|
74
|
+
* (review CONCERN B): 'log-replay' whenever the replay supplied the
|
|
75
|
+
* verdict (empty included), 'projection' only when the fallback did (or
|
|
76
|
+
* the replay never armed).
|
|
54
77
|
*/
|
|
55
|
-
export declare function
|
|
78
|
+
export declare function resolveClaimedText(input: {
|
|
79
|
+
/** The pending window as read at handler time (post-splice on 0.1.5+). */
|
|
80
|
+
projection: readonly unknown[];
|
|
81
|
+
/** Session-log prefix up to (excluding) `seq`, or undefined when unreachable. */
|
|
82
|
+
logEvents: readonly InboxEventLike[] | undefined;
|
|
83
|
+
/** The splice event's own seq — a number marks a replay-capable host. */
|
|
84
|
+
seq: unknown;
|
|
85
|
+
target: 'next-turn' | 'next-step';
|
|
86
|
+
start: number;
|
|
87
|
+
removedCount: number;
|
|
88
|
+
}): ClaimedTextResolution;
|
|
89
|
+
export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
90
|
+
repo: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
91
|
+
autoInject: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
92
|
+
injectDedup: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
93
|
+
suppressEcho: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
94
|
+
topK: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
95
|
+
perTopicBudget: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
96
|
+
totalBudget: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
97
|
+
matchThreshold: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
98
|
+
tagBoost: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
99
|
+
injectMode: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
100
|
+
qualityLane: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
101
|
+
graphDepth: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
102
|
+
recencyWindowDays: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
103
|
+
autoObserve: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
104
|
+
includeSubagents: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
105
|
+
observationMaxChars: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
106
|
+
distillEveryTurns: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
107
|
+
distillOnSessionEnd: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
108
|
+
distillProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
109
|
+
distillModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
110
|
+
distillBatchSize: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
111
|
+
distillMaxModelCalls: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
112
|
+
consolidateCadence: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
113
|
+
deprecatedTtlDays: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
114
|
+
usageBoost: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
115
|
+
pushDebounceSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
116
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
117
|
+
repo: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
118
|
+
autoInject: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
119
|
+
injectDedup: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
120
|
+
suppressEcho: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
121
|
+
topK: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
122
|
+
perTopicBudget: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
123
|
+
totalBudget: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
124
|
+
matchThreshold: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
125
|
+
tagBoost: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
126
|
+
injectMode: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
127
|
+
qualityLane: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
128
|
+
graphDepth: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
129
|
+
recencyWindowDays: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
130
|
+
autoObserve: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
131
|
+
includeSubagents: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
132
|
+
observationMaxChars: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
133
|
+
distillEveryTurns: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
134
|
+
distillOnSessionEnd: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
|
|
135
|
+
distillProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
136
|
+
distillModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
137
|
+
distillBatchSize: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
138
|
+
distillMaxModelCalls: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
139
|
+
consolidateCadence: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
140
|
+
deprecatedTtlDays: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
141
|
+
usageBoost: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
142
|
+
pushDebounceSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
143
|
+
}>>, "plain">;
|
|
144
|
+
/** Live config reference as a 0.1.7 host passes it (cron / mcp-adapter
|
|
145
|
+
* spelling): `.get()` snapshots the current value — the host swaps the
|
|
146
|
+
* reference in-place when a volatile field is edited on the settings page. */
|
|
147
|
+
interface VolatileRef<T> {
|
|
148
|
+
get(): T;
|
|
149
|
+
}
|
|
150
|
+
/** apply()-time config: live VolatileRef fields on real hosts, plain values
|
|
151
|
+
* on bare test harnesses — {@link readConfigValue} accepts both. */
|
|
152
|
+
export type TopicsRuntimeConfig = Partial<{
|
|
153
|
+
[K in keyof TopicsConfigValue]: VolatileRef<TopicsConfigValue[K]> | TopicsConfigValue[K];
|
|
154
|
+
}>;
|
|
56
155
|
/**
|
|
57
156
|
* Strip a leading YAML frontmatter block (`---` / body / `---`) from a skill
|
|
58
157
|
* markdown file. `SkillDefinition.content` must be the instruction body after
|
|
@@ -64,5 +163,5 @@ export declare function migrateLegacySettings(settings: SettingsNsLike, warn?: (
|
|
|
64
163
|
* delimiter semantics of the upstream skill-filesystem provider.
|
|
65
164
|
*/
|
|
66
165
|
export declare function stripFrontmatter(raw: string): string;
|
|
67
|
-
export declare function apply(ctx: Context): void;
|
|
166
|
+
export declare function apply(ctx: Context, config?: TopicsRuntimeConfig): void;
|
|
68
167
|
export {};
|