dsh-thinking-guard 0.1.20 → 0.1.22

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/lib/client.js CHANGED
@@ -2687,7 +2687,7 @@ async function waitForMcpTools(remote, serverName, retryDelays) {
2687
2687
  * second enable switch here.
2688
2688
  */
2689
2689
  const CSS_ID$1 = "capability-menu-location-css";
2690
- const CSS$1 = `
2690
+ const CSS$2 = `
2691
2691
  .lm-block{display:flex;flex-direction:column;gap:8px}
2692
2692
  .lm-btn{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:20px;padding:0 10px;cursor:pointer;white-space:nowrap}
2693
2693
  .lm-btn:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}
@@ -2731,7 +2731,7 @@ textarea.lm-input{resize:vertical;font-family:var(--dsw-font-markdown-code-block
2731
2731
  if (typeof document !== "undefined" && document.querySelector(`style[data-css-id="${CSS_ID$1}"]`) === null) {
2732
2732
  const tag = document.createElement("style");
2733
2733
  tag.dataset.cssId = CSS_ID$1;
2734
- tag.textContent = CSS$1;
2734
+ tag.textContent = CSS$2;
2735
2735
  document.head.appendChild(tag);
2736
2736
  }
2737
2737
  /** Render a key/value map as `Key: Value` lines. */
@@ -3228,6 +3228,196 @@ function LocationModal(props) {
3228
3228
  })]
3229
3229
  });
3230
3230
  }
3231
+ //#endregion
3232
+ //#region src/client/v3-ui.ts
3233
+ /**
3234
+ * v3 设计层(D-044)——设置页四个子页面共用的 token、容器脚手架与块级词汇表。
3235
+ *
3236
+ * WHY A SHARED MODULE: v3 的视觉识别来自一套统一的 token 与块(panel / stats /
3237
+ * board / rank / table / gitem / catstat)。四个页面各写一份必然漂移,所以跨页面
3238
+ * 复用的部分集中在这里;页面专属样式仍留在各自文件里(call-index.tsx 的
3239
+ * `CallIndexStyles()`、CapabilitySection.tsx 的 mc-* 样式块)。
3240
+ *
3241
+ * CONTAINER, NOT VIEWPORT: 设置面板是宿主写死的 800px 弹窗,内容列只有 564px,
3242
+ * 与视口宽度无关(D-044 实测)。因此 v3 的断点全部写成 `@container v3`,页面根
3243
+ * 节点加上 `v3-root` 即成为容器。用 `@media` 会在 1200px 窗口里误判成「宽」,
3244
+ * 画出实际放不下的双栏。
3245
+ *
3246
+ * THEME: v3 原始 CSS 写死深色;这里全部映射到宿主 `--dsw-alias-*`,深浅色跟随
3247
+ * 应用。卡片底色用「label-primary 的 4% 叠在 bg-layer-2 上」而不是半透明白,
3248
+ * 否则浅色主题下会发脏。
3249
+ *
3250
+ * 本模块只输出样式与一个 style 元素,不假设任何 DOM 结构。
3251
+ */
3252
+ /** Every rule the v3 pages share. Class names are global like the existing mc-*. */
3253
+ const CSS$1 = [
3254
+ ".v3-root{container:v3 / inline-size;--v3-bg:var(--dsw-alias-bg-layer-2,Canvas);--v3-panel:color-mix(in srgb,var(--dsw-alias-label-primary,CanvasText) 4%,var(--dsw-alias-bg-layer-2,Canvas));--v3-panel-2:color-mix(in srgb,var(--dsw-alias-label-primary,CanvasText) 8%,var(--dsw-alias-bg-layer-2,Canvas));--v3-line:color-mix(in srgb,var(--dsw-alias-label-primary,CanvasText) 10%,transparent);--v3-line-2:color-mix(in srgb,var(--dsw-alias-label-primary,CanvasText) 17%,transparent);--v3-t1:var(--dsw-alias-label-primary,CanvasText);--v3-t2:var(--dsw-alias-label-secondary,var(--dsw-alias-label-primary,CanvasText));--v3-t3:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary,CanvasText));--v3-t4:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,CanvasText));--v3-ok:var(--dsw-alias-state-success-primary,#18894a);--v3-bad:var(--dsw-alias-state-error-primary,#c92b2b);--v3-warn:var(--dsw-alias-state-warn-primary,#a2730a);--v3-info:var(--dsw-alias-link,var(--dsw-alias-brand-primary,#2066c4));--v3-ok-dim:color-mix(in srgb,var(--v3-ok) 16%,transparent);--v3-bad-dim:color-mix(in srgb,var(--v3-bad) 14%,transparent);--v3-warn-dim:color-mix(in srgb,var(--v3-warn) 14%,transparent);--v3-info-dim:color-mix(in srgb,var(--v3-info) 14%,transparent);--v3-r-sm:var(--dsw-radius-sm,6px);--v3-r-md:var(--dsw-radius-md,9px);--v3-r-lg:var(--dsw-radius-lg,12px);--v3-font-code:var(--dsw-font-markdown-code-font-family,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);color:var(--v3-t1);font-size:13px;line-height:20px}",
3255
+ ".v3-num{font-variant-numeric:tabular-nums}",
3256
+ ".v3-mono{font-family:var(--v3-font-code)}",
3257
+ ".v3-statusline{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--v3-t3);padding:5px 10px;border-radius:999px;background:var(--v3-panel);border:1px solid var(--v3-line);min-width:0}",
3258
+ ".v3-statusline b{color:var(--v3-t1);font-weight:600}",
3259
+ ".v3-statusline[data-tone='bad']{background:var(--v3-bad-dim);border-color:color-mix(in srgb,var(--v3-bad) 30%,transparent)}",
3260
+ ".v3-dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex:none;background:var(--v3-t4)}",
3261
+ ".v3-dot[data-tone='ok']{background:var(--v3-ok);box-shadow:0 0 0 3px var(--v3-ok-dim)}",
3262
+ ".v3-dot[data-tone='bad']{background:var(--v3-bad);box-shadow:0 0 0 3px var(--v3-bad-dim)}",
3263
+ ".v3-dot[data-tone='warn']{background:var(--v3-warn);box-shadow:0 0 0 3px var(--v3-warn-dim)}",
3264
+ ".v3-dot[data-tone='run']{width:8px;height:8px;background:transparent;border:1.5px solid var(--v3-t3);border-top-color:transparent;animation:v3-spin .9s linear infinite;box-shadow:none}",
3265
+ "@keyframes v3-spin{to{transform:rotate(360deg)}}",
3266
+ ".v3-panel{background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);padding:13px 15px;min-width:0}",
3267
+ ".v3-ph{display:flex;align-items:center;gap:8px;margin-bottom:11px;min-height:18px;flex-wrap:wrap}",
3268
+ ".v3-pt{margin:0;font-size:11px;font-weight:600;color:var(--v3-t3);letter-spacing:.07em;text-transform:uppercase}",
3269
+ ".v3-pn{margin-left:auto;font-size:11px;color:var(--v3-t4);white-space:nowrap;text-transform:none;letter-spacing:0}",
3270
+ ".v3-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--v3-line);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);overflow:hidden}",
3271
+ ".v3-stat{background:var(--v3-panel);padding:13px 15px;min-width:0}",
3272
+ ".v3-stat-l{font-size:11px;color:var(--v3-t3);letter-spacing:.02em}",
3273
+ ".v3-stat-v{font-size:26px;line-height:34px;font-weight:650;letter-spacing:-.025em;margin-top:3px;font-variant-numeric:tabular-nums}",
3274
+ ".v3-stat-v small{font-size:12px;font-weight:400;color:var(--v3-t3);margin-left:4px;letter-spacing:0}",
3275
+ ".v3-stat-d{font-size:11px;color:var(--v3-t4);margin-top:2px;display:flex;align-items:center;gap:5px}",
3276
+ ".v3-spark{margin-top:8px;display:block;width:100%;height:22px;overflow:visible}",
3277
+ ".v3-spark .v3-spark-ln{fill:none;stroke-width:1.4;stroke-linejoin:round;stroke-linecap:round}",
3278
+ ".v3-spark .v3-spark-fl{opacity:.14}",
3279
+ ".v3-board{display:grid;grid-template-columns:minmax(0,2.15fr) minmax(0,1fr);gap:12px;align-items:stretch}",
3280
+ ".v3-chartbox{position:relative;min-width:0}",
3281
+ ".v3-chart{display:block;width:100%;height:212px;overflow:visible}",
3282
+ ".v3-chart .v3-grid{stroke:var(--v3-line);stroke-width:1}",
3283
+ ".v3-chart .v3-axis{fill:var(--v3-t4);font-size:9.5px}",
3284
+ ".v3-chart .v3-area{fill:var(--v3-info);opacity:.13}",
3285
+ ".v3-chart .v3-line{fill:none;stroke:var(--v3-info);stroke-width:1.8;stroke-linejoin:round;stroke-linecap:round}",
3286
+ ".v3-chart .v3-pt{fill:var(--v3-panel);stroke:var(--v3-info);stroke-width:1.6}",
3287
+ ".v3-chart .v3-bar-er{fill:var(--v3-bad);opacity:.85}",
3288
+ ".v3-chart .v3-hlbox{fill:var(--v3-info-dim);stroke:var(--v3-info);stroke-width:1;stroke-dasharray:3 3}",
3289
+ ".v3-chart .v3-lbl{fill:var(--v3-t2);font-size:10px}",
3290
+ ".v3-chart .v3-lbl-b{fill:var(--v3-t1);font-size:10.5px;font-weight:600}",
3291
+ ".v3-legend{display:flex;gap:16px;margin-top:8px;font-size:11px;color:var(--v3-t4);flex-wrap:wrap}",
3292
+ ".v3-legend span{display:inline-flex;align-items:center;gap:6px}",
3293
+ ".v3-legend i{width:14px;height:2px;border-radius:1px;flex:none;background:var(--v3-info)}",
3294
+ ".v3-legend i[data-mark='square']{width:8px;height:8px;border-radius:2px;background:var(--v3-bad)}",
3295
+ ".v3-donut-wrap{display:flex;align-items:center;gap:14px;flex-wrap:wrap}",
3296
+ ".v3-donut{flex:none;width:104px;height:104px;position:relative}",
3297
+ ".v3-donut svg{width:104px;height:104px;transform:rotate(-90deg)}",
3298
+ ".v3-donut .v3-trk{fill:none;stroke:var(--v3-panel-2);stroke-width:9}",
3299
+ ".v3-donut .v3-seg{fill:none;stroke:var(--v3-t4);stroke-width:9}",
3300
+ ".v3-donut .v3-mid{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;pointer-events:none}",
3301
+ ".v3-donut .v3-mid b{font-size:17px;font-weight:650;letter-spacing:-.02em;line-height:20px}",
3302
+ ".v3-donut .v3-mid span{font-size:9.5px;color:var(--v3-t4);line-height:12px;letter-spacing:.04em}",
3303
+ ".v3-src-list{flex:1 1 180px;min-width:0;display:flex;flex-direction:column;gap:5px}",
3304
+ ".v3-src{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--v3-t2);min-width:0}",
3305
+ ".v3-src i{width:7px;height:7px;border-radius:2px;flex:none;background:var(--v3-t4);opacity:.5}",
3306
+ ".v3-src .v3-nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
3307
+ ".v3-src b{margin-left:auto;color:var(--v3-t1);font-weight:600;font-variant-numeric:tabular-nums}",
3308
+ ".v3-src em{font-style:normal;color:var(--v3-t4);min-width:44px;text-align:right;font-variant-numeric:tabular-nums;font-size:11px}",
3309
+ ".v3-src-note{margin:9px 0 0;padding-top:9px;border-top:1px solid var(--v3-line);font-size:11px;line-height:17px;color:var(--v3-t4)}",
3310
+ ".v3-rank{display:flex;flex-direction:column;gap:1px;margin-top:-2px}",
3311
+ ".v3-rank-row{display:flex;align-items:center;gap:9px;min-height:27px;font-size:12px;padding:0 2px;border-radius:4px}",
3312
+ ".v3-rank-row:hover{background:var(--v3-panel-2)}",
3313
+ ".v3-rank-row .v3-nm{font-family:var(--v3-font-code);font-size:11.5px;color:var(--v3-t2);width:158px;flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
3314
+ ".v3-rank-row .v3-bar{flex:1 1 auto;min-width:30px;height:6px;background:var(--v3-panel-2);border-radius:3px;position:relative;overflow:hidden}",
3315
+ ".v3-rank-row .v3-bar span{position:absolute;left:0;top:0;bottom:0;min-width:3px;background:var(--v3-info);opacity:.65;border-radius:3px}",
3316
+ ".v3-rank-row .v3-bar span[data-bad='true']{background:var(--v3-bad);opacity:.85}",
3317
+ ".v3-rank-row .v3-n{width:52px;flex:none;text-align:right;font-variant-numeric:tabular-nums;color:var(--v3-t2);white-space:nowrap}",
3318
+ ".v3-rank-row .v3-e{flex:none;text-align:right;font-variant-numeric:tabular-nums;font-size:11px;color:var(--v3-t4);white-space:nowrap}",
3319
+ ".v3-rank-row .v3-e[data-bad='true']{color:var(--v3-bad);font-weight:600}",
3320
+ ".v3-ts{max-height:min(60vh,560px);overflow:auto;overscroll-behavior:contain;border-top:1px solid var(--v3-line)}",
3321
+ ".v3-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12.5px}",
3322
+ ".v3-table thead th{position:sticky;top:0;z-index:2;background:var(--v3-panel);color:var(--v3-t4);font-weight:500;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;text-align:left;padding:0 11px;height:29px;white-space:nowrap;border-bottom:1px solid var(--v3-line)}",
3323
+ ".v3-table thead th.v3-r{text-align:right}",
3324
+ ".v3-table tbody td{padding:0 11px;height:32px;border-bottom:1px solid var(--v3-line);vertical-align:middle;white-space:nowrap}",
3325
+ ".v3-table tbody tr:last-child td{border-bottom:0}",
3326
+ ".v3-table tbody tr:hover td{background:var(--v3-panel-2)}",
3327
+ ".v3-table td.v3-r{text-align:right;font-variant-numeric:tabular-nums;color:var(--v3-t2)}",
3328
+ ".v3-c-tool{display:flex;align-items:center;gap:7px;min-width:0}",
3329
+ ".v3-c-tool .v3-nm{font-family:var(--v3-font-code);font-size:11.5px;overflow:hidden;text-overflow:ellipsis}",
3330
+ ".v3-c-tool .v3-srv{font-size:10.5px;color:var(--v3-t4)}",
3331
+ ".v3-srcnote{flex:none;font-size:9px;line-height:13px;height:13px;padding:0 4px;border-radius:3px;color:var(--v3-t4);background:var(--v3-panel-2)}",
3332
+ ".v3-rt{display:inline-flex;align-items:center;gap:7px;justify-content:flex-end}",
3333
+ ".v3-rt .v3-tk{width:34px;height:3px;border-radius:2px;background:var(--v3-panel-2);overflow:hidden;flex:none}",
3334
+ ".v3-rt .v3-fl{display:block;height:100%;background:var(--v3-ok);border-radius:2px}",
3335
+ ".v3-rt .v3-fl[data-bad='true']{background:var(--v3-bad)}",
3336
+ ".v3-rt b{font-weight:500;min-width:40px;text-align:right;font-variant-numeric:tabular-nums}",
3337
+ ".v3-tfoot{display:flex;align-items:center;gap:9px;padding:9px 15px;border-top:1px solid var(--v3-line);color:var(--v3-t4);font-size:11px;flex-wrap:wrap}",
3338
+ ".v3-empty{background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);padding:34px 24px;text-align:center}",
3339
+ ".v3-empty h3{margin:0 0 5px;font-size:14px;font-weight:600;color:var(--v3-t1)}",
3340
+ ".v3-empty p{margin:0 auto;max-width:56ch;font-size:12.5px;line-height:20px;color:var(--v3-t3)}",
3341
+ ".v3-banner{display:flex;align-items:flex-start;gap:10px;padding:11px 13px;border-radius:var(--v3-r-md);background:var(--v3-bad-dim);border:1px solid color-mix(in srgb,var(--v3-bad) 32%,transparent);font-size:12px;line-height:19px;color:var(--v3-t2)}",
3342
+ ".v3-banner b{display:block;font-size:12.5px;margin-bottom:2px;color:var(--v3-t1)}",
3343
+ ".v3-banner code{font-family:var(--v3-font-code);font-size:10.5px;padding:1px 5px;border-radius:3px;background:var(--v3-panel-2);color:var(--v3-t3);margin-left:5px}",
3344
+ ".v3-faultcard{background:var(--v3-panel);border:1px solid var(--v3-line);border-left:3px solid var(--v3-bad);border-radius:var(--v3-r-md);overflow:hidden}",
3345
+ ".v3-faultcard .v3-fh{display:flex;align-items:center;gap:8px;padding:9px 13px}",
3346
+ ".v3-faultcard .v3-fh b{font-size:12.5px;font-weight:600}",
3347
+ ".v3-faultcard .v3-fh code{font-family:var(--v3-font-code);font-size:10.5px;padding:1px 5px;border-radius:3px;background:var(--v3-panel-2);color:var(--v3-t3)}",
3348
+ ".v3-faultcard p{margin:0;padding:0 13px 9px;font-size:11.5px;line-height:18px;color:var(--v3-t3)}",
3349
+ ".v3-gpanel{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px}",
3350
+ ".v3-gitem{background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);padding:13px 15px;min-width:0}",
3351
+ ".v3-gitem .v3-gh{display:flex;align-items:center;gap:8px;margin-bottom:7px;flex-wrap:wrap}",
3352
+ ".v3-gitem .v3-gnm{font-size:13px;font-weight:600}",
3353
+ ".v3-gitem .v3-gid{font-family:var(--v3-font-code);font-size:10.5px;color:var(--v3-t4)}",
3354
+ ".v3-gitem p{margin:0;font-size:11.5px;line-height:18px;color:var(--v3-t3)}",
3355
+ ".v3-gitem .v3-gfoot{display:flex;align-items:center;gap:6px;margin-top:9px;font-size:10.5px;color:var(--v3-t4);padding-top:9px;border-top:1px solid var(--v3-line);flex-wrap:wrap}",
3356
+ ".v3-kbd{font-family:var(--v3-font-code);font-size:10.5px;padding:1px 5px;border-radius:3px;background:var(--v3-panel-2);color:var(--v3-t2);border:1px solid var(--v3-line)}",
3357
+ ".v3-notebox{margin:0;padding:11px 13px;border-radius:var(--v3-r-md);background:var(--v3-panel);border:1px solid var(--v3-line);color:var(--v3-t3);font-size:11.5px;line-height:18px}",
3358
+ ".v3-notebox b{color:var(--v3-t2);font-weight:600}",
3359
+ ".v3-sw{position:relative;width:34px;height:19px;border:0;border-radius:999px;padding:0;cursor:pointer;flex:none;background:color-mix(in srgb,var(--dsw-alias-label-primary,CanvasText) 16%,transparent);transition:background .16s}",
3360
+ ".v3-sw::after{content:'';position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:var(--dsw-alias-switch-thumb,var(--v3-bg));transition:transform .16s}",
3361
+ ".v3-sw[aria-checked='true']{background:var(--v3-ok)}",
3362
+ ".v3-sw[aria-checked='true']::after{transform:translateX(15px)}",
3363
+ ".v3-sw:disabled{opacity:.55;cursor:default}",
3364
+ ".v3-sw:focus-visible{outline:2px solid var(--v3-info);outline-offset:2px}",
3365
+ ".v3-catgrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px}",
3366
+ ".v3-catstat{display:flex;background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);overflow:hidden;margin-bottom:12px}",
3367
+ ".v3-catstat>div{flex:1 1 0;padding:11px 14px;min-width:0}",
3368
+ ".v3-catstat>div+div{border-left:1px solid var(--v3-line)}",
3369
+ ".v3-catstat .v3-l{font-size:10.5px;color:var(--v3-t4);letter-spacing:.05em;text-transform:uppercase}",
3370
+ ".v3-catstat .v3-v{font-size:19px;font-weight:650;margin-top:2px;letter-spacing:-.02em;font-variant-numeric:tabular-nums}",
3371
+ ".v3-catstat .v3-v small{font-size:11px;font-weight:400;color:var(--v3-t3);margin-left:4px;letter-spacing:0}",
3372
+ ".v3-group{background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-md);overflow:hidden;margin-bottom:10px;min-width:0}",
3373
+ ".v3-group:last-child{margin-bottom:0}",
3374
+ ".v3-group>summary{display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;list-style:none;min-height:36px;flex-wrap:wrap}",
3375
+ ".v3-group>summary::-webkit-details-marker{display:none}",
3376
+ ".v3-group>summary:hover{background:var(--v3-panel-2)}",
3377
+ ".v3-group[open]>summary{border-bottom:1px solid var(--v3-line)}",
3378
+ ".v3-group>summary .v3-chev{width:10px;height:10px;color:var(--v3-t4);flex:none;transition:transform .14s}",
3379
+ ".v3-group[open]>summary .v3-chev{transform:rotate(90deg)}",
3380
+ ".v3-group>summary .v3-gnm{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}",
3381
+ ".v3-group>summary .v3-cnt{font-size:10.5px;color:var(--v3-t4);flex:none}",
3382
+ ".v3-group>summary .v3-right{margin-left:auto;display:flex;gap:4px;align-items:center;flex:none}",
3383
+ ".v3-trow{display:flex;align-items:center;gap:8px;padding:0 12px 0 29px;min-height:30px;border-top:1px solid var(--v3-line);font-size:12px;min-width:0}",
3384
+ ".v3-trow:first-of-type{border-top:0}",
3385
+ ".v3-trow:hover{background:var(--v3-panel-2)}",
3386
+ ".v3-trow .v3-nm{font-family:var(--v3-font-code);font-size:11.5px;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
3387
+ ".v3-trow .v3-uses{flex:none;font-size:11px;color:var(--v3-t4);font-variant-numeric:tabular-nums;white-space:nowrap}",
3388
+ ".v3-tag{display:inline-flex;align-items:center;gap:4px;height:17px;padding:0 6px;border-radius:4px;font-size:10px;line-height:17px;white-space:nowrap;color:var(--v3-t3);background:var(--v3-panel-2)}",
3389
+ ".v3-tag[data-tone='info']{color:var(--v3-info);background:var(--v3-info-dim)}",
3390
+ ".v3-tag[data-tone='warn']{color:var(--v3-warn);background:var(--v3-warn-dim)}",
3391
+ ".v3-tag[data-tone='bad']{color:var(--v3-bad);background:var(--v3-bad-dim)}",
3392
+ ".v3-hint{margin:0;font-size:11px;line-height:17px;color:var(--v3-t4)}",
3393
+ ".v3-scopehint{display:flex;align-items:center;gap:7px;margin-top:9px;padding:6px 10px;border-radius:var(--v3-r-sm);background:var(--v3-panel-2);font-size:11.5px;line-height:17px;color:var(--v3-t3);flex-wrap:wrap}",
3394
+ ".v3-scopehint .v3-id{font-family:var(--v3-font-code);font-size:11px;color:var(--v3-t2);padding:1px 5px;border-radius:3px;background:var(--v3-panel);border:1px solid var(--v3-line)}",
3395
+ ".v3-scopehint[data-tone='warn']{background:var(--v3-warn-dim);color:var(--v3-t2)}",
3396
+ ".v3-scan{display:flex;align-items:center;gap:10px;margin-top:9px;font-size:11.5px;color:var(--v3-t3)}",
3397
+ ".v3-scan .v3-track{position:relative;flex:1 1 auto;height:5px;border-radius:3px;background:var(--v3-panel-2);overflow:hidden;min-width:70px}",
3398
+ ".v3-scan .v3-fill{position:absolute;inset:0 auto 0 0;background:var(--v3-info);border-radius:3px;transition:width .3s ease}",
3399
+ ".v3-scan[data-running='true'] .v3-fill{width:34% !important;animation:v3-indet 1.25s ease-in-out infinite}",
3400
+ "@keyframes v3-indet{0%{left:-34%}100%{left:100%}}",
3401
+ ".v3-scan .v3-pct{font-variant-numeric:tabular-nums;color:var(--v3-t2);font-weight:600;min-width:38px;text-align:right}",
3402
+ "@container v3 (max-width:980px){.v3-board{grid-template-columns:minmax(0,1fr)}}",
3403
+ "@container v3 (max-width:900px){.v3-gpanel,.v3-catgrid{grid-template-columns:minmax(0,1fr)}}",
3404
+ "@container v3 (max-width:720px){.v3-stats{grid-template-columns:repeat(2,minmax(0,1fr))}.v3-catstat{flex-wrap:wrap}.v3-catstat>div{flex:1 1 45%}.v3-catstat>div+div{border-left:0}}",
3405
+ "@container v3 (max-width:620px){.v3-rank-row .v3-nm{width:104px}}",
3406
+ "@media (prefers-reduced-motion:reduce){.v3-dot[data-tone='run'],.v3-scan[data-running='true'] .v3-fill{animation:none}}",
3407
+ "[hidden]{display:none !important}"
3408
+ ].join("");
3409
+ /**
3410
+ * Mount the shared v3 stylesheet once per document.
3411
+ *
3412
+ * React does not dedupe <style> elements across the four pages, and all four
3413
+ * render their own root, so the element carries a marker attribute and an
3414
+ * identical copy in the DOM is harmless. Kept as an element (not a singleton
3415
+ * side effect at import time) so the hook-free views stay pure and the test
3416
+ * suite can still walk the tree.
3417
+ */
3418
+ function V3Styles() {
3419
+ return (0, react.createElement)("style", { "data-v3-style": "true" }, CSS$1);
3420
+ }
3231
3421
  /**
3232
3422
  * dsh's source labels for a project's own skill roots — the `project`-prefixed
3233
3423
  * providers of `@deepseek-ai/dsh-skill-filesystem`, which scan
@@ -3717,6 +3907,42 @@ body[data-ds-dark-theme] .mc-count--disabled{color:#b8abad}
3717
3907
  .mc-guard-switch--on{background:var(--dsw-alias-state-business-primary)}
3718
3908
  .mc-guard-switch--on::after{transform:translateX(14px)}
3719
3909
  .mc-guard-switch:disabled{opacity:.5;cursor:not-allowed}
3910
+ /* ---- v3 工具与技能(V3-c):仅新增,不改动上面任何 .mc-* / .mc-guard-* ---- */
3911
+ .v3-dot[data-tone='info']{background:var(--v3-info)}
3912
+ .v3-trow{cursor:pointer}
3913
+ .v3-trow:focus-visible{outline:2px solid var(--v3-info);outline-offset:-2px}
3914
+ .v3-racts{display:flex;align-items:center;gap:4px;margin-left:auto;flex:none}
3915
+ .v3-uses-bad{color:var(--v3-bad)}
3916
+ button.v3-tag.v3-tag--act{color:var(--v3-t3);background:transparent}
3917
+ button.v3-tag.v3-tag--act:hover{color:var(--v3-t1);background:var(--v3-panel-2)}
3918
+ button.v3-tag{border:0;cursor:pointer;font-family:inherit}
3919
+ button.v3-tag:disabled{opacity:.55;cursor:default}
3920
+ .v3-group>summary:focus-visible{outline:2px solid var(--v3-info);outline-offset:-2px}
3921
+ .v3-tools{display:flex;flex-direction:column}
3922
+ /* ---- v3 catalog rework: 一行工具栏 / 能力诊断卡片 / 卡片向总览看齐 ---- */
3923
+ .mc-cat-root{display:flex;flex-direction:column;gap:12px;min-width:0}
3924
+ .mc-cat-root .v3-catstat{margin-bottom:0}
3925
+ .mc-cat-bar{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;min-width:0}
3926
+ .mc-cat-bar>.mc-cat-actions{flex:none}
3927
+ .mc-cat-bar .mc-cat-actions button{font-size:11px;white-space:nowrap}
3928
+ .mc-cat-tabs{display:flex;align-items:center;flex:none;min-width:0}
3929
+ .mc-cat-tabs .mc-main-tabs{border-bottom:0}
3930
+ .mc-cat-search{flex:1 1 auto;width:auto;min-width:96px;max-width:220px;display:flex}
3931
+ .mc-cat-search input{box-sizing:border-box;width:100%;height:28px;padding:0 9px;border:1px solid var(--v3-line-2);border-radius:var(--v3-r-sm);background:var(--v3-panel);color:var(--v3-t1);font:inherit;font-size:12.5px}
3932
+ .mc-cat-search input:hover{border-color:var(--v3-line-2)}
3933
+ .mc-cat-search input:focus-visible{outline:2px solid var(--v3-info);outline-offset:1px;border-color:transparent}
3934
+ .mc-cat-actions{display:flex;align-items:center;gap:6px;flex:none;margin-left:auto;flex-wrap:nowrap}
3935
+ .mc-cat-diag{background:var(--v3-panel);border:1px solid var(--v3-line);border-radius:var(--v3-r-lg);overflow:hidden}
3936
+ .mc-cat-diag>summary{display:flex;align-items:center;gap:8px;padding:11px 15px;min-height:38px;cursor:pointer;list-style:none;font-size:12.5px;font-weight:600;color:var(--v3-t2)}
3937
+ .mc-cat-diag>summary::-webkit-details-marker{display:none}
3938
+ .mc-cat-diag>summary:hover{background:var(--v3-panel-2)}
3939
+ .mc-cat-diag[open]>summary{border-bottom:1px solid var(--v3-line)}
3940
+ .mc-cat-diag-body{display:flex;flex-direction:column;gap:4px;padding:11px 15px;font-size:11.5px;line-height:18px;color:var(--v3-t3)}
3941
+ .v3-root .v3-catgrid{gap:14px}
3942
+ .v3-root .v3-group{border-radius:var(--v3-r-lg);margin-bottom:0}
3943
+ .v3-root .v3-group>summary{padding:11px 15px;min-height:38px}
3944
+ .v3-root .v3-tag{font-size:11px;height:19px;line-height:19px;padding:0 7px}
3945
+ .v3-root .v3-trow{min-height:34px;font-size:12.5px}
3720
3946
  `;
3721
3947
  if (typeof document !== "undefined" && document.querySelector(`style[data-css-id="${CSS_ID}"]`) === null) {
3722
3948
  const tag = document.createElement("style");
@@ -3789,14 +4015,6 @@ function MarkdownFilePreview(props) {
3789
4015
  })] });
3790
4016
  }
3791
4017
  /**
3792
- * Which build of the plugin is on screen. Several rounds of "is my profile
3793
- * running the version I just installed?" had to be answered by grepping
3794
- * `node_modules`, because the panel said nothing about itself. The value is
3795
- * inlined by `tsdown` (see `src/client/env.d.ts`).
3796
- */
3797
- const PLUGIN_VERSION = "0.1.20";
3798
- const PLUGIN_TITLE = `capability-menu v${PLUGIN_VERSION}`;
3799
- /**
3800
4018
  * dsh's skill source labels, shown on rows this plugin cannot edit — so "why is
3801
4019
  * there no 编辑 button" has a visible answer. Labels that name one directory are
3802
4020
  * shown as that path; the rest name a category and go through the dictionary, so
@@ -3821,6 +4039,200 @@ function sourceLabel(source, t) {
3821
4039
  const key = SOURCE_LABEL_KEYS[source];
3822
4040
  return key === void 0 ? source : t(key);
3823
4041
  }
4042
+ /** Tone of a class chip / state dot, mapped to the v3 `data-tone` palette. */
4043
+ const CLASS_TONE = {
4044
+ resident: "info",
4045
+ "on-demand": "warn",
4046
+ disabled: "bad"
4047
+ };
4048
+ /** State-dot tone inside a row: resident=info, on-demand=warn, disabled=neutral. */
4049
+ const CLASS_DOT_TONE = {
4050
+ resident: "info",
4051
+ "on-demand": "warn",
4052
+ disabled: ""
4053
+ };
4054
+ /** The four-cell `.v3-catstat` strip at the top of the catalog page. */
4055
+ function CatalogStatStrip(props) {
4056
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4057
+ className: "v3-catstat",
4058
+ "data-v3-catstat": "true",
4059
+ children: props.cells.map((cell, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4060
+ className: "v3-l",
4061
+ children: cell.label
4062
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4063
+ className: "v3-v",
4064
+ children: [cell.value, cell.hint !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: cell.hint }) : null]
4065
+ })] }, index))
4066
+ });
4067
+ }
4068
+ /** Zero-call capability count when a usage map exists, else an honest dash. */
4069
+ function zeroCallCount(usage, rows) {
4070
+ if (usage === void 0) return "—";
4071
+ return rows.filter((row) => {
4072
+ const entry = usage.get(row.id);
4073
+ return entry === void 0 || entry.uses === 0;
4074
+ }).length;
4075
+ }
4076
+ /** Strip a tool’s mcp__<server>__ prefix for display, like the old row did. */
4077
+ function displayToolName(server, name) {
4078
+ const prefix = "mcp__" + server + "__";
4079
+ return server !== "built-in" && name.startsWith(prefix) ? name.slice(prefix.length) : name;
4080
+ }
4081
+ /**
4082
+ * Two-column `.v3-catgrid` of native `<details>` server groups, each summary
4083
+ * carrying the chevron and the class-count tags, each row a 30px `.v3-trow`.
4084
+ * Pure: every interaction is a callback.
4085
+ */
4086
+ function ToolGroupsView(props) {
4087
+ const { t, groups, openServers, busy, usage } = props;
4088
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4089
+ className: "v3-catgrid",
4090
+ "data-v3-catgrid": "true",
4091
+ children: groups.map(({ server, tools }) => {
4092
+ const open = openServers.has(server);
4093
+ const counts = CLASS_KEYS.map((cls) => ({
4094
+ cls,
4095
+ count: countByClass(tools, cls)
4096
+ }));
4097
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
4098
+ className: "v3-group",
4099
+ open,
4100
+ "data-v3-group": server,
4101
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
4102
+ onClick: (e) => {
4103
+ e.preventDefault();
4104
+ props.onToggle(server);
4105
+ },
4106
+ children: [
4107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4108
+ className: "v3-chev",
4109
+ viewBox: "0 0 12 12",
4110
+ fill: "none",
4111
+ "aria-hidden": "true",
4112
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4113
+ d: "M4.5 2.5 8 6l-3.5 3.5",
4114
+ stroke: "currentColor",
4115
+ strokeWidth: 1.4,
4116
+ strokeLinecap: "round",
4117
+ strokeLinejoin: "round"
4118
+ })
4119
+ }),
4120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4121
+ className: "v3-gnm",
4122
+ children: server === "built-in" ? t("builtInGroup") : server
4123
+ }),
4124
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4125
+ className: "v3-cnt",
4126
+ children: tools.length
4127
+ }),
4128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4129
+ className: "v3-right",
4130
+ children: [counts.filter(({ count }) => count > 0).map(({ cls, count }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4131
+ type: "button",
4132
+ className: "v3-tag",
4133
+ "data-tone": CLASS_TONE[cls],
4134
+ disabled: busy,
4135
+ title: t("cycleHint"),
4136
+ onClick: (e) => {
4137
+ e.stopPropagation();
4138
+ props.onCycle(tools.filter((tool) => tool.class === cls).map((tool) => tool.id), "tool");
4139
+ },
4140
+ children: [
4141
+ t(CLASS_SHORT_KEYS[cls]),
4142
+ " ",
4143
+ count
4144
+ ]
4145
+ }, cls)), server !== "built-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4146
+ type: "button",
4147
+ className: "v3-tag v3-tag--act",
4148
+ disabled: busy,
4149
+ onClick: (e) => {
4150
+ e.stopPropagation();
4151
+ props.onEditServer(server);
4152
+ },
4153
+ children: t("edit")
4154
+ })]
4155
+ })
4156
+ ]
4157
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4158
+ className: "v3-tools",
4159
+ children: tools.map((tool) => {
4160
+ const rowUsage = usage === void 0 ? void 0 : usage.get(tool.id);
4161
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4162
+ className: "v3-trow",
4163
+ title: tool.classLabel,
4164
+ role: "button",
4165
+ tabIndex: 0,
4166
+ "aria-label": tool.name,
4167
+ onClick: () => props.onOpenDetail(tool.id),
4168
+ onKeyDown: (e) => {
4169
+ if (e.key === "Enter" || e.key === " ") {
4170
+ e.preventDefault();
4171
+ props.onOpenDetail(tool.id);
4172
+ }
4173
+ },
4174
+ children: [
4175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4176
+ className: "v3-dot",
4177
+ "data-tone": CLASS_DOT_TONE[tool.class],
4178
+ "aria-hidden": "true"
4179
+ }),
4180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4181
+ className: "v3-nm",
4182
+ title: tool.name,
4183
+ children: displayToolName(server, tool.name)
4184
+ }),
4185
+ rowUsage !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4186
+ className: "v3-uses",
4187
+ children: [
4188
+ rowUsage.uses,
4189
+ " 次",
4190
+ rowUsage.failures !== void 0 && rowUsage.failures > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4191
+ className: "v3-uses-bad",
4192
+ children: [" · 失败 ", rowUsage.failures]
4193
+ }) : null
4194
+ ]
4195
+ }),
4196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4197
+ className: "v3-racts",
4198
+ children: [
4199
+ tool.mandatory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4200
+ className: "v3-tag",
4201
+ children: t("mandatory")
4202
+ }),
4203
+ tool.class === "on-demand" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4204
+ type: "button",
4205
+ className: "v3-tag v3-tag--act",
4206
+ title: t("editSearchMetadata"),
4207
+ onClick: (e) => {
4208
+ e.stopPropagation();
4209
+ props.onEditSearch(tool.id);
4210
+ },
4211
+ children: t("searchMetadataButton")
4212
+ }),
4213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4214
+ type: "button",
4215
+ className: "v3-tag",
4216
+ "data-tone": CLASS_TONE[tool.class],
4217
+ disabled: busy || tool.mandatory,
4218
+ title: tool.classLabel,
4219
+ "aria-label": tool.classLabel,
4220
+ onClick: (e) => {
4221
+ e.stopPropagation();
4222
+ props.onCycle([tool.id], "tool");
4223
+ },
4224
+ children: t(CLASS_SHORT_KEYS[tool.class])
4225
+ })
4226
+ ]
4227
+ })
4228
+ ]
4229
+ }, tool.id);
4230
+ })
4231
+ })]
4232
+ }, server);
4233
+ })
4234
+ });
4235
+ }
3824
4236
  /**
3825
4237
  * Whether the document is visible. A hidden settings tab does no polling and
3826
4238
  * takes no re-reads; coming back is itself a revalidation trigger.
@@ -3836,7 +4248,7 @@ function useDocumentVisible() {
3836
4248
  return visible;
3837
4249
  }
3838
4250
  function CapabilitySection(props) {
3839
- const { remote, t, mountError, subscribeSignals } = props;
4251
+ const { remote, t, mountError, subscribeSignals, usage } = props;
3840
4252
  const [state, setState] = (0, react.useState)(() => {
3841
4253
  const cached = cachedSnapshot();
3842
4254
  return cached === void 0 ? { status: "loading" } : {
@@ -4169,13 +4581,14 @@ function CapabilitySection(props) {
4169
4581
  onCycle: cycleClass,
4170
4582
  onRefresh: () => void refreshCatalog(),
4171
4583
  onRefreshMcp: (serverName) => void refreshRegisteredMcp(serverName),
4172
- onNotice: setNotice
4584
+ onNotice: setNotice,
4585
+ ...usage === void 0 ? {} : { usage }
4173
4586
  })
4174
4587
  ]
4175
4588
  });
4176
4589
  }
4177
4590
  function ReadyBody(props) {
4178
- const { remote, snapshot, diagnostics, openServers, busy, activeTab, t, onTabChange, onToggleServer, onCycle, onRefresh, onRefreshMcp, onNotice } = props;
4591
+ const { remote, snapshot, diagnostics, openServers, busy, activeTab, t, onTabChange, onToggleServer, onCycle, onRefresh, onRefreshMcp, onNotice, usage } = props;
4179
4592
  const [updateStatus, setUpdateStatus] = (0, react.useState)(null);
4180
4593
  const [updatePhase, setUpdatePhase] = (0, react.useState)("idle");
4181
4594
  const [updateError, setUpdateError] = (0, react.useState)("");
@@ -4463,347 +4876,204 @@ function ReadyBody(props) {
4463
4876
  t
4464
4877
  ]);
4465
4878
  const skillDiagnosticCount = diagnostics === null ? 0 : diagnostics.unavailableSkills.length + diagnostics.shadowedSkills.length + diagnostics.brokenPresets.length + diagnostics.unbrowseableSkills.length + diagnostics.undocumentedSkills.length;
4466
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4467
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4468
- className: "mc-heading-row",
4469
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4470
- className: "mc-heading-meta",
4471
- children: [
4472
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
4473
- className: "mc-heading",
4474
- children: t("title")
4475
- }),
4476
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4477
- className: "mc-version",
4478
- title: PLUGIN_TITLE,
4479
- children: ["v", PLUGIN_VERSION]
4480
- }),
4481
- updateStatus?.updateAvailable && (updateStatus.updatable || updateStatus.reason === "local") && updatePhase !== "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4482
- type: "button",
4483
- variant: "primary",
4484
- size: "sm",
4485
- className: "mc-update-check",
4486
- title: t("updateAvailable", { version: updateStatus.latest }),
4487
- disabled: updatePhase === "installing",
4488
- onClick: () => setUpdateDialogOpen(true),
4489
- children: t("newVersion")
4490
- })
4491
- ]
4492
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4493
- className: "mc-heading-actions",
4879
+ const catalogUsage = usage;
4880
+ const classCount = (cls) => summary.find((entry) => entry.cls === cls)?.count ?? 0;
4881
+ const statCells = [
4882
+ {
4883
+ label: t("tierResident"),
4884
+ value: classCount("resident")
4885
+ },
4886
+ {
4887
+ label: t("tierOnDemand"),
4888
+ value: classCount("on-demand")
4889
+ },
4890
+ {
4891
+ label: t("tierDisabled"),
4892
+ value: classCount("disabled")
4893
+ },
4894
+ {
4895
+ label: t("catalogZeroCall"),
4896
+ value: zeroCallCount(catalogUsage, statRows),
4897
+ hint: t("catalogZeroCallHint")
4898
+ }
4899
+ ];
4900
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4901
+ className: "v3-root mc-cat-root",
4902
+ children: [
4903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(V3Styles, {}),
4904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4905
+ className: "mc-cat-bar",
4906
+ "data-v3-catalog-bar": "true",
4494
4907
  children: [
4495
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4496
- size: "sm",
4497
- variant: "outline",
4498
- onClick: () => setRegisterOpen(true),
4499
- children: t("registerCapability")
4908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4909
+ className: "mc-cat-tabs",
4910
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
4911
+ className: "mc-main-tabs",
4912
+ items: [{
4913
+ value: "tools",
4914
+ id: "mc-tab-tools",
4915
+ panelId: "mc-panel-tools",
4916
+ label: t("toolsGroup")
4917
+ }, {
4918
+ value: "skills",
4919
+ id: "mc-tab-skills",
4920
+ panelId: "mc-panel-skills",
4921
+ label: t("skillsGroup")
4922
+ }],
4923
+ value: activeTab,
4924
+ onChange: onTabChange,
4925
+ label: t("title")
4926
+ })
4500
4927
  }),
4501
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4502
- size: "sm",
4503
- variant: "outline",
4504
- onClick: () => void openCatalogDocs(),
4505
- children: t("viewCatalog")
4928
+ snapshot.rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4929
+ className: "mc-cat-search",
4930
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
4931
+ type: "search",
4932
+ value: filter,
4933
+ placeholder: t("filterByName"),
4934
+ "aria-label": t("filterByName"),
4935
+ title: t("filterHint"),
4936
+ onChange: (e) => setFilter(e.target.value)
4937
+ })
4506
4938
  }),
4507
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
4508
- className: "mc-feedback-link",
4509
- href: "https://github.com/PKUfudawei/dsh-capability-menu",
4510
- target: "_blank",
4511
- rel: "noopener noreferrer",
4512
- "aria-label": t("feedback"),
4513
- children: t("feedback")
4939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4940
+ className: "mc-cat-actions",
4941
+ children: [
4942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4943
+ size: "sm",
4944
+ variant: "outline",
4945
+ onClick: () => setRegisterOpen(true),
4946
+ children: t("registerCapability")
4947
+ }),
4948
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4949
+ size: "sm",
4950
+ variant: "outline",
4951
+ onClick: () => void openCatalogDocs(),
4952
+ children: t("viewCatalog")
4953
+ }),
4954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4955
+ type: "button",
4956
+ size: "sm",
4957
+ variant: "outline",
4958
+ onClick: () => window.open("https://github.com/PKUfudawei/dsh-capability-menu", "_blank", "noopener,noreferrer"),
4959
+ children: t("feedback")
4960
+ }),
4961
+ updateStatus?.updateAvailable && (updateStatus.updatable || updateStatus.reason === "local") && updatePhase !== "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4962
+ type: "button",
4963
+ variant: "primary",
4964
+ size: "sm",
4965
+ className: "mc-update-check",
4966
+ title: t("updateAvailable", { version: updateStatus.latest }),
4967
+ disabled: updatePhase === "installing",
4968
+ onClick: () => setUpdateDialogOpen(true),
4969
+ children: t("newVersion")
4970
+ })
4971
+ ]
4514
4972
  })
4515
4973
  ]
4516
- })]
4517
- }),
4518
- (updatePhase === "done" || updateStatus?.updateAvailable && !updateStatus.updatable && updateStatus.reason !== "local") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4519
- className: "mc-update-status",
4520
- role: "status",
4521
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: updatePhase === "done" ? t(updateStatus?.reason === "local" ? "pluginSwitched" : "pluginUpdated") : t("updateAvailable", { version: updateStatus?.latest ?? "" }) }), updatePhase !== "done" && updateStatus?.updateAvailable && !updateStatus.updatable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(updateStatus.reason === "non-registry" ? "pluginNonRegistrySource" : "pluginProfileUnavailable") })]
4522
- }),
4523
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4524
- className: "mc-desc",
4525
- children: t("desc")
4526
- }),
4527
- diagnostics !== null && skillDiagnosticCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
4528
- className: "mc-diagnostics",
4529
- children: [
4530
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("diagnosticTitle", { count: skillDiagnosticCount }) }),
4531
- diagnostics.unavailableSkills.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUnavailable", {
4532
- name: item.name,
4533
- source: item.source,
4534
- preset: item.preset ?? ""
4535
- }) }, `unavailable-${item.name}-${item.preset ?? ""}-${index}`)),
4536
- diagnostics.shadowedSkills.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticShadowed", {
4537
- name: item.name,
4538
- winner: item.selectedFrom,
4539
- hidden: item.shadowedFrom
4540
- }) }, `shadowed-${item.name}-${item.shadowedFrom}-${index}`)),
4541
- diagnostics.brokenPresets.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticBroken", {
4542
- id: item.id,
4543
- reason: item.reason
4544
- }) }, `broken-${item.id}`)),
4545
- diagnostics.unbrowseableSkills.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUnbrowseable", {
4546
- name: item.name,
4547
- reason: item.reason
4548
- }) }, `browse-${item.name}`)),
4549
- diagnostics.undocumentedSkills.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUndocumented", {
4550
- name: item.name,
4551
- summary: item.summary
4552
- }) }, `undocumented-${item.name}`))
4553
- ]
4554
- }),
4555
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4556
- className: "mc-tabs",
4557
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
4558
- className: "mc-main-tabs",
4559
- items: [{
4560
- value: "tools",
4561
- id: "mc-tab-tools",
4562
- panelId: "mc-panel-tools",
4563
- label: t("toolsGroup")
4564
- }, {
4565
- value: "skills",
4566
- id: "mc-tab-skills",
4567
- panelId: "mc-panel-skills",
4568
- label: t("skillsGroup")
4569
- }],
4570
- value: activeTab,
4571
- onChange: onTabChange,
4572
- label: t("title")
4573
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4574
- className: "mc-summary",
4575
- children: summary.map(({ cls, count }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4576
- className: `mc-chip mc-chip--${cls}`,
4974
+ }),
4975
+ (updatePhase === "done" || updateStatus?.updateAvailable && !updateStatus.updatable && updateStatus.reason !== "local") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4976
+ className: "mc-update-status",
4977
+ role: "status",
4978
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: updatePhase === "done" ? t(updateStatus?.reason === "local" ? "pluginSwitched" : "pluginUpdated") : t("updateAvailable", { version: updateStatus?.latest ?? "" }) }), updatePhase !== "done" && updateStatus?.updateAvailable && !updateStatus.updatable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(updateStatus.reason === "non-registry" ? "pluginNonRegistrySource" : "pluginProfileUnavailable") })]
4979
+ }),
4980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CatalogStatStrip, { cells: statCells }),
4981
+ diagnostics !== null && skillDiagnosticCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
4982
+ className: "mc-cat-diag",
4983
+ "data-v3-catalog-diagnostics": "true",
4984
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("diagnosticTitle", { count: skillDiagnosticCount }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4985
+ className: "mc-cat-diag-body",
4577
4986
  children: [
4578
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4579
- className: `mc-dot mc-dot--${cls}`,
4580
- "aria-hidden": "true"
4581
- }),
4582
- t(CLASS_SHORT_KEYS[cls]),
4583
- " · ",
4584
- count
4987
+ diagnostics.unavailableSkills.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUnavailable", {
4988
+ name: item.name,
4989
+ source: item.source,
4990
+ preset: item.preset ?? ""
4991
+ }) }, `unavailable-${item.name}-${item.preset ?? ""}-${index}`)),
4992
+ diagnostics.shadowedSkills.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticShadowed", {
4993
+ name: item.name,
4994
+ winner: item.selectedFrom,
4995
+ hidden: item.shadowedFrom
4996
+ }) }, `shadowed-${item.name}-${item.shadowedFrom}-${index}`)),
4997
+ diagnostics.brokenPresets.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticBroken", {
4998
+ id: item.id,
4999
+ reason: item.reason
5000
+ }) }, `broken-${item.id}`)),
5001
+ diagnostics.unbrowseableSkills.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUnbrowseable", {
5002
+ name: item.name,
5003
+ reason: item.reason
5004
+ }) }, `browse-${item.name}`)),
5005
+ diagnostics.undocumentedSkills.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("diagnosticUndocumented", {
5006
+ name: item.name,
5007
+ summary: item.summary
5008
+ }) }, `undocumented-${item.name}`))
4585
5009
  ]
4586
- }, cls))
4587
- })]
4588
- }),
4589
- snapshot.rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4590
- className: "mc-filter",
4591
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
4592
- type: "search",
4593
- value: filter,
4594
- placeholder: t("filterByName"),
4595
- "aria-label": t("filterByName"),
4596
- title: t("filterHint"),
4597
- onChange: (e) => setFilter(e.target.value)
4598
- })
4599
- }),
4600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4601
- role: "tabpanel",
4602
- id: "mc-panel-tools",
4603
- "aria-labelledby": "mc-tab-tools",
4604
- hidden: activeTab !== "tools",
4605
- className: "mc-panel",
4606
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4607
- className: "mc-panel-inner",
4608
- children: servers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4609
- className: "mc-empty",
4610
- children: needle === "" ? t("emptyTools") : t("filterNoMatch")
4611
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: servers.map(({ server, tools }) => {
4612
- const open = openServers.has(server);
4613
- const counts = CLASS_KEYS.map((cls) => ({
4614
- cls,
4615
- count: countByClass(tools, cls)
4616
- }));
4617
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4618
- className: "mc-group",
4619
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4620
- className: "mc-group-header",
4621
- role: "button",
4622
- tabIndex: 0,
4623
- "aria-expanded": open,
4624
- onClick: () => onToggleServer(server),
4625
- onKeyDown: (e) => {
4626
- if (e.key === "Enter" || e.key === " ") {
4627
- e.preventDefault();
4628
- onToggleServer(server);
4629
- }
4630
- },
4631
- children: [
4632
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTriangleRightFill, {
4633
- size: 12,
4634
- className: `mc-chevron${open ? " mc-chevron--open" : ""}`
4635
- }),
4636
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4637
- className: "mc-server-name",
4638
- children: server === "built-in" ? t("builtInGroup") : server
4639
- }),
4640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4641
- className: "mc-server-count",
4642
- children: t("toolCount", { count: tools.length })
4643
- }),
4644
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4645
- className: "mc-server-meta",
4646
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4647
- className: "mc-counts",
4648
- children: counts.filter(({ count }) => count > 0).map(({ cls, count }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4649
- type: "button",
4650
- className: `mc-count mc-count--${cls}`,
4651
- disabled: busy,
4652
- title: t("cycleHint"),
4653
- onClick: (e) => {
4654
- e.stopPropagation();
4655
- onCycle(tools.filter((t) => t.class === cls).map((t) => t.id), "tool");
4656
- },
4657
- children: [
4658
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4659
- className: `mc-dot mc-dot--${cls}`,
4660
- "aria-hidden": "true"
4661
- }),
4662
- t(CLASS_SHORT_KEYS[cls]),
4663
- " ",
4664
- count
4665
- ]
4666
- }, cls))
4667
- }), server !== "built-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4668
- type: "button",
4669
- className: "mc-count mc-count--action",
4670
- disabled: busy,
4671
- onClick: (e) => {
4672
- e.stopPropagation();
4673
- openMcpEdit(server);
4674
- },
4675
- children: t("edit")
4676
- })]
4677
- })
4678
- ]
4679
- }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4680
- className: "mc-tools",
4681
- children: tools.map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4682
- className: "mc-tool",
4683
- title: tool.classLabel,
4684
- role: "button",
4685
- tabIndex: 0,
4686
- "aria-label": tool.name,
4687
- onClick: () => void openToolDetail(tool.id),
4688
- onKeyDown: (e) => {
4689
- if (e.key === "Enter" || e.key === " ") {
4690
- e.preventDefault();
4691
- openToolDetail(tool.id);
4692
- }
4693
- },
4694
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4695
- className: "mc-tool-name",
4696
- title: tool.name,
4697
- children: server !== "built-in" && tool.name.startsWith(`mcp__${server}__`) ? tool.name.slice(`mcp__${server}__`.length) : tool.name
4698
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4699
- className: "mc-tool-meta",
4700
- children: [
4701
- tool.mandatory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, { children: t("mandatory") }),
4702
- tool.class === "on-demand" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4703
- size: "sm",
4704
- variant: "ghost",
4705
- className: "mc-count--action",
4706
- title: t("editSearchMetadata"),
4707
- onClick: (e) => {
4708
- e.stopPropagation();
4709
- openSearchEdit(tool.id, "tool");
4710
- },
4711
- children: t("searchMetadataButton")
4712
- }),
4713
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4714
- type: "button",
4715
- className: `mc-dot-btn mc-count--${tool.class}`,
4716
- disabled: busy || tool.mandatory,
4717
- title: tool.classLabel,
4718
- "aria-label": tool.classLabel,
4719
- onClick: (e) => {
4720
- e.stopPropagation();
4721
- onCycle([tool.id], "tool");
4722
- },
4723
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4724
- className: `mc-dot mc-dot--${tool.class}`,
4725
- "aria-hidden": "true"
4726
- })
4727
- })
4728
- ]
4729
- })]
4730
- }, tool.id))
4731
- })]
4732
- }, server);
4733
- }) })
4734
- })
4735
- }),
4736
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4737
- role: "tabpanel",
4738
- id: "mc-panel-skills",
4739
- "aria-labelledby": "mc-tab-skills",
4740
- hidden: activeTab !== "skills",
4741
- className: `mc-panel${skills.length > 0 ? " mc-panel--tight" : ""}`,
4742
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4743
- className: "mc-panel-inner",
4744
- children: skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4745
- className: "mc-empty",
4746
- children: needle === "" ? t("emptySkills") : t("filterNoMatch")
4747
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4748
- className: "mc-subtabs",
4749
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
4750
- items: [
4751
- {
4752
- value: "global",
4753
- id: "mc-skill-tab-global",
4754
- panelId: "mc-skill-panel-global",
4755
- label: t("globalSkills")
4756
- },
4757
- {
4758
- value: "project",
4759
- id: "mc-skill-tab-project",
4760
- panelId: "mc-skill-panel-project",
4761
- label: t("projectSkills")
4762
- },
4763
- {
4764
- value: "preset",
4765
- id: "mc-skill-tab-preset",
4766
- panelId: "mc-skill-panel-preset",
4767
- label: t("presetSkills")
4768
- }
4769
- ],
4770
- value: skillTab,
4771
- onChange: setSkillTab,
4772
- label: t("skillsGroup")
5010
+ })]
5011
+ }),
5012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5013
+ role: "tabpanel",
5014
+ id: "mc-panel-tools",
5015
+ "aria-labelledby": "mc-tab-tools",
5016
+ hidden: activeTab !== "tools",
5017
+ className: "mc-panel",
5018
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5019
+ className: "mc-panel-inner",
5020
+ children: servers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5021
+ className: "mc-empty",
5022
+ children: needle === "" ? t("emptyTools") : t("filterNoMatch")
5023
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolGroupsView, {
5024
+ t,
5025
+ groups: servers,
5026
+ openServers,
5027
+ busy,
5028
+ usage: catalogUsage,
5029
+ onToggle: onToggleServer,
5030
+ onCycle,
5031
+ onOpenDetail: (id) => void openToolDetail(id),
5032
+ onEditServer: (server) => void openMcpEdit(server),
5033
+ onEditSearch: (id) => void openSearchEdit(id, "tool")
4773
5034
  })
4774
- }), skillTab === "global" ? globalSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
4775
- skills: globalSkills,
4776
- remote,
4777
- busy,
4778
- t,
4779
- onCycle,
4780
- editableSkills,
4781
- onEditSkill: openSkillEdit,
4782
- onAdoptSkill: adoptSkill,
4783
- onEditSearch: (id) => void openSearchEdit(id, "skill")
4784
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4785
- className: "mc-empty",
4786
- children: t("emptyGlobalSkills")
4787
- }) : skillTab === "project" ? projectSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
4788
- skills: projectSkills,
4789
- remote,
4790
- busy,
4791
- t,
4792
- onCycle,
4793
- editableSkills,
4794
- onEditSkill: openSkillEdit,
4795
- onAdoptSkill: adoptSkill,
4796
- onEditSearch: (id) => void openSearchEdit(id, "skill")
4797
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4798
- className: "mc-empty",
4799
- children: t("emptyProjectSkills")
4800
- }) : presetGroups.length > 0 ? presetGroups.map(([preset, group]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4801
- className: "mc-skill-group",
4802
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4803
- className: "mc-skill-group-title",
4804
- children: preset
4805
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
4806
- skills: group,
5035
+ })
5036
+ }),
5037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5038
+ role: "tabpanel",
5039
+ id: "mc-panel-skills",
5040
+ "aria-labelledby": "mc-tab-skills",
5041
+ hidden: activeTab !== "skills",
5042
+ className: `mc-panel${skills.length > 0 ? " mc-panel--tight" : ""}`,
5043
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5044
+ className: "mc-panel-inner",
5045
+ children: skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5046
+ className: "mc-empty",
5047
+ children: needle === "" ? t("emptySkills") : t("filterNoMatch")
5048
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5049
+ className: "mc-subtabs",
5050
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
5051
+ items: [
5052
+ {
5053
+ value: "global",
5054
+ id: "mc-skill-tab-global",
5055
+ panelId: "mc-skill-panel-global",
5056
+ label: t("globalSkills")
5057
+ },
5058
+ {
5059
+ value: "project",
5060
+ id: "mc-skill-tab-project",
5061
+ panelId: "mc-skill-panel-project",
5062
+ label: t("projectSkills")
5063
+ },
5064
+ {
5065
+ value: "preset",
5066
+ id: "mc-skill-tab-preset",
5067
+ panelId: "mc-skill-panel-preset",
5068
+ label: t("presetSkills")
5069
+ }
5070
+ ],
5071
+ value: skillTab,
5072
+ onChange: setSkillTab,
5073
+ label: t("skillsGroup")
5074
+ })
5075
+ }), skillTab === "global" ? globalSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
5076
+ skills: globalSkills,
4807
5077
  remote,
4808
5078
  busy,
4809
5079
  t,
@@ -4812,307 +5082,339 @@ function ReadyBody(props) {
4812
5082
  onEditSkill: openSkillEdit,
4813
5083
  onAdoptSkill: adoptSkill,
4814
5084
  onEditSearch: (id) => void openSearchEdit(id, "skill")
4815
- })]
4816
- }, preset)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4817
- className: "mc-empty",
4818
- children: t("emptyPresetSkills")
4819
- })] })
4820
- })
4821
- }),
4822
- toolDetail !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4823
- open: true,
4824
- onClose: () => setToolDetail(null),
4825
- title: toolDetail.detail?.name ?? toolDetail.id,
4826
- closeLabel: t("previewClose"),
4827
- children: [
4828
- toolDetail.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4829
- className: "mc-preview-hint",
4830
- children: "…"
4831
- }),
4832
- toolDetail.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4833
- className: "mc-preview-hint",
4834
- children: toolDetail.message
4835
- }),
4836
- toolDetail.status === "ready" && toolDetail.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4837
- className: "mc-preview-body",
4838
- children: [toolDetail.detail.description.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4839
- className: "mc-tool-description",
4840
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkdownPreview, {
4841
- text: toolDetail.detail.description,
4842
- t
4843
- })
4844
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
4845
- className: "mc-preview-plain",
4846
- children: JSON.stringify({
4847
- type: "function",
4848
- function: {
4849
- name: toolDetail.detail.name,
4850
- parameters: toolDetail.detail.parameters
4851
- }
4852
- }, null, 2)
4853
- })]
5085
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5086
+ className: "mc-empty",
5087
+ children: t("emptyGlobalSkills")
5088
+ }) : skillTab === "project" ? projectSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
5089
+ skills: projectSkills,
5090
+ remote,
5091
+ busy,
5092
+ t,
5093
+ onCycle,
5094
+ editableSkills,
5095
+ onEditSkill: openSkillEdit,
5096
+ onAdoptSkill: adoptSkill,
5097
+ onEditSearch: (id) => void openSearchEdit(id, "skill")
5098
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5099
+ className: "mc-empty",
5100
+ children: t("emptyProjectSkills")
5101
+ }) : presetGroups.length > 0 ? presetGroups.map(([preset, group]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5102
+ className: "mc-skill-group",
5103
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5104
+ className: "mc-skill-group-title",
5105
+ children: preset
5106
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
5107
+ skills: group,
5108
+ remote,
5109
+ busy,
5110
+ t,
5111
+ onCycle,
5112
+ editableSkills,
5113
+ onEditSkill: openSkillEdit,
5114
+ onAdoptSkill: adoptSkill,
5115
+ onEditSearch: (id) => void openSearchEdit(id, "skill")
5116
+ })]
5117
+ }, preset)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5118
+ className: "mc-empty",
5119
+ children: t("emptyPresetSkills")
5120
+ })] })
4854
5121
  })
4855
- ]
4856
- }),
4857
- updateDialogOpen && updateStatus?.updateAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4858
- open: true,
4859
- onClose: closeUpdateDialog,
4860
- title: t("updateDialogTitle"),
4861
- closeLabel: t("previewClose"),
4862
- className: "mc-update-dialog",
4863
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4864
- className: "mc-confirm-actions mc-update-actions",
5122
+ }),
5123
+ toolDetail !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5124
+ open: true,
5125
+ onClose: () => setToolDetail(null),
5126
+ title: toolDetail.detail?.name ?? toolDetail.id,
5127
+ closeLabel: t("previewClose"),
4865
5128
  children: [
4866
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4867
- type: "button",
4868
- variant: "outline",
4869
- size: "sm",
4870
- className: "mc-update-cancel",
4871
- disabled: updatePhase === "installing",
4872
- onClick: closeUpdateDialog,
4873
- children: t("cancel")
5129
+ toolDetail.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5130
+ className: "mc-preview-hint",
5131
+ children: "…"
4874
5132
  }),
4875
- updateStatus.reason === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4876
- type: "button",
4877
- variant: "outline",
4878
- size: "sm",
4879
- disabled: updatePhase === "installing",
4880
- onClick: () => setSourceStepsOpen((value) => !value),
4881
- children: t(sourceStepsOpen ? "updateSourceStepsHide" : "updateSourceSteps")
5133
+ toolDetail.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5134
+ className: "mc-preview-hint",
5135
+ children: toolDetail.message
4882
5136
  }),
4883
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4884
- type: "button",
4885
- variant: "primary",
4886
- size: "sm",
4887
- disabled: updatePhase === "installing",
4888
- onClick: () => void installUpdate(updateStatus.reason === "local" ? "local" : "registry"),
4889
- children: t(updateStatus.reason === "local" ? "switchAndInstall" : "installUpdate")
5137
+ toolDetail.status === "ready" && toolDetail.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5138
+ className: "mc-preview-body",
5139
+ children: [toolDetail.detail.description.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5140
+ className: "mc-tool-description",
5141
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkdownPreview, {
5142
+ text: toolDetail.detail.description,
5143
+ t
5144
+ })
5145
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
5146
+ className: "mc-preview-plain",
5147
+ children: JSON.stringify({
5148
+ type: "function",
5149
+ function: {
5150
+ name: toolDetail.detail.name,
5151
+ parameters: toolDetail.detail.parameters
5152
+ }
5153
+ }, null, 2)
5154
+ })]
4890
5155
  })
4891
5156
  ]
4892
5157
  }),
4893
- children: [
4894
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateVersionComparison", {
4895
- current: updateStatus.current,
4896
- latest: updateStatus.latest
4897
- }) }),
4898
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(updateStatus.reason === "local" ? "updateLocalNote" : "updateNpmNote") }),
4899
- updateStatus.reason === "local" && sourceStepsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4900
- className: "mc-update-steps",
5158
+ updateDialogOpen && updateStatus?.updateAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5159
+ open: true,
5160
+ onClose: closeUpdateDialog,
5161
+ title: t("updateDialogTitle"),
5162
+ closeLabel: t("previewClose"),
5163
+ className: "mc-update-dialog",
5164
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5165
+ className: "mc-confirm-actions mc-update-actions",
4901
5166
  children: [
4902
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateSourceStepsIntro") }),
4903
- updateStatus.sourcePath && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4904
- className: "mc-confirm-path",
4905
- children: updateStatus.sourcePath
4906
- }),
4907
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: "git pull\npnpm install\npnpm run build" }),
4908
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateSourceStepsRestart") })
4909
- ]
4910
- }),
4911
- updatePhase === "installing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4912
- role: "status",
4913
- children: t("updatingPlugin")
4914
- }),
4915
- updatePhase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4916
- className: "mc-error",
4917
- role: "alert",
4918
- children: [
4919
- t("pluginUpdateFailed"),
4920
- " ",
4921
- updateError
4922
- ]
4923
- })
4924
- ]
4925
- }),
4926
- searchEdit !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4927
- open: true,
4928
- onClose: () => setSearchEdit(null),
4929
- title: `${t("editSearchMetadata")} · ${searchEdit.id}`,
4930
- closeLabel: t("previewClose"),
4931
- className: "mc-search-dialog",
4932
- children: [
4933
- searchEdit.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4934
- className: "mc-preview-hint",
4935
- children: "…"
4936
- }),
4937
- searchEdit.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4938
- className: "mc-preview-hint",
4939
- children: searchEdit.message
4940
- }),
4941
- searchEdit.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4942
- className: "mc-confirm-body",
4943
- children: [
4944
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4945
- className: "mc-search-field",
4946
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4947
- className: "mc-search-label-row",
4948
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4949
- htmlFor: "mc-search-description",
4950
- children: t("searchDescription")
4951
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4952
- id: "mc-search-description-note",
4953
- children: t("searchMetadataNote")
4954
- })]
4955
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4956
- id: "mc-search-description",
4957
- "aria-describedby": "mc-search-description-note",
4958
- value: searchDescription,
4959
- maxLength: searchEdit.metadata?.maxChars,
4960
- disabled: searchSaving,
4961
- onChange: (e) => setSearchDescription(e.target.value)
4962
- })]
4963
- }),
4964
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4965
- className: "mc-search-hint",
4966
- children: [
4967
- searchDescription.length,
4968
- "/",
4969
- searchEdit.metadata?.maxChars
4970
- ]
5167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5168
+ type: "button",
5169
+ variant: "outline",
5170
+ size: "sm",
5171
+ className: "mc-update-cancel",
5172
+ disabled: updatePhase === "installing",
5173
+ onClick: closeUpdateDialog,
5174
+ children: t("cancel")
4971
5175
  }),
4972
- searchEdit.message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4973
- className: "mc-error",
4974
- children: searchEdit.message
5176
+ updateStatus.reason === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5177
+ type: "button",
5178
+ variant: "outline",
5179
+ size: "sm",
5180
+ disabled: updatePhase === "installing",
5181
+ onClick: () => setSourceStepsOpen((value) => !value),
5182
+ children: t(sourceStepsOpen ? "updateSourceStepsHide" : "updateSourceSteps")
4975
5183
  }),
4976
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4977
- className: "mc-confirm-actions",
4978
- children: [searchDescription !== searchEdit.metadata?.defaultDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4979
- size: "sm",
4980
- variant: "ghost",
4981
- disabled: searchSaving,
4982
- title: t("searchMetadataRestoreHint"),
4983
- onClick: () => setSearchDescription(searchEdit.metadata?.defaultDescription ?? ""),
4984
- children: t("searchMetadataRestore")
4985
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4986
- size: "sm",
4987
- variant: "primary",
4988
- disabled: searchSaving,
4989
- onClick: () => void saveSearchEdit(),
4990
- children: t("searchMetadataSave")
4991
- })]
5184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5185
+ type: "button",
5186
+ variant: "primary",
5187
+ size: "sm",
5188
+ disabled: updatePhase === "installing",
5189
+ onClick: () => void installUpdate(updateStatus.reason === "local" ? "local" : "registry"),
5190
+ children: t(updateStatus.reason === "local" ? "switchAndInstall" : "installUpdate")
4992
5191
  })
4993
5192
  ]
4994
- })
4995
- ]
4996
- }),
4997
- (registerOpen || editMcp !== void 0 || editSkill !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LocationModal, {
4998
- remote,
4999
- t,
5000
- defaultKind: activeTab === "skills" ? "skill" : "mcp",
5001
- ...editMcp !== void 0 ? { editMcp } : {},
5002
- ...editSkill !== void 0 ? { editSkill } : {},
5003
- onClose: () => {
5004
- setRegisterOpen(false);
5005
- setEditMcp(void 0);
5006
- setEditSkill(void 0);
5007
- },
5008
- onChanged: (notice, refreshMcpServer) => {
5009
- if (refreshMcpServer !== void 0) onRefreshMcp(refreshMcpServer);
5010
- else onRefresh();
5011
- if (notice !== void 0) onNotice(notice);
5012
- }
5013
- }),
5014
- catalogDocs !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5015
- open: true,
5016
- onClose: () => setCatalogDocs(null),
5017
- title: t("viewCatalog"),
5018
- closeLabel: t("previewClose"),
5019
- children: [
5020
- catalogDocs.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5021
- className: "mc-preview-hint",
5022
- children: "…"
5023
- }),
5024
- catalogDocs.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5025
- className: "mc-preview-hint",
5026
- children: catalogDocs.message
5027
5193
  }),
5028
- catalogDocs.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5029
- className: "mc-subtabs mc-catalog-tabs",
5030
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
5031
- items: [{
5032
- value: "policy",
5033
- id: "mc-cat-tab-policy",
5034
- panelId: "mc-cat-panel-policy",
5035
- label: t("catalogPolicy")
5036
- }, {
5037
- value: "catalog",
5038
- id: "mc-cat-tab-catalog",
5039
- panelId: "mc-cat-panel-catalog",
5040
- label: t("catalogOnDemand")
5041
- }],
5042
- value: catalogTab,
5043
- onChange: setCatalogTab,
5044
- label: t("viewCatalog")
5194
+ children: [
5195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateVersionComparison", {
5196
+ current: updateStatus.current,
5197
+ latest: updateStatus.latest
5198
+ }) }),
5199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(updateStatus.reason === "local" ? "updateLocalNote" : "updateNpmNote") }),
5200
+ updateStatus.reason === "local" && sourceStepsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5201
+ className: "mc-update-steps",
5202
+ children: [
5203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateSourceStepsIntro") }),
5204
+ updateStatus.sourcePath && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5205
+ className: "mc-confirm-path",
5206
+ children: updateStatus.sourcePath
5207
+ }),
5208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: "git pull\npnpm install\npnpm run build" }),
5209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("updateSourceStepsRestart") })
5210
+ ]
5211
+ }),
5212
+ updatePhase === "installing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5213
+ role: "status",
5214
+ children: t("updatingPlugin")
5215
+ }),
5216
+ updatePhase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5217
+ className: "mc-error",
5218
+ role: "alert",
5219
+ children: [
5220
+ t("pluginUpdateFailed"),
5221
+ " ",
5222
+ updateError
5223
+ ]
5045
5224
  })
5046
- }), catalogTab === "policy" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5047
- className: "mc-catalog-path",
5048
- children: t("catalogPolicyNote")
5049
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
5050
- className: "mc-preview-body",
5051
- children: catalogDocs.docs.policyYaml
5052
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5053
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5054
- className: "mc-catalog-toolbar",
5055
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5056
- className: "mc-catalog-path",
5057
- children: t("catalogSearchNote", { count: catalogDocs.docs.onDemand.length })
5058
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5059
- className: "mc-summary-setting",
5060
- children: [
5061
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5062
- htmlFor: "mc-summary-limit",
5063
- children: t("summaryLimit")
5064
- }),
5065
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5066
- id: "mc-summary-limit",
5067
- type: "number",
5068
- min: 20,
5069
- step: 1,
5070
- value: summaryLimitDraft,
5071
- disabled: !catalogDocs.docs.searchSettingsEnabled || summaryLimitSaving,
5072
- onChange: (e) => {
5073
- setSummaryLimitDraft(e.target.value);
5074
- setSummaryLimitError(void 0);
5075
- }
5076
- }),
5077
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("summaryLimitUnit") }),
5078
- catalogDocs.docs.searchSettingsEnabled && summaryLimitDraft !== String(catalogDocs.docs.summaryMaxChars) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5225
+ ]
5226
+ }),
5227
+ searchEdit !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5228
+ open: true,
5229
+ onClose: () => setSearchEdit(null),
5230
+ title: `${t("editSearchMetadata")} · ${searchEdit.id}`,
5231
+ closeLabel: t("previewClose"),
5232
+ className: "mc-search-dialog",
5233
+ children: [
5234
+ searchEdit.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5235
+ className: "mc-preview-hint",
5236
+ children: "…"
5237
+ }),
5238
+ searchEdit.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5239
+ className: "mc-preview-hint",
5240
+ children: searchEdit.message
5241
+ }),
5242
+ searchEdit.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5243
+ className: "mc-confirm-body",
5244
+ children: [
5245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5246
+ className: "mc-search-field",
5247
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5248
+ className: "mc-search-label-row",
5249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5250
+ htmlFor: "mc-search-description",
5251
+ children: t("searchDescription")
5252
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5253
+ id: "mc-search-description-note",
5254
+ children: t("searchMetadataNote")
5255
+ })]
5256
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
5257
+ id: "mc-search-description",
5258
+ "aria-describedby": "mc-search-description-note",
5259
+ value: searchDescription,
5260
+ maxLength: searchEdit.metadata?.maxChars,
5261
+ disabled: searchSaving,
5262
+ onChange: (e) => setSearchDescription(e.target.value)
5263
+ })]
5264
+ }),
5265
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5266
+ className: "mc-search-hint",
5267
+ children: [
5268
+ searchDescription.length,
5269
+ "/",
5270
+ searchEdit.metadata?.maxChars
5271
+ ]
5272
+ }),
5273
+ searchEdit.message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5274
+ className: "mc-error",
5275
+ children: searchEdit.message
5276
+ }),
5277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5278
+ className: "mc-confirm-actions",
5279
+ children: [searchDescription !== searchEdit.metadata?.defaultDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5280
+ size: "sm",
5281
+ variant: "ghost",
5282
+ disabled: searchSaving,
5283
+ title: t("searchMetadataRestoreHint"),
5284
+ onClick: () => setSearchDescription(searchEdit.metadata?.defaultDescription ?? ""),
5285
+ children: t("searchMetadataRestore")
5286
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5079
5287
  size: "sm",
5080
5288
  variant: "primary",
5081
- disabled: summaryLimitSaving,
5082
- onClick: () => void saveSummaryLimit(),
5289
+ disabled: searchSaving,
5290
+ onClick: () => void saveSearchEdit(),
5083
5291
  children: t("searchMetadataSave")
5084
- }),
5085
- !catalogDocs.docs.searchSettingsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("summaryLimitDisabled") })
5086
- ]
5087
- })]
5292
+ })]
5293
+ })
5294
+ ]
5295
+ })
5296
+ ]
5297
+ }),
5298
+ (registerOpen || editMcp !== void 0 || editSkill !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LocationModal, {
5299
+ remote,
5300
+ t,
5301
+ defaultKind: activeTab === "skills" ? "skill" : "mcp",
5302
+ ...editMcp !== void 0 ? { editMcp } : {},
5303
+ ...editSkill !== void 0 ? { editSkill } : {},
5304
+ onClose: () => {
5305
+ setRegisterOpen(false);
5306
+ setEditMcp(void 0);
5307
+ setEditSkill(void 0);
5308
+ },
5309
+ onChanged: (notice, refreshMcpServer) => {
5310
+ if (refreshMcpServer !== void 0) onRefreshMcp(refreshMcpServer);
5311
+ else onRefresh();
5312
+ if (notice !== void 0) onNotice(notice);
5313
+ }
5314
+ }),
5315
+ catalogDocs !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5316
+ open: true,
5317
+ onClose: () => setCatalogDocs(null),
5318
+ title: t("viewCatalog"),
5319
+ closeLabel: t("previewClose"),
5320
+ children: [
5321
+ catalogDocs.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5322
+ className: "mc-preview-hint",
5323
+ children: "…"
5088
5324
  }),
5089
- summaryLimitError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5090
- className: "mc-error mc-catalog-limit-error",
5091
- children: summaryLimitError
5325
+ catalogDocs.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5326
+ className: "mc-preview-hint",
5327
+ children: catalogDocs.message
5092
5328
  }),
5093
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5329
+ catalogDocs.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5330
+ className: "mc-subtabs mc-catalog-tabs",
5331
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
5332
+ items: [{
5333
+ value: "policy",
5334
+ id: "mc-cat-tab-policy",
5335
+ panelId: "mc-cat-panel-policy",
5336
+ label: t("catalogPolicy")
5337
+ }, {
5338
+ value: "catalog",
5339
+ id: "mc-cat-tab-catalog",
5340
+ panelId: "mc-cat-panel-catalog",
5341
+ label: t("catalogOnDemand")
5342
+ }],
5343
+ value: catalogTab,
5344
+ onChange: setCatalogTab,
5345
+ label: t("viewCatalog")
5346
+ })
5347
+ }), catalogTab === "policy" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5348
+ className: "mc-catalog-path",
5349
+ children: t("catalogPolicyNote")
5350
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
5094
5351
  className: "mc-preview-body",
5095
- children: [catalogDocs.docs.onDemand.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5096
- className: "mc-preview-hint",
5097
- children: t("catalogEmpty")
5098
- }), catalogDocs.docs.onDemand.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5099
- className: "mc-search-result",
5100
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5101
- className: "mc-search-result-head",
5102
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: row.kind === "tool" ? "Tool" : "Skill" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5103
- className: "mc-search-result-id",
5104
- children: row.id
5105
- })]
5106
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5107
- className: "mc-search-result-summary",
5108
- children: row.summary
5352
+ children: catalogDocs.docs.policyYaml
5353
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5355
+ className: "mc-catalog-toolbar",
5356
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5357
+ className: "mc-catalog-path",
5358
+ children: t("catalogSearchNote", { count: catalogDocs.docs.onDemand.length })
5359
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5360
+ className: "mc-summary-setting",
5361
+ children: [
5362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5363
+ htmlFor: "mc-summary-limit",
5364
+ children: t("summaryLimit")
5365
+ }),
5366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5367
+ id: "mc-summary-limit",
5368
+ type: "number",
5369
+ min: 20,
5370
+ step: 1,
5371
+ value: summaryLimitDraft,
5372
+ disabled: !catalogDocs.docs.searchSettingsEnabled || summaryLimitSaving,
5373
+ onChange: (e) => {
5374
+ setSummaryLimitDraft(e.target.value);
5375
+ setSummaryLimitError(void 0);
5376
+ }
5377
+ }),
5378
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("summaryLimitUnit") }),
5379
+ catalogDocs.docs.searchSettingsEnabled && summaryLimitDraft !== String(catalogDocs.docs.summaryMaxChars) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5380
+ size: "sm",
5381
+ variant: "primary",
5382
+ disabled: summaryLimitSaving,
5383
+ onClick: () => void saveSummaryLimit(),
5384
+ children: t("searchMetadataSave")
5385
+ }),
5386
+ !catalogDocs.docs.searchSettingsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("summaryLimitDisabled") })
5387
+ ]
5109
5388
  })]
5110
- }, `${row.kind}:${row.id}`))]
5111
- })
5112
- ] })] })
5113
- ]
5114
- })
5115
- ] });
5389
+ }),
5390
+ summaryLimitError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5391
+ className: "mc-error mc-catalog-limit-error",
5392
+ children: summaryLimitError
5393
+ }),
5394
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5395
+ className: "mc-preview-body",
5396
+ children: [catalogDocs.docs.onDemand.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5397
+ className: "mc-preview-hint",
5398
+ children: t("catalogEmpty")
5399
+ }), catalogDocs.docs.onDemand.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5400
+ className: "mc-search-result",
5401
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5402
+ className: "mc-search-result-head",
5403
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: row.kind === "tool" ? "Tool" : "Skill" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5404
+ className: "mc-search-result-id",
5405
+ children: row.id
5406
+ })]
5407
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5408
+ className: "mc-search-result-summary",
5409
+ children: row.summary
5410
+ })]
5411
+ }, `${row.kind}:${row.id}`))]
5412
+ })
5413
+ ] })] })
5414
+ ]
5415
+ })
5416
+ ]
5417
+ });
5116
5418
  }
5117
5419
  function projectRootForSkill(path) {
5118
5420
  return path.match(/^(.*)\/(?:\.dsh|\.agents)\/skills(?:\/.*)?$/)?.[1] ?? path;
@@ -5298,111 +5600,118 @@ function SkillList(props) {
5298
5600
  show the directory the click will link. */
5299
5601
  const confirmAdoptRow = confirmAdopt === null ? void 0 : skills.find((row) => row.id === confirmAdopt);
5300
5602
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5301
- skills.map((skill) => {
5302
- const open = openSkill === skill.id;
5303
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5304
- className: "mc-skill",
5305
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5306
- className: "mc-skill-row",
5307
- role: "button",
5308
- tabIndex: 0,
5309
- "aria-expanded": open,
5310
- onClick: () => void toggleSkill(skill.id),
5311
- onKeyDown: (e) => {
5312
- if (e.key === "Enter" || e.key === " ") {
5603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5604
+ className: "v3-catgrid",
5605
+ children: skills.map((skill) => {
5606
+ const open = openSkill === skill.id;
5607
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
5608
+ className: "v3-group",
5609
+ open,
5610
+ "data-v3-skill": skill.id,
5611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
5612
+ onClick: (e) => {
5313
5613
  e.preventDefault();
5314
5614
  toggleSkill(skill.id);
5315
- }
5316
- },
5317
- children: [
5318
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTriangleRightFill, {
5319
- size: 12,
5320
- className: `mc-chevron${open ? " mc-chevron--open" : ""}`
5321
- }),
5322
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5323
- className: "mc-skill-name",
5324
- children: skill.name
5325
- }),
5326
- PROJECT_SKILL_SOURCES.has(skill.source ?? "") && skill.path !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5327
- className: "mc-source",
5328
- title: skill.path,
5329
- children: t("projectRootLabel", { path: projectRootForSkill(skill.path) })
5330
- }),
5331
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5332
- className: "mc-skill-meta",
5333
- children: [
5334
- skill.mandatory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, { children: t("mandatory") }),
5335
- skill.class === "on-demand" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5336
- size: "sm",
5337
- variant: "ghost",
5338
- className: "mc-count--action",
5339
- title: t("editSearchMetadata"),
5340
- onClick: (e) => {
5341
- e.stopPropagation();
5342
- onEditSearch(skill.id);
5343
- },
5344
- children: t("searchMetadataButton")
5345
- }),
5346
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5347
- type: "button",
5348
- className: `mc-count mc-count--${skill.class}`,
5349
- disabled: busy || skill.mandatory,
5350
- title: skill.classLabel,
5351
- onClick: (e) => {
5352
- e.stopPropagation();
5353
- onCycle([skill.id], "skill");
5354
- },
5355
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5356
- className: `mc-dot mc-dot--${skill.class}`,
5357
- "aria-hidden": "true"
5358
- }), t(CLASS_SHORT_KEYS[skill.class])]
5359
- }),
5360
- !editableSkills.has(skill.id) && (skill.preset !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5361
- className: "mc-source",
5362
- title: t("skillFromPresetHint"),
5363
- children: t("skillFromPreset", { preset: skill.preset })
5364
- }) : ADOPTABLE_SKILL_SOURCES.has(skill.source ?? "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5365
- type: "button",
5366
- className: "mc-count mc-count--action",
5367
- disabled: busy,
5368
- title: t("adoptSkillHint"),
5369
- onClick: (e) => {
5370
- e.stopPropagation();
5371
- setConfirmAdopt(skill.id);
5372
- },
5373
- children: t("adoptSkill")
5374
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5375
- className: "mc-source",
5376
- title: t("skillSourceHint"),
5377
- children: t("skillSource", { source: sourceLabel(skill.source, t) })
5378
- })),
5379
- editableSkills.has(skill.id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5380
- type: "button",
5381
- className: "mc-count mc-count--action",
5382
- disabled: busy,
5383
- onClick: (e) => {
5384
- e.stopPropagation();
5385
- onEditSkill(skill.id, skill.source);
5386
- },
5387
- children: t("edit")
5615
+ },
5616
+ children: [
5617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
5618
+ className: "v3-chev",
5619
+ viewBox: "0 0 12 12",
5620
+ fill: "none",
5621
+ "aria-hidden": "true",
5622
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
5623
+ d: "M4.5 2.5 8 6l-3.5 3.5",
5624
+ stroke: "currentColor",
5625
+ strokeWidth: 1.4,
5626
+ strokeLinecap: "round",
5627
+ strokeLinejoin: "round"
5388
5628
  })
5389
- ]
5629
+ }),
5630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5631
+ className: "v3-gnm",
5632
+ children: skill.name
5633
+ }),
5634
+ PROJECT_SKILL_SOURCES.has(skill.source ?? "") && skill.path !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5635
+ className: "v3-cnt",
5636
+ title: skill.path,
5637
+ children: t("projectRootLabel", { path: projectRootForSkill(skill.path) })
5638
+ }),
5639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5640
+ className: "v3-right",
5641
+ children: [
5642
+ skill.mandatory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5643
+ className: "v3-tag",
5644
+ children: t("mandatory")
5645
+ }),
5646
+ skill.class === "on-demand" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5647
+ type: "button",
5648
+ className: "v3-tag v3-tag--act",
5649
+ title: t("editSearchMetadata"),
5650
+ onClick: (e) => {
5651
+ e.stopPropagation();
5652
+ onEditSearch(skill.id);
5653
+ },
5654
+ children: t("searchMetadataButton")
5655
+ }),
5656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5657
+ type: "button",
5658
+ className: "v3-tag",
5659
+ "data-tone": CLASS_TONE[skill.class],
5660
+ disabled: busy || skill.mandatory,
5661
+ title: skill.classLabel,
5662
+ onClick: (e) => {
5663
+ e.stopPropagation();
5664
+ onCycle([skill.id], "skill");
5665
+ },
5666
+ children: t(CLASS_SHORT_KEYS[skill.class])
5667
+ }),
5668
+ !editableSkills.has(skill.id) && (skill.preset !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5669
+ className: "v3-tag",
5670
+ title: t("skillFromPresetHint"),
5671
+ children: t("skillFromPreset", { preset: skill.preset })
5672
+ }) : ADOPTABLE_SKILL_SOURCES.has(skill.source ?? "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5673
+ type: "button",
5674
+ className: "v3-tag v3-tag--act",
5675
+ disabled: busy,
5676
+ title: t("adoptSkillHint"),
5677
+ onClick: (e) => {
5678
+ e.stopPropagation();
5679
+ setConfirmAdopt(skill.id);
5680
+ },
5681
+ children: t("adoptSkill")
5682
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5683
+ className: "v3-tag",
5684
+ title: t("skillSourceHint"),
5685
+ children: t("skillSource", { source: sourceLabel(skill.source, t) })
5686
+ })),
5687
+ editableSkills.has(skill.id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5688
+ type: "button",
5689
+ className: "v3-tag v3-tag--act",
5690
+ disabled: busy,
5691
+ onClick: (e) => {
5692
+ e.stopPropagation();
5693
+ onEditSkill(skill.id, skill.source);
5694
+ },
5695
+ children: t("edit")
5696
+ })
5697
+ ]
5698
+ })
5699
+ ]
5700
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5701
+ className: "mc-skill-body",
5702
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5703
+ className: "mc-tree",
5704
+ children: [browseErrors[`${skill.id}:`] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5705
+ className: "mc-preview-hint",
5706
+ children: browseErrors[`${skill.id}:`]
5707
+ }), renderEntries(tree.dirs[`${skill.id}:`], skill.id, "") ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5708
+ className: "mc-tree-row",
5709
+ children: "…"
5710
+ })]
5390
5711
  })
5391
- ]
5392
- }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5393
- className: "mc-skill-body",
5394
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5395
- className: "mc-tree",
5396
- children: [browseErrors[`${skill.id}:`] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5397
- className: "mc-preview-hint",
5398
- children: browseErrors[`${skill.id}:`]
5399
- }), renderEntries(tree.dirs[`${skill.id}:`], skill.id, "") ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5400
- className: "mc-tree-row",
5401
- children: "…"
5402
- })]
5403
- })
5404
- })]
5405
- }, skill.id);
5712
+ })]
5713
+ }, skill.id);
5714
+ })
5406
5715
  }),
5407
5716
  confirmAdopt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5408
5717
  open: true,
@@ -6208,11 +6517,7 @@ const COVERAGE_LABELS = Object.freeze({
6208
6517
  partial: "部分",
6209
6518
  unknown: "未知"
6210
6519
  });
6211
- /**
6212
- * Zero-count placeholders. Required for `plugin-tool` / `skill`; the other
6213
- * kinds get a neutral line so no zero is ever presented as "未使用".
6214
- */
6215
- const CALL_INDEX_ZERO_SOURCE_NOTES = Object.freeze({
6520
+ Object.freeze({
6216
6521
  mcp: "本次索引未解析到 MCP 来源的调用;覆盖不完整时不能据此判定未使用。",
6217
6522
  native: "本次索引未解析到原生工具调用;覆盖不完整时不能据此判定未使用。",
6218
6523
  "plugin-tool": "本次索引未解析出插件工具归属(插件 owner 不可证明),0 在这里不等于未使用。",
@@ -6251,10 +6556,6 @@ function formatCallIndexCount(value, totalExact) {
6251
6556
  const text = Math.trunc(value).toLocaleString("en-US");
6252
6557
  return totalExact === true ? text : `≥${text}`;
6253
6558
  }
6254
- /** Human label for the time filter. */
6255
- function callIndexRangeLabel(range) {
6256
- return range === "all" ? "全部" : `${range} 天`;
6257
- }
6258
6559
  /**
6259
6560
  * Local-day window for `range`, expressed as the wire's half-open
6260
6561
  * `[fromMs, toMs)`. `'all'` carries no lower bound.
@@ -6499,24 +6800,6 @@ function callIndexOutcomeMix(summary) {
6499
6800
  total: summary.total
6500
6801
  };
6501
6802
  }
6502
- /**
6503
- * `bySource` for display. Always returns all five kinds in a fixed order, so
6504
- * the distribution never reshuffles between reads; a zero carries its
6505
- * placeholder instead of disappearing.
6506
- */
6507
- function callIndexSourceData(bySource) {
6508
- const total = bySource === void 0 ? 0 : SOURCE_DISPLAY_ORDER.reduce((sum, kind) => sum + bySource[kind], 0);
6509
- return SOURCE_DISPLAY_ORDER.map((kind) => {
6510
- const count = bySource?.[kind] ?? 0;
6511
- return {
6512
- kind,
6513
- label: CALL_INDEX_SOURCE_LABELS[kind],
6514
- count,
6515
- share: total > 0 ? count / total : 0,
6516
- ...count === 0 ? { zeroNote: CALL_INDEX_ZERO_SOURCE_NOTES[kind] } : {}
6517
- };
6518
- });
6519
- }
6520
6803
  /** Fixed chart box; the SVG scales with CSS but the geometry is deterministic. */
6521
6804
  const CALL_INDEX_TREND_GEOMETRY = Object.freeze({
6522
6805
  width: 640,
@@ -6697,25 +6980,293 @@ function callIndexOverviewMetrics(overview, status) {
6697
6980
  ...status?.task === void 0 ? {} : { tasks: status.task }
6698
6981
  };
6699
6982
  }
6700
- /** True when `plugin-tool` and `skill` both resolved to zero. */
6701
- function callIndexHasUnexplainedSources(aggregates) {
6702
- if (aggregates === void 0) return false;
6703
- return aggregates.bySource["plugin-tool"] === 0 && aggregates.bySource.skill === 0;
6704
- }
6705
6983
  /** Short Session id for a dense table cell; the full id stays in `title`. */
6706
6984
  function shortCallIndexSessionId(sessionId) {
6707
6985
  return sessionId.length <= 12 ? sessionId : sessionId.slice(0, 12);
6708
6986
  }
6709
6987
  /** Inline stylesheet; a `<style>` element keeps this file dependency-free. */
6710
6988
  function CallIndexStyles() {
6711
- return (0, react.createElement)("style", { "data-call-index-style": "true" }, ".mc-ci-filter{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0}.mc-ci-filter label{display:flex;gap:4px;align-items:center;font-size:12px}.mc-ci-table-scroll{width:100%;overflow:auto;max-height:min(60vh,560px);overscroll-behavior:contain}.mc-ci-table{width:100%;border-collapse:collapse;font-size:12px}.mc-ci-table thead th{position:sticky;top:0;z-index:1;background:var(--mc-surface,Canvas);box-shadow:inset 0 -1px 0 color-mix(in srgb,currentColor 12%,transparent)}.mc-ci-progress{display:flex;flex-direction:column;gap:4px;margin:8px 0}.mc-ci-progress-track{height:6px;border-radius:999px;overflow:hidden;background:color-mix(in srgb,currentColor 12%,transparent)}.mc-ci-progress-fill{height:100%;border-radius:999px;background:color-mix(in srgb,currentColor 55%,transparent);transition:width .3s ease}.mc-ci-progress-fill.is-indeterminate{animation:mc-ci-indeterminate 1.1s ease-in-out infinite}@keyframes mc-ci-indeterminate{0%{transform:translateX(-60%)}100%{transform:translateX(340%)}}@media (prefers-reduced-motion:reduce){.mc-ci-progress-fill{transition:none}.mc-ci-progress-fill.is-indeterminate{animation:none;width:100%!important;opacity:.4}}.mc-ci-session-picker{display:inline-flex;align-items:center;gap:6px;font-size:12px}.mc-ci-session-picker-label{opacity:.7}.mc-ci-session-select{max-width:280px;padding:3px 6px;border-radius:6px;font-size:12px;border:1px solid color-mix(in srgb,currentColor 22%,transparent);background:transparent;color:inherit}.mc-ci-session-select:disabled{opacity:.5}.mc-ci-progress-meta{display:flex;gap:8px;align-items:baseline;font-size:12px}.mc-ci-progress-value{font-variant-numeric:tabular-nums;font-weight:600}.mc-ci-progress-detail{opacity:.72}.mc-ci-table th,.mc-ci-table td{padding:4px 6px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent);white-space:nowrap}.mc-ci-table tbody tr{cursor:pointer}.mc-ci-table tbody tr[aria-selected=\"true\"]{background:color-mix(in srgb,currentColor 8%,transparent)}.mc-ci-table td:nth-child(2),.mc-ci-table td:nth-child(4){max-width:220px;overflow:hidden;text-overflow:ellipsis}.mc-ci-table th:nth-child(2),.mc-ci-table th:nth-child(4){max-width:220px}.mc-ci-num{font-variant-numeric:tabular-nums}.mc-ci-bar{display:flex;height:8px;gap:1px;margin:4px 0}.mc-ci-bar > span{display:block;height:100%;min-width:2px}.mc-ci-aside{border-left:1px solid color-mix(in srgb,currentColor 15%,transparent);padding-left:12px;min-width:240px;font-size:12px}.mc-ci-metric{display:inline-flex;flex-direction:column;margin-right:18px;min-width:110px;vertical-align:top}.mc-ci-metric-label{font-size:11px;opacity:.7}.mc-ci-metric-value{font-size:18px;font-variant-numeric:tabular-nums}.mc-ci-metric-hint{font-size:11px;opacity:.75}.mc-ci-trend svg{width:100%;max-width:640px;height:auto}");
6989
+ return (0, react.createElement)("style", { "data-call-index-style": "true" }, ".mc-ci-filter{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0}.mc-ci-filter label{display:flex;gap:4px;align-items:center;font-size:12px}.mc-ci-table-scroll{width:100%;overflow:auto;max-height:min(60vh,560px);overscroll-behavior:contain}.mc-ci-table{width:100%;border-collapse:collapse;font-size:12px}.mc-ci-table thead th{position:sticky;top:0;z-index:1;background:var(--mc-surface,Canvas);box-shadow:inset 0 -1px 0 color-mix(in srgb,currentColor 12%,transparent)}.mc-ci-progress{display:flex;flex-direction:column;gap:4px;margin:8px 0}.mc-ci-progress-track{height:6px;border-radius:999px;overflow:hidden;background:color-mix(in srgb,currentColor 12%,transparent)}.mc-ci-progress-fill{height:100%;border-radius:999px;background:color-mix(in srgb,currentColor 55%,transparent);transition:width .3s ease}.mc-ci-progress-fill.is-indeterminate{animation:mc-ci-indeterminate 1.1s ease-in-out infinite}@keyframes mc-ci-indeterminate{0%{transform:translateX(-60%)}100%{transform:translateX(340%)}}@media (prefers-reduced-motion:reduce){.mc-ci-progress-fill{transition:none}.mc-ci-progress-fill.is-indeterminate{animation:none;width:100%!important;opacity:.4}}.mc-ci-session-picker{display:inline-flex;align-items:center;gap:6px;font-size:12px}.mc-ci-session-picker-label{opacity:.7}.mc-ci-session-select{max-width:280px;padding:3px 6px;border-radius:6px;font-size:12px;border:1px solid color-mix(in srgb,currentColor 22%,transparent);background:transparent;color:inherit}.mc-ci-session-select:disabled{opacity:.5}.mc-ci-progress-meta{display:flex;gap:8px;align-items:baseline;font-size:12px}.mc-ci-progress-value{font-variant-numeric:tabular-nums;font-weight:600}.mc-ci-progress-detail{opacity:.72}.mc-ci-table th,.mc-ci-table td{padding:4px 6px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent);white-space:nowrap}.mc-ci-table tbody tr{cursor:pointer}.mc-ci-table tbody tr[aria-selected=\"true\"]{background:color-mix(in srgb,currentColor 8%,transparent)}.mc-ci-table td:nth-child(2),.mc-ci-table td:nth-child(4){max-width:220px;overflow:hidden;text-overflow:ellipsis}.mc-ci-table th:nth-child(2),.mc-ci-table th:nth-child(4){max-width:220px}.mc-ci-num{font-variant-numeric:tabular-nums}.mc-ci-bar{display:flex;height:8px;gap:1px;margin:4px 0}.mc-ci-bar > span{display:block;height:100%;min-width:2px}.mc-ci-aside{border-left:1px solid color-mix(in srgb,currentColor 15%,transparent);padding-left:12px;min-width:240px;font-size:12px}.mc-ci-metric{display:inline-flex;flex-direction:column;margin-right:18px;min-width:110px;vertical-align:top}.mc-ci-metric-label{font-size:11px;opacity:.7}.mc-ci-metric-value{font-size:18px;font-variant-numeric:tabular-nums}.mc-ci-metric-hint{font-size:11px;opacity:.75}.mc-ci-trend svg{width:100%;max-width:640px;height:auto}.v3-status-pills{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:12px}.v3-statusline.v3-status-dash{border-style:dashed;color:var(--v3-t4)}.v3-filtercard{padding:11px 14px 13px}.v3-filter-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:9px}.v3-search{display:flex;align-items:center;gap:6px;height:26px;padding:0 8px;border:1px solid var(--v3-line);border-radius:var(--v3-r-sm);background:var(--v3-panel-2);min-width:150px;flex:1 1 150px}.v3-search input{border:0;background:transparent;color:var(--v3-t1);font:inherit;font-size:12px;width:100%;outline:0;min-width:0}.v3-search input::placeholder{color:var(--v3-t4)}.v3-kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px}.v3-kv-l{font-size:11px;color:var(--v3-t4)}.v3-kv-v{font-size:12px;color:var(--v3-t2);margin-top:2px;overflow-wrap:anywhere}.v3-ts.v3-ts--h330{max-height:330px}.v3-chartview{overflow-x:auto;cursor:grab;overscroll-behavior-x:contain;scrollbar-width:thin;user-select:none;-webkit-user-select:none}.v3-chartview:active{cursor:grabbing}.v3-chartview svg{display:block}.v3-src i[data-zero=\"true\"]{background:var(--v3-panel-2)}");
6990
+ }
6991
+ /** One v3 `.v3-stat` cell: label, big value, optional hint line, optional sparkline. */
6992
+ function V3Stat(props) {
6993
+ return (0, react.createElement)("div", {
6994
+ className: "v3-stat",
6995
+ "data-call-index-metric": props.label
6996
+ }, (0, react.createElement)("div", { className: "v3-stat-l" }, props.label), (0, react.createElement)("div", { className: "v3-stat-v v3-num" }, props.value), props.hint === void 0 ? null : (0, react.createElement)("div", { className: "v3-stat-d" }, props.hint), props.spark ?? null);
6997
+ }
6998
+ /** Values → `x,y` points inside a 100×22 viewBox, baselined at 0. */
6999
+ function sparkPoints(values) {
7000
+ const W = 100, H = 22;
7001
+ const max = values.reduce((m, v) => Math.max(m, v), 0);
7002
+ const scale = max > 0 ? max : 1;
7003
+ const xAt = (i) => values.length <= 1 ? W / 2 : i * W / (values.length - 1);
7004
+ const yAt = (v) => 20 - v / scale * 18;
7005
+ const pts = values.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`);
7006
+ return {
7007
+ line: pts.join(" "),
7008
+ area: `0,${H} ${pts.join(" ")} ${W},${H}`
7009
+ };
7010
+ }
7011
+ /** 22px trend sparkline: a line plus a translucent area fill, coloured by tone. */
7012
+ function Sparkline(props) {
7013
+ if (props.values.length < 2) return null;
7014
+ const points = sparkPoints(props.values);
7015
+ const colour = props.tone === "ok" ? "var(--v3-ok)" : props.tone === "bad" ? "var(--v3-bad)" : "var(--v3-info)";
7016
+ return (0, react.createElement)("svg", {
7017
+ className: "v3-spark",
7018
+ viewBox: "0 0 100 22",
7019
+ preserveAspectRatio: "none",
7020
+ "aria-hidden": "true",
7021
+ "data-call-index-spark": props.tone
7022
+ }, (0, react.createElement)("polygon", {
7023
+ className: "v3-spark-fl",
7024
+ points: points.area,
7025
+ fill: colour
7026
+ }), (0, react.createElement)("polyline", {
7027
+ className: "v3-spark-ln",
7028
+ points: points.line,
7029
+ stroke: colour
7030
+ }));
7031
+ }
7032
+ /** `server · tool` for an `mcp__` name, otherwise the bare tool name. */
7033
+ function toolDisplayLabel(toolName) {
7034
+ if (toolName.startsWith("mcp__")) {
7035
+ const parts = toolName.split("__");
7036
+ return parts.length >= 3 ? `${parts[1]} · ${parts.slice(2).join("__")}` : toolName;
7037
+ }
7038
+ return toolName;
7039
+ }
7040
+ /** Pixel width of the trend for `days` buckets: days x min column, floored. */
7041
+ function callIndexTrendWidth(days) {
7042
+ return Math.max(320, (Number.isFinite(days) && days > 0 ? Math.floor(days) : 1) * 34);
6712
7043
  }
6713
- /** Small label + value cell used by both pages. */
6714
- function Metric(label, value, hint) {
7044
+ /**
7045
+ * The v3 overview trend: a line + area (calls), failure底柱 (error), a dashed
7046
+ * "recent week" band and a peak label, drawn inside a horizontally scrollable
7047
+ * strip so a long series can be panned instead of squeezed.
7048
+ *
7049
+ * Geometry comes from `callIndexTrend`'s bars (one data path). The SVG is
7050
+ * days x min-column wide (never narrower than the card), so day labels stay
7051
+ * legible; the wrapper scrolls and supports pointer drag-to-pan. The last day
7052
+ * label is right-anchored at the padding edge so it cannot overlap the one
7053
+ * before it.
7054
+ */
7055
+ function OverviewTrendSvg(props) {
7056
+ const { chart } = props;
7057
+ const bars = chart.bars;
7058
+ const n = bars.length;
7059
+ const W = callIndexTrendWidth(n), H = 212, padL = 34, padR = 22, padT = 12;
7060
+ const plotW = W - padL - padR;
7061
+ const plotH = 174;
7062
+ const maxV = chart.maxCalls > 0 ? Math.ceil(chart.maxCalls / 10) * 10 : 1;
7063
+ const xAt = (i) => padL + (n <= 1 ? plotW / 2 : i * plotW / (n - 1));
7064
+ const yAt = (v) => 186 - v / maxV * plotH;
7065
+ const points = bars.map((bar, i) => xAt(i).toFixed(1) + "," + yAt(bar.calls).toFixed(1));
7066
+ let peak = 0;
7067
+ for (let i = 1; i < n; i++) if (bars[i].calls > bars[peak].calls) peak = i;
7068
+ const children = [];
7069
+ if (n > 0) {
7070
+ const start = Math.max(0, n - 7);
7071
+ const slot = n > 1 ? plotW / (n - 1) : plotW;
7072
+ const left = Math.max(padL, xAt(start) - slot / 2);
7073
+ const right = Math.min(W - padR, xAt(n - 1) + slot / 2);
7074
+ children.push((0, react.createElement)("rect", {
7075
+ key: "hl",
7076
+ className: "v3-hlbox",
7077
+ x: left.toFixed(1),
7078
+ y: padT,
7079
+ width: Math.max(0, right - left).toFixed(1),
7080
+ height: plotH.toFixed(1),
7081
+ rx: 4
7082
+ }));
7083
+ }
7084
+ for (let g = 0; g <= 4; g++) {
7085
+ const value = maxV * g / 4;
7086
+ const y = yAt(value);
7087
+ children.push((0, react.createElement)("line", {
7088
+ key: "grid-" + g,
7089
+ className: "v3-grid",
7090
+ x1: padL,
7091
+ y1: y.toFixed(1),
7092
+ x2: W - padR,
7093
+ y2: y.toFixed(1)
7094
+ }));
7095
+ children.push((0, react.createElement)("text", {
7096
+ key: "tick-" + g,
7097
+ className: "v3-axis",
7098
+ x: 27,
7099
+ y: (y + 3.5).toFixed(1),
7100
+ "text-anchor": "end"
7101
+ }, String(Math.round(value))));
7102
+ }
7103
+ const barWidth = Math.max(4, plotW / Math.max(1, n) - 8);
7104
+ bars.forEach((bar, i) => {
7105
+ if (bar.error <= 0) return;
7106
+ const errH = Math.max(2.5, bar.error / maxV * plotH);
7107
+ children.push((0, react.createElement)("rect", {
7108
+ key: "err-" + bar.day,
7109
+ className: "v3-bar-er",
7110
+ x: (xAt(i) - barWidth / 2).toFixed(1),
7111
+ y: (186 - errH).toFixed(1),
7112
+ width: barWidth.toFixed(1),
7113
+ height: errH.toFixed(1),
7114
+ rx: 1.5
7115
+ }, (0, react.createElement)("title", null, bar.day + ":失败 " + bar.error + " 次")));
7116
+ });
7117
+ children.push((0, react.createElement)("polygon", {
7118
+ key: "area",
7119
+ className: "v3-area",
7120
+ points: "34,186 " + points.join(" ") + " " + (W - padR) + ",186"
7121
+ }));
7122
+ children.push((0, react.createElement)("polyline", {
7123
+ key: "line",
7124
+ className: "v3-line",
7125
+ points: points.join(" ")
7126
+ }));
7127
+ bars.forEach((bar, i) => {
7128
+ if (i % 4 !== 0 && i !== n - 1 && i !== peak) return;
7129
+ children.push((0, react.createElement)("circle", {
7130
+ key: "pt-" + bar.day,
7131
+ className: "v3-pt",
7132
+ cx: xAt(i).toFixed(1),
7133
+ cy: yAt(bar.calls).toFixed(1),
7134
+ r: 2.6
7135
+ }, (0, react.createElement)("title", null, bar.day + ":" + bar.calls + " 次调用,失败 " + bar.error + " 次")));
7136
+ });
7137
+ if (n > 0) {
7138
+ children.push((0, react.createElement)("text", {
7139
+ key: "peak",
7140
+ className: "v3-lbl-b",
7141
+ x: (xAt(peak) - 8).toFixed(1),
7142
+ y: (yAt(bars[peak].calls) - 9).toFixed(1),
7143
+ "text-anchor": "end"
7144
+ }, "峰值 " + bars[peak].calls));
7145
+ const step = Math.max(1, Math.ceil(n / 6));
7146
+ for (let m = 0; m < n - 1; m += step) children.push((0, react.createElement)("text", {
7147
+ key: "xl-" + m,
7148
+ className: "v3-axis",
7149
+ x: xAt(m).toFixed(1),
7150
+ y: 204,
7151
+ "text-anchor": "middle"
7152
+ }, bars[m].day.slice(5)));
7153
+ children.push((0, react.createElement)("text", {
7154
+ key: "xl-last",
7155
+ className: "v3-axis",
7156
+ x: (W - padR).toFixed(1),
7157
+ y: 204,
7158
+ "text-anchor": "end",
7159
+ "data-call-index-lasttick": "true"
7160
+ }, bars[n - 1].day.slice(5)));
7161
+ }
7162
+ const svg = (0, react.createElement)("svg", {
7163
+ className: "v3-chart",
7164
+ viewBox: "0 0 " + W + " 212",
7165
+ preserveAspectRatio: "none",
7166
+ role: "img",
7167
+ width: W,
7168
+ height: H,
7169
+ style: {
7170
+ width: W + "px",
7171
+ minWidth: "100%",
7172
+ height: "212px"
7173
+ },
7174
+ "aria-label": "每日调用与失败数:" + n + " 天,峰值 " + chart.maxCalls + " 次",
7175
+ "data-call-index-svg": "trend",
7176
+ "data-call-index-width": String(W)
7177
+ }, ...children);
7178
+ let dragging = false;
7179
+ let startX = 0;
7180
+ let startScroll = 0;
6715
7181
  return (0, react.createElement)("div", {
6716
- className: "mc-ci-metric",
6717
- "data-call-index-metric": label
6718
- }, (0, react.createElement)("div", { className: "mc-ci-metric-label" }, label), (0, react.createElement)("div", { className: "mc-ci-metric-value mc-ci-num" }, value), hint === void 0 ? null : (0, react.createElement)("div", { className: "mc-ci-metric-hint" }, hint));
7182
+ className: "v3-chartview",
7183
+ "data-call-index-chartview": "true",
7184
+ onPointerDown: (event) => {
7185
+ dragging = true;
7186
+ startX = event.clientX;
7187
+ startScroll = event.currentTarget.scrollLeft;
7188
+ event.currentTarget.style.cursor = "grabbing";
7189
+ if (event.pointerId !== void 0 && typeof event.currentTarget.setPointerCapture === "function") try {
7190
+ event.currentTarget.setPointerCapture(event.pointerId);
7191
+ } catch {}
7192
+ },
7193
+ onPointerMove: (event) => {
7194
+ if (!dragging) return;
7195
+ event.currentTarget.scrollLeft = startScroll - (event.clientX - startX);
7196
+ },
7197
+ onPointerUp: (event) => {
7198
+ dragging = false;
7199
+ event.currentTarget.style.cursor = "grab";
7200
+ },
7201
+ onPointerLeave: (event) => {
7202
+ if (!dragging) return;
7203
+ dragging = false;
7204
+ event.currentTarget.style.cursor = "grab";
7205
+ }
7206
+ }, svg);
7207
+ }
7208
+ /**
7209
+ * 来源构成,按工具名前缀启发式分类。
7210
+ *
7211
+ * NOT Host attribution: the Host cannot prove a plugin owner, so this card does
7212
+ * not pretend to. It splits the already-read per-tool rows by a plain rule —
7213
+ * a `mcp__` prefix is MCP, everything else is native — and says so at the foot.
7214
+ * When the per-tool read has not happened there is nothing to classify, so the
7215
+ * card shows 「来源未解析」 and explains that this is not a zero count.
7216
+ */
7217
+ function OverviewSourcePanel(props) {
7218
+ const note = (0, react.createElement)("p", { className: "v3-src-note" }, (0, react.createElement)("span", null, "按工具名前缀推断,不是 Host 归属。"), (0, react.createElement)("b", { style: { color: "var(--v3-t3)" } }, " 0 在这里不等于未使用"));
7219
+ if (props.tools === void 0) return (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "来源构成")), (0, react.createElement)("p", {
7220
+ className: "v3-hint",
7221
+ "data-call-index-missing": "by-source"
7222
+ }, "尚未读取逐工具明细,来源未解析;这不是「全部为 0」。"), note);
7223
+ const mcpCalls = props.tools.filter((row) => row.toolName.startsWith("mcp__")).reduce((sum, row) => sum + row.calls, 0);
7224
+ const nativeCalls = props.tools.reduce((sum, row) => sum + row.calls, 0) - mcpCalls;
7225
+ const total = mcpCalls + nativeCalls;
7226
+ const data = [{
7227
+ kind: "mcp",
7228
+ label: CALL_INDEX_SOURCE_LABELS.mcp,
7229
+ count: mcpCalls,
7230
+ color: "var(--v3-info)"
7231
+ }, {
7232
+ kind: "native",
7233
+ label: CALL_INDEX_SOURCE_LABELS.native,
7234
+ count: nativeCalls,
7235
+ color: "var(--v3-ok)"
7236
+ }];
7237
+ const radius = 43;
7238
+ const circumference = 2 * Math.PI * radius;
7239
+ let offset = 0;
7240
+ const segments = data.filter((item) => item.count > 0).map((item) => {
7241
+ const length = total > 0 ? item.count / total * circumference : 0;
7242
+ const circle = (0, react.createElement)("circle", {
7243
+ key: item.kind,
7244
+ className: "v3-seg",
7245
+ cx: 52,
7246
+ cy: 52,
7247
+ r: radius,
7248
+ "data-seg": item.kind,
7249
+ strokeDasharray: `${length.toFixed(1)} ${(circumference - length).toFixed(1)}`,
7250
+ strokeDashoffset: (-offset).toFixed(1),
7251
+ style: { stroke: item.color }
7252
+ });
7253
+ offset += length;
7254
+ return circle;
7255
+ });
7256
+ return (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "来源构成"), (0, react.createElement)("span", { className: "v3-pn" }, `${formatCallIndexCount(total, props.totalExact)} 次`)), (0, react.createElement)("div", { className: "v3-donut-wrap" }, (0, react.createElement)("div", { className: "v3-donut" }, (0, react.createElement)("svg", {
7257
+ viewBox: "0 0 104 104",
7258
+ "aria-hidden": "true"
7259
+ }, (0, react.createElement)("circle", {
7260
+ className: "v3-trk",
7261
+ cx: 52,
7262
+ cy: 52,
7263
+ r: radius
7264
+ }), ...segments), (0, react.createElement)("div", { className: "v3-mid" }, (0, react.createElement)("b", { className: "v3-num" }, formatCallIndexCount(total, props.totalExact)), (0, react.createElement)("span", null, "次调用"))), (0, react.createElement)("div", { className: "v3-src-list" }, ...data.map((item) => (0, react.createElement)("div", {
7265
+ key: item.kind,
7266
+ className: "v3-src",
7267
+ "data-source-row": item.kind,
7268
+ "data-source-count": String(item.count)
7269
+ }, (0, react.createElement)("i", item.count === 0 ? { "data-zero": "true" } : {}), (0, react.createElement)("span", { className: "v3-nm" }, item.label), (0, react.createElement)("b", null, formatCallIndexCount(item.count, props.totalExact)), (0, react.createElement)("em", null, total > 0 ? `${Math.round(item.count / total * 100)}%` : "0%"))))), note);
6719
7270
  }
6720
7271
  function callIndexDiagnostics(input) {
6721
7272
  const items = [];
@@ -6775,96 +7326,6 @@ function StateNotice({ state, failure }) {
6775
7326
  "data-call-index-state": state.kind
6776
7327
  }, (0, react.createElement)("strong", null, state.title), state.detail === "" ? null : (0, react.createElement)("p", { className: "mc-desc" }, state.detail), failure?.retryable === true && state.canRetry ? (0, react.createElement)("p", { className: "mc-desc" }, "这是一个可重试的错误;重试会重新读取,不会重放旧游标。") : null);
6777
7328
  }
6778
- /** Source distribution bars (bySource). */
6779
- function CallIndexSourceBars(props) {
6780
- const data = callIndexSourceData(props.bySource);
6781
- const present = data.filter((item) => item.count > 0);
6782
- return (0, react.createElement)("div", {
6783
- className: "mc-ci-sources",
6784
- "data-call-index-block": "by-source"
6785
- }, (0, react.createElement)("h4", {
6786
- className: "mc-heading",
6787
- style: { fontSize: 13 }
6788
- }, "来源分布"), props.bySource === void 0 ? (0, react.createElement)("p", {
6789
- className: "mc-empty",
6790
- "data-call-index-missing": "by-source"
6791
- }, "Host 未返回来源分布(bySource);这不是「全部为 0」。") : (0, react.createElement)("div", {
6792
- className: "mc-ci-bar",
6793
- role: "img",
6794
- "aria-label": "来源分布条"
6795
- }, ...present.map((item) => (0, react.createElement)("span", {
6796
- key: item.kind,
6797
- "data-source-kind": item.kind,
6798
- title: `${item.label}:${formatCallIndexCount(item.count, props.totalExact)}`,
6799
- style: {
6800
- flexGrow: item.count,
6801
- background: `var(--mc-source-${item.kind}, currentColor)`,
6802
- opacity: .65
6803
- }
6804
- }))), ...data.map((item) => (0, react.createElement)("div", {
6805
- className: "mc-ci-source-row",
6806
- key: item.kind,
6807
- "data-source-row": item.kind
6808
- }, (0, react.createElement)("span", { className: "mc-source" }, item.label), (0, react.createElement)("span", { className: "mc-ci-num" }, formatCallIndexCount(item.count, props.totalExact)), item.zeroNote === void 0 ? null : (0, react.createElement)("span", {
6809
- className: "mc-desc",
6810
- "data-call-index-zero": item.kind
6811
- }, item.zeroNote))));
6812
- }
6813
- /** Self-drawn SVG trend: no chart library, deterministic geometry. */
6814
- function CallIndexTrendChart(props) {
6815
- const chart = callIndexTrend(props.aggregates?.byDay);
6816
- const geometry = CALL_INDEX_TREND_GEOMETRY;
6817
- if (props.aggregates === void 0) return (0, react.createElement)("div", {
6818
- className: "mc-ci-trend",
6819
- "data-call-index-block": "trend"
6820
- }, (0, react.createElement)("h4", {
6821
- className: "mc-heading",
6822
- style: { fontSize: 13 }
6823
- }, "按日趋势"), (0, react.createElement)("p", {
6824
- className: "mc-empty",
6825
- "data-call-index-missing": "aggregates"
6826
- }, "Host 未返回按日聚合(aggregates);这不是「没有调用」,趋势不可用。"));
6827
- if (!chart.available) return (0, react.createElement)("div", {
6828
- className: "mc-ci-trend",
6829
- "data-call-index-block": "trend"
6830
- }, (0, react.createElement)("h4", {
6831
- className: "mc-heading",
6832
- style: { fontSize: 13 }
6833
- }, "按日趋势"), (0, react.createElement)("p", {
6834
- className: "mc-empty",
6835
- "data-call-index-missing": "by-day"
6836
- }, "该筛选在所选范围内没有任何按日桶,趋势为空;单个日粒度为 0 不参与绘制。"));
6837
- return (0, react.createElement)("div", {
6838
- className: "mc-ci-trend",
6839
- "data-call-index-block": "trend"
6840
- }, (0, react.createElement)("h4", {
6841
- className: "mc-heading",
6842
- style: { fontSize: 13 }
6843
- }, "按日趋势"), (0, react.createElement)("svg", {
6844
- viewBox: `0 0 ${geometry.width} ${geometry.height}`,
6845
- role: "img",
6846
- "aria-label": `按日调用趋势(已索引部分):${chart.bars.length} 天,峰值 ${chart.maxCalls} 次`,
6847
- "data-call-index-svg": "trend"
6848
- }, ...chart.bars.map((bar) => (0, react.createElement)("rect", {
6849
- key: bar.day,
6850
- x: bar.x,
6851
- y: bar.y,
6852
- width: bar.width,
6853
- height: bar.height,
6854
- "data-day": bar.day,
6855
- "data-calls": bar.calls,
6856
- fill: bar.error > 0 ? "var(--mc-trend-error, currentColor)" : "currentColor",
6857
- opacity: bar.error > 0 ? .85 : .55
6858
- })), ...chart.bars.map((bar) => (0, react.createElement)("text", {
6859
- key: `label-${bar.day}`,
6860
- x: bar.x + bar.width / 2,
6861
- y: geometry.height - 6,
6862
- "font-size": 9,
6863
- "text-anchor": "middle",
6864
- fill: "currentColor",
6865
- opacity: .7
6866
- }, bar.day.slice(5)))), (0, react.createElement)("p", { className: "mc-desc" }, `图上 ${chart.bars.length} 天、${chart.totalCalls} 次调用;峰值 ${chart.maxCalls} 次/天。柱高按峰值归一。趋势只统计已索引部分,覆盖不完整时右侧天数与总数都可能偏少。`));
6867
- }
6868
7329
  /**
6869
7330
  * Turn the index census into a progress percentage.
6870
7331
  *
@@ -7033,26 +7494,73 @@ function callIndexToolRows(page) {
7033
7494
  ...entry.durationCount === 0 ? {} : { averageDurationMs: entry.durationSum / entry.durationCount }
7034
7495
  })).sort((a, b) => b.calls - a.calls || a.toolName.localeCompare(b.toolName));
7035
7496
  }
7036
- function CallIndexCallTable(props) {
7037
- const { page } = props;
7038
- const rows = callIndexToolRows(page);
7039
- return (0, react.createElement)("div", { className: "mc-ci-table-scroll" }, (0, react.createElement)("table", {
7040
- className: "mc-ci-table",
7497
+ /** Short source marker for the tool cell: MCP / 技能 / 插件 / 来源未解析. */
7498
+ function sourceNoteForKind(kind) {
7499
+ switch (kind) {
7500
+ case "mcp": return "MCP";
7501
+ case "skill": return "技能";
7502
+ case "plugin-tool": return "插件";
7503
+ case "unknown": return "来源未解析";
7504
+ default: return;
7505
+ }
7506
+ }
7507
+ /**
7508
+ * The v3 "完整工具表": one sticky-header row per TOOL, inside a bounded
7509
+ * `.v3-ts` scroller (330px here — the two charts above it already tell the big
7510
+ * picture). Takes pre-computed rows so the same filtered set feeds the charts.
7511
+ */
7512
+ function CallIndexToolTable(props) {
7513
+ return (0, react.createElement)("div", { className: "v3-ts v3-ts--h330" }, (0, react.createElement)("table", {
7514
+ className: "v3-table",
7041
7515
  "data-call-index-table": "tools"
7042
7516
  }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, ...[
7043
- "时间",
7044
- "工具",
7045
- "来源",
7046
- "成功",
7047
- "失败",
7048
- "平均耗时"
7049
- ].map((title) => (0, react.createElement)("th", {
7050
- key: title,
7051
- scope: "col"
7052
- }, title)))), (0, react.createElement)("tbody", null, ...rows.map((row) => (0, react.createElement)("tr", {
7053
- key: row.toolName + "::" + row.sourceKind,
7054
- "data-call-index-tool": row.toolName
7055
- }, (0, react.createElement)("td", { className: "mc-ci-num" }, formatCallIndexTime(row.lastAt)), (0, react.createElement)("td", { title: row.toolName }, row.toolName), (0, react.createElement)("td", null, CALL_INDEX_SOURCE_LABELS[row.sourceKind]), (0, react.createElement)("td", { className: "mc-ci-num" }, String(row.success)), (0, react.createElement)("td", { className: "mc-ci-num" }, String(row.error)), (0, react.createElement)("td", { className: "mc-ci-num" }, formatCallIndexDuration(row.averageDurationMs)))))));
7517
+ {
7518
+ label: "工具",
7519
+ right: false
7520
+ },
7521
+ {
7522
+ label: "调用",
7523
+ right: true
7524
+ },
7525
+ {
7526
+ label: "失败",
7527
+ right: true
7528
+ },
7529
+ {
7530
+ label: "成功率",
7531
+ right: true
7532
+ },
7533
+ {
7534
+ label: "平均耗时",
7535
+ right: true
7536
+ },
7537
+ {
7538
+ label: "最近调用",
7539
+ right: true
7540
+ }
7541
+ ].map((col) => (0, react.createElement)("th", {
7542
+ key: col.label,
7543
+ scope: "col",
7544
+ ...col.right ? { className: "v3-r" } : {}
7545
+ }, col.label)))), (0, react.createElement)("tbody", null, ...props.rows.map((row) => {
7546
+ const known = row.success + row.error;
7547
+ const rate = known > 0 ? row.success / known : void 0;
7548
+ const bad = row.error > 0 && row.error / row.calls >= .2;
7549
+ const note = sourceNoteForKind(row.sourceKind);
7550
+ const server = row.toolName.startsWith("mcp__") ? row.toolName.split("__")[1] : void 0;
7551
+ const shortName = row.sourceKind === "mcp" && row.toolName.startsWith("mcp__") ? row.toolName.split("__").slice(2).join("__") : row.toolName;
7552
+ return (0, react.createElement)("tr", {
7553
+ key: row.toolName + "::" + row.sourceKind,
7554
+ "data-call-index-tool": row.toolName
7555
+ }, (0, react.createElement)("td", null, (0, react.createElement)("div", { className: "v3-c-tool" }, note === void 0 ? null : (0, react.createElement)("span", { className: "v3-srcnote" }, note), (0, react.createElement)("span", {
7556
+ className: "v3-nm",
7557
+ title: row.toolName
7558
+ }, shortName), server === void 0 ? null : (0, react.createElement)("span", { className: "v3-srv" }, server))), (0, react.createElement)("td", { className: "v3-r" }, String(row.calls)), (0, react.createElement)("td", { className: "v3-r" }, String(row.error)), (0, react.createElement)("td", { className: "v3-r" }, (0, react.createElement)("span", { className: "v3-rt" }, (0, react.createElement)("span", { className: "v3-tk" }, (0, react.createElement)("span", {
7559
+ className: "v3-fl",
7560
+ ...bad ? { "data-bad": "true" } : {},
7561
+ style: { width: `${Math.round((rate ?? 0) * 100)}%` }
7562
+ })), (0, react.createElement)("b", null, formatCallIndexRate(rate)))), (0, react.createElement)("td", { className: "v3-r" }, formatCallIndexDuration(row.averageDurationMs)), (0, react.createElement)("td", { className: "v3-r" }, formatCallIndexTime(row.lastAt)));
7563
+ }))));
7056
7564
  }
7057
7565
  function callIndexRates(summary) {
7058
7566
  if (summary === void 0) return {};
@@ -7084,7 +7592,7 @@ function formatCallIndexRate(value) {
7084
7592
  * Modal; the container decides it from a media query, so this stays pure.
7085
7593
  */
7086
7594
  function CallIndexDetailPanelView(props) {
7087
- const { query, status, page, loading, loadingMore, failure, onQueryChange, onResolveScope, catalog, moreFailure, startFailure, startTask } = props;
7595
+ const { query, status, page, loading, loadingMore, failure, onQueryChange, onResolveScope, catalog, moreFailure, startFailure, startTask, search, onSearchChange } = props;
7088
7596
  const state = callIndexDetailState({
7089
7597
  status,
7090
7598
  page,
@@ -7093,95 +7601,362 @@ function CallIndexDetailPanelView(props) {
7093
7601
  ...failure === void 0 ? {} : { failure }
7094
7602
  });
7095
7603
  const totalText = page === void 0 ? void 0 : formatCallIndexCount(page.total ?? page.summary.total, page.totalExact);
7096
- const activity = loading === true ? page === void 0 ? "正在读取…" : "正在刷新…" : page === void 0 ? "尚未读取" : `快照代 ${page.generation}`;
7604
+ const scanning = status !== void 0 && (status.phase === "running" || (status.sessions?.pending ?? 0) > 0 || (status.sessions?.pendingLive ?? 0) > 0);
7605
+ const statusText = page === void 0 ? loading === true ? "正在读取调用明细…" : "尚未读取调用明细" : scanning ? "索引仍在扫描,数字会继续增加" : "索引已完成";
7097
7606
  const rates = callIndexRates(page?.summary);
7098
7607
  const successStats = rates.success;
7099
7608
  const failureStats = rates.failure;
7100
7609
  const rateText = (value) => formatCallIndexRate(value);
7101
7610
  const rateHint = (stats) => stats === void 0 ? "没有已知结果的调用,无法计算比例" : `${stats.numerator} / ${stats.denominator} 次已知结果`;
7102
7611
  const suggestionCount = page === void 0 ? void 0 : (page.summary.unresolved ?? 0) + page.summary.error;
7103
- const suggestionText = () => suggestionCount === void 0 ? "—" : String(suggestionCount);
7104
7612
  const suggestionHint = () => page === void 0 ? "尚未读取" : `失败 ${page.summary.error} + 未配对 ${page.summary.unresolved ?? 0};未配对表示没有捕获到结果,不等于失败`;
7613
+ const allRows = page === void 0 ? [] : callIndexToolRows(page);
7614
+ const term = (search ?? "").trim().toLowerCase();
7615
+ const rows = term === "" ? allRows : allRows.filter((row) => row.toolName.toLowerCase().includes(term));
7616
+ const durations = page === void 0 ? [] : page.items.map((item) => item.durationMs).filter((ms) => ms !== void 0 && Number.isFinite(ms));
7617
+ const averageDuration = durations.length > 0 ? durations.reduce((sum, ms) => sum + ms, 0) / durations.length : void 0;
7618
+ const readCalls = rows.reduce((sum, row) => sum + row.calls, 0);
7619
+ const totalCalls = page === void 0 ? void 0 : page.total ?? page.summary.total;
7620
+ const readIsPartial = totalCalls !== void 0 && readCalls < totalCalls;
7621
+ const rankRows = rows.slice(0, 12);
7622
+ const rankMax = rankRows.length > 0 ? rankRows[0].calls : 1;
7623
+ const failingRows = rows.filter((row) => row.error > 0).slice().sort((a, b) => b.error - a.error);
7624
+ const failMax = failingRows.length > 0 ? failingRows[0].error : 1;
7625
+ const searchInput = (0, react.createElement)("label", { className: "v3-search" }, (0, react.createElement)("svg", {
7626
+ width: 12,
7627
+ height: 12,
7628
+ viewBox: "0 0 14 14",
7629
+ fill: "none",
7630
+ "aria-hidden": "true",
7631
+ style: {
7632
+ color: "var(--v3-t4)",
7633
+ flex: "none"
7634
+ }
7635
+ }, (0, react.createElement)("circle", {
7636
+ cx: 6.2,
7637
+ cy: 6.2,
7638
+ r: 4.4,
7639
+ stroke: "currentColor",
7640
+ "stroke-width": 1.3
7641
+ }), (0, react.createElement)("path", {
7642
+ d: "m9.6 9.6 3 3",
7643
+ stroke: "currentColor",
7644
+ "stroke-width": 1.3,
7645
+ "stroke-linecap": "round"
7646
+ })), (0, react.createElement)("input", onSearchChange === void 0 ? {
7647
+ type: "search",
7648
+ placeholder: "筛选工具名…",
7649
+ "aria-label": "筛选工具名",
7650
+ defaultValue: search ?? "",
7651
+ "data-call-index-search": "true"
7652
+ } : {
7653
+ type: "search",
7654
+ placeholder: "筛选工具名…",
7655
+ "aria-label": "筛选工具名",
7656
+ value: search ?? "",
7657
+ "data-call-index-search": "true",
7658
+ onChange: (event) => onSearchChange(event.target.value)
7659
+ }));
7105
7660
  const indexBlockedReason = status === void 0 ? "尚未收到索引状态,无法判断索引进度(Host 未应答或回复被拒绝)" : status.scope.status !== "ready" ? "索引范围未启用,插件不会自动索引历史会话" : void 0;
7106
7661
  return (0, react.createElement)("section", {
7107
- className: "mc-section",
7662
+ className: "v3-root mc-section",
7108
7663
  "aria-label": "调用明细",
7109
7664
  "aria-busy": loading === true
7110
- }, CallIndexStyles(), (0, react.createElement)("header", { className: "mc-heading-row" }, (0, react.createElement)("div", { className: "mc-heading-meta" }, (0, react.createElement)("h2", { className: "mc-heading" }, "调用明细"), (0, react.createElement)("span", { className: "mc-version" }, activity)), null), (0, react.createElement)("p", { className: "mc-desc" }, "按工具调用记录会话内的每一次调用;只统计调用次数与元数据,不代表 Token 数,也不包含参数与返回值。"), indexBlockedReason === void 0 ? null : (0, react.createElement)("p", {
7111
- className: "mc-desc",
7112
- "data-call-index-note": "index-blocked",
7113
- role: "status"
7114
- }, `自动索引不可用:${indexBlockedReason}`), CallIndexFilters({
7665
+ }, CallIndexStyles(), V3Styles(), (0, react.createElement)("div", { className: "v3-status-pills" }, (0, react.createElement)("span", {
7666
+ className: "v3-statusline",
7667
+ ...state.kind === "failure" || state.kind === "disconnected" ? { "data-tone": "bad" } : {}
7668
+ }, (0, react.createElement)("span", {
7669
+ className: "v3-dot",
7670
+ "data-tone": state.kind === "failure" || state.kind === "disconnected" ? "bad" : scanning ? "run" : "ok",
7671
+ "aria-hidden": "true"
7672
+ }), (0, react.createElement)("span", null, statusText))), (0, react.createElement)("div", { className: "v3-panel v3-filtercard" }, (0, react.createElement)("div", { className: "v3-filter-row" }, CallIndexFilters({
7115
7673
  query,
7116
7674
  ...onQueryChange === void 0 ? {} : { onChange: onQueryChange },
7117
7675
  ...onResolveScope === void 0 ? {} : { onResolveScope },
7118
7676
  ...catalog === void 0 ? {} : { catalog }
7119
- }), CallIndexScanBar({ ...status === void 0 ? {} : { status } }), (0, react.createElement)(StateNotice, {
7677
+ }), searchInput), CallIndexScanBar({ ...status === void 0 ? {} : { status } })), (0, react.createElement)(StateNotice, {
7120
7678
  state,
7121
7679
  ...failure === void 0 ? {} : { failure }
7122
7680
  }), startFailure === void 0 ? null : (0, react.createElement)("p", {
7123
- className: "mc-desc",
7681
+ className: "v3-hint",
7124
7682
  "data-call-index-note": "start-failure",
7125
7683
  role: "status"
7126
7684
  }, `自动索引失败:${callIndexFailureText(startFailure)}`), startTask === void 0 ? null : (0, react.createElement)("p", {
7127
- className: "mc-desc",
7685
+ className: "v3-hint",
7128
7686
  "data-call-index-note": "start-task"
7129
- }, `自动索引:${startTask.phase}${startTask.mode === "full" ? "(全量)" : "(增量)"} · 任务 ${startTask.taskId.slice(0, 8)}`), CallIndexDiagnostics({
7687
+ }, `自动索引:${startTask.phase}${startTask.mode === "full" ? "(全量)" : "(增量)"} · 任务 ${startTask.taskId.slice(0, 8)}`), indexBlockedReason === void 0 ? null : (0, react.createElement)("p", {
7688
+ className: "v3-hint",
7689
+ "data-call-index-note": "index-blocked",
7690
+ role: "status"
7691
+ }, `自动索引不可用:${indexBlockedReason}`), CallIndexDiagnostics({
7130
7692
  ...status === void 0 ? {} : { status },
7131
7693
  ...page === void 0 ? {} : { page },
7132
7694
  ...failure === void 0 ? {} : { failure },
7133
7695
  ...startFailure === void 0 ? {} : { startFailure },
7134
7696
  ...moreFailure === void 0 ? {} : { moreFailure }
7135
7697
  }), (0, react.createElement)("div", {
7136
- className: "mc-ci-summary",
7698
+ className: "v3-stats",
7137
7699
  "data-call-index-summary": "rates"
7138
- }, Metric("成功率", rateText(successStats?.rate), `${rateHint(successStats)}${totalText === void 0 ? "" : ` · 本页合计 ${totalText}`}`), Metric("失败率", rateText(failureStats?.rate), rateHint(failureStats)), Metric("建议", suggestionText(), suggestionHint())), page !== void 0 && page.droppedItems > 0 ? (0, react.createElement)("p", {
7139
- className: "mc-desc",
7140
- "data-call-index-note": "dropped"
7141
- }, `本页有 ${page.droppedItems} 行未通过白名单投影(字段不合法),已丢弃,未计入合计。`) : null, (0, react.createElement)("div", null, (0, react.createElement)("div", { style: { minWidth: 0 } }, page !== void 0 && page.items.length > 0 ? (0, react.createElement)(CallIndexCallTable, { page }) : null, page?.nextCursor !== void 0 && loadingMore === true ? (0, react.createElement)("p", {
7142
- className: "mc-desc",
7143
- "data-call-index-note": "loading-more"
7700
+ }, V3Stat({
7701
+ label: "成功率",
7702
+ value: rateText(successStats?.rate),
7703
+ hint: totalText === void 0 ? rateHint(successStats) : `${rateHint(successStats)} · 本页合计 ${totalText}`
7704
+ }), V3Stat({
7705
+ label: "失败率",
7706
+ value: rateText(failureStats?.rate),
7707
+ hint: rateHint(failureStats)
7708
+ }), V3Stat({
7709
+ label: "需要关注",
7710
+ value: suggestionCount === void 0 ? "—" : String(suggestionCount),
7711
+ hint: suggestionHint()
7712
+ }), V3Stat({
7713
+ label: "平均耗时",
7714
+ value: formatCallIndexDuration(averageDuration),
7715
+ hint: "按有耗时的调用平均"
7716
+ })), page !== void 0 && page.droppedItems > 0 ? (0, react.createElement)("p", {
7717
+ className: "v3-hint",
7718
+ "data-call-index-note": "dropped",
7719
+ style: { marginTop: 8 }
7720
+ }, `本页有 ${page.droppedItems} 行未通过白名单投影(字段不合法),已丢弃,未计入合计。`) : null, (0, react.createElement)("div", {
7721
+ className: "v3-board",
7722
+ style: { marginTop: 12 }
7723
+ }, (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "调用量分布(本范围工具)"), (0, react.createElement)("span", { className: "v3-pn" }, term === "" ? "前 12 名" : "筛选结果")), rankRows.length === 0 ? (0, react.createElement)("p", { className: "v3-hint" }, term === "" ? "当前范围没有工具调用。" : "没有匹配的工具。") : (0, react.createElement)("div", { className: "v3-rank" }, ...rankRows.map((row) => {
7724
+ const bad = row.error > 0 && row.error / row.calls >= .2;
7725
+ return (0, react.createElement)("div", {
7726
+ key: row.toolName + "::" + row.sourceKind,
7727
+ className: "v3-rank-row",
7728
+ title: row.toolName
7729
+ }, (0, react.createElement)("span", { className: "v3-nm" }, toolDisplayLabel(row.toolName)), (0, react.createElement)("span", { className: "v3-bar" }, (0, react.createElement)("span", {
7730
+ ...bad ? { "data-bad": "true" } : {},
7731
+ style: { width: `${Math.max(3, Math.round(row.calls / rankMax * 100))}%` }
7732
+ })), (0, react.createElement)("span", { className: "v3-n" }, String(row.calls)));
7733
+ })), (0, react.createElement)("p", {
7734
+ className: "v3-hint",
7735
+ style: { marginTop: 9 }
7736
+ }, `条长 = 调用量,按最大值归一;失败率 ≥ 20% 的条变红。${readIsPartial ? `本页已读取 ${formatCallIndexCount(readCalls, page?.totalExact)} 次调用,全部共 ${formatCallIndexCount(totalCalls, page?.totalExact)} 次,未读取部分不参与排行。` : ""}`)), (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "失败与耗时"), (0, react.createElement)("span", { className: "v3-pn" }, "按失败数排序")), failingRows.length === 0 ? (0, react.createElement)("p", { className: "v3-hint" }, "当前范围没有失败调用。") : (0, react.createElement)("div", { className: "v3-rank" }, ...failingRows.slice(0, 8).map((row) => {
7737
+ const perTool = Math.round(row.error / row.calls * 100);
7738
+ return (0, react.createElement)("div", {
7739
+ key: "fail-" + row.toolName + "::" + row.sourceKind,
7740
+ className: "v3-rank-row",
7741
+ title: row.toolName
7742
+ }, (0, react.createElement)("span", { className: "v3-nm" }, toolDisplayLabel(row.toolName)), (0, react.createElement)("span", { className: "v3-bar" }, (0, react.createElement)("span", {
7743
+ "data-bad": "true",
7744
+ style: { width: `${Math.max(4, Math.round(row.error / failMax * 100))}%` }
7745
+ })), (0, react.createElement)("span", {
7746
+ className: "v3-n",
7747
+ style: {
7748
+ color: "var(--v3-bad)",
7749
+ fontWeight: 600
7750
+ }
7751
+ }, `${row.error} 次`), (0, react.createElement)("span", {
7752
+ className: "v3-e",
7753
+ "data-bad": "true"
7754
+ }, `占该工具 ${perTool}%`));
7755
+ })), (0, react.createElement)("p", {
7756
+ className: "v3-hint",
7757
+ style: { marginTop: 9 }
7758
+ }, `条长 = 失败次数(不是失败率)。共 ${failingRows.length} 个工具出现过失败。`))), (0, react.createElement)("div", {
7759
+ className: "v3-panel",
7760
+ style: { marginTop: 12 }
7761
+ }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "完整工具表"), (0, react.createElement)("span", { className: "v3-pn" }, `${rows.length} 行 · 表内滚动`)), (0, react.createElement)(CallIndexToolTable, { rows }), (0, react.createElement)("div", { className: "v3-tfoot" }, (0, react.createElement)("span", null, `${rows.length} 个工具 · 表内滚动`))), page?.nextCursor !== void 0 && loadingMore === true ? (0, react.createElement)("p", {
7762
+ className: "v3-hint",
7763
+ "data-call-index-note": "loading-more",
7764
+ style: { marginTop: 8 }
7144
7765
  }, "正在加载其余工具…") : null, moreFailure === void 0 ? null : (0, react.createElement)("p", {
7145
- className: "mc-desc",
7766
+ className: "v3-hint",
7146
7767
  "data-call-index-note": "more-failure",
7147
- role: "status"
7148
- }, `加载失败:${callIndexFailureText(moreFailure)}`))));
7768
+ role: "status",
7769
+ style: { marginTop: 8 }
7770
+ }, `加载失败:${callIndexFailureText(moreFailure)}`));
7149
7771
  }
7150
- /** Pure view for 总览. */
7772
+ /** Pure view for 总览 (v3 看板). */
7151
7773
  function CallIndexOverviewPanelView(props) {
7152
- const { status, overview, loading, failure, onRefresh } = props;
7774
+ const { status, overview, loading, failure, onRefresh, tools, toolsLoading, toolsFailure } = props;
7153
7775
  const metrics = callIndexOverviewMetrics(overview, status);
7154
- const rangeLabel = callIndexRangeLabel("all");
7155
7776
  const exact = metrics.callsTotalExact;
7156
7777
  const mix = callIndexOutcomeMix(overview?.summary);
7157
7778
  const aggregates = metrics.aggregates;
7158
7779
  const notice = failure === void 0 ? void 0 : failure.disconnected ? `与 Host 断连:${callIndexFailureText(failure)}${failure.hostCode === void 0 ? "" : `(${failure.hostCode})`}` : callIndexFailureText(failure);
7780
+ const coverageLabel = status === void 0 ? "未知" : COVERAGE_LABELS[status.coverage];
7781
+ const scanning = status !== void 0 && (status.phase === "running" || (status.sessions?.pending ?? 0) > 0 || (status.sessions?.pendingLive ?? 0) > 0);
7782
+ const pendingSessions = status?.sessions?.pending ?? 0;
7783
+ const scanPhrase = status !== void 0 && status.phase === "running" ? "仍在扫描" : pendingSessions > 0 ? `还有 ${pendingSessions} 个会话待补扫` : "扫描已完成";
7784
+ const statusSentence = `已索引 ${metrics.sessionsListed ?? "—"} 个会话 · 覆盖${coverageLabel} · ${scanPhrase}`;
7785
+ const coverageHint = exact === true ? (0, react.createElement)("span", { style: {
7786
+ display: "inline-flex",
7787
+ alignItems: "center",
7788
+ gap: 5
7789
+ } }, (0, react.createElement)("span", {
7790
+ className: "v3-dot",
7791
+ "data-tone": "ok",
7792
+ "aria-hidden": "true"
7793
+ }), "覆盖完整,计数精确") : (0, react.createElement)("span", { style: {
7794
+ display: "inline-flex",
7795
+ alignItems: "center",
7796
+ gap: 5
7797
+ } }, (0, react.createElement)("span", {
7798
+ className: "v3-dot",
7799
+ "data-tone": "warn",
7800
+ "aria-hidden": "true"
7801
+ }), "覆盖部分,实际更多");
7802
+ const failHint = (0, react.createElement)("span", { style: {
7803
+ display: "inline-flex",
7804
+ alignItems: "center",
7805
+ gap: 5
7806
+ } }, (0, react.createElement)("span", {
7807
+ className: "v3-dot",
7808
+ "data-tone": "bad",
7809
+ "aria-hidden": "true"
7810
+ }), `另有 ${formatCallIndexCount(mix?.unresolved, exact)} 次未配对`);
7811
+ const rates = callIndexRates(overview?.summary);
7812
+ const rateHint = (stats) => stats === void 0 ? "没有已知结果的调用" : `${stats.numerator} / ${stats.denominator} 次已知结果`;
7813
+ const trend = callIndexTrend(aggregates?.byDay);
7814
+ const callSpark = aggregates?.byDay === void 0 ? void 0 : aggregates.byDay.map((bucket) => bucket.calls);
7815
+ const successSpark = aggregates?.byDay === void 0 ? void 0 : aggregates.byDay.map((bucket) => bucket.calls > 0 ? bucket.success / bucket.calls : 0);
7816
+ const errorSpark = aggregates?.byDay === void 0 ? void 0 : aggregates.byDay.map((bucket) => bucket.error);
7817
+ const totalToolCalls = tools === void 0 ? 0 : tools.reduce((sum, row) => sum + row.calls, 0);
7818
+ const topRows = tools === void 0 ? void 0 : tools.slice(0, 12);
7819
+ const rankMax = topRows !== void 0 && topRows.length > 0 ? topRows[0].calls : 1;
7820
+ const topCoverage = topRows === void 0 || totalToolCalls === 0 ? void 0 : Math.round(topRows.reduce((sum, row) => sum + row.calls, 0) / totalToolCalls * 100);
7821
+ const failingRows = tools === void 0 ? void 0 : tools.filter((row) => row.error > 0).slice().sort((a, b) => b.error - a.error);
7822
+ const failingTop = failingRows === void 0 ? [] : failingRows.slice(0, 3);
7823
+ const failingRest = failingRows === void 0 ? [] : failingRows.slice(3);
7824
+ const failingRestErrors = failingRest.reduce((sum, row) => sum + row.error, 0);
7825
+ const toolsNotice = tools === void 0 ? (0, react.createElement)("p", {
7826
+ className: "v3-hint",
7827
+ "data-call-index-note": "tools"
7828
+ }, toolsFailure !== void 0 ? `逐工具明细读取失败:${callIndexFailureText(toolsFailure)}` : toolsLoading === true ? "正在读取逐工具明细…" : "尚未读取逐工具明细;上方调用总量与趋势来自总览快照,排行需要逐工具数据。") : tools.length === 0 ? (0, react.createElement)("p", {
7829
+ className: "v3-hint",
7830
+ "data-call-index-note": "tools"
7831
+ }, "该范围没有逐工具调用记录。") : null;
7832
+ const sessionInfo = status?.sessions;
7833
+ const taskNote = metrics.tasks === void 0 ? null : (0, react.createElement)("p", {
7834
+ className: "v3-hint",
7835
+ "data-call-index-note": "task",
7836
+ style: { marginTop: 9 }
7837
+ }, `最近一次扫描:${metrics.tasks.mode} / ${metrics.tasks.phase}(开始 ${formatCallIndexTime(metrics.tasks.startedAt)})`);
7159
7838
  return (0, react.createElement)("section", {
7160
- className: "mc-section",
7839
+ className: "v3-root mc-section",
7161
7840
  "aria-label": "调用统计总览",
7162
7841
  "aria-busy": loading === true
7163
- }, CallIndexStyles(), (0, react.createElement)("header", { className: "mc-heading-row" }, (0, react.createElement)("div", { className: "mc-heading-meta" }, (0, react.createElement)("h2", { className: "mc-heading" }, "总览"), (0, react.createElement)("span", { className: "mc-version" }, loading === true ? "正在读取…" : rangeLabel === "全部" ? "范围:全部已索引会话" : `范围:最近 ${rangeLabel}`)), (0, react.createElement)("div", { className: "mc-heading-actions" }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7842
+ }, CallIndexStyles(), V3Styles(), (0, react.createElement)("div", { style: {
7843
+ display: "flex",
7844
+ justifyContent: "flex-end"
7845
+ } }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
7164
7846
  size: "sm",
7165
7847
  variant: "outline",
7166
7848
  disabled: loading === true || onRefresh === void 0,
7167
7849
  onClick: () => onRefresh?.()
7168
- }, "刷新"))), (0, react.createElement)("p", { className: "mc-desc" }, "调用索引的总览:调用次数、Session 覆盖、来源构成与按日趋势。所有统计口径以覆盖状态为准,未知一律如实标注。"), notice === void 0 ? null : (0, react.createElement)("div", {
7850
+ }, "刷新")), notice === void 0 ? null : (0, react.createElement)("div", {
7169
7851
  className: "mc-notice",
7170
7852
  role: failure?.disconnected === true ? "alert" : "status",
7171
7853
  "data-call-index-state": failure?.disconnected === true ? "disconnected" : "failure"
7172
- }, notice), (0, react.createElement)("div", {
7173
- className: "mc-ci-metrics",
7854
+ }, notice), (0, react.createElement)("div", { className: "v3-status-pills" }, (0, react.createElement)("span", { className: "v3-statusline" }, (0, react.createElement)("span", {
7855
+ className: "v3-dot",
7856
+ "data-tone": scanning ? "run" : "ok",
7857
+ "aria-hidden": "true"
7858
+ }), (0, react.createElement)("span", null, statusSentence))), (0, react.createElement)("div", {
7859
+ className: "v3-stats",
7174
7860
  "data-call-index-metrics": "overview"
7175
- }, Metric("总调用", formatCallIndexCount(metrics.calls, exact), mix === void 0 ? void 0 : `成功 ${formatCallIndexCount(mix.succeeded, exact)}`), Metric("失败 / 未完成", metrics.failed === void 0 ? "—" : `${formatCallIndexCount(metrics.failed, exact)} / ${formatCallIndexCount(metrics.unfinished, exact)}`, "未完成 = 未配对结果 + 进行中"), Metric("已索引 / 可见 Session", metrics.sessionsListed === void 0 ? "—" : `${formatCallIndexCount(metrics.sessionsVisible)} / ${formatCallIndexCount(metrics.sessionsListed)}`, "可见 = 当前 Scope 仍可读,已索引 = 扫描覆盖到的"), Metric("不同工具数", formatCallIndexCount(metrics.distinctTools, exact), aggregates === void 0 ? "Host 未返回聚合" : "按 toolName 去重")), callIndexHasUnexplainedSources(aggregates) ? (0, react.createElement)("p", {
7176
- className: "mc-desc",
7177
- "data-call-index-note": "unexplained-sources"
7178
- }, "插件工具与技能执行入口的归属目前都解析为 0:Host 无法证明插件 owner,技能执行归属也未验证,所以这两个 0 表示「未解析出」,不表示「未使用」。") : null, (0, react.createElement)(CallIndexTrendChart, { aggregates }), (0, react.createElement)(CallIndexSourceBars, {
7179
- bySource: aggregates?.bySource,
7861
+ }, V3Stat({
7862
+ label: "调用次数",
7863
+ value: formatCallIndexCount(metrics.calls, exact),
7864
+ hint: coverageHint,
7865
+ ...callSpark === void 0 ? {} : { spark: Sparkline({
7866
+ values: callSpark,
7867
+ tone: "info"
7868
+ }) }
7869
+ }), V3Stat({
7870
+ label: "成功率",
7871
+ value: formatCallIndexRate(rates.success?.rate),
7872
+ hint: rateHint(rates.success),
7873
+ ...successSpark === void 0 ? {} : { spark: Sparkline({
7874
+ values: successSpark,
7875
+ tone: "ok"
7876
+ }) }
7877
+ }), V3Stat({
7878
+ label: "失败",
7879
+ value: formatCallIndexCount(metrics.failed, exact),
7880
+ hint: failHint,
7881
+ ...errorSpark === void 0 ? {} : { spark: Sparkline({
7882
+ values: errorSpark,
7883
+ tone: "bad"
7884
+ }) }
7885
+ }), V3Stat({
7886
+ label: "不同工具",
7887
+ value: formatCallIndexCount(metrics.distinctTools, exact),
7888
+ hint: "按 toolName 去重"
7889
+ })), (0, react.createElement)("div", {
7890
+ className: "v3-board",
7891
+ style: { marginTop: 12 }
7892
+ }, (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "按日调用趋势"), (0, react.createElement)("span", { className: "v3-pn" }, aggregates === void 0 ? "趋势不可用" : `合计 ${trend.totalCalls} 次 · 峰值 ${trend.maxCalls} 次`)), aggregates === void 0 ? (0, react.createElement)("p", {
7893
+ className: "v3-hint",
7894
+ "data-call-index-missing": "aggregates"
7895
+ }, "Host 未返回按日聚合(aggregates);这不是「没有调用」,趋势不可用。") : !trend.available ? (0, react.createElement)("p", {
7896
+ className: "v3-hint",
7897
+ "data-call-index-missing": "by-day"
7898
+ }, "该筛选在所选范围内没有任何按日桶,趋势为空;单个日粒度为 0 不参与绘制。") : (0, react.createElement)(OverviewTrendSvg, { chart: trend }), aggregates === void 0 || !trend.available ? null : (0, react.createElement)("div", { className: "v3-legend" }, (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--v3-info)" } }), "每日调用量(折线 + 面积)"), (0, react.createElement)("span", null, (0, react.createElement)("i", {
7899
+ "data-mark": "square",
7900
+ style: { background: "var(--v3-bad)" }
7901
+ }), "每日失败数(底柱)"), (0, react.createElement)("span", null, (0, react.createElement)("i", { style: {
7902
+ background: "var(--v3-info)",
7903
+ opacity: .3,
7904
+ height: 8,
7905
+ borderRadius: 2
7906
+ } }), "最近一周"))), OverviewSourcePanel({
7907
+ ...tools === void 0 ? {} : { tools },
7180
7908
  ...exact === void 0 ? {} : { totalExact: exact }
7181
- }), metrics.tasks === void 0 ? null : (0, react.createElement)("p", {
7182
- className: "mc-desc",
7183
- "data-call-index-note": "task"
7184
- }, `最近一次扫描:${metrics.tasks.mode} / ${metrics.tasks.phase}(开始 ${formatCallIndexTime(metrics.tasks.startedAt)})`));
7909
+ })), (0, react.createElement)("div", {
7910
+ className: "v3-board",
7911
+ style: { marginTop: 12 }
7912
+ }, (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "工具调用排行"), (0, react.createElement)("span", { className: "v3-pn" }, topRows === void 0 ? "前 12 名" : `前 12 名${topCoverage === void 0 ? "" : ` · 占已读取调用的 ${topCoverage}%`}` + (metrics.calls !== void 0 && totalToolCalls < metrics.calls ? ` · 已读取 ${formatCallIndexCount(totalToolCalls, exact)} / 共 ${formatCallIndexCount(metrics.calls, exact)} 次` : ""))), toolsNotice !== null ? toolsNotice : (0, react.createElement)("div", { className: "v3-rank" }, ...topRows.map((row) => {
7913
+ const bad = row.error > 0 && row.error / row.calls >= .2;
7914
+ return (0, react.createElement)("div", {
7915
+ key: "rank-" + row.toolName + "::" + row.sourceKind,
7916
+ className: "v3-rank-row",
7917
+ title: row.toolName
7918
+ }, (0, react.createElement)("span", { className: "v3-nm" }, toolDisplayLabel(row.toolName)), (0, react.createElement)("span", { className: "v3-bar" }, (0, react.createElement)("span", {
7919
+ ...bad ? { "data-bad": "true" } : {},
7920
+ style: { width: `${Math.max(3, Math.round(row.calls / rankMax * 100))}%` }
7921
+ })), (0, react.createElement)("span", { className: "v3-n" }, String(row.calls)), (0, react.createElement)("span", {
7922
+ className: "v3-e",
7923
+ ...bad ? { "data-bad": "true" } : {},
7924
+ ...row.error === 0 ? { "data-zero": "true" } : {}
7925
+ }, `失败 ${row.error}`));
7926
+ }))), (0, react.createElement)("div", { className: "v3-panel" }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "工具调用失败"), (0, react.createElement)("span", { className: "v3-pn" }, tools === void 0 ? "按失败数排序" : `${formatCallIndexCount(metrics.failed, exact)} 次失败`)), toolsNotice !== null ? toolsNotice : failingRows === void 0 || failingRows.length === 0 ? (0, react.createElement)("p", { className: "v3-hint" }, "当前范围没有失败调用。") : (0, react.createElement)("div", {
7927
+ className: "v3-rank",
7928
+ "data-call-index-failures": "true"
7929
+ }, ...failingTop.map((row) => (0, react.createElement)("div", {
7930
+ key: "fc-" + row.toolName + "::" + row.sourceKind,
7931
+ className: "v3-rank-row"
7932
+ }, (0, react.createElement)("span", {
7933
+ className: "v3-nm",
7934
+ style: {
7935
+ width: "auto",
7936
+ flex: "1 1 auto"
7937
+ }
7938
+ }, toolDisplayLabel(row.toolName)), (0, react.createElement)("span", {
7939
+ className: "v3-n",
7940
+ style: {
7941
+ color: "var(--v3-bad)",
7942
+ fontWeight: 600
7943
+ }
7944
+ }, `${row.error} 次`))), ...failingRest.length === 0 ? [] : [(0, react.createElement)("div", {
7945
+ key: "fc-rest",
7946
+ className: "v3-rank-row"
7947
+ }, (0, react.createElement)("span", {
7948
+ className: "v3-nm",
7949
+ style: {
7950
+ width: "auto",
7951
+ flex: "1 1 auto"
7952
+ }
7953
+ }, `其余 ${failingRest.length} 个工具合计`), (0, react.createElement)("span", {
7954
+ className: "v3-n",
7955
+ style: { color: "var(--v3-t2)" }
7956
+ }, `${failingRestErrors} 次`))]))), (0, react.createElement)("div", {
7957
+ className: "v3-panel",
7958
+ style: { marginTop: 12 }
7959
+ }, (0, react.createElement)("div", { className: "v3-ph" }, (0, react.createElement)("span", { className: "v3-pt" }, "索引状态"), (0, react.createElement)("span", { className: "v3-pn" }, metrics.tasks === void 0 ? "最近扫描未记录" : `最近扫描 ${metrics.tasks.mode} · ${metrics.tasks.phase}`)), (0, react.createElement)("div", { className: "v3-kv" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "v3-kv-l" }, "数据范围"), (0, react.createElement)("div", { className: "v3-kv-v" }, overview?.corpus === "persisted-plus-observed-live" ? "已持久化 + 挂载后实时观测" : "Host 未声明")), (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "v3-kv-l" }, "Session 列出 / 可见"), (0, react.createElement)("div", { className: "v3-kv-v v3-num" }, sessionInfo === void 0 ? "—" : `${sessionInfo.listed} / ${sessionInfo.visible}`)), (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "v3-kv-l" }, "待处理 / 失败"), (0, react.createElement)("div", { className: "v3-kv-v v3-num" }, sessionInfo === void 0 ? "—" : `${sessionInfo.pending} / ${sessionInfo.failed}`)), (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "v3-kv-l" }, "覆盖(事件 / 枚举 / 语义)"), (0, react.createElement)("div", { className: "v3-kv-v" }, status === void 0 ? "—" : `${COVERAGE_LABELS[status.eventCoverage]} / ${COVERAGE_LABELS[status.enumerationCoverage]} / ${COVERAGE_LABELS[status.coverage]}`))), taskNote));
7185
7960
  }
7186
7961
  function useNarrowViewport() {
7187
7962
  const [narrow, setNarrow] = (0, react.useState)(false);
@@ -7239,6 +8014,14 @@ function detailAfterRefresh(previous, page) {
7239
8014
  detailFailure: void 0
7240
8015
  };
7241
8016
  }
8017
+ /** Pure policy decision, so the container's refresh strategy is testable. */
8018
+ function callIndexRefreshPolicy(status) {
8019
+ const scanning = status !== void 0 && (status.phase === "running" || status.sessions.pending > 0 || status.sessions.pendingLive > 0);
8020
+ return {
8021
+ scanning,
8022
+ mode: scanning ? "status-only" : "full"
8023
+ };
8024
+ }
7242
8025
  /**
7243
8026
  * Container for 调用明细: owns the query, the paging cursor and the detail read.
7244
8027
  * Keeps the previous page visible while the next one loads, so a range click
@@ -7252,6 +8035,7 @@ function CallIndexDetailPanel({ api, initialQuery, resolveScopeTarget, resolveSe
7252
8035
  loadingDetail: false
7253
8036
  });
7254
8037
  const [refreshToken, setRefreshToken] = (0, react.useState)(0);
8038
+ const [search, setSearch] = (0, react.useState)("");
7255
8039
  const generation = (0, react.useRef)(0);
7256
8040
  const pageAnchor = (0, react.useRef)(void 0);
7257
8041
  /** How many extra pages 加载更多 has appended; 0 means only the first page. */
@@ -7313,23 +8097,35 @@ function CallIndexDetailPanel({ api, initialQuery, resolveScopeTarget, resolveSe
7313
8097
  refreshToken
7314
8098
  ]);
7315
8099
  /**
7316
- * Re-read while a scan is running.
8100
+ * Refresh status/progress while a scan is running — WITHOUT re-reading calls.
7317
8101
  *
7318
- * The 刷新 button is gone (D-031): indexing starts by itself, so the page must
7319
- * also refresh by itself or it would freeze on the first, empty snapshot. This
7320
- * polls ONLY while the Host reports an unfinished scan, then stops — it is not
7321
- * a permanent timer, and it never triggers indexing, only reads.
8102
+ * The old loop bumped `refreshToken`, which re-read status + calls + sessions
8103
+ * every 2s and replaced the data page under the operator mid-scan. Now it polls
8104
+ * `getStatus` only, keeps the current rows untouched, and triggers ONE full read
8105
+ * when the scan finishes (or on the explicit 刷新 button).
7322
8106
  */
7323
- const scanningNow = state.status !== void 0 && (state.status.phase === "running" || state.status.sessions.pending > 0);
8107
+ const scanningNow = callIndexRefreshPolicy(state.status).scanning;
7324
8108
  (0, react.useEffect)(() => {
7325
8109
  if (!scanningNow) return;
7326
- const timer = setTimeout(() => setRefreshToken((value) => value + 1), 2e3);
7327
- return () => clearTimeout(timer);
7328
- }, [
7329
- scanningNow,
7330
- refreshToken,
7331
- state.page?.generation
7332
- ]);
8110
+ let active = true;
8111
+ const id = setInterval(() => {
8112
+ (async () => {
8113
+ const result = await api.getStatus();
8114
+ if (!active || !result.ok) return;
8115
+ const next = result.value;
8116
+ const stillScanning = callIndexRefreshPolicy(next).scanning;
8117
+ setState((previous) => ({
8118
+ ...previous,
8119
+ status: next
8120
+ }));
8121
+ if (!stillScanning) setRefreshToken((value) => value + 1);
8122
+ })();
8123
+ }, 2e3);
8124
+ return () => {
8125
+ active = false;
8126
+ clearInterval(id);
8127
+ };
8128
+ }, [scanningNow, api]);
7333
8129
  /**
7334
8130
  * Append the next page.
7335
8131
  *
@@ -7488,6 +8284,8 @@ function CallIndexDetailPanel({ api, initialQuery, resolveScopeTarget, resolveSe
7488
8284
  loadingMore: state.loadingMore,
7489
8285
  loadingDetail: state.loadingDetail,
7490
8286
  narrow,
8287
+ search,
8288
+ onSearchChange: setSearch,
7491
8289
  onQueryChange: setQuery,
7492
8290
  onRefresh: () => setRefreshToken((value) => value + 1),
7493
8291
  onStartIndex: startIndex,
@@ -7498,8 +8296,12 @@ function CallIndexDetailPanel({ api, initialQuery, resolveScopeTarget, resolveSe
7498
8296
  });
7499
8297
  }
7500
8298
  /**
7501
- * Container for 总览. Reads the status once and one 30-day summary; the window
7502
- * is stated in the header so no metric is silently scoped.
8299
+ * Container for 总览. Reads the status once, one all-time summary, AND one
8300
+ * `listCalls` snapshot — because the overview reply carries no per-tool counts,
8301
+ * and the v3 ranking / fault panels need them. The per-tool read fills up to
8302
+ * `MAX_AUTO_PAGES` pages (same bound the detail page uses) then aggregates with
8303
+ * the shared pure `callIndexToolRows`. Failing to read rows never blocks the
8304
+ * headline numbers; it only shows an honest empty state in the two panels.
7503
8305
  */
7504
8306
  function CallIndexOverviewPanel({ api }) {
7505
8307
  const [state, setState] = (0, react.useState)({ loading: true });
@@ -7511,26 +8313,80 @@ function CallIndexOverviewPanel({ api }) {
7511
8313
  loading: true
7512
8314
  }));
7513
8315
  const query = { range: "all" };
8316
+ const anchor = Date.now();
7514
8317
  (async () => {
7515
- const [statusResult, overviewResult] = await Promise.all([api.getStatus(), api.getOverview(query)]);
8318
+ const [statusResult, overviewResult, callsResult] = await Promise.all([
8319
+ api.getStatus(),
8320
+ api.getOverview(query),
8321
+ api.listCalls(query, void 0, anchor)
8322
+ ]);
7516
8323
  if (!active) return;
7517
8324
  const failure = overviewResult.ok ? statusResult.ok ? void 0 : statusResult : overviewResult;
8325
+ let tools;
8326
+ let toolsFailure;
8327
+ if (callsResult.ok) {
8328
+ const items = [...callsResult.value.items];
8329
+ let cursor = callsResult.value.nextCursor;
8330
+ let pages = 0;
8331
+ while (cursor !== void 0 && pages < 20) {
8332
+ const next = await api.listCalls(query, cursor, anchor);
8333
+ if (!next.ok) {
8334
+ toolsFailure = next;
8335
+ break;
8336
+ }
8337
+ items.push(...next.value.items);
8338
+ cursor = next.value.nextCursor;
8339
+ pages += 1;
8340
+ }
8341
+ tools = callIndexToolRows({
8342
+ ...callsResult.value,
8343
+ items
8344
+ });
8345
+ } else toolsFailure = callsResult;
8346
+ if (!active) return;
7518
8347
  setState({
7519
8348
  loading: false,
7520
8349
  ...statusResult.ok ? { status: statusResult.value } : {},
7521
8350
  ...overviewResult.ok ? { overview: overviewResult.value } : {},
7522
- ...failure === void 0 ? {} : { failure }
8351
+ ...failure === void 0 ? {} : { failure },
8352
+ ...tools === void 0 ? {} : { tools },
8353
+ ...toolsFailure === void 0 ? {} : { toolsFailure }
7523
8354
  });
7524
8355
  })();
7525
8356
  return () => {
7526
8357
  active = false;
7527
8358
  };
7528
8359
  }, [api, refreshToken]);
8360
+ const scanning = callIndexRefreshPolicy(state.status).scanning;
8361
+ (0, react.useEffect)(() => {
8362
+ if (!scanning) return;
8363
+ let active = true;
8364
+ const id = setInterval(() => {
8365
+ (async () => {
8366
+ const result = await api.getStatus();
8367
+ if (!active || !result.ok) return;
8368
+ const next = result.value;
8369
+ const stillScanning = callIndexRefreshPolicy(next).scanning;
8370
+ setState((previous) => ({
8371
+ ...previous,
8372
+ status: next
8373
+ }));
8374
+ if (!stillScanning) setRefreshToken((value) => value + 1);
8375
+ })();
8376
+ }, 2e3);
8377
+ return () => {
8378
+ active = false;
8379
+ clearInterval(id);
8380
+ };
8381
+ }, [scanning, api]);
7529
8382
  return (0, react.createElement)(CallIndexOverviewPanelView, {
7530
8383
  ...state.status === void 0 ? {} : { status: state.status },
7531
8384
  ...state.overview === void 0 ? {} : { overview: state.overview },
8385
+ ...state.tools === void 0 ? {} : { tools: state.tools },
8386
+ ...state.toolsFailure === void 0 ? {} : { toolsFailure: state.toolsFailure },
7532
8387
  ...state.failure === void 0 ? {} : { failure: state.failure },
7533
8388
  loading: state.loading,
8389
+ toolsLoading: state.loading,
7534
8390
  onRefresh: () => setRefreshToken((value) => value + 1)
7535
8391
  });
7536
8392
  }
@@ -7659,18 +8515,26 @@ function OverviewContainer(props) {
7659
8515
  //#endregion
7660
8516
  //#region src/client/GuardPanel.tsx
7661
8517
  /**
7662
- * 思考限制 — the Guard configuration surface.
8518
+ * 思考限制 — the Guard configuration surface (v3 · 数据看板, D-044).
7663
8519
  *
7664
- * Three sections, in the order the product specified: repeated identical calls,
7665
- * alternating cycles, no-progress protection. Each carries a model-side
7666
- * identifier (`duplicate_call` / `alternating_cycle` / `no_progress`) shown as
7667
- * plain text, because the reminder text injected into the conversation is
7668
- * English while the label a user reads here is Chinese. The identifier is what
7669
- * ties the two together, so it is displayed rather than hidden.
8520
+ * Three sections, in product order: repeated identical calls, alternating
8521
+ * cycles, no-progress protection. Each carries a model-side identifier
8522
+ * ('duplicate_call' / 'alternating_cycle' / 'no_progress') shown as plain text,
8523
+ * because the reminder injected into the conversation is English while the label
8524
+ * a user reads here is Chinese. The identifier ties the two together.
7670
8525
  *
7671
- * NO summary panel: the current effective state is not repeated here. The
7672
- * 总览 page owns "what is on right now"; duplicating it produced two places to
7673
- * read the same thing and a second place to keep in sync.
8526
+ * v3 SHAPE: a two-column grid of .v3-gitem cards (>900px two columns, auto single
8527
+ * column via the container query in v3-ui.ts). The Guard counters sit at the very
8528
+ * top as a four-cell .v3-stats strip, followed by one short boundary hint. No page
8529
+ * title, no "activity" sub-title, no "counting since" line — the tab already says
8530
+ * what this page is.
8531
+ *
8532
+ * DOM CONTRACT kept for the shared test suite: nine <li class="mc-guard-point">
8533
+ * whose text still comes from t(); the three data-guard-section values in order;
8534
+ * the threshold editor wrapped in data-guard-thresholds (now three number inputs,
8535
+ * values from the Host); the four data-guard-stat cells and data-guard-stats; and
8536
+ * three role="switch" buttons whose aria-checked follows the props. No Chinese
8537
+ * colon (':') is introduced by this file — spacing does the separating instead.
7674
8538
  *
7675
8539
  * Kept hook-free and prop-driven so the settings test suite (no DOM harness) can
7676
8540
  * walk the returned tree.
@@ -7678,9 +8542,12 @@ function OverviewContainer(props) {
7678
8542
  /**
7679
8543
  * Section id → localized title plus the lines that explain what the switch does.
7680
8544
  *
7681
- * The descriptions are part of the contract, not decoration: a bare switch does
8545
+ * the descriptions are part of the contract, not decoration: a bare switch does
7682
8546
  * not tell a reader whether turning it off stops reminders only or also stops
7683
8547
  * local denials. Each bullet states the real behaviour the detector implements.
8548
+ *
8549
+ * v3 keeps all three bullets (nine <li class="mc-guard-point"> in total, as the
8550
+ * shared suite asserts) but lays them out as one compact wrapped row per card.
7684
8551
  */
7685
8552
  const SECTIONS = [
7686
8553
  {
@@ -7711,52 +8578,67 @@ const SECTIONS = [
7711
8578
  ]
7712
8579
  }
7713
8580
  ];
7714
- /** `—` for a counter the Host did not report; never a fabricated 0. */
8581
+ /**
8582
+ * Page-specific rules for the v3 Guard grid. Everything else comes from
8583
+ * v3-ui.ts; the only local vocabulary here is the compact bullet row, the
8584
+ * threshold editor chrome and the per-card "fired" foot. Colours are always v3
8585
+ * semantic variables — nothing is hard-coded, so light and dark themes both
8586
+ * track the host.
8587
+ */
8588
+ const GUARD_CSS = [
8589
+ ".mc-section.v3-root{display:flex;flex-direction:column;gap:12px}",
8590
+ ".v3-gpoints{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:0 5px}",
8591
+ ".v3-gpoints>.mc-guard-point{font-size:11.5px;line-height:18px;color:var(--v3-t3)}",
8592
+ ".v3-gpoints>.mc-guard-point+.mc-guard-point::before{content:'·';margin-right:5px;color:var(--v3-t4)}",
8593
+ ".v3-gitem--info{background:var(--v3-panel-2)}",
8594
+ ".v3-gitem .v3-gh .v3-sw{margin-left:auto}",
8595
+ ".v3-gfoot-edit{margin-top:9px}",
8596
+ ".v3-guard-stats{margin:0}",
8597
+ ".v3-gfoot-note{color:var(--v3-t4)}",
8598
+ ".mc-guard-threshold-edit{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
8599
+ ".mc-guard-threshold-edit-label{font-size:11.5px;color:var(--v3-t3)}",
8600
+ ".mc-guard-threshold-num{width:56px;padding:3px 6px;border-radius:var(--v3-r-sm);font-size:12px;text-align:center;font-family:var(--v3-font-code);color:var(--v3-t1);background:var(--v3-panel-2);border:1px solid var(--v3-line)}",
8601
+ ".mc-guard-threshold-num:focus-visible{outline:2px solid var(--v3-info);outline-offset:1px}",
8602
+ ".mc-guard-threshold-num:disabled{opacity:.6}",
8603
+ ".mc-guard-threshold-hint{margin:6px 0 0}"
8604
+ ].join("");
8605
+ /** '—' for a counter the Host did not report; never a fabricated 0. */
7715
8606
  function statText(value) {
7716
8607
  return value === void 0 || !Number.isFinite(value) ? "—" : String(value);
7717
8608
  }
7718
8609
  /**
7719
- * The Guard's real activity board.
8610
+ * The Guard's real activity board, restyled as a v3 stat strip.
7720
8611
  *
7721
8612
  * Answers the two questions the page previously could not: 「它是否在工作」
7722
- * (observations / since) and 「调用成功还是失败」 (outcome split). Every number
7723
- * comes from the Host; a missing counter renders `—`, so an older Host reads as
7724
- * "unknown" rather than "zero".
8613
+ * (observations) and 「调用成功还是失败」 (outcome split). Every number comes
8614
+ * from the Host; a missing counter renders '—', so an older Host reads as
8615
+ * "unknown" rather than "zero". The four data-guard-stat cells and the
8616
+ * data-guard-stats hook are the published contract. No sub-title and no
8617
+ * "counting since" line: the strip is the first thing on the page.
7725
8618
  */
7726
8619
  function GuardStatsBoard(props) {
7727
8620
  const { t, stats } = props;
7728
8621
  const outcomes = stats?.outcomes;
7729
8622
  const reminders = stats?.reminders;
7730
8623
  const cell = (label, value, hint) => (0, react.createElement)("div", {
7731
- className: "mc-guard-stat",
8624
+ className: "v3-stat",
7732
8625
  "data-guard-stat": label
7733
- }, (0, react.createElement)("div", { className: "mc-guard-stat-label" }, label), (0, react.createElement)("div", { className: "mc-guard-stat-value" }, value), hint === void 0 ? null : (0, react.createElement)("div", { className: "mc-guard-stat-hint" }, hint));
7734
- return (0, react.createElement)("div", {
7735
- className: "mc-guard-stats",
8626
+ }, (0, react.createElement)("div", { className: "v3-stat-l" }, label), (0, react.createElement)("div", { className: "v3-stat-v" }, value), hint === void 0 ? null : (0, react.createElement)("div", { className: "v3-stat-d" }, hint));
8627
+ return (0, react.createElement)("div", { className: "v3-guard-stats" }, (0, react.createElement)("div", {
8628
+ className: "v3-stats",
7736
8629
  "data-guard-stats": "true",
7737
8630
  role: "group",
7738
8631
  "aria-label": t("guardStatsTitle")
7739
- }, cell(t("guardStatObservations"), statText(stats?.observations), t("guardStatObservationsHint")), cell(t("guardStatOutcomes"), `${statText(outcomes?.success)} / ${statText(outcomes?.error)}`, t("guardStatOutcomesHint")), cell(t("guardStatReminders"), `${statText(reminders?.repeat)} / ${statText(reminders?.cycle)}`, t("guardStatRemindersHint")), cell(t("guardStatDenials"), statText(stats?.denials), t("guardStatDenialsHint")), stats?.since === void 0 ? null : (0, react.createElement)("p", {
7740
- className: "mc-guard-stats-since",
7741
- "data-guard-stats-since": "true"
7742
- }, `${t("guardStatsSince")} ${formatGuardTime(stats.since)}`));
7743
- }
7744
- /** `YYYY-MM-DD HH:mm` in the browser zone; `—` when unknown. */
7745
- function formatGuardTime(time) {
7746
- if (time === void 0 || !Number.isFinite(time)) return "—";
7747
- const date = new Date(time);
7748
- if (Number.isNaN(date.getTime())) return "—";
7749
- const pad = (value) => String(value).padStart(2, "0");
7750
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
8632
+ }, cell(t("guardStatObservations"), statText(stats?.observations), t("guardStatObservationsHint")), cell(t("guardStatOutcomes"), statText(outcomes?.success) + " / " + statText(outcomes?.error), t("guardStatOutcomesHint")), cell(t("guardStatReminders"), statText(reminders?.repeat) + " / " + statText(reminders?.cycle), t("guardStatRemindersHint")), cell(t("guardStatDenials"), statText(stats?.denials), t("guardStatDenialsHint"))));
7751
8633
  }
7752
8634
  /**
7753
8635
  * Editable reminder thresholds.
7754
8636
  *
7755
- * The thresholds were a hard-coded 3/5/8 the operator could read but not change,
7756
- * which made the section look decorative. They are now one comma-separated
7757
- * field: the list is short, order matters, and a number-per-input row would be
7758
- * more chrome than content. Invalid input is refused by the Host and reported
7759
- * back, so this input never claims a value the detector rejected.
8637
+ * Three plain number inputs instead of one comma-separated text field: the
8638
+ * operator sees one box per firing point and never has to learn a syntax. Values
8639
+ * always come from the Host; a keystroke is assembled into a fresh array and
8640
+ * handed to onThresholdsChange, and an older Host that does not accept a write
8641
+ * gets disabled boxes. data-guard-thresholds marks the wrapper.
7760
8642
  */
7761
8643
  function GuardThresholdEditor(props) {
7762
8644
  const { t, thresholds, onChange } = props;
@@ -7765,81 +8647,84 @@ function GuardThresholdEditor(props) {
7765
8647
  return (0, react.createElement)("div", {
7766
8648
  className: "mc-guard-threshold-edit",
7767
8649
  "data-guard-thresholds": "true"
7768
- }, (0, react.createElement)("label", {
7769
- className: "mc-guard-threshold-edit-label",
7770
- htmlFor: "mc-guard-thresholds-input"
7771
- }, t("guardThresholds")), (0, react.createElement)("input", {
7772
- id: "mc-guard-thresholds-input",
7773
- className: "mc-guard-threshold-edit-input",
7774
- type: "text",
7775
- inputMode: "numeric",
7776
- value: thresholds.join(", "),
8650
+ }, (0, react.createElement)("label", { className: "mc-guard-threshold-edit-label" }, t("guardThresholds")), ...thresholds.map((value, index) => (0, react.createElement)("input", {
8651
+ key: index,
8652
+ type: "number",
8653
+ min: 2,
8654
+ max: 50,
8655
+ step: 1,
8656
+ className: "mc-guard-threshold-num",
8657
+ value: String(value),
7777
8658
  readOnly: !editable,
7778
8659
  disabled: !editable,
7779
- "aria-label": t("guardThresholds"),
8660
+ "aria-label": t("guardThresholds") + " " + String(index + 1),
7780
8661
  onChange: (event) => {
7781
8662
  if (!editable) return;
7782
- const parsed = parseGuardThresholds(event.target.value);
7783
- if (parsed === void 0) return;
7784
- onChange(parsed);
8663
+ const raw = Number(event.target.value);
8664
+ if (!Number.isSafeInteger(raw) || raw < 2 || raw > 50) return;
8665
+ const next = thresholds.slice();
8666
+ next[index] = raw;
8667
+ onChange(next);
7785
8668
  }
7786
- }), (0, react.createElement)("span", { className: "mc-guard-threshold-edit-hint" }, editable ? t("guardThresholdsEditableHint") : t("guardThresholdsFixedHint")));
8669
+ })));
7787
8670
  }
7788
- /**
7789
- * Parse `3, 5, 8` into a threshold list.
7790
- *
7791
- * Returns `undefined` for anything the Host would reject (empty, non-integer,
7792
- * out of range, not strictly increasing). Parsing here as well as on the Host is
7793
- * deliberate: it keeps an invalid keystroke from round-tripping through the
7794
- * remote and flickering the field.
7795
- */
7796
- function parseGuardThresholds(text) {
7797
- const parts = text.split(",").map((part) => part.trim()).filter((part) => part !== "");
7798
- if (parts.length === 0 || parts.length > 5) return void 0;
7799
- const parsed = [];
7800
- for (const part of parts) {
7801
- if (!/^\d+$/.test(part)) return void 0;
7802
- const value = Number(part);
7803
- if (!Number.isSafeInteger(value) || value < 2 || value > 50) return void 0;
7804
- if (parsed.length > 0 && value <= parsed[parsed.length - 1]) return void 0;
7805
- parsed.push(value);
7806
- }
7807
- return parsed;
8671
+ /** The counter a card reports as "fired", per the product mapping. */
8672
+ function firedValue(id, stats) {
8673
+ if (id === "duplicate_call") return stats?.reminders?.repeat;
8674
+ if (id === "alternating_cycle") return stats?.reminders?.cycle;
8675
+ return stats?.denials;
7808
8676
  }
7809
8677
  function GuardPanel(props) {
7810
8678
  const { t, enabled, duplicateThresholds, onToggle, onThresholdsChange, stats, onReset } = props;
7811
8679
  const section = (entry) => {
7812
8680
  const isOn = enabled?.[entry.id] ?? false;
7813
8681
  const canToggle = onToggle !== void 0;
8682
+ const dot = isOn ? (0, react.createElement)("span", {
8683
+ className: "v3-dot",
8684
+ "data-tone": "ok",
8685
+ "aria-hidden": "true"
8686
+ }) : (0, react.createElement)("span", {
8687
+ className: "v3-dot",
8688
+ "aria-hidden": "true"
8689
+ });
8690
+ const belowBullets = entry.id === "duplicate_call" ? (0, react.createElement)("div", { className: "v3-gfoot-edit" }, (0, react.createElement)(GuardThresholdEditor, {
8691
+ t,
8692
+ thresholds: duplicateThresholds,
8693
+ ...onThresholdsChange === void 0 ? {} : { onChange: onThresholdsChange }
8694
+ }), (0, react.createElement)("p", { className: "v3-hint mc-guard-threshold-hint" }, t("guardThresholdHint"))) : (0, react.createElement)("p", { className: "v3-hint" }, t(entry.id === "alternating_cycle" ? "guardPurposeB" : "guardPurposeC"));
8695
+ const footNote = entry.id === "alternating_cycle" ? (0, react.createElement)("span", { className: "v3-gfoot-note" }, t("guardFootCycle")) : entry.id === "no_progress" ? (0, react.createElement)("span", { className: "v3-gfoot-note" }, t("guardFootNoProgress")) : null;
7814
8696
  return (0, react.createElement)("div", {
7815
8697
  key: entry.id,
7816
- className: "mc-guard-card",
8698
+ className: "v3-gitem",
7817
8699
  "data-guard-section": entry.id
7818
- }, (0, react.createElement)("div", { className: "mc-guard-head" }, (0, react.createElement)("span", { className: "mc-guard-title" }, t(entry.titleKey)), (0, react.createElement)("span", { className: "mc-guard-model" }, `${t("guardModelLabel")} ${entry.id}`), (0, react.createElement)("button", {
8700
+ }, (0, react.createElement)("div", { className: "v3-gh" }, dot, (0, react.createElement)("span", { className: "v3-gnm" }, t(entry.titleKey)), (0, react.createElement)("span", { className: "v3-gid" }, entry.id), (0, react.createElement)("button", {
7819
8701
  type: "button",
7820
8702
  role: "switch",
7821
8703
  "aria-checked": isOn ? "true" : "false",
7822
8704
  "aria-label": t(entry.titleKey),
7823
- className: `mc-guard-switch${isOn ? " mc-guard-switch--on" : ""}`,
8705
+ className: "v3-sw",
7824
8706
  disabled: !canToggle,
7825
8707
  onClick: () => canToggle && onToggle(entry.id, !isOn)
7826
- })), (0, react.createElement)("div", { className: "mc-guard-body" }, (0, react.createElement)("ul", { className: "mc-guard-points" }, ...entry.bodyKeys.map((key) => (0, react.createElement)("li", {
8708
+ })), (0, react.createElement)("ul", { className: "v3-gpoints" }, ...entry.bodyKeys.map((key) => (0, react.createElement)("li", {
7827
8709
  key,
7828
8710
  className: "mc-guard-point"
7829
- }, t(key)))), entry.id === "duplicate_call" && duplicateThresholds !== void 0 && (0, react.createElement)("div", { className: "mc-guard-thresholds" }, (0, react.createElement)(GuardThresholdEditor, {
7830
- t,
7831
- thresholds: duplicateThresholds,
7832
- ...onThresholdsChange === void 0 ? {} : { onChange: onThresholdsChange }
7833
- }))));
8711
+ }, t(key)))), belowBullets, (0, react.createElement)("div", { className: "v3-gfoot" }, (0, react.createElement)("span", null, t("guardFired")), (0, react.createElement)("span", { className: "v3-kbd" }, statText(firedValue(entry.id, stats))), footNote));
7834
8712
  };
7835
- return (0, react.createElement)("section", { className: "mc-section" }, (0, react.createElement)("header", { className: "mc-heading-row" }, (0, react.createElement)("h2", { className: "mc-heading" }, t("guardTitle")), (0, react.createElement)("div", { className: "mc-heading-actions" }, onReset !== void 0 && (0, react.createElement)("button", {
8713
+ const scopeCell = (0, react.createElement)("div", {
8714
+ key: "scope",
8715
+ className: "v3-gitem v3-gitem--info"
8716
+ }, (0, react.createElement)("div", { className: "v3-gh" }, (0, react.createElement)("span", { className: "v3-gnm" }, t("guardScopeTitle"))), (0, react.createElement)("p", null, t("guardScopeBody")), (0, react.createElement)("div", { className: "v3-gfoot" }, (0, react.createElement)("span", null, t("guardScopeFoot"))));
8717
+ return (0, react.createElement)("section", { className: "mc-section v3-root" }, V3Styles(), (0, react.createElement)("style", { "data-guard-style": "true" }, GUARD_CSS), onReset === void 0 ? null : (0, react.createElement)("div", { style: {
8718
+ display: "flex",
8719
+ justifyContent: "flex-end"
8720
+ } }, (0, react.createElement)("button", {
7836
8721
  type: "button",
7837
8722
  className: "mc-count mc-count--action",
7838
8723
  onClick: onReset
7839
- }, t("guardReset")))), (0, react.createElement)("p", { className: "mc-desc" }, t("guardDesc")), (0, react.createElement)("p", { className: "mc-guard-boundary" }, t("guardBoundary")), (0, react.createElement)(GuardStatsBoard, {
8724
+ }, t("guardReset"))), (0, react.createElement)(GuardStatsBoard, {
7840
8725
  t,
7841
8726
  ...stats === void 0 ? {} : { stats }
7842
- }), ...SECTIONS.map(section));
8727
+ }), (0, react.createElement)("p", { className: "v3-hint" }, t("guardBoundaryShort")), (0, react.createElement)("div", { className: "v3-gpanel" }, ...SECTIONS.map(section), scopeCell));
7843
8728
  }
7844
8729
  //#endregion
7845
8730
  //#region src/client/GuardContainer.tsx
@@ -8136,6 +9021,7 @@ function PluginSection(props) {
8136
9021
  const [active, setActive] = (0, react.useState)("overview");
8137
9022
  const callIndexApi = callIndexApiFor(props.callIndexRemote);
8138
9023
  const [diagnostics, setDiagnostics] = (0, react.useState)(void 0);
9024
+ const [catalogUsage, setCatalogUsage] = (0, react.useState)(void 0);
8139
9025
  (0, react.useEffect)(() => {
8140
9026
  let live = true;
8141
9027
  (async () => {
@@ -8147,6 +9033,35 @@ function PluginSection(props) {
8147
9033
  live = false;
8148
9034
  };
8149
9035
  }, [callIndexApi]);
9036
+ (0, react.useEffect)(() => {
9037
+ let live = true;
9038
+ (async () => {
9039
+ const result = await callIndexApi.listCalls({ range: "all" });
9040
+ if (!live || !result.ok) return;
9041
+ const map = /* @__PURE__ */ new Map();
9042
+ for (const row of callIndexToolRows(result.value)) map.set(row.toolName, {
9043
+ uses: row.calls,
9044
+ failures: row.error
9045
+ });
9046
+ setCatalogUsage(map);
9047
+ })();
9048
+ return () => {
9049
+ live = false;
9050
+ };
9051
+ }, [callIndexApi]);
9052
+ /**
9053
+ * Reset the settings content scroller when the category changes.
9054
+ *
9055
+ * The shell owns the scrolling column and swaps panels with `hidden`, so the
9056
+ * previous tab's scrollTop survives the switch and the next page opens halfway
9057
+ * down (observed: 执行保护 opened below its counter board). Reset the nearest
9058
+ * scrollable ancestor of the active panel.
9059
+ */
9060
+ (0, react.useEffect)(() => {
9061
+ let node = document.getElementById("mc-category-panel-" + active);
9062
+ while (node !== null && !(node.scrollHeight > node.clientHeight + 4)) node = node.parentElement;
9063
+ if (node !== null) node.scrollTop = 0;
9064
+ }, [active]);
8150
9065
  return (0, react.createElement)(PluginSectionView, {
8151
9066
  capability: props.capability,
8152
9067
  usageApi: props.usageApi,
@@ -8157,7 +9072,8 @@ function PluginSection(props) {
8157
9072
  t: props.t,
8158
9073
  active,
8159
9074
  onSelect: setActive,
8160
- ...diagnostics === void 0 ? {} : { diagnostics }
9075
+ ...diagnostics === void 0 ? {} : { diagnostics },
9076
+ ...catalogUsage === void 0 ? {} : { catalogUsage }
8161
9077
  });
8162
9078
  }
8163
9079
  /**
@@ -8166,7 +9082,7 @@ function PluginSection(props) {
8166
9082
  * `UsagePanelView`.
8167
9083
  */
8168
9084
  function PluginSectionView(props) {
8169
- const { capability, usageApi, callIndexRemote, scopeTarget, sessionCatalog, guardRemote, t, active, onSelect, diagnostics } = props;
9085
+ const { capability, usageApi, callIndexRemote, scopeTarget, sessionCatalog, guardRemote, t, active, onSelect, diagnostics, catalogUsage } = props;
8170
9086
  const callIndexApi = callIndexApiFor(callIndexRemote);
8171
9087
  const panel = (category, children) => (0, react.createElement)("div", {
8172
9088
  role: "tabpanel",
@@ -8199,7 +9115,10 @@ function PluginSectionView(props) {
8199
9115
  5,
8200
9116
  8
8201
9117
  ]
8202
- })), panel("catalog", (0, react.createElement)(CapabilitySection, capability)), panel("usage", callIndexRemote === void 0 && usageApi !== void 0 ? (0, react.createElement)(UsagePanel, { api: usageApi }) : (0, react.createElement)(CallIndexDetailPanel, {
9118
+ })), panel("catalog", (0, react.createElement)(CapabilitySection, {
9119
+ ...capability,
9120
+ ...catalogUsage === void 0 ? {} : { usage: catalogUsage }
9121
+ })), panel("usage", callIndexRemote === void 0 && usageApi !== void 0 ? (0, react.createElement)(UsagePanel, { api: usageApi }) : (0, react.createElement)(CallIndexDetailPanel, {
8203
9122
  api: callIndexApi,
8204
9123
  ...scopeTarget === void 0 ? {} : { resolveScopeTarget: scopeTarget },
8205
9124
  ...sessionCatalog === void 0 ? {} : { resolveSessionCatalog: sessionCatalog }
@@ -8279,6 +9198,17 @@ async function apply(ctx) {
8279
9198
  guardDesc: "只针对有证据的重复与无进展拦截具体操作;不因总步数、工具总次数或任务时长终止任务。",
8280
9199
  guardBoundary: "统计与限制是两件事:Step、工具调用次数、任务时长只用于统计与诊断,不构成硬停止条件。Quick / Normal / Deep 三档里的 Step、工具次数、时长阈值(soft* / hard*)是兼容字段与统计口径,只参与配置校验与计数,不产生提醒,也不停止任务。单个工具自身的超时另由宿主或该工具负责。",
8281
9200
  guardReset: "恢复默认",
9201
+ guardScopeTitle: "生效范围",
9202
+ guardScopeBody: "三个开关按 Agent / Turn 隔离,不跨 Session 继承;关闭只停止对应的提醒与局部拒绝,不改动任何配置或历史。",
9203
+ guardScopeFoot: "恢复默认只写回开关值,不重写其他配置",
9204
+ guardBoundaryLabel: "边界",
9205
+ guardBoundaryShort: "这里的数字只用于展示,不构成硬停止;三档阈值只参与校验与计数,不产生提醒也不停止任务。",
9206
+ guardThresholdHint: "默认 3 / 5 / 8 次;改的是第几次重复时提醒,只提醒、不拦截。",
9207
+ guardFired: "实际触发",
9208
+ guardPurposeB: "作用:识别 A→B→A→B 这类交替循环并提醒,避免来回空转。",
9209
+ guardPurposeC: "作用:证据明确时拦下已证实的重复操作,其余情况只提醒。",
9210
+ guardFootCycle: "命中后只给出说明,不阻断调用",
9211
+ guardFootNoProgress: "降级条件 日志缺失 · 结果不可比较 · 识别不确定",
8282
9212
  guardSectionA: "A · 相同调用重复提醒",
8283
9213
  guardSectionB: "B · 交替循环识别",
8284
9214
  guardSectionC: "C · 无进展保护",
@@ -8327,7 +9257,7 @@ async function apply(ctx) {
8327
9257
  emptyGlobalSkills: "暂无全局技能",
8328
9258
  emptyProjectSkills: "暂无项目技能",
8329
9259
  emptyPresetSkills: "暂无预设技能",
8330
- filterByName: "按名称、分组或正则表达式筛选…",
9260
+ filterByName: "筛选名称或分组",
8331
9261
  filterHint: "忽略大小写。匹配工具/技能名称、MCP 服务器、技能来源标签与预设 ID;也可以输入正则表达式,例如 cordis|ptc 或 /^mcp__dbx/。",
8332
9262
  filterNoMatch: "没有匹配的能力(清空过滤框可恢复完整列表)",
8333
9263
  emptyTools: "暂无工具",
@@ -8336,6 +9266,8 @@ async function apply(ctx) {
8336
9266
  residentShort: "常驻",
8337
9267
  onDemandShort: "按需",
8338
9268
  disabledShort: "禁用",
9269
+ catalogZeroCall: "零调用",
9270
+ catalogZeroCallHint: "按已索引调用统计",
8339
9271
  cycleHint: "点击标签切换分类(按需 → 禁用 → 常驻)",
8340
9272
  notPreviewable: "该文件不是可预览的文本文件",
8341
9273
  previewClose: "关闭",
@@ -8428,7 +9360,7 @@ async function apply(ctx) {
8428
9360
  summaryLimitInvalid: "请输入不小于 20 的整数",
8429
9361
  summaryLimitDisabled: "检索信息存储未启用",
8430
9362
  summaryLimitSaved: "检索描述上限已保存",
8431
- diagnosticTitle: "能力诊断:{count} 项",
9363
+ diagnosticTitle: "能力诊断 · {count} 项",
8432
9364
  diagnosticUnavailable: "不可调用:{name}(来源 {source}{preset})",
8433
9365
  diagnosticShadowed: "同名覆盖:{name} 保留 {winner},隐藏 {hidden}",
8434
9366
  diagnosticBroken: "损坏的预设:{id}({reason};受影响技能数量由 DSH 未提供)",
@@ -8477,6 +9409,17 @@ async function apply(ctx) {
8477
9409
  guardDesc: "Blocks specific operations only when there is evidence of repetition or no progress; never stops a task for step count, tool count or duration.",
8478
9410
  guardBoundary: "Counting and limiting are separate: steps, tool calls and task duration are statistics and diagnostics only, never hard-stop conditions. The per-level (Quick / Normal / Deep) step, tool-call and duration thresholds (soft* / hard*) are compatibility fields and counting units: they only take part in config validation and tallies, never producing a reminder or stopping a task. A single tool timeout stays with the host or that tool.",
8479
9411
  guardReset: "Restore defaults",
9412
+ guardScopeTitle: "Scope",
9413
+ guardScopeBody: "The three switches are isolated per Agent / turn and never inherited across Sessions; turning one off stops only its reminders and local denials, and changes no configuration or history.",
9414
+ guardScopeFoot: "Restoring defaults writes only the switch values; no other configuration is rewritten.",
9415
+ guardBoundaryLabel: "Boundary",
9416
+ guardBoundaryShort: "These numbers are informational only and never stop a task; the three tier thresholds participate in validation and counting only.",
9417
+ guardThresholdHint: "Defaults are 3 / 5 / 8; editing changes when the reminder fires. Reminders never block a call.",
9418
+ guardFired: "Fired",
9419
+ guardPurposeB: "Purpose: detect alternating A→B→A→B cycles and remind, so the agent stops going in circles.",
9420
+ guardPurposeC: "Purpose: block a confirmed repeat when the evidence is clear; otherwise only remind.",
9421
+ guardFootCycle: "Fires an explanation only; it never blocks the call.",
9422
+ guardFootNoProgress: "Degrades when: logs missing · results incomparable · match uncertain",
8480
9423
  guardSectionA: "A · Repeated identical calls",
8481
9424
  guardSectionB: "B · Alternating cycle detection",
8482
9425
  guardSectionC: "C · No-progress protection",
@@ -8525,7 +9468,7 @@ async function apply(ctx) {
8525
9468
  emptyGlobalSkills: "No global skills",
8526
9469
  emptyProjectSkills: "No project skills",
8527
9470
  emptyPresetSkills: "No preset skills",
8528
- filterByName: "Filter by name, group, or regex…",
9471
+ filterByName: "Filter name or group",
8529
9472
  filterHint: "Case-insensitive. Matches tool/skill names, MCP servers, skill source labels and preset ids; a regular expression works too, e.g. cordis|ptc or /^mcp__dbx/.",
8530
9473
  filterNoMatch: "No capability matches (clear the filter to see the full list)",
8531
9474
  emptyTools: "No tools",
@@ -8534,6 +9477,8 @@ async function apply(ctx) {
8534
9477
  residentShort: "Resident",
8535
9478
  onDemandShort: "On-demand",
8536
9479
  disabledShort: "Disabled",
9480
+ catalogZeroCall: "No calls",
9481
+ catalogZeroCallHint: "Based on indexed calls",
8537
9482
  cycleHint: "Click a tag to cycle its classification (On-demand → Disabled → Resident)",
8538
9483
  notPreviewable: "This file is not a previewable text file",
8539
9484
  previewClose: "Close",