dsh-plugin-mind-tree 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +312 -2
- package/cordis.patch.yml +4 -0
- package/lib/client.js +23597 -0
- package/lib/format.js +364 -0
- package/lib/index.js +1020 -0
- package/lib/pricing.js +99 -0
- package/lib/reduce.js +186 -0
- package/lib/remote.js +178 -0
- package/lib/typert.host.js +30 -0
- package/package.json +109 -3
package/lib/pricing.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// 成本估算层:把 token 消耗折算成人民币。
|
|
2
|
+
// 单价来源:DeepSeek 官方「模型 & 价格」页(抓取于 2026-11-15),单位:元 / 百万 tokens。
|
|
3
|
+
// 高峰时段:北京时间 周一至周五(不含法定节假日)9:00–12:00、14:00–18:00;其余为**空闲时段**,价格减半。
|
|
4
|
+
// 说明:法定节假日无法从本地推断,此处只按"工作日 + 时段"近似。
|
|
5
|
+
//
|
|
6
|
+
// 纯函数、无依赖,客户端(界面显示)与宿主(mind_tree 工具)共用。
|
|
7
|
+
|
|
8
|
+
/** 单价表版本标记(改了数字就更新它,便于界面/文档追溯)。 */
|
|
9
|
+
export const PRICING_VERSION = '2026-11-15';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 每百万 token 单价(元)。每个字段是 [空闲时段, 高峰时段]。
|
|
13
|
+
* hit = 缓存命中输入;miss = 缓存未命中输入;out = 输出。
|
|
14
|
+
*/
|
|
15
|
+
export const MODEL_PRICES = {
|
|
16
|
+
'deepseek-flash': { hit: [0.02, 0.04], miss: [1, 2], out: [4, 8] },
|
|
17
|
+
'deepseek-v4-flash': { hit: [0.02, 0.04], miss: [1, 2], out: [4, 8] },
|
|
18
|
+
'deepseek-v4-flash-vision-exp': { hit: [0.02, 0.04], miss: [1, 2], out: [4, 8] },
|
|
19
|
+
'deepseek-v4-pro': { hit: [0.15, 0.3], miss: [4.5, 9], out: [13.5, 27] },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** 未知模型时的兜底单价(按 flash 计,界面用 ≈ 标注)。 */
|
|
23
|
+
export const FALLBACK_PRICE = MODEL_PRICES['deepseek-flash'];
|
|
24
|
+
|
|
25
|
+
const BJ_OFFSET_MS = 8 * 3600 * 1000;
|
|
26
|
+
|
|
27
|
+
/** 是否处于高峰时段(按北京时间近似判断,忽略法定节假日)。 */
|
|
28
|
+
export function isPeakHour(atMs) {
|
|
29
|
+
const at = typeof atMs === 'number' && Number.isFinite(atMs) ? atMs : Date.now();
|
|
30
|
+
const shifted = new Date(at + BJ_OFFSET_MS);
|
|
31
|
+
const day = shifted.getUTCDay(); // 0=周日 … 6=周六
|
|
32
|
+
const minutes = shifted.getUTCHours() * 60 + shifted.getUTCMinutes();
|
|
33
|
+
const weekday = day >= 1 && day <= 5;
|
|
34
|
+
const inMorning = minutes >= 9 * 60 && minutes < 12 * 60;
|
|
35
|
+
const inAfternoon = minutes >= 14 * 60 && minutes < 18 * 60;
|
|
36
|
+
return weekday && (inMorning || inAfternoon);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 取某个模型在某时段的单价三元组。 */
|
|
40
|
+
export function priceOf(model, atMs) {
|
|
41
|
+
const table = MODEL_PRICES[model] ?? FALLBACK_PRICE;
|
|
42
|
+
const slot = isPeakHour(atMs) ? 1 : 0;
|
|
43
|
+
return {
|
|
44
|
+
model: MODEL_PRICES[model] ? model : null,
|
|
45
|
+
peak: slot === 1,
|
|
46
|
+
hit: table.hit[slot],
|
|
47
|
+
miss: table.miss[slot],
|
|
48
|
+
out: table.out[slot],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 一次 token 消耗折算成元。
|
|
54
|
+
* @param usage - { input, output, cacheRead, cacheWrite }(单位 token;input 为**未命中缓存**部分)
|
|
55
|
+
* @param model - 模型 id,如 deepseek-v4-pro;未知模型按兜底价并标记 known=false
|
|
56
|
+
* @param atMs - 该次调用时间(毫秒),用于判断高峰/空闲;缺省按当前时间
|
|
57
|
+
* @returns { cny, peak, model, known }
|
|
58
|
+
*/
|
|
59
|
+
export function costOf(usage, model, atMs) {
|
|
60
|
+
const input = Math.max(0, Number(usage?.input) || 0);
|
|
61
|
+
const output = Math.max(0, Number(usage?.output) || 0);
|
|
62
|
+
const cacheRead = Math.max(0, Number(usage?.cacheRead) || 0);
|
|
63
|
+
const cacheWrite = Math.max(0, Number(usage?.cacheWrite) || 0);
|
|
64
|
+
const price = priceOf(model, atMs);
|
|
65
|
+
// 缓存写入按"未命中输入"计费;缓存读取按"命中输入"计费。
|
|
66
|
+
const cny = ((input + cacheWrite) * price.miss + cacheRead * price.hit + output * price.out) / 1_000_000;
|
|
67
|
+
return { cny, peak: price.peak, model: price.model, known: price.model !== null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 汇总多份成本。 */
|
|
71
|
+
export function sumCost(entries) {
|
|
72
|
+
let cny = 0;
|
|
73
|
+
let anyUnknown = false;
|
|
74
|
+
for (const entry of entries ?? []) {
|
|
75
|
+
if (!entry) continue;
|
|
76
|
+
cny += Number(entry.cny) || 0;
|
|
77
|
+
if (entry.known === false) anyUnknown = true;
|
|
78
|
+
}
|
|
79
|
+
return { cny, anyUnknown };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 人民币显示:小于 1 分显示 <¥0.01,其余两位小数。 */
|
|
83
|
+
export function formatCny(value) {
|
|
84
|
+
const cny = Number(value) || 0;
|
|
85
|
+
if (cny <= 0) return '¥0';
|
|
86
|
+
if (cny < 0.005) return '<¥0.01';
|
|
87
|
+
if (cny < 1) return `¥${cny.toFixed(3)}`;
|
|
88
|
+
return `¥${cny.toFixed(2)}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 从整棵树聚合成本(按轮 usage + 模型)。 */
|
|
92
|
+
export function costOfTree(tree) {
|
|
93
|
+
const entries = [];
|
|
94
|
+
for (const node of tree?.nodes ?? []) {
|
|
95
|
+
if (node?.kind !== 'turn' || !node.usage) continue;
|
|
96
|
+
entries.push(costOf(node.usage, node.usage.model, node.usage.startedAt));
|
|
97
|
+
}
|
|
98
|
+
return sumCost(entries);
|
|
99
|
+
}
|
package/lib/reduce.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// 过程信息削减层:把"事件日志式"的完整树压缩成"对话结构式"的思维树。
|
|
2
|
+
// Copyright (c) 2026 ShOpenMind · https://github.com/ShOpenMind/dsh-plugin-mind-tree · MIT
|
|
3
|
+
// 纯函数、无 DSH 依赖,客户端渲染与导出共用,可独立单测。
|
|
4
|
+
import { buildHierarchy, isInjectedLabel } from './format.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 四档信息量(由轻到重):
|
|
8
|
+
* - user:只留 轮次 + 你的真实发言 + 分支节点 —— 相当于"把整场对话压成需求清单",可直接喂给别的模型;
|
|
9
|
+
* - compact (默认):只留 轮次 + 用户真实发言 + 分支节点 + 每轮工具活动聚合 1 行 + 每轮最终助手回复;
|
|
10
|
+
* - standard:在 compact 基础上保留实质性助手过程文本(≥ MIN_ASSISTANT_STANDARD 字)与工具明细行,去掉工具结果;
|
|
11
|
+
* - full:不做任何削减(与事件日志一一对应)。
|
|
12
|
+
*/
|
|
13
|
+
export const DETAIL_LEVELS = ['user', 'compact', 'standard', 'full'];
|
|
14
|
+
|
|
15
|
+
/** standard 档保留的助手文本最小字数。 */
|
|
16
|
+
export const MIN_ASSISTANT_STANDARD = 60;
|
|
17
|
+
/** 工具聚合行里展示的工具名个数。 */
|
|
18
|
+
const GROUP_NAME_LIMIT = 4;
|
|
19
|
+
|
|
20
|
+
const charLength = (text) => [...String(text ?? '')].length;
|
|
21
|
+
|
|
22
|
+
/** 工具名:去掉"工具 "前缀与参数部分。 */
|
|
23
|
+
function toolName(label) {
|
|
24
|
+
return String(label ?? '工具')
|
|
25
|
+
.replace(/^工具\s*/, '')
|
|
26
|
+
.split(/[\s((::]/)[0] || '工具';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 每轮最后一条助手消息(视为该轮答复)。 */
|
|
30
|
+
function lastAssistantIds(nodes) {
|
|
31
|
+
const byTurn = new Map();
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
if (node.kind !== 'assistant') continue;
|
|
34
|
+
const key = node.turn ?? -1;
|
|
35
|
+
if (!byTurn.has(key)) byTurn.set(key, []);
|
|
36
|
+
byTurn.get(key).push(node);
|
|
37
|
+
}
|
|
38
|
+
const ids = new Set();
|
|
39
|
+
for (const list of byTurn.values()) ids.add(list[list.length - 1].id);
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 削减思维树。
|
|
45
|
+
* @param tree - { sessionId, title, nodes, edges, turnCount }
|
|
46
|
+
* @param level - compact | standard | full
|
|
47
|
+
* @returns 与原结构同形的新对象;full 档原样返回。附 `reduction` 统计与 `grouped` 折叠信息。
|
|
48
|
+
*/
|
|
49
|
+
export function reduceGraph(tree, level = 'compact') {
|
|
50
|
+
if (tree === null || tree === undefined) return tree;
|
|
51
|
+
if (level === 'full') return tree;
|
|
52
|
+
const nodes = tree.nodes ?? [];
|
|
53
|
+
const keepIds = new Set();
|
|
54
|
+
const isUserOnly = level === 'user';
|
|
55
|
+
const isCompact = level === 'compact';
|
|
56
|
+
const minAssistant = isCompact ? Number.POSITIVE_INFINITY : MIN_ASSISTANT_STANDARD;
|
|
57
|
+
const lastOfTurn = lastAssistantIds(nodes);
|
|
58
|
+
/** 归入工具聚合行的节点(turn -> 节点数组)。 */
|
|
59
|
+
const groupBuckets = new Map();
|
|
60
|
+
|
|
61
|
+
for (const node of nodes) {
|
|
62
|
+
// 根节点与轮次节点始终保留(树骨架)。
|
|
63
|
+
if (node.id === 'root' || node.kind === 'turn') { keepIds.add(node.id); continue; }
|
|
64
|
+
// 分支节点(决策点)始终保留,哪怕它是工具调用或助手消息。
|
|
65
|
+
if (node.branch) { keepIds.add(node.id); continue; }
|
|
66
|
+
switch (node.kind) {
|
|
67
|
+
case 'result':
|
|
68
|
+
continue; // 工具输出:折叠掉(可在工具行详情中查看)
|
|
69
|
+
case 'info':
|
|
70
|
+
continue; // system / developer 注入内容
|
|
71
|
+
case 'assistant':
|
|
72
|
+
if (isUserOnly) continue; // 「输入」档完全不要助手文本
|
|
73
|
+
if (!(lastOfTurn.has(node.id) || charLength(node.label) >= minAssistant)) continue;
|
|
74
|
+
break;
|
|
75
|
+
case 'user':
|
|
76
|
+
if (isInjectedLabel(node.label)) continue; // 模型切换、运行时上下文、后台任务通知
|
|
77
|
+
break;
|
|
78
|
+
case 'tool': {
|
|
79
|
+
if (isUserOnly) continue; // 「输入」档不要工具行(等同于"需求清单")
|
|
80
|
+
if (!isCompact) break; // standard 保留工具明细行
|
|
81
|
+
const key = node.turn ?? -1;
|
|
82
|
+
if (!groupBuckets.has(key)) groupBuckets.set(key, []);
|
|
83
|
+
groupBuckets.get(key).push(node);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
keepIds.add(node.id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 生成每轮的"工具活动"聚合行。
|
|
93
|
+
const synthetic = [];
|
|
94
|
+
for (const [turn, list] of groupBuckets) {
|
|
95
|
+
if (list.length === 0) continue;
|
|
96
|
+
const counts = new Map();
|
|
97
|
+
for (const item of list) {
|
|
98
|
+
const name = toolName(item.label);
|
|
99
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
100
|
+
}
|
|
101
|
+
const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, GROUP_NAME_LIMIT)
|
|
102
|
+
.map(([name, count]) => (count > 1 ? `${name}×${count}` : name));
|
|
103
|
+
const rest = counts.size - top.length;
|
|
104
|
+
synthetic.push({
|
|
105
|
+
id: `tg:${turn}`,
|
|
106
|
+
seq: list[0].seq,
|
|
107
|
+
type: 'tool/group',
|
|
108
|
+
kind: 'toolgroup',
|
|
109
|
+
...(turn >= 0 ? { turn } : {}),
|
|
110
|
+
label: `工具活动 ×${list.length} · ${top.join(' ')}${rest > 0 ? ` +${rest}` : ''}`,
|
|
111
|
+
surface: 'current',
|
|
112
|
+
// 自带折叠清单(含标签与序号),这样聚合行在"精简"档下也能独立展示与搜索。
|
|
113
|
+
grouped: list.map((item) => ({ id: item.id, label: item.label, seq: item.seq, kind: item.kind })),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const reducedNodes = [...nodes.filter((n) => keepIds.has(n.id)), ...synthetic];
|
|
118
|
+
const keptIds = new Set(reducedNodes.map((n) => n.id));
|
|
119
|
+
const { children, parentOf } = buildHierarchy(tree);
|
|
120
|
+
|
|
121
|
+
// 重新挂接:每个保留节点挂到"最近的保留祖先";找不到则挂到所属轮次,再退到根。
|
|
122
|
+
const nearestKeptAncestor = (node) => {
|
|
123
|
+
let cursor = parentOf.get(node.id);
|
|
124
|
+
const seen = new Set([node.id]);
|
|
125
|
+
while (cursor !== undefined && !seen.has(cursor)) {
|
|
126
|
+
if (keptIds.has(cursor)) return cursor;
|
|
127
|
+
seen.add(cursor);
|
|
128
|
+
cursor = parentOf.get(cursor);
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
};
|
|
132
|
+
const turnIdOf = (node) => (node.turn != null && keptIds.has(`t${node.turn}`) ? `t${node.turn}` : undefined);
|
|
133
|
+
const fallbackParent = (node) => turnIdOf(node)
|
|
134
|
+
?? (keptIds.has('root') ? 'root' : undefined);
|
|
135
|
+
|
|
136
|
+
const edges = [];
|
|
137
|
+
const roots = reducedNodes.filter((n) => n.kind === 'turn' || n.id === 'root');
|
|
138
|
+
const rootId = reducedNodes.find((n) => n.id === 'root')?.id;
|
|
139
|
+
const turns = reducedNodes.filter((n) => n.kind === 'turn').sort((a, b) => (a.turn ?? 0) - (b.turn ?? 0));
|
|
140
|
+
let previous = rootId;
|
|
141
|
+
for (const turn of turns) {
|
|
142
|
+
edges.push({ from: previous ?? turn.id, to: turn.id, kind: 'next' });
|
|
143
|
+
previous = turn.id;
|
|
144
|
+
}
|
|
145
|
+
for (const node of reducedNodes) {
|
|
146
|
+
if (node.id === 'root' || node.kind === 'turn') continue;
|
|
147
|
+
const parent = node.kind === 'toolgroup'
|
|
148
|
+
? (fallbackParent(node) ?? rootId)
|
|
149
|
+
: (nearestKeptAncestor(node) ?? fallbackParent(node) ?? rootId);
|
|
150
|
+
if (parent === undefined || parent === node.id) continue;
|
|
151
|
+
edges.push({ from: parent, to: node.id, kind: 'step' });
|
|
152
|
+
}
|
|
153
|
+
// 次级边(被替换 / 派生):两端都保留才保留。
|
|
154
|
+
for (const edge of tree.edges ?? []) {
|
|
155
|
+
if (edge.kind !== 'replaced-by' && edge.kind !== 'derived') continue;
|
|
156
|
+
if (!keptIds.has(edge.from) || !keptIds.has(edge.to)) continue;
|
|
157
|
+
if (edge.from === edge.to) continue;
|
|
158
|
+
edges.push({ from: edge.from, to: edge.to, kind: edge.kind });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...tree,
|
|
163
|
+
nodes: reducedNodes,
|
|
164
|
+
edges,
|
|
165
|
+
reduction: {
|
|
166
|
+
level,
|
|
167
|
+
before: nodes.length,
|
|
168
|
+
after: reducedNodes.length,
|
|
169
|
+
grouped: synthetic.length,
|
|
170
|
+
foldedTools: [...groupBuckets.values()].reduce((sum, list) => sum + list.length, 0),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 便于 UI 显示:三档的中文/英文标签与说明。 */
|
|
176
|
+
export const DETAIL_LABELS = {
|
|
177
|
+
compact: { zh: '精简', en: 'Compact' },
|
|
178
|
+
standard: { zh: '标准', en: 'Standard' },
|
|
179
|
+
full: { zh: '完整', en: 'Full' },
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** 统计:树里有多少"过程信息"节点(用于提示可以精简)。 */
|
|
183
|
+
export function processNodeCount(tree) {
|
|
184
|
+
return (tree?.nodes ?? []).filter((n) => n.kind === 'result' || n.kind === 'info'
|
|
185
|
+
|| (n.kind === 'tool' && !n.branch) || (n.kind === 'user' && isInjectedLabel(n.label))).length;
|
|
186
|
+
}
|
package/lib/remote.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// dsh-plugin-mind-tree 远程契约:主机(host)与网页客户端(client)之间唯一的数据面。
|
|
2
|
+
// Copyright (c) 2026 ShOpenMind · https://github.com/ShOpenMind/dsh-plugin-mind-tree · MIT
|
|
3
|
+
// 本文件同时被宿主加载器(经 ./typert)与客户端构建工具(打包进 lib/client.js)引用,
|
|
4
|
+
// 因此只依赖 zod,不依赖任何 DSH 实现代码。
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
export const PACKAGE = 'dsh-plugin-mind-tree';
|
|
8
|
+
export const NAMESPACE = 'mindTree';
|
|
9
|
+
|
|
10
|
+
export const MindNodeSchema = z.object({
|
|
11
|
+
sessionId: z.string(),
|
|
12
|
+
title: z.string().optional(),
|
|
13
|
+
parentSessionId: z.string().nullable().optional(),
|
|
14
|
+
origin: z.string().nullable().optional(),
|
|
15
|
+
delegationDepth: z.number().optional(),
|
|
16
|
+
live: z.boolean(),
|
|
17
|
+
persisted: z.boolean(),
|
|
18
|
+
createdAt: z.number(),
|
|
19
|
+
cwd: z.string().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const MindEdgeSchema = z.object({
|
|
23
|
+
from: z.string(),
|
|
24
|
+
to: z.string(),
|
|
25
|
+
kind: z.string(),
|
|
26
|
+
spawnSeq: z.number().optional(),
|
|
27
|
+
spawnLabel: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const ForestViewSchema = z.object({
|
|
31
|
+
nodes: z.array(MindNodeSchema),
|
|
32
|
+
edges: z.array(MindEdgeSchema),
|
|
33
|
+
roots: z.array(z.string()),
|
|
34
|
+
warnings: z.array(z.string()).optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const LineageViewSchema = z.object({
|
|
38
|
+
complete: z.boolean(),
|
|
39
|
+
unresolvedParentId: z.string().optional(),
|
|
40
|
+
target: z.string(),
|
|
41
|
+
root: z.string().optional(),
|
|
42
|
+
nodes: z.array(MindNodeSchema),
|
|
43
|
+
edges: z.array(MindEdgeSchema),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const BranchNodeSchema = z.object({
|
|
47
|
+
id: z.string(),
|
|
48
|
+
seq: z.number(),
|
|
49
|
+
type: z.string(),
|
|
50
|
+
kind: z.string(),
|
|
51
|
+
turn: z.number().optional(),
|
|
52
|
+
step: z.number().optional(),
|
|
53
|
+
label: z.string(),
|
|
54
|
+
summary: z.string().optional(), // 轮次摘要(约 6–15 字)
|
|
55
|
+
text: z.string().optional(),
|
|
56
|
+
usage: z
|
|
57
|
+
.object({
|
|
58
|
+
input: z.number(),
|
|
59
|
+
output: z.number(),
|
|
60
|
+
total: z.number(),
|
|
61
|
+
cacheRead: z.number().optional(),
|
|
62
|
+
cacheWrite: z.number().optional(),
|
|
63
|
+
reasoning: z.number().optional(),
|
|
64
|
+
messageCount: z.number().optional(),
|
|
65
|
+
durationMs: z.number().optional(),
|
|
66
|
+
})
|
|
67
|
+
.optional(), // 轮次 token 消耗聚合
|
|
68
|
+
surface: z.string(),
|
|
69
|
+
branch: z
|
|
70
|
+
.object({
|
|
71
|
+
kind: z.string(),
|
|
72
|
+
detail: z.unknown().optional(),
|
|
73
|
+
})
|
|
74
|
+
.optional(),
|
|
75
|
+
sessionId: z.string().optional(),
|
|
76
|
+
origin: z.string().optional(),
|
|
77
|
+
error: z.boolean().optional(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const BranchEdgeSchema = z.object({
|
|
81
|
+
from: z.string(),
|
|
82
|
+
to: z.string(),
|
|
83
|
+
kind: z.string(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const ThoughtTreeSchema = z.object({
|
|
87
|
+
sessionId: z.string(),
|
|
88
|
+
title: z.string().optional(),
|
|
89
|
+
nodes: z.array(BranchNodeSchema),
|
|
90
|
+
edges: z.array(BranchEdgeSchema),
|
|
91
|
+
turnCount: z.number(),
|
|
92
|
+
warnings: z.array(z.string()).optional(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const EventWindowSchema = z.object({
|
|
96
|
+
sessionId: z.string(),
|
|
97
|
+
seq: z.number(),
|
|
98
|
+
startSeq: z.number(),
|
|
99
|
+
endSeq: z.number(),
|
|
100
|
+
inheritedEventCount: z.number(),
|
|
101
|
+
target: z.unknown(),
|
|
102
|
+
events: z.array(z.unknown()),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const FollowFrameSchema = z.object({
|
|
106
|
+
sessionId: z.string(),
|
|
107
|
+
seq: z.number(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const FOREST_DESCRIPTOR = {
|
|
111
|
+
id: `${PACKAGE}#mindTree/forest`,
|
|
112
|
+
service: NAMESPACE,
|
|
113
|
+
namespace: NAMESPACE,
|
|
114
|
+
method: 'forest',
|
|
115
|
+
invocation: { kind: 'direct' },
|
|
116
|
+
parameters: [],
|
|
117
|
+
cancellation: { parameter: 'signal' },
|
|
118
|
+
result: { mode: 'strict', typeSymbol: `${PACKAGE}#ForestView`, create: () => ForestViewSchema },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const LINEAGE_DESCRIPTOR = {
|
|
122
|
+
id: `${PACKAGE}#mindTree/lineage`,
|
|
123
|
+
service: NAMESPACE,
|
|
124
|
+
namespace: NAMESPACE,
|
|
125
|
+
method: 'lineage',
|
|
126
|
+
invocation: { kind: 'direct' },
|
|
127
|
+
parameters: [
|
|
128
|
+
{ name: 'sessionId', wire: 'sessionId', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#SessionId`, create: () => z.string() } },
|
|
129
|
+
],
|
|
130
|
+
cancellation: { parameter: 'signal' },
|
|
131
|
+
result: { mode: 'strict', typeSymbol: `${PACKAGE}#LineageView`, create: () => LineageViewSchema },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const TREE_DESCRIPTOR = {
|
|
135
|
+
id: `${PACKAGE}#mindTree/tree`,
|
|
136
|
+
service: NAMESPACE,
|
|
137
|
+
namespace: NAMESPACE,
|
|
138
|
+
method: 'tree',
|
|
139
|
+
invocation: { kind: 'direct' },
|
|
140
|
+
parameters: [
|
|
141
|
+
{ name: 'sessionId', wire: 'sessionId', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#SessionId`, create: () => z.string() } },
|
|
142
|
+
],
|
|
143
|
+
cancellation: { parameter: 'signal' },
|
|
144
|
+
result: { mode: 'strict', typeSymbol: `${PACKAGE}#ThoughtTree`, create: () => ThoughtTreeSchema },
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const DETAIL_DESCRIPTOR = {
|
|
148
|
+
id: `${PACKAGE}#mindTree/detail`,
|
|
149
|
+
service: NAMESPACE,
|
|
150
|
+
namespace: NAMESPACE,
|
|
151
|
+
method: 'detail',
|
|
152
|
+
invocation: { kind: 'direct' },
|
|
153
|
+
parameters: [
|
|
154
|
+
{ name: 'sessionId', wire: 'sessionId', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#SessionId`, create: () => z.string() } },
|
|
155
|
+
{ name: 'seq', wire: 'seq', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#Seq`, create: () => z.number() } },
|
|
156
|
+
{ name: 'before', wire: 'before', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#Seq`, create: () => z.number() } },
|
|
157
|
+
{ name: 'after', wire: 'after', source: 'json', codec: { mode: 'strict', typeSymbol: `${PACKAGE}#Seq`, create: () => z.number() } },
|
|
158
|
+
],
|
|
159
|
+
cancellation: { parameter: 'signal' },
|
|
160
|
+
result: { mode: 'strict', typeSymbol: `${PACKAGE}#EventWindow`, create: () => EventWindowSchema },
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const FOLLOW_DESCRIPTOR = {
|
|
164
|
+
id: `${PACKAGE}#mindTree/follow`,
|
|
165
|
+
service: NAMESPACE,
|
|
166
|
+
namespace: NAMESPACE,
|
|
167
|
+
method: 'follow',
|
|
168
|
+
mode: 'stream',
|
|
169
|
+
invocation: { kind: 'direct' },
|
|
170
|
+
parameters: [],
|
|
171
|
+
cancellation: { parameter: 'signal' },
|
|
172
|
+
result: { mode: 'strict', typeSymbol: `${PACKAGE}#FollowFrame`, create: () => FollowFrameSchema },
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const TYPERT_REMOTE = {
|
|
176
|
+
package: PACKAGE,
|
|
177
|
+
descriptors: [FOREST_DESCRIPTOR, LINEAGE_DESCRIPTOR, TREE_DESCRIPTOR, DETAIL_DESCRIPTOR, FOLLOW_DESCRIPTOR],
|
|
178
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 严格反射清单,由 DSH Typert 加载器校验。
|
|
2
|
+
// Copyright (c) 2026 ShOpenMind · https://github.com/ShOpenMind/dsh-plugin-mind-tree · MIT
|
|
3
|
+
import { PACKAGE, NAMESPACE, FOREST_DESCRIPTOR, LINEAGE_DESCRIPTOR, TREE_DESCRIPTOR, DETAIL_DESCRIPTOR, FOLLOW_DESCRIPTOR } from './remote.js';
|
|
4
|
+
|
|
5
|
+
export const TYPERT = {
|
|
6
|
+
package: PACKAGE,
|
|
7
|
+
face: 'host',
|
|
8
|
+
schemas: [],
|
|
9
|
+
invocations: [FOREST_DESCRIPTOR, LINEAGE_DESCRIPTOR, TREE_DESCRIPTOR, DETAIL_DESCRIPTOR, FOLLOW_DESCRIPTOR],
|
|
10
|
+
model: {
|
|
11
|
+
services: [{
|
|
12
|
+
key: NAMESPACE,
|
|
13
|
+
exportName: 'MindTreeService',
|
|
14
|
+
description: '只读的对话分支分析服务:遍历会话事件日志,识别分支节点(替换、重试、提问、批准、派生、目标、待办、计划、压缩),并提供会话血统(派生/子代理)追踪与思维树数据。不写入会话,不发起模型调用。',
|
|
15
|
+
summary: '对话上下文分支节点与思维树分析',
|
|
16
|
+
tags: ['mind-map', 'tree', 'session', 'branch'],
|
|
17
|
+
members: [
|
|
18
|
+
{ kind: 'method', name: 'forest', signature: 'forest(signal?: AbortSignal): Promise<ForestView>' },
|
|
19
|
+
{ kind: 'method', name: 'lineage', signature: 'lineage(sessionId: string, signal?: AbortSignal): Promise<LineageView>' },
|
|
20
|
+
{ kind: 'method', name: 'tree', signature: 'tree(sessionId: string, signal?: AbortSignal): Promise<ThoughtTree>' },
|
|
21
|
+
{ kind: 'method', name: 'detail', signature: 'detail(sessionId: string, seq: number, before: number, after: number, signal?: AbortSignal): Promise<EventWindow>' },
|
|
22
|
+
{ kind: 'method', name: 'follow', signature: 'follow(signal?: AbortSignal): AsyncIterable<FollowFrame>' },
|
|
23
|
+
],
|
|
24
|
+
types: [],
|
|
25
|
+
}],
|
|
26
|
+
events: [],
|
|
27
|
+
objects: [],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export default TYPERT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,112 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-mind-tree",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "遍历对话上下文,识别分支节点并渲染思维树/会话树的 DeepSeek Harness 插件(右侧栏 GUI + mind_tree 工具 + Obsidian 导出)。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "ShOpenMind",
|
|
9
|
+
"email": "ShOpenMind@users.noreply.github.com",
|
|
10
|
+
"url": "https://github.com/ShOpenMind/dsh-plugin-mind-tree"
|
|
11
|
+
},
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./lib/index.js",
|
|
15
|
+
"./client": "./lib/client.js",
|
|
16
|
+
"./format": "./lib/format.js",
|
|
17
|
+
"./typert": "./lib/typert.host.js",
|
|
18
|
+
"./remote": "./lib/remote.js",
|
|
19
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
20
|
+
"./package.json": "./package.json",
|
|
21
|
+
"./pricing": "./lib/pricing.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"cordis.patch.yml",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ShOpenMind/dsh-plugin-mind-tree.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/ShOpenMind/dsh-plugin-mind-tree#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/ShOpenMind/dsh-plugin-mind-tree/issues"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"dsh",
|
|
39
|
+
"deepseek-harness",
|
|
40
|
+
"plugin",
|
|
41
|
+
"mindmap",
|
|
42
|
+
"session-tree",
|
|
43
|
+
"observability",
|
|
44
|
+
"dsh-plugin",
|
|
45
|
+
"dsh-bundle",
|
|
46
|
+
"dsh-market"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"zod": "^4.4.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"esbuild": "^0.25.0",
|
|
53
|
+
"happy-dom": "^20.14.6",
|
|
54
|
+
"mermaid": "^12.1.0",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "^18.3.1"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@deepseek-ai/dsh": ">=0.2.0-rc.2 <0.3.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@deepseek-ai/dsh": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22"
|
|
68
|
+
},
|
|
69
|
+
"dsh": {
|
|
70
|
+
"bundle": {
|
|
71
|
+
"patch": "./cordis.patch.yml"
|
|
72
|
+
},
|
|
73
|
+
"client": {
|
|
74
|
+
"platform": "web",
|
|
75
|
+
"inject": [
|
|
76
|
+
"@deepseek-ai/dsh-client-locale",
|
|
77
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
78
|
+
"@deepseek-ai/dsh-client-connection",
|
|
79
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
86
|
+
],
|
|
87
|
+
"external": [
|
|
88
|
+
"@deepseek-ai/dsh-client-locale",
|
|
89
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
90
|
+
"@deepseek-ai/dsh-client-connection",
|
|
91
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"build": "node tools/build.mjs",
|
|
103
|
+
"test": "node tools/run-all.mjs",
|
|
104
|
+
"test:host": "node tools/host-test.mjs",
|
|
105
|
+
"test:reduce": "node tools/reduce-test.mjs",
|
|
106
|
+
"test:client": "node tools/client-test.mjs",
|
|
107
|
+
"test:mount": "node tools/mount-test.mjs",
|
|
108
|
+
"test:render": "node tools/render-test.mjs",
|
|
109
|
+
"test:mermaid": "node tools/mermaid-check.mjs",
|
|
110
|
+
"test:integration": "node tools/integration-test.mjs"
|
|
111
|
+
}
|
|
6
112
|
}
|