dsh-agent-board 1.7.4 → 1.7.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.
@@ -217,6 +217,29 @@
217
217
  if (groups.blocked.length) { lines.push('## 阻塞'); groups.blocked.forEach(function (t) { lines.push('- ' + t.title + (t.escalation ? '(待裁决)' : '')) }); lines.push('') }
218
218
  var doneAll = state.archived.length ? state.archived : state.tasks.filter(function (t) { return t.status === 'resolved' }); var done = doneAll.filter(function (t) { return !(rg.from || rg.to) || tsInRange(taskLastTs(t), rg.from, rg.to) })
219
219
  if (done.length) { lines.push('## 已完成(含归档,近 ' + Math.min(done.length, 20) + ' 条)'); done.slice(0, 20).forEach(function (t) { lines.push('- ' + t.title + (t.verification ? '|验收: ' + t.verification.verdict : '') + (t.deliverable && t.deliverable.summary ? '|' + t.deliverable.summary.slice(0, 80) : '')) }); lines.push('') }
220
+ // ===== 本版自测清单(verifyUserGuide 开关门禁,task-muxyyvg0)=====
221
+ // 已验收卡的 userTest 聚合:按验收通过时间(resolvedAt,兜底 verification.at)倒序取近 10 张,
222
+ // tier=internal 的收末尾并标注「无用户可感知面」——用户照着清单逐张自测;开关关掉整段不出现。
223
+ if (state.verifyUserGuide !== false) {
224
+ var ugList = done.filter(function (t) { return t.verification && t.verification.userTest })
225
+ ugList.sort(function (a, b) { return String(b.resolvedAt || b.verification.at || '').localeCompare(String(a.resolvedAt || a.verification.at || '')) })
226
+ var ugTop = ugList.slice(0, 10)
227
+ var ugAct = ugTop.filter(function (t) { return t.verification.userTest.tier !== 'internal' })
228
+ var ugInternal = ugTop.filter(function (t) { return t.verification.userTest.tier === 'internal' })
229
+ var ugOrdered = ugAct.concat(ugInternal)
230
+ if (ugOrdered.length) {
231
+ lines.push('## 本版自测清单(验收通过时间倒序 · 近 ' + ugOrdered.length + ' 张)')
232
+ ugOrdered.forEach(function (t) {
233
+ var ut = t.verification.userTest
234
+ var tierTag = ut.tier === 'ui' ? 'UI 可操作' : (ut.tier === 'metric' ? '看指标' : '纯内部 · 无用户可感知面')
235
+ lines.push('### ' + t.title + '(' + tierTag + ')')
236
+ if (ut.gist) lines.push('- 改动:' + ut.gist)
237
+ if (ut.steps && ut.steps.length) { lines.push('- 自测步骤:'); ut.steps.forEach(function (s, i) { lines.push(' ' + (i + 1) + '. ' + s) }) }
238
+ if (ut.expect) lines.push('- 预期:' + ut.expect)
239
+ lines.push('')
240
+ })
241
+ }
242
+ }
220
243
  return lines.join('\n')
221
244
  }
222
245
 
@@ -239,9 +262,10 @@
239
262
  // usageSummary.total 为 0,一律显示「暂无数据」。
240
263
  // 日账(usageSummary.byDayFull:{'YYYY-MM-DD': {t,e}})用于「今日」大数字与「近 7 天」条形。
241
264
  // 双指标口径:t = 总量(含缓存读)、e = 有效消耗(输入+输出+缓存写,不含缓存读)。
242
- // e === null 表示该日只有老形态 number 日账 / 存量兜底(没有逐 run 拆分,有效值不可知)——
243
- // 旧口径日不拿 t 冒充有效(task-muwsnyqv ③:本板 10-05 t=154M 含 97% 缓存读曾被画成巨柱):
244
- // 今日大数字显示「—」+ title 说明,近 7 天画矮灰柱 + tooltip 标「旧口径数据(仅总量)」。
265
+ // 主显口径(task-muxnqunk,用户 2026-10-07 裁决「算总的吧」,推翻 task-muwq9u04 的有效主显):
266
+ // 本区主数字一律总量 t(含缓存读),有效消耗 e 退居悬浮 title / 次要小字;e === null 的旧口径日
267
+ // (老 number 日账 / 存量兜底,没有逐 run 拆分)现在就是正常柱(t 恒有值)——
268
+ // task-muwsnyqv ③ 的「今日显— / 矮灰柱」旧口径规则顺势退役。
245
269
  // dayKey 口径与 host 记账完全一致——本地 getters 拼,绝不用 toISOString()(UTC 会让
246
270
  // 晚上 8 点后的消耗落到次日,今日消耗直接错位)。
247
271
  function localDayKey(d) {
@@ -285,44 +309,43 @@
285
309
  // ===== 主窗口(本会话对话)消耗(task-muwsol23):host 增量尾读主会话 v4 日志聚合 =====
286
310
  // 隔离口径:只在 Token 区单列一行 + 模型分布尾部追加一条;不进累计 / Top 8 / 模型归属摊派。
287
311
  // 缺字段(老 host)/ 读不到日志 / 无 usage → mw=null → 两处都不渲染(静默降级,不炸轮询)。
312
+ // 口径翻转(task-muxnqunk):今日/累计主数字显总量(含缓存读——缓存读不再单列,已含在 total 里),
313
+ // 有效消耗进悬浮 title。
288
314
  var mw = (u && u.mainWindow && typeof u.mainWindow === 'object' && typeof u.mainWindow.effective === 'number') ? u.mainWindow : null
289
315
  var todayKey0 = localDayKey()
290
316
  var mwToday = 0
291
317
  if (mw && mw.byDay && typeof mw.byDay === 'object') {
292
318
  var mwc0 = mw.byDay[todayKey0]
293
- mwToday = (mwc0 && typeof mwc0.e === 'number') ? mwc0.e : 0
319
+ mwToday = (mwc0 && typeof mwc0.t === 'number') ? mwc0.t : 0
294
320
  }
295
- // 主窗口单列行(累计行下方):「主窗口(本会话):今日有效 X · 累计 Y(缓存读 Z)」
321
+ // 主窗口单列行(累计行下方):「主窗口(本会话):今日 X · 累计 Y」(总量口径,有效值见悬浮 title)
296
322
  function mwLine() {
297
323
  if (!mw) return null
298
- return React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 8 }, title: '主窗口(本会话对话)消耗:host 增量尾读本会话 v4 日志聚合,有效口径 = 输入+输出+缓存写(不含缓存读)' + (tgOn ? ';随统计范围裁剪(按助手消息的本地日落点)' : '') + '。与看板派发口径并列展示——不进累计 / Top 8 / 模型归属摊派' },
299
- '主窗口(本会话):今日有效 ',
324
+ return React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 8 }, title: '主窗口(本会话对话)消耗:总量口径(含缓存读);有效消耗(输入+输出+缓存写,不含缓存读)= ' + String(mw.effective) + ' tok。host 增量尾读本会话 v4 日志聚合' + (tgOn ? ';随统计范围裁剪(按助手消息的本地日落点)' : '') + '。与看板派发口径并列展示——不进累计 / Top 8 / 模型归属摊派' },
325
+ '主窗口(本会话):今日 ',
300
326
  React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(mwToday)),
301
327
  ' · 累计 ',
302
- React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(mw.effective)),
303
- '(缓存读 ',
304
- React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(mw.cacheRead)),
305
- ')')
328
+ React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(mw.total)))
306
329
  }
307
330
  if (!u || !u.total) return React.createElement('div', { style: box }, head, React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无数据(Worker/Verifier 会话日志里还没有 usage 记录)'), mwLine())
308
- // ===== 主数字口径统一(task-muwq9u04):模型分布行与 Top8 行的主数字一律显示**有效消耗**(e),
309
- // 含缓存读的合计(t)退到 title 悬浮;否则同一块里「今日有效 3.7M」与「单任务 21M」并排自相矛盾
310
- // (用户就是这么判成 bug 的)。数据源:host 的 byModelEff(模型有效分摊)与 topTasks[].effective;
311
- // 老 host 缺 byModelEff / 老卡无五分量留账 → 退化用合计值并在 title 标 ~(口径不伪造)。
312
- // 排序按**显示口径**(有效)降序:条形长度与行序一致,否则首行不是最长的条。
331
+ // ===== 主数字口径翻转(task-muxnqunk,用户 2026-10-07 裁决「算总的吧」,推翻 task-muwq9u04 的
332
+ // 有效主显):模型分布行与 Top8 行的主数字一律显示**总量**(byModel / topTasks[].total,含缓存读),
333
+ // 有效消耗(byModelEff / topTasks[].effective)退到 title 悬浮;老 host 缺 byModelEff / 老卡无
334
+ // 五分量留账时 title 标「有效不可知」(口径不伪造)。
335
+ // 排序按**显示口径**(总量)降序:条形长度与行序一致,否则首行不是最长的条。
313
336
  var models = Object.keys(u.byModel || {}).map(function (m) {
314
337
  var tt = u.byModel[m] || 0
315
338
  var ee = (u.byModelEff && typeof u.byModelEff[m] === 'number') ? u.byModelEff[m] : null
316
- return { model: m, total: tt, eff: (ee === null ? tt : ee), approx: ee === null }
317
- }).sort(function (a, b) { return b.eff - a.eff })
318
- var maxM = models.length ? (models[0].eff || 1) : 1
339
+ return { model: m, total: tt, eff: ee }
340
+ }).sort(function (a, b) { return b.total - a.total })
341
+ var maxM = models.length ? (models[0].total || 1) : 1
319
342
  // 主窗口行与 run 模型同一把尺(参与归一,条形长度才可比),但固定尾部追加、不参与排序(见渲染处)
320
- if (mw && mw.effective > maxM) maxM = mw.effective
343
+ if (mw && mw.total > maxM) maxM = mw.total
321
344
  var top = (u.topTasks || []).map(function (x) {
322
345
  var ee = (typeof x.effective === 'number') ? x.effective : null
323
- return { id: x.id, title: x.title, total: x.total, cacheRead: x.cacheRead, runs: x.runs, eff: (ee === null ? x.total : ee), approx: ee === null }
324
- }).sort(function (a, b) { return b.eff - a.eff })
325
- var maxT = top.length ? (top[0].eff || 1) : 1
346
+ return { id: x.id, title: x.title, total: x.total, cacheRead: x.cacheRead, runs: x.runs, eff: ee }
347
+ }).sort(function (a, b) { return b.total - a.total })
348
+ var maxT = top.length ? (top[0].total || 1) : 1
326
349
  // 日账:今日数字取本地日 key,没有日账(缺字段/老 host)时退化为 0,不炸也不误报。
327
350
  // 读侧兼容两种单元形态:老 number(只有总量,有效值不可知)→ { t: n, e: null }。
328
351
  // 固定口径数据源(task-muwsnyqv ②):「今日」「近 7 天」读**未过滤**的 byDayFull——caption
@@ -335,70 +358,321 @@
335
358
  }
336
359
  var todayKey = localDayKey()
337
360
  var todayCell = dayOf(byDay[todayKey])
338
- // 旧口径日(e===null 且有总量:老 number 日账/存量兜底,只有含缓存读的总量)不拿 t 冒充有效
339
- // (task-muwsnyqv ③)——大数字显示「—」+ title 说明;t=0 且无日账 = 今日尚无结算,照常显示 0。
340
- var todayLegacy = todayCell.e === null && todayCell.t > 0
341
- var todayEff = todayCell.e === null ? 0 : todayCell.e
361
+ // 口径翻转(task-muxnqunk):今日大数字显总量 t(含缓存读),有效 e 退次要小字/悬浮;
362
+ // e===null 的旧口径日不影响主显(t 恒有值),只在悬浮注明有效不可知;t=0 且无日账 = 今日尚无结算,照常显示 0。
342
363
  var days = lastNDays(7)
343
364
  var maxDay = 1
344
365
  var hasDayData = false
345
- var hasLegacy = false
346
- // maxDay 只按有效消耗归一:旧口径日(e===null)的 t 含缓存读(本板实测占 97%),参与归一会
347
- // 把其余日子的有效柱压成平地(task-muwsnyqv ③:10-05 t=154M 巨柱就是这么来的)。
348
- for (var di = 0; di < days.length; di++) { var dc = dayOf(byDay[days[di]]); if (dc.e !== null && dc.e > maxDay) maxDay = dc.e; if (dc.e === null && dc.t > 0) hasLegacy = true; if (dc.t > 0 || dc.e > 0) hasDayData = true }
366
+ // maxDay 按总量归一(主显口径)——task-muwsnyqv ③ 的「只按有效归一 + 旧口径灰柱」规则随
367
+ // 口径翻转退役:总量就是主口径,不再存在「缓存读撑歪有效柱」的问题。
368
+ for (var di = 0; di < days.length; di++) { var dc = dayOf(byDay[days[di]]); if (dc.t > maxDay) maxDay = dc.t; if (dc.t > 0 || dc.e > 0) hasDayData = true }
349
369
  function mini(label, value) { return React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, label + ' ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(value))) }
350
370
  // 累计三分量:有效(真实成本)+ 缓存读(占了 total 的大头,必须单列才看得出虚高来源)+ 合计
351
371
  var effTotal = (typeof u.effective === 'number') ? u.effective : ((u.input || 0) + (u.output || 0) + (u.cacheWrite || 0))
352
372
  return React.createElement('div', { style: box },
353
373
  head,
354
374
  React.createElement('div', { style: { display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap', marginBottom: 8 } },
355
- React.createElement('span', { style: { fontSize: 20, fontWeight: 700, color: todayLegacy ? C.text2 : C.brand }, title: todayLegacy ? ('今日有效消耗不可知:该日账为旧口径数据(仅总量 ' + fmtTokens(todayCell.t) + ' tok,含缓存读),总量不冒充有效值') : ('今日有效消耗(本地日 ' + todayKey + ')= 输入+输出+缓存写,不含缓存读;一次 run 的消耗整笔记在结算日') }, todayLegacy ? '—' : fmtTokens(todayEff)),
356
- React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, 'tokens(今日有效' + (todayLegacy ? ' · 旧口径不可知' : '') + ')'),
357
- todayCell.t > todayEff ? React.createElement('span', { style: { fontSize: 10, color: C.text2 }, title: '今日总量(含缓存读)——与有效消耗的差额就是缓存读' + (todayLegacy ? ';该日为旧口径数据,有效消耗不可知' : '') }, '含缓存读共 ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(todayCell.t))) : null,
375
+ React.createElement('span', { style: { fontSize: 20, fontWeight: 700, color: C.brand }, title: '今日总量(本地日 ' + todayKey + ')= 输入+输出+缓存写+缓存读(含缓存读);一次 run 的消耗整笔记在结算日;有效消耗(不含缓存读)' + (todayCell.e === null ? '不可知:该日账为旧口径数据,只有总量' : '= ' + String(todayCell.e) + ' tok') }, fmtTokens(todayCell.t)),
376
+ React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, 'tokens(今日总量,含缓存读)'),
377
+ (todayCell.e !== null && todayCell.t > todayCell.e) ? React.createElement('span', { style: { fontSize: 10, color: C.text2 }, title: '今日有效消耗 = 输入+输出+缓存写(不含缓存读);与总量的差额就是缓存读' }, '其中有效 ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(todayCell.e))) : null,
358
378
  React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, '累计(本看板) 有效 ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(effTotal)), ' · 缓存读 ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(u.cacheRead)), ' · 合计 ', React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, fmtTokens(u.total))),
359
379
  mini('输入', u.input), mini('输出', u.output), u.cacheWrite ? mini('缓存写', u.cacheWrite) : null),
360
380
  // 主窗口(本会话对话)单列行:累计行下方,与看板派发口径并列不混入(mw=null 静默不渲染)
361
381
  mwLine(),
362
382
  // 近 7 天迷你条形:高按区间 max 归一(今天高亮 brand,其余浅底 + 边框),
363
- // 柱高一律取**有效消耗**;e 不可知的旧口径日不拿总量冒充——画固定矮灰柱(tooltip 标明
364
- // 「旧口径数据(仅总量,含缓存读)」),有效柱高不被缓存读撑歪。7 天全为 0 时整块不渲染。
383
+ // 柱高一律取**总量**(含缓存读,口径翻转 task-muxnqunk);e===null 的旧口径日就是正常柱
384
+ // (t 恒有值),有效值在 tooltip 里对照(不可知则注明)。7 天全为 0 时整块不渲染。
365
385
  hasDayData ? React.createElement('div', { style: { marginBottom: 8 } },
366
- React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '近 7 天(有效消耗)', hasLegacy ? React.createElement('span', { style: { fontSize: 9, fontWeight: 400, marginLeft: 6 } }, '灰柱 = 旧口径数据(仅总量)') : null),
386
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '近 7 天(总量,含缓存读)'),
367
387
  React.createElement('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 4 } },
368
388
  days.map(function (k) {
369
389
  var c = dayOf(byDay[k])
370
- // 旧口径日(e===null 且有总量):不拿 t 冒充有效画柱(task-muwsnyqv ③)——
371
- // 画固定矮灰柱 + tooltip 标明;有效日柱高照常按有效消耗归一。
372
- var legacy = c.e === null && c.t > 0
373
- var v = legacy ? 0 : (c.e || 0)
390
+ var v = c.t || 0
374
391
  var isToday = k === todayKey
375
- var h = legacy ? 6 : (v > 0 ? Math.max(3, Math.round(v / maxDay * 32)) : 3)
376
- return React.createElement('div', { key: k, title: legacy ? (k.slice(5) + ':旧口径数据(仅总量 ' + String(c.t) + ' tok,含缓存读;有效消耗不可知,不拿总量画柱)') : (k.slice(5) + ':有效 ' + String(v) + ' tok' + (c.t > v ? ' / 含缓存读共 ' + String(c.t) + ' tok' : '')), style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 } },
377
- React.createElement('div', { style: { width: '100%', height: h, background: legacy ? C.border : (isToday ? C.brand : C.nested), border: '1px solid ' + (legacy ? C.border2 : (isToday ? C.brand : C.border)), borderRadius: 2 } }),
392
+ var h = v > 0 ? Math.max(3, Math.round(v / maxDay * 32)) : 3
393
+ return React.createElement('div', { key: k, title: k.slice(5) + ':总量 ' + String(v) + ' tok(含缓存读)' + (c.e !== null ? ' / 有效 ' + String(c.e) + ' tok' : ';有效不可知(旧口径日账)'), style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 } },
394
+ React.createElement('div', { style: { width: '100%', height: h, background: isToday ? C.brand : C.nested, border: '1px solid ' + (isToday ? C.brand : C.border), borderRadius: 2 } }),
378
395
  React.createElement('span', { style: { fontSize: 8, color: isToday ? C.brand : C.text2, whiteSpace: 'nowrap' } }, k.slice(5)))
379
396
  }))) : null,
380
397
  React.createElement('div', { style: { display: 'flex', gap: 12, flexWrap: 'wrap' } },
381
398
  React.createElement('div', { style: { flex: '1 1 240px', minWidth: 200 } },
382
- React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '按模型分布(有效消耗)'),
399
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '按模型分布(总量)'),
383
400
  models.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无数据') : models.map(function (m) {
384
- return React.createElement(UsageRow, { key: m.model, label: m.model, value: m.eff, max: maxM, color: C.brand, title: m.model + ':有效 ' + (m.approx ? '~' : '') + String(m.eff) + ' tokens(不含缓存读)' + (m.approx ? '——本模型无有效分量留账,以合计近似' : '') + ' · 含缓存读合计 ' + String(m.total) + ' tokens' })
401
+ return React.createElement(UsageRow, { key: m.model, label: m.model, value: m.total, max: maxM, color: C.brand, title: m.model + ':总量 ' + String(m.total) + ' tokens(含缓存读)' + (m.eff === null ? ';有效不可知(本模型无分量留账)' : ' · 有效 ' + String(m.eff) + ' tokens(输入+输出+缓存写)') })
385
402
  }),
386
- // 模型分布尾部追加「主窗口(对话)」一条(task-muwsol23):有效为 0 不渲染;
403
+ // 模型分布尾部追加「主窗口(对话)」一条(task-muwsol23):总量为 0 不渲染;
387
404
  // 琥珀色与 run 模型(brand)区分,固定尾部不参与排序,与派发口径并列不混入
388
- (mw && mw.effective > 0) ? React.createElement(UsageRow, { key: 'main-window', label: '主窗口(对话)', value: mw.effective, max: maxM, color: C.warn, title: '主窗口(对话):有效 ' + String(mw.effective) + ' tokens(不含缓存读)· 缓存读 ' + String(mw.cacheRead) + ' tokens——本会话对话消耗,与看板派发口径并列不混入(不进累计 / Top 8)' }) : null),
405
+ (mw && mw.total > 0) ? React.createElement(UsageRow, { key: 'main-window', label: '主窗口(对话)', value: mw.total, max: maxM, color: C.warn, title: '主窗口(对话):总量 ' + String(mw.total) + ' tokens(含缓存读)· 有效 ' + String(mw.effective) + ' tokens(不含缓存读)——本会话对话消耗,与看板派发口径并列不混入(不进累计 / Top 8)' }) : null),
389
406
  React.createElement('div', { style: { flex: '1 1 240px', minWidth: 200 } },
390
- React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '任务消耗 Top 8(有效消耗)'),
407
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '任务消耗 Top 8(总量)'),
391
408
  top.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无数据') : top.map(function (x) {
392
409
  var t = getTask(x.id)
393
- return React.createElement(UsageRow, { key: x.id, label: x.title || x.id, value: x.eff, max: maxT, color: C.ok, labelColor: t ? C.brand : C.text2, title: x.title + '(有效 ' + (x.approx ? '~' : '') + String(x.eff) + ' tokens(不含缓存读) · 含缓存读合计 ' + String(x.total) + ' · 其中缓存读 ' + String(x.cacheRead || 0) + ' · ' + (x.runs || 0) + ' 次 run)' + (x.approx ? '(老卡无五分量留账,有效值以合计近似)' : '') + (t ? '——点击查看详情' : ''), onClick: t ? function () { state.detailId = x.id; notify() } : undefined })
410
+ return React.createElement(UsageRow, { key: x.id, label: x.title || x.id, value: x.total, max: maxT, color: C.ok, labelColor: t ? C.brand : C.text2, title: x.title + '(总量 ' + String(x.total) + ' tokens(含缓存读) · 其中缓存读 ' + String(x.cacheRead || 0) + (x.eff === null ? ';有效不可知(老卡无五分量留账)' : ' · 有效 ' + String(x.eff) + ' tokens(输入+输出+缓存写)') + ' · ' + (x.runs || 0) + ' 次 run)' + (t ? '——点击查看详情' : ''), onClick: t ? function () { state.detailId = x.id; notify() } : undefined })
394
411
  }))),
395
412
  // 口径边界:累计 / Top 8 只统计看板派发的 Worker/Verifier run;主窗口对话消耗自 task-muwsol23 起
396
413
  // 单列一行 + 模型分布尾部一条并列展示(不越界混入累计/Top8/归属摊派),caption 必须讲清并列关系。
397
414
  // 范围说明(task-muwc7hjd):用户常把 RangeFilter 当成「整页过滤」,但今日大数字与近 7 天柱子
398
415
  // 是自身固定口径(今日=本地今天、近 7 天=最近 7 个本地日)——不随范围变,必须在文案里讲清,
399
416
  // 否则「选了范围数字没变」看起来像 bug。模型分布/Top8/累计才是范围生效的三处。
400
- React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 6, lineHeight: 1.5 } }, '口径:累计与 Top 8 仅看板派发的 Worker/Verifier run 消耗;主窗口行=本会话对话消耗,与看板派发口径并列不混入(单列一行 + 模型分布尾部一条,不进累计 / Top 8);主数字(模型分布 / Top 8)与「今日」「近 7 天」均为有效消耗口径(输入+输出+缓存写,不含缓存读),含缓存读的合计在悬浮 title 里单列对照'),
401
- React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2, lineHeight: 1.5 } }, '统计范围作用于按模型分布 / 任务消耗 Top 8 / 累计三分量(按 run 的本地日落点过滤);今日与「近 7 天」为固定口径,不随范围变化。'))
417
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 6, lineHeight: 1.5 } }, '口径:累计与 Top 8 仅看板派发的 Worker/Verifier run 消耗;主窗口行=本会话对话消耗,与看板派发口径并列不混入(单列一行 + 模型分布尾部一条,不进累计 / Top 8);主数字(今日 / 近 7 天 / 模型分布 / Top 8 / 主窗口行)均为总量口径(含缓存读);有效消耗(input+output+cacheWrite,不含缓存读)见悬浮 title'),
418
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2, lineHeight: 1.5 } }, '统计范围作用于按模型分布 / 任务消耗 Top 8 / 累计三分量(按 run 的本地日落点过滤);今日与「近 7 天」为固定口径,不随范围变化。'),
419
+ // 「→ 模型表现」滚动锚链接(task-muxhtgi2 卡3):一键滚到下方模型×场景七指标表
420
+ // (与详情页 goArbitration 同一 scrollIntoView 原语;目标区空态也挂锚 id,链接始终可达)
421
+ React.createElement('div', { style: { fontSize: 9, marginTop: 4 } },
422
+ React.createElement('span', { onClick: function () { var el = document.getElementById('tskb-model-perf'); if (el && el.scrollIntoView) el.scrollIntoView({ block: 'start' }) }, style: { color: C.brand, cursor: 'pointer' }, title: '滚动到下方「模型表现」区:模型 × 场景(角色 × 规模段)七指标对比表' }, '→ 模型表现')))
423
+ }
424
+
425
+ // ===== 模型表现区(记分卡卡3,task-muxhtgi2):七指标表格 × 场景筛选器(本地过滤不重拉)=====
426
+ // 数据源:state.usageSummary.scoreboard——卡2 host 聚合底座(buildScoreboard)挂进 get-tasks 响应,
427
+ // 与 Token 区同通道透传、同一 range 口径(范围切换重拉后随 notify 同步刷新,本区零自持请求)。
428
+ // 每桶 = 模型 × 角色(worker/verifier)× 规模段(small/medium/large,按任务有效 token 分桶)。
429
+ // 本区三件事全部本地现算:
430
+ // ① 场景筛选器:角色(全部/worker/verifier)+ 规模段(全部/小/中/大)——scoreboard 已在响应里,
431
+ // 筛选只是 buckets 数组 pass,绝不重拉(组件内无 rpc/fetchTasks);
432
+ // ② 七指标表格:每 模型×场景桶 一行,七列——有效均值/耗时中位/驳回率/一次通过率/超时率/续跑率/
433
+ // 缓存命中率;表头列名带 title 口径说明(与卡2 host 注释同源);
434
+ // ③ 样本护栏:insufficient(runs<5)行整行灰显 + title 前缀「样本 <5,仅供参考」——
435
+ // 仅供参考,不参与任何「最优」视觉强调(本区 v1 不做任何高亮排名,从源头规避)。
436
+ // 空态:scoreboard 缺字段 / buckets 空 → 「暂无足够 run 数据」(锚 id 照挂,Token 区链接始终可达);
437
+ // 筛选后空 → 「当前筛选无匹配桶」。
438
+ // 锚点:区根 id='tskb-model-perf',Token 区 caption 的「→ 模型表现」链接 scrollIntoView 过来。
439
+ var MP_SIZE_LABEL = { small: '小', medium: '中', large: '大' }
440
+ // 率类/耗时格式化:null = 无样本(不假装 0,与 host 同哲学)
441
+ function mpPct(r) { return (r === null || r === undefined || !isFinite(r)) ? '—' : Math.round(r * 100) + '%' }
442
+ function mpDur(ms) { if (ms === null || ms === undefined || !isFinite(ms)) return '—'; var m = Math.round(ms / 60000); if (m < 1) return '<1m'; if (m < 60) return m + 'm'; return (m / 60).toFixed(1) + 'h' }
443
+ // 七列定义:label + title 口径说明 + 取值格式化(口径一句话版与 README 仪表盘段同步维护)
444
+ var MP_COLS = [
445
+ { k: 'effAvg', label: '有效均值', title: '有效 token 均值 = 桶内 run 有效消耗(输入+输出+缓存写,不含缓存读)的均值;无 usage 留账的 run 不进均值(不伪造 0)', fmt: function (b) { return b.effAvg === null ? '—' : fmtTokens(b.effAvg) } },
446
+ { k: 'durMedianMs', label: '耗时中位', title: 'run 耗时(endedAt - at)中位数;P90 见行 title;无合法时刻的 run 不进分布', fmt: function (b) { return mpDur(b.durMedianMs) } },
447
+ { k: 'rejectRate', label: '驳回率', title: '驳回率 = 归因到本桶 run 的验收驳回次数 / 桶内 runs(归因 = 驳回前最近 worker run)', fmt: function (b) { return mpPct(b.rejectRate) } },
448
+ { k: 'firstPassRate', label: '一次通过率', title: '一次通过率 = 桶内 resolved 任务中 rejectCount=0(零驳回)的占比;桶内无 resolved 任务 → —', fmt: function (b) { return mpPct(b.firstPassRate) } },
449
+ { k: 'timeoutRate', label: '超时率', title: '超时率 = outcome 超时落定(timeout/error)的 run / 桶内 runs', fmt: function (b) { return mpPct(b.timeoutRate) } },
450
+ { k: 'resumeRate', label: '续跑率', title: '续跑率 = resume:true 续跑记录 / 桶内 runs', fmt: function (b) { return mpPct(b.resumeRate) } },
451
+ { k: 'cacheHitRate', label: '缓存命中率', title: '缓存命中率 = cacheRead / (input + cacheRead);⚠️ 跨 provider 口径可能不一(input 是否含缓存读各家不同),对比前先核口径;无 usage 样本 → —', fmt: function (b) { return mpPct(b.cacheHitRate) } }
452
+ ]
453
+ function ModelPerf(props) {
454
+ var _R = React; var useState = _R.useState
455
+ var _a = useState(state.mpRole || ''), roleF = _a[0], setRoleF = _a[1]
456
+ var _b = useState(state.mpSize || ''), sizeF = _b[0], setSizeF = _b[1]
457
+ function pickRole(v) { state.mpRole = v; setRoleF(v) }
458
+ function pickSize(v) { state.mpSize = v; setSizeF(v) }
459
+ var box = { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 }
460
+ // 范围激活标记与 Token 区同源:scoreboard 由 host 按同一 range 裁剪,范围内数据必须亮明身份
461
+ var tg = activeRange()
462
+ var tgOn = !!(tg.from || tg.to)
463
+ var head = React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('bot', 11), '模型表现',
464
+ tgOn ? React.createElement('span', { style: { fontSize: 9, fontWeight: 400, color: C.brand, border: '1px solid ' + C.brand, borderRadius: 8, padding: '0 6px' }, title: '本区桶数据已按统计范围过滤(与 Token 区同一 range 口径):' + rangeLabel() }, '范围内: ' + rangeLabel()) : null)
465
+ var sb = props.sb
466
+ var buckets = (sb && Array.isArray(sb.buckets)) ? sb.buckets : []
467
+ // 空态:无 scoreboard(老 host)/ 无入桶 run → 一句话空态(锚 id 照挂,Token 区链接始终可达)
468
+ if (!buckets.length) return React.createElement('div', { id: 'tskb-model-perf', style: box }, head, React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无足够 run 数据(还没有可入桶的已落定 Worker/Verifier run 留账)'))
469
+ // 本地筛选:角色 × 规模段,纯数组 pass 不重拉
470
+ var shown = buckets.filter(function (b) { return (!roleF || b.role === roleF) && (!sizeF || b.size === sizeF) })
471
+ function chip(cur, setter, v, label) {
472
+ var on = cur === v
473
+ return React.createElement('button', { onClick: function () { setter(v) }, style: { fontSize: 10, padding: '1px 8px', border: '1px solid ' + (on ? C.brand : C.border2), borderRadius: 10, background: on ? C.nested : C.card, color: on ? C.brand : C.text2, cursor: 'pointer' } }, label)
474
+ }
475
+ var thL = { fontSize: 9, color: C.text2, fontWeight: 600, textAlign: 'left', padding: '2px 4px', borderBottom: '1px solid ' + C.border, whiteSpace: 'nowrap' }
476
+ var thR = { fontSize: 9, color: C.text2, fontWeight: 600, textAlign: 'right', padding: '2px 4px', borderBottom: '1px solid ' + C.border, whiteSpace: 'nowrap', cursor: 'help' }
477
+ var tdL = { fontSize: 10, color: C.text, textAlign: 'left', padding: '3px 4px', borderBottom: '1px solid ' + C.nested, whiteSpace: 'nowrap' }
478
+ var tdR = { fontSize: 10, color: C.text, textAlign: 'right', padding: '3px 4px', borderBottom: '1px solid ' + C.nested, whiteSpace: 'nowrap' }
479
+ var insufN = shown.filter(function (b) { return b.insufficient }).length
480
+ return React.createElement('div', { id: 'tskb-model-perf', style: box },
481
+ head,
482
+ // 场景筛选器(本地):角色 + 规模段两组 chip;右侧计数「显示/总数 · 样本不足 N」
483
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap', marginBottom: 6 } },
484
+ React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, '角色'),
485
+ chip(roleF, pickRole, '', '全部'), chip(roleF, pickRole, 'worker', 'worker'), chip(roleF, pickRole, 'verifier', 'verifier'),
486
+ React.createElement('span', { style: { fontSize: 10, color: C.text2, marginLeft: 6 } }, '规模段'),
487
+ chip(sizeF, pickSize, '', '全部'), chip(sizeF, pickSize, 'small', '小'), chip(sizeF, pickSize, 'medium', '中'), chip(sizeF, pickSize, 'large', '大'),
488
+ React.createElement('span', { style: { fontSize: 9, color: C.text2, marginLeft: 'auto' } }, shown.length + '/' + buckets.length + ' 桶' + (insufN ? ' · ' + insufN + ' 桶样本不足' : ''))),
489
+ shown.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2, padding: '4px 0' } }, '当前筛选无匹配桶(试试放宽角色/规模段)')
490
+ : React.createElement('div', { style: { overflowX: 'auto' } },
491
+ React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse' } },
492
+ React.createElement('thead', null, React.createElement('tr', null,
493
+ React.createElement('th', { style: thL }, '模型'),
494
+ React.createElement('th', { style: thL }, '场景'),
495
+ React.createElement('th', { style: thR, title: '桶内已落定 run 数;<5 为样本不足,整行灰显仅供参考' }, '样本'),
496
+ MP_COLS.map(function (c) { return React.createElement('th', { key: c.k, style: thR, title: c.title }, c.label) }))),
497
+ React.createElement('tbody', null, shown.map(function (b) {
498
+ // 样本护栏:insufficient 行整行灰显 + title 前缀「样本 <5,仅供参考」,不进任何最优强调
499
+ var tip = (b.insufficient ? '样本 <5,仅供参考——' : '') + b.model + ' × ' + b.role + ' × ' + (MP_SIZE_LABEL[b.size] || b.size) + ':runs=' + b.runs + ';有效总额 ' + fmtTokens(b.effSum) + ' tok;耗时 P90 ' + mpDur(b.durP90Ms) + '(七指标口径见表头 title)'
500
+ return React.createElement('tr', { key: b.model + '|' + b.role + '|' + b.size, title: tip, style: { opacity: b.insufficient ? 0.45 : 1 } },
501
+ React.createElement('td', { style: tdL }, b.model),
502
+ React.createElement('td', { style: tdL }, b.role + '·' + (MP_SIZE_LABEL[b.size] || b.size)),
503
+ React.createElement('td', { style: tdR }, String(b.runs)),
504
+ MP_COLS.map(function (c) { return React.createElement('td', { key: c.k, style: tdR }, c.fmt(b)) }))
505
+ })))),
506
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 6, lineHeight: 1.5 } }, '口径:每行 = 模型 × 角色 × 规模段桶(规模按任务有效 token:小 <1M / 中 1~10M / 大 >10M);率类分母为桶内 runs,一次通过率分母为桶内 resolved 任务;runs<5 行灰显 = 样本不足仅供参考;随统计范围裁剪(与 Token 区同一 range)'))
507
+ }
508
+
509
+ // ===== 质量趋势区(记分卡卡4,task-muxhtgil):一次通过率趋势 / 卡时长分布 / 超时率走势 / 续跑成功率 =====
510
+ // 数据源:state.usageSummary.scoreboard.trends——卡2 host 聚合底座(assembleScoreboard)的全局 rollup,
511
+ // 与模型表现区同通道(get-tasks 响应透传)、同一 range 口径(host 已按 run/落定日裁剪,client 只渲染,
512
+ // 组件体内零 rpc/fetchTasks 不重拉)。
513
+ // 四块(P1 质量 KPI 并入的全局视图):
514
+ // ① 一次通过率趋势:近 14 天柱形(firstPassByDay:resolved 任务按落定日,当日零驳回占比),今日柱高亮 + 块头标今日值;
515
+ // ② 卡时长分布:四桶横条(durationBuckets:<10m / 10-30m / 30-60m / >60m,创建→落定墙钟,60m 端点归 30-60m 桶);
516
+ // ③ 超时率走势:近 14 天迷你柱行(timeoutByDay:当日超时落定 run / 当日 runs);
517
+ // ④ 续跑成功率:resume 续跑 run 的 completed 占比(卡2出参为范围总量口径、无 byDay——迷你行显率 + 成功/次数,不伪造逐日)。
518
+ // 空态灰显:trends 缺字段(老 host)/ 四块皆无数据 → 整区一句灰字(锚 id 照挂);单块无数据 → 块内灰字「暂无…」。
519
+ // 范围联动:trends 各字段已被 host 按统计范围裁剪,区头带「范围内: …」徽章(与 Token / 模型表现区同源 activeRange);
520
+ // 近 14 天为固定窗口(设计口径:标今日值),范围落在窗口外时对应日柱为空,caption 写明。
521
+ var QT_DUR_BUCKETS = [
522
+ { k: 'lt10m', label: '<10m', tip: '卡时长 <10 分钟' },
523
+ { k: 'm10to30', label: '10-30m', tip: '卡时长 10~30 分钟' },
524
+ { k: 'm30to60', label: '30-60m', tip: '卡时长 30~60 分钟(含 60m 端点)' },
525
+ { k: 'gt60m', label: '>60m', tip: '卡时长 >60 分钟' }
526
+ ]
527
+ // 近 14 天柱形通用画法(① 通过率 / ③ 超时率共用):cells = [{ k, rate(null=当日无样本), tip }]
528
+ // 今日柱 brand 高亮 + 率值顶标;无样本日画 3px 灰基线(title 写明无样本,不假装 0%)。
529
+ function qtDayBars(cells, color, todayKey) {
530
+ return React.createElement('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 3, height: 46 } },
531
+ cells.map(function (c) {
532
+ var isToday = c.k === todayKey
533
+ var h = c.rate === null ? 3 : Math.max(4, Math.round(c.rate * 34))
534
+ var kp = c.k.split('-')
535
+ var dLabel = Number(kp[1]) + '/' + Number(kp[2])
536
+ return React.createElement('div', { key: c.k, style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', gap: 1 }, title: c.tip },
537
+ c.rate !== null ? React.createElement('span', { style: { fontSize: 8, color: isToday ? C.brand : C.text2 } }, mpPct(c.rate)) : null,
538
+ React.createElement('div', { style: { width: '100%', maxWidth: 22, height: h + 'px', borderRadius: 2, background: c.rate === null ? C.nested : (isToday ? C.brand : color), border: '1px solid ' + (isToday ? C.brand : (c.rate === null ? C.border : color)) } }),
539
+ React.createElement('span', { style: { fontSize: 8, color: isToday ? C.brand : C.text2 } }, dLabel))
540
+ }))
541
+ }
542
+ function QualityTrends(props) {
543
+ var sb = props.sb
544
+ var tr = (sb && sb.trends && typeof sb.trends === 'object') ? sb.trends : null
545
+ var box = { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 }
546
+ // 范围激活标记与 Token / 模型表现区同源:trends 由 host 按同一 range 裁剪,范围内数据必须亮明身份
547
+ var tg = activeRange()
548
+ var tgOn = !!(tg.from || tg.to)
549
+ var head = React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('check-circle', 11), '质量趋势',
550
+ tgOn ? React.createElement('span', { style: { fontSize: 9, fontWeight: 400, color: C.brand, border: '1px solid ' + C.brand, borderRadius: 8, padding: '0 6px' }, title: '本区趋势数据已按统计范围过滤(与 Token 区同一 range 口径):' + rangeLabel() }, '范围内: ' + rangeLabel()) : null)
551
+ var fpByDay = (tr && tr.firstPassByDay && typeof tr.firstPassByDay === 'object') ? tr.firstPassByDay : {}
552
+ var durB = (tr && tr.durationBuckets && typeof tr.durationBuckets === 'object') ? tr.durationBuckets : null
553
+ var toByDay = (tr && tr.timeoutByDay && typeof tr.timeoutByDay === 'object') ? tr.timeoutByDay : {}
554
+ var rsm = (tr && tr.resume && typeof tr.resume === 'object') ? tr.resume : null
555
+ // 整区空态判定:四块皆无数据(率类按日单元求和 / 时长按四桶求和 / 续跑按 runs)
556
+ var fpSum = 0; for (var fk in fpByDay) { if (Object.prototype.hasOwnProperty.call(fpByDay, fk)) fpSum += (fpByDay[fk] && fpByDay[fk].resolved) || 0 }
557
+ var toSum = 0; for (var tk2 in toByDay) { if (Object.prototype.hasOwnProperty.call(toByDay, tk2)) toSum += (toByDay[tk2] && toByDay[tk2].runs) || 0 }
558
+ var durSum = 0
559
+ if (durB) QT_DUR_BUCKETS.forEach(function (b) { durSum += Number(durB[b.k]) || 0 })
560
+ var rsmRuns = (rsm && rsm.runs) || 0
561
+ if (!tr || (fpSum + toSum + durSum + rsmRuns) === 0) {
562
+ return React.createElement('div', { id: 'tskb-quality-trends', style: box }, head,
563
+ React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无足够 run 数据(还没有可统计的已落定 Worker/Verifier run / resolved 任务)'))
564
+ }
565
+ var days14 = lastNDays(14)
566
+ var todayKey = localDayKey()
567
+ // 块头:左块名 + 右侧今日/汇总值(fontWeight 400 小字)
568
+ function blockHead(label, rightEl) {
569
+ return React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4, display: 'flex', alignItems: 'baseline', gap: 6 } }, label,
570
+ rightEl ? React.createElement('span', { style: { marginLeft: 'auto', fontWeight: 400, fontSize: 9 } }, rightEl) : null)
571
+ }
572
+ // ===== ① 一次通过率趋势(近 14 天):当日率 = 落定 resolved 任务中零驳回(rejectCount=0)占比 =====
573
+ var fpCells = days14.map(function (k) {
574
+ var c = fpByDay[k]
575
+ var resolved = (c && c.resolved) || 0
576
+ var firstPass = (c && c.firstPass) || 0
577
+ return { k: k, rate: resolved ? firstPass / resolved : null, tip: k + ':' + (resolved ? ('一次通过率 ' + mpPct(firstPass / resolved) + '(零驳回 ' + firstPass + ' / 落定 ' + resolved + ')') : '无 resolved 任务落定') }
578
+ })
579
+ var fpHas = false; fpCells.forEach(function (c) { if (c.rate !== null) fpHas = true })
580
+ var fpToday = fpCells[fpCells.length - 1]
581
+ var block1 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
582
+ blockHead('一次通过率(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: fpToday.rate !== null ? C.brand : C.text2, fontWeight: 600 } }, fpToday.rate !== null ? mpPct(fpToday.rate) : '—'))),
583
+ fpHas ? qtDayBars(fpCells, C.ok, todayKey)
584
+ : React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 resolved 任务落定'))
585
+ // ===== ② 卡时长分布(四桶横条):创建 → 落定(verifiedAt||resolvedAt)墙钟,含排队/执行/验收全程 =====
586
+ var durMax = 1
587
+ if (durB) QT_DUR_BUCKETS.forEach(function (b) { var v = Number(durB[b.k]) || 0; if (v > durMax) durMax = v })
588
+ var block2 = React.createElement('div', { style: { flex: '1 1 180px', minWidth: 170 } },
589
+ blockHead('卡时长分布', durSum > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String(durSum)), ' 张落定卡') : null),
590
+ durSum === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无时长样本')
591
+ : React.createElement('div', null, QT_DUR_BUCKETS.map(function (b) {
592
+ var v = Number(durB[b.k]) || 0
593
+ var pct = v > 0 ? Math.max(2, Math.round(v / durMax * 100)) : 0
594
+ return React.createElement('div', { key: b.k, style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }, title: b.tip + ':' + v + ' 张(创建→落定墙钟,含排队 / 执行 / 验收全程)' },
595
+ React.createElement('span', { style: { width: 44, fontSize: 10, color: C.text2, textAlign: 'right', flexShrink: 0 } }, b.label),
596
+ React.createElement('div', { style: { flex: 1, height: 8, background: C.nested, borderRadius: 4, overflow: 'hidden' } },
597
+ React.createElement('div', { style: { height: '100%', width: pct + '%', background: C.brand, borderRadius: 4, transition: 'width .3s' } })),
598
+ React.createElement('span', { style: { width: 24, fontSize: 10, color: C.text2, flexShrink: 0 } }, String(v)))
599
+ })))
600
+ // ===== ③ 超时率走势(近 14 天迷你柱行):当日率 = 超时落定(timeout/error)run / 当日 runs =====
601
+ var toCells = days14.map(function (k) {
602
+ var c = toByDay[k]
603
+ var runs = (c && c.runs) || 0
604
+ var timeouts = (c && c.timeout) || 0
605
+ return { k: k, rate: runs ? timeouts / runs : null, tip: k + ':' + (runs ? ('超时率 ' + mpPct(timeouts / runs) + '(超时 ' + timeouts + ' / run ' + runs + ')') : '无 run 落定') }
606
+ })
607
+ var toHas = false; toCells.forEach(function (c) { if (c.rate !== null) toHas = true })
608
+ var toToday = toCells[toCells.length - 1]
609
+ var block3 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
610
+ blockHead('超时率走势(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: toToday.rate !== null ? C.warn : C.text2, fontWeight: 600 } }, toToday.rate !== null ? mpPct(toToday.rate) : '—'))),
611
+ toHas ? qtDayBars(toCells, C.warn, todayKey)
612
+ : React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 run 落定'))
613
+ // ===== ④ 续跑成功率(范围总量口径,卡2出参无 byDay):resume:true 续跑记录中 completed 占比 =====
614
+ var rsmRate = (rsm && typeof rsm.rate === 'number' && isFinite(rsm.rate)) ? rsm.rate : null
615
+ var block4 = React.createElement('div', { style: { flex: '1 1 180px', minWidth: 170 } },
616
+ blockHead('续跑成功率', rsmRuns > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String((rsm && rsm.completed) || 0) + '/' + rsmRuns), ' 续跑成功') : null),
617
+ rsmRuns === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无续跑记录')
618
+ : React.createElement('div', { title: '续跑成功率 = resume:true 续跑记录中 outcome=completed 的占比(范围总量口径,host 出参无逐日);续跑 = 前次 run 超时/失败后续跑' },
619
+ React.createElement('div', { style: { fontSize: 16, fontWeight: 700, color: rsmRate !== null ? C.ok : C.text2 } }, rsmRate !== null ? mpPct(rsmRate) : '—'),
620
+ React.createElement('div', { style: { height: 6, background: C.nested, borderRadius: 3, overflow: 'hidden', marginTop: 4 } },
621
+ React.createElement('div', { style: { height: '100%', width: (rsmRate !== null ? Math.round(rsmRate * 100) : 0) + '%', background: C.ok, borderRadius: 3, transition: 'width .3s' } }))))
622
+ return React.createElement('div', { id: 'tskb-quality-trends', style: box },
623
+ head,
624
+ React.createElement('div', { style: { display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 8 } }, block1, block2),
625
+ React.createElement('div', { style: { display: 'flex', gap: 16, flexWrap: 'wrap' } }, block3, block4),
626
+ // 口径 caption:一句讲清统计本体(看板派发 run / 任务落定)+ 驳回定义 + 固定窗口与范围的关系
627
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 8, lineHeight: 1.5 } }, '口径:仅看板派发的 Worker/Verifier run 与任务落定记录(按本地日落点,随统计范围裁剪);一次通过率 = 当日落定 resolved 任务中零驳回占比(驳回 = 验收 verdict rejected,归因驳回前最近 worker run);超时 = run 落定 timeout/error;续跑成功率 = resume 续跑记录中 completed 占比(范围总量口径,无逐日);卡时长 = 创建 → 落定墙钟(含排队 / 验收)。通过率与超时率为近 14 天固定窗口——范围落在窗口外时对应日柱为空'))
628
+ }
629
+
630
+ // ===== 驳回聚类区(记分卡卡5,task-muxhu1zv):驳回原因 Top3 +「建议沉淀为约定」文本(一键复制)=====
631
+ // 数据源:state.usageSummary.scoreboard.rejectionClusters——host 规则法聚类(关键词模板,首命中归类;
632
+ // 按驳回消息自身日落点随统计范围裁剪),与卡3/卡4 同通道透传,本区只渲染零 rpc(不重拉)。
633
+ // 每行 = TopN 类目 + 占比(count/total)+ 结构化建议文本 + 「复制」按钮(建议文本进剪贴板,
634
+ // 沉淀动作人工——v1 只输出文本,不代调 notes 插件);代表原文(截断 80 字 ×≤3 条)放行 title 悬浮。
635
+ // 缺省兼容:老 host 无 rejectionClusters 字段 → 返回 null 整块不渲染(与 HealthHints 同哲学);
636
+ // 有字段但 total=0 → 区头 + 一句空态「暂无驳回记录」。
637
+ function RejectionClusters(props) {
638
+ var _R = React; var useState = _R.useState
639
+ var _c = useState(''), copiedId = _c[0], setCopiedId = _c[1]
640
+ var sb = props.sb
641
+ var rc = sb && sb.rejectionClusters
642
+ if (!rc || typeof rc !== 'object') return null // 老 host 缺字段:整块不渲染(零残留)
643
+ var box = { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 }
644
+ var tg = activeRange()
645
+ var tgOn = !!(tg.from || tg.to)
646
+ var head = React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('flask-conical', 11), '驳回聚类',
647
+ tgOn ? React.createElement('span', { style: { fontSize: 9, fontWeight: 400, color: C.brand, border: '1px solid ' + C.brand, borderRadius: 8, padding: '0 6px' }, title: '本区已按统计范围过滤(按驳回消息本地日落点,与 Token 区同一 range):' + rangeLabel() }, '范围内: ' + rangeLabel()) : null)
648
+ var top = Array.isArray(rc.top) ? rc.top : []
649
+ if (!rc.total || !top.length) {
650
+ return React.createElement('div', { id: 'tskb-rejection-clusters', style: box }, head,
651
+ React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无驳回记录(验收驳回后会按关键词模板聚类出 Top3 类目与约定建议)'))
652
+ }
653
+ // 复制建议文本:clipboard API 不可用/被拒时静默降级(按钮不变样,下轮再试)
654
+ function copySugg(c) {
655
+ var text = String(c.suggestion || '')
656
+ if (!text) return
657
+ try {
658
+ navigator.clipboard.writeText(text).then(function () {
659
+ setCopiedId(c.id); setTimeout(function () { setCopiedId('') }, 2000)
660
+ }, function () {})
661
+ } catch (_) {}
662
+ }
663
+ return React.createElement('div', { id: 'tskb-rejection-clusters', style: box },
664
+ head,
665
+ top.map(function (c, i) {
666
+ var tip = (c.samples && c.samples.length) ? '代表原文:\n' + c.samples.map(function (s) { return '· ' + s }).join('\n') : '(无代表原文)'
667
+ return React.createElement('div', { key: c.id, title: tip, style: { marginBottom: 6, paddingBottom: 6, borderBottom: i < top.length - 1 ? '1px solid ' + C.nested : 'none' } },
668
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 11 } },
669
+ React.createElement('span', { style: { color: C.warn, fontWeight: 700 } }, 'Top' + (i + 1)),
670
+ React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, c.label),
671
+ React.createElement('span', { style: { color: C.text2, fontSize: 10 } }, c.pct + '%(' + c.count + '/' + rc.total + ')'),
672
+ React.createElement('button', { onClick: function () { copySugg(c) }, title: '复制「建议沉淀为约定」文本(粘贴到派发约定/Worker prompt,沉淀动作人工)', style: { marginLeft: 'auto', fontSize: 9, padding: '1px 8px', border: '1px solid ' + C.border2, borderRadius: 9, background: C.card, color: copiedId === c.id ? C.ok : C.text2, cursor: 'pointer' } }, copiedId === c.id ? '✅ 已复制' : '📋 复制约定建议')),
673
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, lineHeight: 1.5, marginTop: 2 } }, String(c.suggestion || '')))
674
+ }),
675
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 4, lineHeight: 1.5 } }, '口径:规则法关键词模板聚类(首命中归类,不上 embedding);占比分母 = ' + (tgOn ? '范围内' : '') + '驳回总数(' + rc.total + ' 条)' + (rc.restCount ? ';另有 ' + rc.restCount + ' 类未进 Top3' : '') + ';建议文本一键复制后人工沉淀为约定'))
402
676
  }
403
677
 
404
678
  // ===== 架构健康提示区(架构自省 L1 · 数据源:state.healthHints)=====
@@ -430,6 +704,12 @@
430
704
  React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 12, flexWrap: 'wrap' } }, React.createElement(StatCard, { label: '总任务', value: stats.total, color: C.text }), React.createElement(StatCard, { label: '待办', value: stats.byStatus['pending'] || 0, color: C.text2 }), React.createElement(StatCard, { label: '进行中', value: stats.byStatus['in-progress'] || 0, color: C.brand }), React.createElement(StatCard, { label: '验证中', value: stats.byStatus['verifying'] || 0, color: C.warn }), React.createElement(StatCard, { label: '已完成', value: stats.byStatus['resolved'] || 0, color: C.ok }), React.createElement(StatCard, { label: '已归档', value: stats.byStatus['archived'] || 0, color: C.text2 })),
431
705
  React.createElement(HealthHints),
432
706
  React.createElement(TokenUsage, { usage: state.usageSummary }),
707
+ // 模型表现区(卡3):Token 区正下方;scoreboard 随 usageSummary 同通道透传(老 host 缺字段 → null → 空态)
708
+ React.createElement(ModelPerf, { sb: (state.usageSummary && state.usageSummary.scoreboard) || null }),
709
+ // 质量趋势区(卡4):模型表现区正下方;同一 scoreboard 出参的 trends 全局 rollup(老 host 缺字段 → null → 空态灰显)
710
+ React.createElement(QualityTrends, { sb: (state.usageSummary && state.usageSummary.scoreboard) || null }),
711
+ // 驳回聚类区(卡5):质量趋势区正下方;scoreboard.rejectionClusters(老 host 缺字段 → 组件返回 null 整块不渲染)
712
+ React.createElement(RejectionClusters, { sb: (state.usageSummary && state.usageSummary.scoreboard) || null }),
433
713
  React.createElement('div', { style: { display: 'flex', gap: 12, marginBottom: 12, flexWrap: 'wrap' } },
434
714
  React.createElement('div', { style: { flex: '1 1 0', minWidth: 200, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '按状态分布'), statusOrder.map(function (s) { return React.createElement(BarRow, { key: s, label: statusLabels[s] || s, count: stats.byStatus[s] || 0, total: stats.total, color: statusColors[s] || C.brand }) })),
435
715
  React.createElement('div', { style: { flex: '1 1 0', minWidth: 200, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '按优先级分布'), prioOrder.map(function (p) { return React.createElement(BarRow, { key: p, label: prioLabel[p] || p, count: stats.byPriority[p] || 0, total: stats.total, color: prioColor[p] || C.brand }) }))),
@@ -587,6 +867,13 @@
587
867
  React.createElement('input', { type: 'checkbox', checked: props.notifyDone !== false, onChange: function (e) { setCfg('notifyDone', e.target.checked) } }),
588
868
  React.createElement('span', null, '✅ 完成回执(任务完成或阻塞时聚合播报)')),
589
869
  React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 3, whiteSpace: 'normal', maxWidth: 260 } }, '歧义裁决通知不受这两个开关影响(任务等人裁决必须提醒)'),
870
+ // 自测指南开关(verifyUserGuide,缺省 true,「通知」小节旁的「验收」小节):关掉后
871
+ // ① Verifier prompt 不再要求「## 自测指南」段(省 token);② 验收落账不挂 userTest;
872
+ // ③ 详情页自测指南块与报告「本版自测清单」段整块不渲染。老看板缺字段=开。
873
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, margin: '7px 0 5px' } }, '验收'),
874
+ React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 5, fontSize: 10, color: C.text, cursor: 'pointer', whiteSpace: 'normal', maxWidth: 260 } },
875
+ React.createElement('input', { type: 'checkbox', checked: props.verifyUserGuide !== false, onChange: function (e) { setCfg('verifyUserGuide', e.target.checked) } }),
876
+ React.createElement('span', null, '📋 自测指南(验收结论附用户自测步骤,详情页与报告展示)')),
590
877
  // 史诗拆分总开关(板级 epicSplit,缺省 true):**只关引导,不禁机制**——关掉后 Team 提示词不再
591
878
  // 注入「大任务必须拆分」第 6 条、create-task 响应不再附 suggestSplit 软提示;显式传 parentId 建
592
879
  // 子卡、史诗自动收口/hooks 状态机照常(用户/主窗口明确要拆时不受阻)。勾选态缺字段=开,与 host 同口径。