dsh-token-total 1.1.4 → 1.1.5

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
@@ -60,7 +60,7 @@ DSH(DeepSeek Harness)Web 插件:**Token 用量统计看板** + **单会话
60
60
  - 两张卡都由会话明细轮询驱动(每 1.5 秒一次),不额外起计时器;旧宿主只提供其中一份数据时,另一张卡显示空态而非整组消失,两份都缺时整组不渲染。窄视口下自动上下堆叠。
61
61
 
62
62
  - 浅色页面两行统计卡:第一行请求数量(附失败数)、总 tokens(附精确值)、缓存命中率(附命中 token)、token 速度(附平均首 token);第二行输入、输出、缓存读取、缓存写入四个 token 桶。不显示费用。
63
- - 子代理明细使用每个子代理的**真实名称**(取自子代理标签,缺失时回退到会话标题、工作目录或会话 id)。
63
+ - 子代理明细与请求记录使用每个子代理的**真实名称**:优先子代理自身标签,再取最新非空会话标题;旧 CLI 记录缺少标题时,读取本次会话树中实际父会话的 `subagent/catalog` 任务标签,最后才回退到日志头名称、工作目录或会话 id。只使用各日志的自有事件,不把继承的父标题/目录当作自身名称,不改写历史日志,也不改变 Token 统计。若直接打开旧子会话、实际父会话不在本次读取树中,则保留原有回退名称。
64
64
  - 中部为等宽、等高的 **子代理明细** 与 **按模型明细**;窄视口自动堆叠。使用固定列布局而非横向滚动,长名称省略并可悬浮查看全名,token 数同样可悬浮查看精确值。按模型明细的名称使用模型选择器的**显示名称**(路由 id 保留在悬浮提示中,无法解析时回退为 id);当多行显示名称相同(忽略大小写与分隔符)时,在名称下方用灰色小字标明各自供应商以便区分。
65
65
  - 请求记录按**轮**折叠(压缩调用归入独立分组),**默认全部折叠**;展开某一轮可查看该轮每一次请求的时间、模型、tokens、命中率、状态与入 / 出 / 读 / 写明细,支持单组展开和全部展开 / 收起。失败尝试、实际重试、压缩调用纳入统计;仅继承的父会话历史不重复计算。
66
66
  - 页面每 1.5 秒串行读取当前会话明细,切换或离开 tab 会取消请求和计时器。统计含本会话及所有后代的自有历史日志,缺失的首 token / 速度显示 `—`,读取错误保留上次成功结果。
package/lib/index.js CHANGED
@@ -2217,8 +2217,11 @@ function nameOf(h, id) {
2217
2217
  if (typeof x?.cwd === "string" && x.cwd) return x.cwd.split(/[\\/]/).filter(Boolean).pop() || id;
2218
2218
  return id;
2219
2219
  }
2220
- function one(log) {
2221
- const { id, header, live, events, inheritedCut: cut } = log;
2220
+ function ownEventsOf(log) {
2221
+ return log.events.filter((e) => !(log.inheritedCut > 0 && typeof e.seq === "number" && e.seq < log.inheritedCut));
2222
+ }
2223
+ function one(log, parentCatalogLabel) {
2224
+ const { id, header, live } = log;
2222
2225
  const fold = new UsageFold();
2223
2226
  const totals = zero$1();
2224
2227
  const records = [];
@@ -2229,15 +2232,14 @@ function one(log) {
2229
2232
  let effort = "";
2230
2233
  let pendingTopic = "";
2231
2234
  const turnTopics = /* @__PURE__ */ new Map();
2232
- const own = events.filter((e) => !(cut > 0 && typeof e.seq === "number" && e.seq < cut));
2235
+ const own = ownEventsOf(log);
2233
2236
  let label = "", title = "";
2234
2237
  for (const ev of own) {
2235
2238
  const d = ev.data ?? {};
2236
2239
  if (!label && ev.type === "subagent/descriptor" && typeof d.label === "string" && d.label.trim()) label = d.label.trim();
2237
- if (!title && ev.type === "session/title" && typeof d.title === "string" && d.title.trim()) title = d.title.trim();
2238
- if (label && title) break;
2240
+ if (ev.type === "session/title" && typeof d.title === "string" && d.title.trim()) title = d.title.trim();
2239
2241
  }
2240
- const name = label || title || nameOf(header, id);
2242
+ const name = label || title || parentCatalogLabel || nameOf(header, id);
2241
2243
  const stepTokens = /* @__PURE__ */ new Map();
2242
2244
  const closedSteps = /* @__PURE__ */ new Set();
2243
2245
  for (const ev of own) {
@@ -2418,7 +2420,21 @@ function one(log) {
2418
2420
  }
2419
2421
  async function buildSessionUsageDetail(persistence, sessions, rootId, ctx) {
2420
2422
  const { root, descendants, coverage } = await readUsageTree(persistence, sessions, rootId);
2421
- const parts = [root, ...descendants].map(one);
2423
+ const logs = [root, ...descendants];
2424
+ const catalogLabels = /* @__PURE__ */ new Map();
2425
+ for (const log of logs) {
2426
+ const labels = /* @__PURE__ */ new Map();
2427
+ for (const event of ownEventsOf(log)) {
2428
+ if (event.type !== "subagent/catalog") continue;
2429
+ const data = event.data;
2430
+ if (typeof data?.childId === "string" && data.childId && typeof data.label === "string" && data.label.trim()) labels.set(data.childId, data.label.trim());
2431
+ }
2432
+ catalogLabels.set(log.id, labels);
2433
+ }
2434
+ const parts = logs.map((log) => {
2435
+ const parentId = log.header?.parentSession ?? log.live?.header?.parentSession ?? log.live?.parentSession;
2436
+ return one(log, parentId === void 0 ? void 0 : catalogLabels.get(parentId)?.get(log.id));
2437
+ });
2422
2438
  const totals = zero$1();
2423
2439
  const models = /* @__PURE__ */ new Map();
2424
2440
  const records = parts.flatMap((x) => x.records);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-token-total",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "DSH web plugin: token 用量统计看板 + 单会话(主智能体 + 子智能体)用量统计。提供今日/近3天/近7天/近1月/近3月/自定义筛选、六个汇总卡片、活跃热力图、缓存命中率折线与分模型柱状图、按模型/按供应商 Top5 列表。",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -86,8 +86,12 @@ function nameOf(h: UsageSessionHeader | undefined, id: string): string {
86
86
  return id
87
87
  }
88
88
 
89
- function one(log: UsageSessionLog) {
90
- const { id, header, live, events, inheritedCut: cut } = log
89
+ function ownEventsOf(log: UsageSessionLog) {
90
+ return log.events.filter(e => !(log.inheritedCut > 0 && typeof e.seq === 'number' && e.seq < log.inheritedCut))
91
+ }
92
+
93
+ function one(log: UsageSessionLog, parentCatalogLabel?: string) {
94
+ const { id, header, live } = log
91
95
  const fold = new UsageFold(); const totals = zero(); const records: MutableRecord[] = []
92
96
  const byKey = new Map<string, MutableRecord>(); let route = live?.requestContext?.() ?? {}
93
97
  let ordinal = 0
@@ -96,18 +100,16 @@ function one(log: UsageSessionLog) {
96
100
  // the prompt usually lands before `turn/start`, so buffer it until the turn opens.
97
101
  let currentTurn = 0; let effort = ''; let pendingTopic = ''
98
102
  const turnTopics = new Map<number, string>()
99
- const own = events.filter(e => !(cut > 0 && typeof e.seq === 'number' && e.seq < cut))
100
- // Real session names: a subagent carries its delegation label on its
101
- // `subagent/descriptor` event ("完成日志明细后端"), every session can carry a
102
- // generated `session/title`; only fall back to the header/cwd/id guesses.
103
+ const own = ownEventsOf(log)
104
+ // Prefer the child's own delegation label or latest title. Older CLI records
105
+ // carry their task name only in the actual parent's subagent/catalog.
103
106
  let label = '', title = ''
104
107
  for (const ev of own) {
105
108
  const d: any = ev.data ?? {}
106
109
  if (!label && ev.type === 'subagent/descriptor' && typeof d.label === 'string' && d.label.trim()) label = d.label.trim()
107
- if (!title && ev.type === 'session/title' && typeof d.title === 'string' && d.title.trim()) title = d.title.trim()
108
- if (label && title) break
110
+ if (ev.type === 'session/title' && typeof d.title === 'string' && d.title.trim()) title = d.title.trim()
109
111
  }
110
- const name = label || title || nameOf(header, id)
112
+ const name = label || title || parentCatalogLabel || nameOf(header, id)
111
113
  // Decode accounting mirrors the official `sessionStats` projection: a step
112
114
  // records its first token once (the earliest attempt wins, surviving an
113
115
  // in-step retry), and only the assembled `assistant/message` closes the
@@ -192,7 +194,23 @@ function one(log: UsageSessionLog) {
192
194
 
193
195
  export async function buildSessionUsageDetail(persistence: UsageSessionPersistence, sessions: UsageSessionStore | undefined, rootId: string, ctx?: Context): Promise<SessionUsageDetail> {
194
196
  const { root, descendants, coverage } = await readUsageTree(persistence, sessions, rootId)
195
- const parts = [root, ...descendants].map(one)
197
+ const logs = [root, ...descendants]
198
+ const catalogLabels = new Map<string, Map<string, string>>()
199
+ for (const log of logs) {
200
+ const labels = new Map<string, string>()
201
+ for (const event of ownEventsOf(log)) {
202
+ if (event.type !== 'subagent/catalog') continue
203
+ const data = event.data as { childId?: unknown; label?: unknown } | null
204
+ if (typeof data?.childId === 'string' && data.childId && typeof data.label === 'string' && data.label.trim()) {
205
+ labels.set(data.childId, data.label.trim())
206
+ }
207
+ }
208
+ catalogLabels.set(log.id, labels)
209
+ }
210
+ const parts = logs.map(log => {
211
+ const parentId = log.header?.parentSession ?? log.live?.header?.parentSession ?? log.live?.parentSession
212
+ return one(log, parentId === undefined ? undefined : catalogLabels.get(parentId)?.get(log.id))
213
+ })
196
214
  const totals = zero(); const models = new Map<string, { totals: SessionUsageTotals; requests: number; providers: Set<string> }>(); const records = parts.flatMap(x => x.records); for (const p of parts) add(totals, p.totals)
197
215
  for (const r of records) { const key = r.model || '(unknown)'; const m = models.get(key) ?? { totals: zero(), requests: 0, providers: new Set<string>() }; add(m.totals, r.totals); m.requests++; if (r.provider) m.providers.add(r.provider); models.set(key, m) }
198
216
  const subagents = parts.slice(1).map(p => ({ sessionId: p.id, name: p.name, totals: p.totals, requests: p.requests, averageFirstTokenMs: p.averageFirstTokenMs, tokensPerSecond: p.tokensPerSecond }))