@aiwayds/dsh-topics-memory 0.14.0 → 0.16.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 +1 -1
- package/README.zh.md +1 -1
- package/lib/commands.d.ts +10 -0
- package/lib/commands.js +54 -3
- package/lib/observer.js +6 -4
- package/package.json +23 -23
- package/src/commands.ts +59 -2
- package/src/observer.ts +6 -3
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.5-rc.2** — 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
|
|
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.5-rc.2** — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。**不再支持 alpha 线。**
|
|
8
8
|
|
|
9
9
|
## 演示
|
|
10
10
|
|
package/lib/commands.d.ts
CHANGED
|
@@ -26,3 +26,13 @@ export type ManualConsolidate = (invocation: CommandInvocation) => Promise<Conso
|
|
|
26
26
|
export declare const HELP: string;
|
|
27
27
|
export declare function buildTopicsCommand(service: TopicsService, mutate: MutateFn, resolveAsk?: AskServiceResolver, resolveLlm?: LlmDirectoryResolver, distillNow?: ManualDistill, consolidateNow?: ManualConsolidate): CommandDefinition;
|
|
28
28
|
export declare function tuningHint(stats: ReturnType<typeof aggregateStats>, threshold: number): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Render the near-miss histogram as a two-line horizontal sparkline: tick
|
|
31
|
+
* labels over a log-scaled (count+1) strip, so a live session's 200:1 count
|
|
32
|
+
* range stays legible. Zero-count buckets inside the observed range draw as
|
|
33
|
+
* '·' to keep the score axis honest. Returns [] for empty or unparsable input.
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderNearMissSparkline(buckets: {
|
|
36
|
+
bucket: string;
|
|
37
|
+
count: number;
|
|
38
|
+
}[]): string[];
|
package/lib/commands.js
CHANGED
|
@@ -267,9 +267,7 @@ async function renderStats(service) {
|
|
|
267
267
|
lines.push(`| \`${cell(t.slug)}\` | ${t.count} |`);
|
|
268
268
|
}
|
|
269
269
|
if (stats.nearMissHistogram.length > 0) {
|
|
270
|
-
lines.push('',
|
|
271
|
-
for (const b of stats.nearMissHistogram)
|
|
272
|
-
lines.push(`| ${cell(b.bucket)} | ${b.count} |`);
|
|
270
|
+
lines.push('', `Near-miss 分布(低于阈值 ${service.cfg.matchThreshold.toFixed(2)} 或被结构门挡下,对数刻度):`, '', '```', ...renderNearMissSparkline(stats.nearMissHistogram), '```');
|
|
273
271
|
const hint = tuningHint(stats, service.cfg.matchThreshold);
|
|
274
272
|
if (hint !== undefined)
|
|
275
273
|
lines.push('', `💡 ${hint}`);
|
|
@@ -286,6 +284,59 @@ export function tuningHint(stats, threshold) {
|
|
|
286
284
|
}
|
|
287
285
|
return undefined;
|
|
288
286
|
}
|
|
287
|
+
const SPARK_LEVELS = '▁▂▃▄▅▆▇█';
|
|
288
|
+
const SPARK_EMPTY = '·';
|
|
289
|
+
/** Columns per histogram bucket; 2 keeps a 25-bucket range at 50 cols so the
|
|
290
|
+
* TUI renders both lines without wrapping. */
|
|
291
|
+
const SPARK_COLS_PER_BUCKET = 2;
|
|
292
|
+
/** Axis tick every 0.30 score units. */
|
|
293
|
+
const SPARK_TICK_STEP = 0.3;
|
|
294
|
+
/** Bucket width of the histogram built by nmBucket() in ilog.ts. */
|
|
295
|
+
const SPARK_BUCKET_WIDTH = 0.05;
|
|
296
|
+
/**
|
|
297
|
+
* Render the near-miss histogram as a two-line horizontal sparkline: tick
|
|
298
|
+
* labels over a log-scaled (count+1) strip, so a live session's 200:1 count
|
|
299
|
+
* range stays legible. Zero-count buckets inside the observed range draw as
|
|
300
|
+
* '·' to keep the score axis honest. Returns [] for empty or unparsable input.
|
|
301
|
+
*/
|
|
302
|
+
export function renderNearMissSparkline(buckets) {
|
|
303
|
+
const starts = buckets.map((b) => Number(b.bucket.split('–')[0]));
|
|
304
|
+
if (buckets.length === 0 || starts.some((s) => Number.isNaN(s)))
|
|
305
|
+
return [];
|
|
306
|
+
const first = Math.min(...starts);
|
|
307
|
+
const last = Math.max(...starts);
|
|
308
|
+
const n = Math.round((last - first) / SPARK_BUCKET_WIDTH) + 1;
|
|
309
|
+
const counts = new Array(n).fill(0);
|
|
310
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
311
|
+
counts[Math.round((starts[i] - first) / SPARK_BUCKET_WIDTH)] += buckets[i].count;
|
|
312
|
+
}
|
|
313
|
+
const max = Math.max(...counts);
|
|
314
|
+
const denom = Math.log10(max + 1);
|
|
315
|
+
const strip = counts
|
|
316
|
+
.map((c) => (c === 0 || denom === 0 ? SPARK_EMPTY : SPARK_LEVELS[Math.round((Math.log10(c + 1) / denom) * (SPARK_LEVELS.length - 1))]))
|
|
317
|
+
.map((ch) => ch.repeat(SPARK_COLS_PER_BUCKET))
|
|
318
|
+
.join('');
|
|
319
|
+
const width = n * SPARK_COLS_PER_BUCKET;
|
|
320
|
+
const line = new Array(width).fill(' ');
|
|
321
|
+
const used = new Array(width).fill(false);
|
|
322
|
+
const put = (at, label) => {
|
|
323
|
+
if (at < 0 || at + label.length > width)
|
|
324
|
+
return;
|
|
325
|
+
if (used.slice(at, at + label.length).some(Boolean))
|
|
326
|
+
return;
|
|
327
|
+
for (let k = 0; k < label.length; k++) {
|
|
328
|
+
line[at + k] = label[k];
|
|
329
|
+
used[at + k] = true;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
for (let i = 0;; i += Math.round(SPARK_TICK_STEP / SPARK_BUCKET_WIDTH)) {
|
|
333
|
+
if (i > 0 && i >= n - 2)
|
|
334
|
+
break;
|
|
335
|
+
put(i * SPARK_COLS_PER_BUCKET, (first + i * SPARK_BUCKET_WIDTH).toFixed(2));
|
|
336
|
+
}
|
|
337
|
+
put(width - 4, (first + n * SPARK_BUCKET_WIDTH).toFixed(2));
|
|
338
|
+
return [line.join('').replace(/ +$/, ''), strip];
|
|
339
|
+
}
|
|
289
340
|
/** /topics list caps the table at this many newest topics. */
|
|
290
341
|
const LIST_LIMIT = 100;
|
|
291
342
|
async function renderList(service) {
|
package/lib/observer.js
CHANGED
|
@@ -79,10 +79,12 @@ export class Observer {
|
|
|
79
79
|
state.assistantText = '';
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
-
case 'assistant/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
case 'assistant/message': {
|
|
83
|
+
// dsh 0.1.5-rc.1: streaming `assistant/chunk` events are gone — the
|
|
84
|
+
// settlement carries the committed message. Accumulate each round's
|
|
85
|
+
// text; turn/end reads the whole turn's transcript. Failed attempts
|
|
86
|
+
// (`assistant/attempt`) never reached the user, so they are skipped.
|
|
87
|
+
state.assistantText += textOf(data);
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
90
|
case 'turn/end': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwayds/dsh-topics-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "dsh-plugin: quality-first topic memory for dsh — a topic keeps the starting question, conclusion, impact and dependencies, not the process; local-first git-traced OKF bundle, budgeted LLM-free injection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"dependencies": {},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
48
|
-
"@deepseek-ai/dsh-tools": ">=0.1.
|
|
49
|
-
"@deepseek-ai/dsh-llm": ">=0.1.
|
|
50
|
-
"@deepseek-ai/dsh-settings": ">=0.1.
|
|
51
|
-
"@deepseek-ai/dsh-skill": ">=0.1.
|
|
52
|
-
"@deepseek-ai/dsh-commands": ">=0.1.
|
|
53
|
-
"@deepseek-ai/dsh-util-values": ">=0.1.
|
|
48
|
+
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-llm": ">=0.1.5-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-settings": ">=0.1.5-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-skill": ">=0.1.5-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-commands": ">=0.1.5-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-util-values": ">=0.1.5-rc.2",
|
|
54
54
|
"@deepseek-ai/schemastery": "^3.18.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
@@ -81,22 +81,22 @@
|
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@deepseek-ai/cordis": "4.0.2",
|
|
84
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
85
|
-
"@deepseek-ai/dsh-attachment": "0.1.
|
|
86
|
-
"@deepseek-ai/dsh-brand": "0.1.
|
|
87
|
-
"@deepseek-ai/dsh-code-runtime": "0.1.
|
|
88
|
-
"@deepseek-ai/dsh-commands": "0.1.
|
|
89
|
-
"@deepseek-ai/dsh-invariants": "0.1.
|
|
90
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
91
|
-
"@deepseek-ai/dsh-scope": "0.1.
|
|
92
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
93
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
94
|
-
"@deepseek-ai/dsh-skill": "0.1.
|
|
95
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.
|
|
96
|
-
"@deepseek-ai/dsh-timeout": "0.1.
|
|
97
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
98
|
-
"@deepseek-ai/dsh-user-approval": "0.1.
|
|
99
|
-
"@deepseek-ai/dsh-util-values": "0.1.
|
|
84
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-brand": "0.1.5-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-code-runtime": "0.1.5-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-commands": "0.1.5-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-invariants": "0.1.5-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-scope": "0.1.5-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-skill": "0.1.5-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-timeout": "0.1.5-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-user-approval": "0.1.5-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-util-values": "0.1.5-rc.2",
|
|
100
100
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
101
101
|
"@types/node": "^22.19.0",
|
|
102
102
|
"typescript": "^5.9.2"
|
package/src/commands.ts
CHANGED
|
@@ -306,8 +306,14 @@ async function renderStats(service: TopicsService): Promise<string> {
|
|
|
306
306
|
for (const t of stats.topTopics.slice(0, 5)) lines.push(`| \`${cell(t.slug)}\` | ${t.count} |`)
|
|
307
307
|
}
|
|
308
308
|
if (stats.nearMissHistogram.length > 0) {
|
|
309
|
-
lines.push(
|
|
310
|
-
|
|
309
|
+
lines.push(
|
|
310
|
+
'',
|
|
311
|
+
`Near-miss 分布(低于阈值 ${service.cfg.matchThreshold.toFixed(2)} 或被结构门挡下,对数刻度):`,
|
|
312
|
+
'',
|
|
313
|
+
'```',
|
|
314
|
+
...renderNearMissSparkline(stats.nearMissHistogram),
|
|
315
|
+
'```',
|
|
316
|
+
)
|
|
311
317
|
const hint = tuningHint(stats, service.cfg.matchThreshold)
|
|
312
318
|
if (hint !== undefined) lines.push('', `💡 ${hint}`)
|
|
313
319
|
}
|
|
@@ -325,6 +331,57 @@ export function tuningHint(stats: ReturnType<typeof aggregateStats>, threshold:
|
|
|
325
331
|
return undefined
|
|
326
332
|
}
|
|
327
333
|
|
|
334
|
+
const SPARK_LEVELS = '▁▂▃▄▅▆▇█'
|
|
335
|
+
const SPARK_EMPTY = '·'
|
|
336
|
+
/** Columns per histogram bucket; 2 keeps a 25-bucket range at 50 cols so the
|
|
337
|
+
* TUI renders both lines without wrapping. */
|
|
338
|
+
const SPARK_COLS_PER_BUCKET = 2
|
|
339
|
+
/** Axis tick every 0.30 score units. */
|
|
340
|
+
const SPARK_TICK_STEP = 0.3
|
|
341
|
+
/** Bucket width of the histogram built by nmBucket() in ilog.ts. */
|
|
342
|
+
const SPARK_BUCKET_WIDTH = 0.05
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Render the near-miss histogram as a two-line horizontal sparkline: tick
|
|
346
|
+
* labels over a log-scaled (count+1) strip, so a live session's 200:1 count
|
|
347
|
+
* range stays legible. Zero-count buckets inside the observed range draw as
|
|
348
|
+
* '·' to keep the score axis honest. Returns [] for empty or unparsable input.
|
|
349
|
+
*/
|
|
350
|
+
export function renderNearMissSparkline(buckets: { bucket: string; count: number }[]): string[] {
|
|
351
|
+
const starts = buckets.map((b) => Number(b.bucket.split('–')[0]))
|
|
352
|
+
if (buckets.length === 0 || starts.some((s) => Number.isNaN(s))) return []
|
|
353
|
+
const first = Math.min(...starts)
|
|
354
|
+
const last = Math.max(...starts)
|
|
355
|
+
const n = Math.round((last - first) / SPARK_BUCKET_WIDTH) + 1
|
|
356
|
+
const counts = new Array<number>(n).fill(0)
|
|
357
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
358
|
+
counts[Math.round((starts[i] - first) / SPARK_BUCKET_WIDTH)] += buckets[i].count
|
|
359
|
+
}
|
|
360
|
+
const max = Math.max(...counts)
|
|
361
|
+
const denom = Math.log10(max + 1)
|
|
362
|
+
const strip = counts
|
|
363
|
+
.map((c) => (c === 0 || denom === 0 ? SPARK_EMPTY : SPARK_LEVELS[Math.round((Math.log10(c + 1) / denom) * (SPARK_LEVELS.length - 1))]))
|
|
364
|
+
.map((ch) => ch.repeat(SPARK_COLS_PER_BUCKET))
|
|
365
|
+
.join('')
|
|
366
|
+
const width = n * SPARK_COLS_PER_BUCKET
|
|
367
|
+
const line = new Array<string>(width).fill(' ')
|
|
368
|
+
const used = new Array<boolean>(width).fill(false)
|
|
369
|
+
const put = (at: number, label: string) => {
|
|
370
|
+
if (at < 0 || at + label.length > width) return
|
|
371
|
+
if (used.slice(at, at + label.length).some(Boolean)) return
|
|
372
|
+
for (let k = 0; k < label.length; k++) {
|
|
373
|
+
line[at + k] = label[k]
|
|
374
|
+
used[at + k] = true
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (let i = 0; ; i += Math.round(SPARK_TICK_STEP / SPARK_BUCKET_WIDTH)) {
|
|
378
|
+
if (i > 0 && i >= n - 2) break
|
|
379
|
+
put(i * SPARK_COLS_PER_BUCKET, (first + i * SPARK_BUCKET_WIDTH).toFixed(2))
|
|
380
|
+
}
|
|
381
|
+
put(width - 4, (first + n * SPARK_BUCKET_WIDTH).toFixed(2))
|
|
382
|
+
return [line.join('').replace(/ +$/, ''), strip]
|
|
383
|
+
}
|
|
384
|
+
|
|
328
385
|
/** /topics list caps the table at this many newest topics. */
|
|
329
386
|
const LIST_LIMIT = 100
|
|
330
387
|
|
package/src/observer.ts
CHANGED
|
@@ -111,9 +111,12 @@ export class Observer {
|
|
|
111
111
|
state.assistantText = ''
|
|
112
112
|
return
|
|
113
113
|
}
|
|
114
|
-
case 'assistant/
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
case 'assistant/message': {
|
|
115
|
+
// dsh 0.1.5-rc.1: streaming `assistant/chunk` events are gone — the
|
|
116
|
+
// settlement carries the committed message. Accumulate each round's
|
|
117
|
+
// text; turn/end reads the whole turn's transcript. Failed attempts
|
|
118
|
+
// (`assistant/attempt`) never reached the user, so they are skipped.
|
|
119
|
+
state.assistantText += textOf(data as UserMessageLike)
|
|
117
120
|
return
|
|
118
121
|
}
|
|
119
122
|
case 'turn/end': {
|