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 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="./assets/hero.svg" width="100%" alt="dsh-xray — 给你的 DeepSeek Harness 拍 X 光">
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="./README.md">English</a>
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
- ![X 光标签页:每个插件的每请求上下文税,已归因并排序](./assets/tab-cost.webp)
27
+ ![X 光标签页:每个插件的每请求上下文税,已归因并排序](../assets/tab-cost.webp)
28
28
 
29
29
  展开插件看它注册了什么;点击任何条目,读它注入每次请求的原文:
30
30
 
@@ -32,12 +32,12 @@
32
32
  <tr>
33
33
  <td width="50%">
34
34
 
35
- ![插件展开为其注册的条目](./assets/tab-expand.webp)
35
+ ![插件展开为其注册的条目](../assets/tab-expand.webp)
36
36
 
37
37
  </td>
38
38
  <td width="50%">
39
39
 
40
- ![~184 token 背后的原文,附字符/token 标尺](./assets/tab-entry.webp)
40
+ ![~184 token 背后的原文,附字符/token 标尺](../assets/tab-entry.webp)
41
41
 
42
42
  </td>
43
43
  </tr>
@@ -56,7 +56,7 @@
56
56
  ---
57
57
 
58
58
  <p align="center">
59
- <img src="./assets/section-context-tax.svg" width="100%" alt="上下文税">
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="./assets/section-cli.svg" width="100%" alt="CLI 命令">
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
- ![dsh-xray 演示](./docs/demo.svg)
91
+ ![dsh-xray 演示](./demo.svg)
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="./assets/section-features.svg" width="100%" alt="能力">
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="./assets/section-agent.svg" width="100%" alt="Agent 工具">
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="./assets/section-safety.svg" width="100%" alt="安全立场">
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](./SECURITY.md)
160
+ - 详见 [SECURITY.md](../SECURITY.md)
160
161
 
161
162
  ---
162
163
 
163
164
  <p align="center">
164
- <img src="./assets/section-install.svg" width="100%" alt="安装">
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="./assets/section-capabilities.svg" width="100%" alt="能力总表">
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](./LICENSE)
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('span', { className: 'xray-num', title: top }, String(r.toolResults)),
845
+ h(ResultsCell, { tokens: r.toolResults, rows: r.toolResultRows, t }),
793
846
  r.prefixStable === null ? '—' : r.prefixStable ? '⚡' : '✂',
794
847
  {
795
848
  cls: 'xray-num',
@@ -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.0",
4
- "description": "X-ray for your DeepSeek Harness — see what's actually loaded, why, and what it costs you.",
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
  ],