dsh-xray 0.10.0 → 0.10.2
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 +3 -1
- package/{README.zh.md → docs/README.zh.md} +17 -15
- package/lib/client.js +58 -5
- package/lib/collect/requests.js +11 -2
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
|
-
<a href="./README.zh.md">🇨🇳 中文文档</a>
|
|
21
|
+
<a href="./docs/README.zh.md">🇨🇳 中文文档</a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
---
|
|
@@ -66,6 +66,7 @@ The cost view answers the question no other tool asks: **who put this in my cont
|
|
|
66
66
|
- **By-plugin rollup** — each plugin's per-request context tax: sections + schemas + tokens + share, ranked.
|
|
67
67
|
- **Entry inspection** — `/xray/api/entry` returns any entry's live text with a chars/tokens ruler. Computed per request, never persisted.
|
|
68
68
|
- **Skill cost** — a dedicated view prices every skill twice: its catalog line (resident on every request once any model-invocable skill exists) and its body (billed per load). Pricing only — toggling belongs to the ecosystem's skill managers.
|
|
69
|
+
- **Per-request ledger** — one classified bill per LLM call: system / tool schemas / history / tool results (aggregated per tool), a **Δprev** growth column, and prefix-stability markers (⚡ system+tools byte-identical to the previous request — KV-cache-friendly; ✂ prefix broken). Compaction and title calls are tagged apart from chat. Only counts, names, and hashes are kept — never message text.
|
|
69
70
|
- **Explained UI** — every view opens with a one-line "what am I looking at"; terms carry plain-language tooltips; the whole tab is localized (English / 中文) through the host locale service.
|
|
70
71
|
|
|
71
72
|
The same data flows through three surfaces: the **X-Ray tab** (native GUI), the standalone **`/xray` page** (works even when the client-module pipeline it diagnoses is broken), and the **CLI**.
|
|
@@ -210,6 +211,7 @@ Diagnostic imaging for a running composition — complementary to [dsh-doctor](h
|
|
|
210
211
|
| --- | --- |
|
|
211
212
|
| Context-tax attribution & entry inspection | 💰 Optimization |
|
|
212
213
|
| Skill cost (catalog line + body pricing) | 💰 Optimization |
|
|
214
|
+
| Per-request ledger (Δprev, prefix stability) | 💰 Optimization |
|
|
213
215
|
| Layer attribution | 🔍 Inspection |
|
|
214
216
|
| Declared vs. actual diff | 🔍 Inspection |
|
|
215
217
|
| Conflict detection | 🔍 Inspection |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="../assets/hero.svg" width="100%" alt="dsh-xray — 给你的 DeepSeek Harness 拍 X 光">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<a href="
|
|
20
|
+
<a href="../README.md">English</a>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
你挂载的每个插件都在悄悄向每次 LLM 请求收费:prompt sections、工具 schema、token。dsh-xray 以 **与 Chat / Trajectory 并列的 X 光标签页** 长在你运行中的 harness 里,把这份账单逐项摊开——按插件、按条目,直到具体到每一个字:
|
|
26
26
|
|
|
27
|
-

|
|
28
28
|
|
|
29
29
|
展开插件看它注册了什么;点击任何条目,读它注入每次请求的原文:
|
|
30
30
|
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
<tr>
|
|
33
33
|
<td width="50%">
|
|
34
34
|
|
|
35
|
-

|
|
36
36
|
|
|
37
37
|
</td>
|
|
38
38
|
<td width="50%">
|
|
39
39
|
|
|
40
|
-

|
|
41
41
|
|
|
42
42
|
</td>
|
|
43
43
|
</tr>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
58
|
<p align="center">
|
|
59
|
-
<img src="
|
|
59
|
+
<img src="../assets/section-context-tax.svg" width="100%" alt="上下文税">
|
|
60
60
|
</p>
|
|
61
61
|
|
|
62
62
|
cost 视图回答一个别的工具都不问的问题:**这段上下文是谁放进来的、花掉我多少?**
|
|
@@ -65,6 +65,7 @@ cost 视图回答一个别的工具都不问的问题:**这段上下文是谁放
|
|
|
65
65
|
- **按插件汇总** —— 每个插件的每请求上下文税:sections + schemas + tokens + 占比,排序呈现。
|
|
66
66
|
- **条目原文查看** —— `/xray/api/entry` 返回任意条目的实时文本,附字符/token 标尺。按请求现算,绝不落盘。
|
|
67
67
|
- **skill 成本** —— 专属视图为每个 skill 计两笔价:catalog 行(只要存在模型可调用的 skill 就随每次请求常驻)与正文(每次加载计费)。只计价——启停归生态里的 skill 管理器。
|
|
68
|
+
- **逐请求账单** —— 每次 LLM 调用一张分类账单:system / 工具 schema / 历史 / 工具结果(按工具聚合),**Δprev** 增量列,前缀稳定性标记(⚡ system+工具与上一请求逐字节一致——KV 缓存友好;✂ 前缀击穿)。compaction 与标题生成单独标注。只存计数、名字和 hash——绝不存消息正文。
|
|
68
69
|
- **界面自解释** —— 每个视图开头一句"你在看什么";术语带白话提示;整个标签页通过宿主 locale 服务双语呈现(English / 中文)。
|
|
69
70
|
|
|
70
71
|
同一份数据流经三个界面:**X 光标签页**(原生 GUI)、独立 **`/xray` 页面**(连它所诊断的 client-module 加载链路挂了都能用)、以及 **CLI**。
|
|
@@ -72,7 +73,7 @@ cost 视图回答一个别的工具都不问的问题:**这段上下文是谁放
|
|
|
72
73
|
---
|
|
73
74
|
|
|
74
75
|
<p align="center">
|
|
75
|
-
<img src="
|
|
76
|
+
<img src="../assets/section-cli.svg" width="100%" alt="CLI 命令">
|
|
76
77
|
</p>
|
|
77
78
|
|
|
78
79
|
```sh
|
|
@@ -87,14 +88,14 @@ npx dsh-xray shadow # 被多个插件同时提供的服务
|
|
|
87
88
|
npx dsh-xray audit # 对 out-of-tree 插件做敏感触点静态扫描
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-

|
|
91
92
|
|
|
92
93
|
`attribute`、`conflicts`、`snapshot` 是纯静态的——dsh 起不来时照样能跑。所有命令支持 `--profile <name>`(默认 `web`)和 `--json`;`diff` 与 `health` 在漂移/不健康时退出码 `1`,可直接进 CI。
|
|
93
94
|
|
|
94
95
|
---
|
|
95
96
|
|
|
96
97
|
<p align="center">
|
|
97
|
-
<img src="
|
|
98
|
+
<img src="../assets/section-features.svg" width="100%" alt="能力">
|
|
98
99
|
</p>
|
|
99
100
|
|
|
100
101
|
<table>
|
|
@@ -135,7 +136,7 @@ npx dsh-xray audit # 对 out-of-tree 插件做敏感触点静态扫描
|
|
|
135
136
|
---
|
|
136
137
|
|
|
137
138
|
<p align="center">
|
|
138
|
-
<img src="
|
|
139
|
+
<img src="../assets/section-agent.svg" width="100%" alt="Agent 工具">
|
|
139
140
|
</p>
|
|
140
141
|
|
|
141
142
|
挂载进树后,dsh-xray 注册 `xray_composition` 工具(`view: summary | deps | health | cost | shadow`),agent 可以自答:
|
|
@@ -147,7 +148,7 @@ npx dsh-xray audit # 对 out-of-tree 插件做敏感触点静态扫描
|
|
|
147
148
|
---
|
|
148
149
|
|
|
149
150
|
<p align="center">
|
|
150
|
-
<img src="
|
|
151
|
+
<img src="../assets/section-safety.svg" width="100%" alt="安全立场">
|
|
151
152
|
</p>
|
|
152
153
|
|
|
153
154
|
**dsh-xray 只读,不执行。**
|
|
@@ -156,12 +157,12 @@ npx dsh-xray audit # 对 out-of-tree 插件做敏感触点静态扫描
|
|
|
156
157
|
- CLI **从不执行**插件代码(`audit` 是对源码文本的模式扫描)
|
|
157
158
|
- 挂载的插件只写 `$DSH_HOME/xray/` 目录——条目原文实时返回,**绝不落盘**
|
|
158
159
|
- entry 端点只返回组合层文本,**绝不返回会话消息**
|
|
159
|
-
- 详见 [SECURITY.md](
|
|
160
|
+
- 详见 [SECURITY.md](../SECURITY.md)
|
|
160
161
|
|
|
161
162
|
---
|
|
162
163
|
|
|
163
164
|
<p align="center">
|
|
164
|
-
<img src="
|
|
165
|
+
<img src="../assets/section-install.svg" width="100%" alt="安装">
|
|
165
166
|
</p>
|
|
166
167
|
|
|
167
168
|
两种用法,彼此独立:
|
|
@@ -200,7 +201,7 @@ npx dsh-xray health # 读取运行时快照
|
|
|
200
201
|
---
|
|
201
202
|
|
|
202
203
|
<p align="center">
|
|
203
|
-
<img src="
|
|
204
|
+
<img src="../assets/section-capabilities.svg" width="100%" alt="能力总表">
|
|
204
205
|
</p>
|
|
205
206
|
|
|
206
207
|
对运行中组合树的诊断成像——与 [dsh-doctor](https://www.npmjs.com/package/dsh-doctor)(救援与恢复)互补。
|
|
@@ -209,6 +210,7 @@ npx dsh-xray health # 读取运行时快照
|
|
|
209
210
|
| --- | --- |
|
|
210
211
|
| 上下文税归因 & 条目原文查看 | 💰 优化 |
|
|
211
212
|
| skill 成本(catalog 行 + 正文计价) | 💰 优化 |
|
|
213
|
+
| 逐请求账单(Δprev、前缀稳定性) | 💰 优化 |
|
|
212
214
|
| 层归因 | 🔍 检视 |
|
|
213
215
|
| 声明 vs 实际 diff | 🔍 检视 |
|
|
214
216
|
| 冲突检测 | 🔍 检视 |
|
|
@@ -223,4 +225,4 @@ npx dsh-xray health # 读取运行时快照
|
|
|
223
225
|
|
|
224
226
|
## 许可
|
|
225
227
|
|
|
226
|
-
[MIT](
|
|
228
|
+
[MIT](../LICENSE)
|
package/lib/client.js
CHANGED
|
@@ -189,6 +189,10 @@ window.__ModuleLoader__.load({
|
|
|
189
189
|
'requests.none': 'no requests observed yet — send one agent message first',
|
|
190
190
|
'requests.session': 'session {id} ({n} request(s))',
|
|
191
191
|
'requests.chat': 'chat',
|
|
192
|
+
'requests.copyId': 'copy call id',
|
|
193
|
+
'requests.copied': 'copied',
|
|
194
|
+
'tip.copyId':
|
|
195
|
+
"Opaque id of this tool's largest result in this request — find the call in the Trajectory tab",
|
|
192
196
|
'entry.loading': 'loading entry…',
|
|
193
197
|
'entry.stats': '{chars} chars · ~{tokens} tokens ({estimator})',
|
|
194
198
|
'entry.close': 'close',
|
|
@@ -304,6 +308,9 @@ window.__ModuleLoader__.load({
|
|
|
304
308
|
'requests.none': '尚未观测到请求——先发送一条 agent 消息',
|
|
305
309
|
'requests.session': '会话 {id}({n} 个请求)',
|
|
306
310
|
'requests.chat': 'chat',
|
|
311
|
+
'requests.copyId': '复制 call id',
|
|
312
|
+
'requests.copied': '已复制',
|
|
313
|
+
'tip.copyId': '该工具在本请求中最大结果的不透明 id——在 Trajectory 标签页中定位那次调用',
|
|
307
314
|
'entry.loading': '正在加载条目…',
|
|
308
315
|
'entry.stats': '{chars} 字符 · 约 {tokens} tokens({estimator})',
|
|
309
316
|
'entry.close': '关闭',
|
|
@@ -433,6 +440,56 @@ window.__ModuleLoader__.load({
|
|
|
433
440
|
return h(Table, { t, headers: ['plugin', 'sections', 'tools', 'tokens', 'share', ''], rows });
|
|
434
441
|
}
|
|
435
442
|
|
|
443
|
+
/** Expandable tool-results cell: unfolds top tools with the opaque call
|
|
444
|
+
* id of each tool's largest result — the manual bridge to Trajectory
|
|
445
|
+
* until the host exposes a requestInspect write-side. */
|
|
446
|
+
function ResultsCell({ tokens, rows, t }) {
|
|
447
|
+
const [open, setOpen] = react.useState(false);
|
|
448
|
+
const [copied, setCopied] = react.useState(null);
|
|
449
|
+
if (!rows?.length) return h('span', { className: 'xray-num' }, String(tokens));
|
|
450
|
+
return h(
|
|
451
|
+
react.Fragment,
|
|
452
|
+
null,
|
|
453
|
+
h(
|
|
454
|
+
'button',
|
|
455
|
+
{ className: 'xray-entry-link xray-num', onClick: () => setOpen(!open) },
|
|
456
|
+
`${open ? '▾' : '▸'} ${tokens}`,
|
|
457
|
+
),
|
|
458
|
+
open
|
|
459
|
+
? h(
|
|
460
|
+
'div',
|
|
461
|
+
{ style: { marginTop: 4 } },
|
|
462
|
+
rows.map((row) =>
|
|
463
|
+
h(
|
|
464
|
+
'div',
|
|
465
|
+
{ key: row.name, style: { whiteSpace: 'nowrap' } },
|
|
466
|
+
h('span', { className: 'xray-muted' }, `${row.name} ~${row.tokens} `),
|
|
467
|
+
row.topCallId
|
|
468
|
+
? h(
|
|
469
|
+
'button',
|
|
470
|
+
{
|
|
471
|
+
className: 'xray-entry-link',
|
|
472
|
+
title: t('tip.copyId'),
|
|
473
|
+
onClick: () => {
|
|
474
|
+
try {
|
|
475
|
+
navigator.clipboard.writeText(String(row.topCallId));
|
|
476
|
+
setCopied(row.name);
|
|
477
|
+
setTimeout(() => setCopied(null), 1500);
|
|
478
|
+
} catch {
|
|
479
|
+
/* clipboard unavailable: the id is still visible */
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
copied === row.name ? t('requests.copied') : t('requests.copyId'),
|
|
484
|
+
)
|
|
485
|
+
: null,
|
|
486
|
+
),
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
: null,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
436
493
|
/** Raw-text inspector: fetches one entry's live text on open. Fetched per
|
|
437
494
|
* view, never cached — the text IS the audit artifact. */
|
|
438
495
|
function EntryModal({ target, onClose, t }) {
|
|
@@ -775,10 +832,6 @@ window.__ModuleLoader__.load({
|
|
|
775
832
|
'delta',
|
|
776
833
|
],
|
|
777
834
|
rows: session.requests.map((r) => {
|
|
778
|
-
const top = (r.toolResultRows ?? [])
|
|
779
|
-
.slice(0, 3)
|
|
780
|
-
.map((row) => `${row.name} ~${row.tokens}`)
|
|
781
|
-
.join(', ');
|
|
782
835
|
return Row(`${session.sessionId}/${r.seq}`, [
|
|
783
836
|
{ cls: 'xray-num', text: String(r.seq) },
|
|
784
837
|
{ cls: 'xray-muted', text: new Date(r.at).toTimeString().slice(0, 8) },
|
|
@@ -789,7 +842,7 @@ window.__ModuleLoader__.load({
|
|
|
789
842
|
{ cls: 'xray-num', text: String(r.system) },
|
|
790
843
|
{ cls: 'xray-num', text: String(r.toolSchemas) },
|
|
791
844
|
{ cls: 'xray-num', text: String(r.history) },
|
|
792
|
-
h(
|
|
845
|
+
h(ResultsCell, { tokens: r.toolResults, rows: r.toolResultRows, t }),
|
|
793
846
|
r.prefixStable === null ? '—' : r.prefixStable ? '⚡' : '✂',
|
|
794
847
|
{
|
|
795
848
|
cls: 'xray-num',
|
package/lib/collect/requests.js
CHANGED
|
@@ -57,7 +57,7 @@ function classify(options) {
|
|
|
57
57
|
let historyMessages = 0;
|
|
58
58
|
let userMessages = 0;
|
|
59
59
|
let assistantMessages = 0;
|
|
60
|
-
const toolResults = new Map(); // tool name -> {count, chars}
|
|
60
|
+
const toolResults = new Map(); // tool name -> {count, chars, topCallId, topChars}
|
|
61
61
|
for (const message of options.messages ?? []) {
|
|
62
62
|
const content = message.content ?? [];
|
|
63
63
|
for (const block of content) {
|
|
@@ -68,15 +68,21 @@ function classify(options) {
|
|
|
68
68
|
const chars = content.reduce((sum, block) => sum + blockChars(block), 0);
|
|
69
69
|
if (isToolResult) {
|
|
70
70
|
let name = 'unknown';
|
|
71
|
+
let callId = null;
|
|
71
72
|
for (const block of content) {
|
|
72
73
|
if (block?.type === 'tool-result') {
|
|
74
|
+
callId = block.toolCallId ?? null;
|
|
73
75
|
name = callNames.get(block.toolCallId) ?? 'unknown';
|
|
74
76
|
break;
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
|
-
const row = toolResults.get(name) ?? { count: 0, chars: 0 };
|
|
79
|
+
const row = toolResults.get(name) ?? { count: 0, chars: 0, topCallId: null, topChars: -1 };
|
|
78
80
|
row.count += 1;
|
|
79
81
|
row.chars += chars;
|
|
82
|
+
if (chars > row.topChars) {
|
|
83
|
+
row.topChars = chars;
|
|
84
|
+
row.topCallId = callId;
|
|
85
|
+
}
|
|
80
86
|
toolResults.set(name, row);
|
|
81
87
|
} else {
|
|
82
88
|
historyChars += chars;
|
|
@@ -90,6 +96,9 @@ function classify(options) {
|
|
|
90
96
|
name,
|
|
91
97
|
count: row.count,
|
|
92
98
|
tokens: estimateTokens(' '.repeat(row.chars)),
|
|
99
|
+
// Opaque provider call id of this tool's LARGEST result — paste it
|
|
100
|
+
// into Trajectory to find the culprit call. No text is retained.
|
|
101
|
+
topCallId: row.topCallId,
|
|
93
102
|
}))
|
|
94
103
|
.sort((a, b) => b.tokens - a.tokens);
|
|
95
104
|
const toolResultTokens = toolResultRows.reduce((sum, r) => sum + r.tokens, 0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-xray",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "X-ray for your DeepSeek Harness —
|
|
3
|
+
"version": "0.10.2",
|
|
4
|
+
"description": "X-ray for your DeepSeek Harness — diagnostics for what's actually loaded, why, and what it costs: per-plugin context-tax attribution, per-request token ledger, skill catalog pricing, dependency cascades.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/alloevil/dsh-xray.git"
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"bin",
|
|
25
25
|
"cordis.patch.yml",
|
|
26
26
|
"README.md",
|
|
27
|
-
"README.zh.md",
|
|
28
27
|
"docs",
|
|
29
28
|
"assets"
|
|
30
29
|
],
|