@morehao/dsh-skills-console 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/client.js +152 -179
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -90,7 +90,7 @@ plugin_manager install_bundle(target = <本仓库绝对路径>)
90
90
  1. **添加技能**:点右上角「添加技能」,填 `owner/repo` 或 GitHub URL,点「检测技能」;仓库里有多个技能时勾选要装的(支持多选,默认全选,列表带每个技能的说明),只有一个会默认勾上。检测不到时可以手填技能名,留空则装仓库内全部。
91
91
  2. **共享至其他 Agent**:只有「只读自己目录」的 Agent 才需要这一步(例如 Claude Code)。点行内「共享设置」勾选目标 Agent;本来就读共享目录的 Agent 不在列表里。之后新装技能默认共享至哪些 Agent,用页头「新安装时自动共享」设定(默认一个都不勾)。
92
92
  3. **检查更新**:点列表头上的刷新图标;有更新的行会亮起「更新」,也可以一次「全部更新」。本地改过的技能会先要求确认。
93
- 4. **调用策略**:点行内「自动 / 仅手动 / 完全停用」状态按钮切换,弹层里同时给出各 Agent 的覆盖情况。自动=模型与 `/` 都能调用;仅手动=只改 `SKILL.md` 的 `disable-model-invocation`,`/` 仍可用;完全停用=把 `SKILL.md` 改名为 `SKILL.md.disabled` 并留 marker,恢复时回到停用前的模式。作者已在 `SKILL.md` 里声明仅手动时,chip 只读展示,不会被覆盖。停用期间更新 / 安装会被拒绝,避免文件被悄悄写回。
93
+ 4. **调用策略**:点行内「自动 / 仅手动 / 完全停用」状态按钮,在居中的调用策略弹窗里切换。自动=模型与 `/` 都能调用;仅手动=只改 `SKILL.md` 的 `disable-model-invocation`,`/` 仍可用;完全停用=把 `SKILL.md` 改名为 `SKILL.md.disabled` 并留 marker,恢复时回到停用前的模式。作者已在 `SKILL.md` 里声明仅手动时,chip 只读展示,不会被覆盖。停用期间更新 / 安装会被拒绝,避免文件被悄悄写回。
94
94
 
95
95
  ## 安全与可靠性
96
96
 
package/client.js CHANGED
@@ -1134,6 +1134,19 @@ var CSS = `
1134
1134
  .dshsc-pop-label em { font-style: normal; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
1135
1135
  .dshsc-pop-name { display: inline-flex; align-items: baseline; gap: 6px; }
1136
1136
  .dshsc-pop-hint { font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
1137
+ /* 与 DSH 自带开关同尺寸/同 token:关是 border-l3 轨道,开是 brand-primary 轨道。
1138
+ 0.5.0 换三档 chip 时这组规则被一起删掉,但「侧边栏入口」还在用 .dshsc-switch,补回来。 */
1139
+ .dshsc-switch { box-sizing: border-box; position: relative; flex: none; width: 36px; height: 20px; padding: 2px;
1140
+ border: 0; border-radius: 999px; background: var(--dsw-alias-border-l3); cursor: pointer;
1141
+ transition: background .14s ease; }
1142
+ .dshsc-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
1143
+ .dshsc-switch:disabled { cursor: not-allowed; opacity: .5; }
1144
+ .dshsc-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); }
1145
+ .dshsc-knob { display: block; width: 16px; height: 16px; border-radius: 50%;
1146
+ background: var(--dsw-alias-switch-thumb, #fff); box-shadow: 0 1px 2px rgba(0, 0, 0, .28);
1147
+ transition: transform .12s ease, background .12s ease; }
1148
+ .dshsc-switch[aria-checked="true"] .dshsc-knob { transform: translateX(16px);
1149
+ background: var(--dsw-alias-label-primary-foreground, #fff); }
1137
1150
 
1138
1151
  .dshsc-btn { font: inherit; font-size: 12px; line-height: 1; padding: 7px 11px; border-radius: 8px; cursor: pointer;
1139
1152
  white-space: nowrap;
@@ -1357,10 +1370,15 @@ var CSS = `
1357
1370
  .dshsc-sub-list { display: flex; flex-direction: column; padding: 0 12px 7px 46px;
1358
1371
  background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 22%, transparent);
1359
1372
  border-top: 1px solid var(--dsw-alias-border-l1); }
1373
+ /* 条目操作列 92px 是照两枚按钮预留的,眼下每行只有一枚「安装」(两字 ≈ 43px:0 9px padding + 边框)。
1374
+ 表头「操作」和已装行的破折号都按按钮宽度做一个右对齐的居中盒子:三者落在同一条中轴上,
1375
+ 既不贴着列右缘,也不用改列宽 / 挪按钮。换语言或字号时盒子只是不再严格等于按钮宽度。 */
1376
+ .dshsc-sub-list { --dshsc-entry-act: 44px; }
1360
1377
  .dshsc-entryhead { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
1361
1378
  align-items: center; height: 26px; border-bottom: 1px solid var(--dsw-alias-border-l1);
1362
1379
  color: var(--dsw-alias-label-tertiary); font-size: 10px; font-weight: 500; letter-spacing: .02em; }
1363
- .dshsc-entryhead .right { text-align: right; padding-right: 2px; }
1380
+ .dshsc-entryhead .right { justify-self: end; box-sizing: border-box; width: var(--dshsc-entry-act);
1381
+ text-align: center; padding-right: 0; }
1364
1382
  .dshsc-sub-entry { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
1365
1383
  align-items: center; font-size: 12px; padding: 6px 0; border-top: 1px solid var(--dsw-alias-border-l1); }
1366
1384
  .dshsc-entryhead + .dshsc-sub-entry { border-top: 0; }
@@ -1374,8 +1392,9 @@ var CSS = `
1374
1392
  .dshsc-state.ok svg { color: var(--dsw-alias-state-success-primary); }
1375
1393
  .dshsc-sub-entry .dshsc-actions { justify-self: end; display: flex; justify-content: flex-end; }
1376
1394
  .dshsc-sub-entry .dshsc-btn { height: 24px; padding: 0 9px; font-size: 11.5px; }
1377
- /* 已装条目的操作列没有动作:破折号占位,保持右对齐与按钮基线一致 */
1378
- .dshsc-sub-entry .dshsc-noop { color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; padding-right: 9px; }
1395
+ /* 已装条目的操作列没有动作:破折号占位,居中在按钮宽度里,和表头、安装按钮同一条中轴 */
1396
+ .dshsc-sub-entry .dshsc-noop { box-sizing: border-box; width: var(--dshsc-entry-act); padding: 0;
1397
+ text-align: center; color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; }
1379
1398
  /* 设置页的真实宽度常常远小于窗口(侧栏 + 页面缩放),视口 media query 不可靠;
1380
1399
  以 .dshsc-root 自身宽度为准,窄面板时切两列,避免固定列把 Agent 标签挤成竖排。
1381
1400
  断点取 860 而不是贴着面板常见的 ~900:四列网格需要 280 + 150 + 操作列 + 3×14 gap + 中间列,
@@ -1404,7 +1423,7 @@ var CSS = `
1404
1423
  }
1405
1424
  .dshsc-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
1406
1425
 
1407
- /* 调用策略 chip:一个入口显示当前模式;点开三档弹层。窄列里允许省略,不撑破 102px */
1426
+ /* 调用策略 chip:一个入口显示当前模式;点开三档弹窗。窄列里允许省略,不撑破 102px */
1408
1427
  .dshsc-chip { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
1409
1428
  height: 24px; padding: 0 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
1410
1429
  background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font-family: inherit;
@@ -1428,40 +1447,40 @@ var CSS = `
1428
1447
  .dshsc-chip.legacy { color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-bg-layer-2); }
1429
1448
  .dshsc-chip.muted { color: var(--dsw-alias-label-tertiary); cursor: default; opacity: .8; }
1430
1449
 
1431
- .dshsc-policy-pop { position: fixed; z-index: 70; box-sizing: border-box; width: min(380px, calc(100vw - 16px));
1432
- max-height: min(600px, calc(100vh - 16px)); overflow: auto; padding: 14px;
1450
+ /* 锚定浮层外壳:介绍浮层等共用(fixed + portal 到宿主弹窗),不再和调用策略弹窗共用 */
1451
+ .dshsc-floatpop { position: fixed; z-index: 70; box-sizing: border-box; width: min(380px, calc(100vw - 16px));
1452
+ max-height: min(600px, calc(100vh - 16px)); overflow: hidden; padding: 14px;
1433
1453
  background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 14px;
1434
1454
  box-shadow: 0 18px 48px rgba(0,0,0,.32), 0 2px 8px rgba(0,0,0,.16);
1435
1455
  display: flex; flex-direction: column; gap: 10px; }
1436
- .dshsc-policy-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
1437
- .dshsc-policy-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
1438
- .dshsc-policy-sub { margin: 2px 0 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
1439
- .dshsc-policy-close { flex: none; width: 24px; height: 24px; margin: -4px -4px 0 0; border: 0; border-radius: 6px;
1456
+ .dshsc-floatpop-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; flex: none; }
1457
+ .dshsc-floatpop-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
1458
+ .dshsc-floatpop-sub { margin: 2px 0 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
1459
+ .dshsc-floatpop-close { flex: none; width: 24px; height: 24px; margin: -4px -4px 0 0; border: 0; border-radius: 6px;
1440
1460
  background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; cursor: pointer; }
1441
- .dshsc-policy-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
1442
- .dshsc-policy-options { display: flex; flex-direction: column; gap: 6px; }
1443
- .dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 10px; width: 100%;
1444
- padding: 9px 10px; text-align: left; border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px;
1445
- background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
1446
- .dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l2); }
1447
- .dshsc-policyopt.active { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, var(--dsw-alias-border-l1));
1448
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 7%, transparent); }
1461
+ .dshsc-floatpop-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
1462
+
1463
+ /* 调用策略:标准居中弹窗(Overlay)里的单选列表;行距 / 圆点尺寸对齐共享设置的 .dshsc-check */
1464
+ .dshsc-policy-options { max-height: none; overflow: visible; }
1465
+ .dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 9px; width: 100%;
1466
+ padding: 5px 8px; text-align: left; border: 0; border-radius: 8px;
1467
+ background: transparent; color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
1468
+ .dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); }
1469
+ .dshsc-policyopt:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
1449
1470
  .dshsc-policyopt:disabled { cursor: not-allowed; opacity: .55; }
1450
1471
  .dshsc-policyopt.static { cursor: default; }
1451
- .dshsc-policyopt-dot { flex: none; width: 14px; height: 14px; margin-top: 1px; border-radius: 50%;
1452
- border: 1.5px solid var(--dsw-alias-border-l3); }
1453
- .dshsc-policyopt.active .dshsc-policyopt-dot { border-color: var(--dsw-alias-brand-primary);
1454
- background: radial-gradient(circle, var(--dsw-alias-brand-primary) 0 40%, transparent 47%); }
1472
+ .dshsc-policyopt.static:hover { background: transparent; }
1473
+ .dshsc-policyopt-dot { box-sizing: border-box; flex: none; width: 15px; height: 15px; margin-top: 2px;
1474
+ border-radius: 50%; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); }
1475
+ .dshsc-policyopt.active .dshsc-policyopt-dot { border: 5px solid var(--dsw-alias-brand-primary); }
1455
1476
  .dshsc-policyopt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1456
- .dshsc-policyopt-body b { font-size: 12px; font-weight: 600; }
1477
+ .dshsc-policyopt-body b { font-size: 13px; font-weight: 600; }
1457
1478
  .dshsc-policyopt-body small { font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
1458
1479
  .dshsc-policy-note { padding: 7px 9px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2);
1459
1480
  color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 15px; }
1460
1481
  .dshsc-policy-note.warn, .dshsc-policy-note.author { color: var(--dsw-alias-state-warn-primary);
1461
1482
  background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 9%, transparent); }
1462
1483
  .dshsc-policy-hidden { display: flex; flex-direction: column; gap: 8px; }
1463
- .dshsc-policy-restore { align-self: flex-start; }
1464
- .dshsc-policy-foot { display: flex; justify-content: flex-end; gap: 6px; }
1465
1484
  .dshsc-policy-confirm { display: flex; flex-direction: column; gap: 6px; padding: 10px; border-radius: 9px;
1466
1485
  border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, var(--dsw-alias-border-l1));
1467
1486
  background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 7%, transparent); }
@@ -1657,7 +1676,7 @@ var CSS = `
1657
1676
  .dshsc-intro-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
1658
1677
  .dshsc-intro-btn .dshsc-spark { font-size: 10px; color: var(--dsw-alias-brand-primary); }
1659
1678
 
1660
- /* 介绍浮层:复用 policy 浮层的外壳(fixed + portal),只调内容排版 */
1679
+ /* 介绍浮层:复用通用 floatpop 外壳(fixed + portal),只调内容排版 */
1661
1680
  .dshsc-intro-pop { width: min(360px, calc(100vw - 16px)); gap: 7px; }
1662
1681
  .dshsc-intro-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--dsw-alias-label-primary);
1663
1682
  white-space: pre-wrap; overflow-wrap: anywhere; }
@@ -2558,23 +2577,23 @@ function IntroPopover({
2558
2577
  "div",
2559
2578
  {
2560
2579
  ref,
2561
- className: "dshsc-policy-pop dshsc-intro-pop",
2580
+ className: "dshsc-floatpop dshsc-intro-pop",
2562
2581
  role: "dialog",
2563
2582
  "aria-label": `${t("intro.title")} · ${name}`,
2564
2583
  style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
2565
2584
  },
2566
2585
  h(
2567
2586
  "div",
2568
- { className: "dshsc-policy-head" },
2587
+ { className: "dshsc-floatpop-head" },
2569
2588
  h(
2570
2589
  "div",
2571
2590
  null,
2572
- h("div", { className: "dshsc-policy-title" }, `✨ ${t("intro.title")}`),
2573
- h("div", { className: "dshsc-policy-sub", title: subtitle || name }, subtitle || name)
2591
+ h("div", { className: "dshsc-floatpop-title" }, `✨ ${t("intro.title")}`),
2592
+ h("div", { className: "dshsc-floatpop-sub", title: subtitle || name }, subtitle || name)
2574
2593
  ),
2575
2594
  h("button", {
2576
2595
  type: "button",
2577
- className: "dshsc-policy-close",
2596
+ className: "dshsc-floatpop-close",
2578
2597
  "aria-label": t("action.close"),
2579
2598
  onClick: onClose
2580
2599
  }, "×")
@@ -2589,40 +2608,40 @@ function IntroPopover({
2589
2608
  ));
2590
2609
  }
2591
2610
 
2592
- // client/views/policy-popover.js
2611
+ // client/views/policy-modal.js
2593
2612
  var import_react4 = __toESM(require("react"), 1);
2594
- function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpark }) {
2613
+
2614
+ // client/views/overlay.js
2615
+ function Overlay({ t, title, children, footer, onCancel }) {
2616
+ useOverlayLayer(true, onCancel);
2617
+ return portalize(h(
2618
+ "div",
2619
+ { className: "dshsc-overlay", onClick: (event) => {
2620
+ if (event.target === event.currentTarget) onCancel?.();
2621
+ } },
2622
+ h(
2623
+ "div",
2624
+ { className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
2625
+ h("button", {
2626
+ className: "dshsc-modal-close",
2627
+ "aria-label": t("action.cancel"),
2628
+ onClick: (event) => {
2629
+ event.stopPropagation();
2630
+ onCancel?.();
2631
+ }
2632
+ }, "×"),
2633
+ h("h3", null, title),
2634
+ children,
2635
+ h("div", { className: "dshsc-modal-foot" }, footer)
2636
+ )
2637
+ ), overlayHost());
2638
+ }
2639
+
2640
+ // client/views/policy-modal.js
2641
+ function PolicyModal({ t, row, busy, onClose, onSave, onUnpark }) {
2595
2642
  const initial = row.mode === "hidden" ? "auto" : row.mode;
2596
2643
  const [mode, setMode] = import_react4.default.useState(initial);
2597
2644
  const [confirming, setConfirming] = import_react4.default.useState(false);
2598
- const ref = import_react4.default.useRef(null);
2599
- const [pos, setPos] = import_react4.default.useState({ left: 0, top: 0, visibility: "hidden" });
2600
- useOverlayLayer(true, onClose);
2601
- import_react4.default.useLayoutEffect(() => {
2602
- const node = ref.current;
2603
- if (!node || !anchor || typeof window === "undefined") return;
2604
- const rect = node.getBoundingClientRect();
2605
- const width = rect.width || 360;
2606
- const height = rect.height || 320;
2607
- let left = anchor.left + anchor.width / 2 - width / 2;
2608
- left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
2609
- let top = anchor.bottom + 8;
2610
- if (top + height > window.innerHeight - 8) {
2611
- top = Math.max(8, anchor.top - height - 8);
2612
- }
2613
- setPos({ left, top, visibility: "visible" });
2614
- }, [anchor, confirming, mode]);
2615
- import_react4.default.useEffect(() => {
2616
- if (typeof document === "undefined") return void 0;
2617
- const onPointerDown = (event) => {
2618
- const target = event?.target;
2619
- if (ref.current?.contains?.(target)) return;
2620
- if (anchorEl?.contains?.(target)) return;
2621
- onClose();
2622
- };
2623
- document.addEventListener("pointerdown", onPointerDown, true);
2624
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
2625
- }, [anchorEl, onClose]);
2626
2645
  const hidden = row.mode === "hidden";
2627
2646
  const author = row.origin === "author" && row.mode === "manual";
2628
2647
  const flat = row.kind === "file";
@@ -2644,7 +2663,8 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
2644
2663
  type: "button",
2645
2664
  key: `policy-${id}`,
2646
2665
  className: `dshsc-policyopt${mode === id ? " active" : ""}`,
2647
- "aria-pressed": String(mode === id),
2666
+ role: "radio",
2667
+ "aria-checked": String(mode === id),
2648
2668
  disabled: busy || flat,
2649
2669
  onClick: () => choose(id)
2650
2670
  },
@@ -2656,95 +2676,85 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
2656
2676
  h("small", null, desc)
2657
2677
  )
2658
2678
  );
2659
- return portalize(h(
2660
- "div",
2679
+ const footer = confirming ? [
2680
+ h("button", {
2681
+ type: "button",
2682
+ key: "cancel",
2683
+ className: "dshsc-btn ghost",
2684
+ disabled: busy,
2685
+ onClick: () => setConfirming(false)
2686
+ }, t("action.cancel")),
2687
+ h("button", {
2688
+ type: "button",
2689
+ key: "confirm",
2690
+ className: "dshsc-btn danger",
2691
+ disabled: busy,
2692
+ onClick: () => onSave("hidden")
2693
+ }, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
2694
+ ] : hidden ? [
2695
+ h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2696
+ h("button", {
2697
+ type: "button",
2698
+ key: "restore",
2699
+ className: "dshsc-btn primary",
2700
+ disabled: busy,
2701
+ onClick: onUnpark
2702
+ }, t("policy.unpark"))
2703
+ ] : [
2704
+ h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2705
+ h("button", {
2706
+ type: "button",
2707
+ key: "save",
2708
+ className: "dshsc-btn primary",
2709
+ disabled: busy || !dirty || flat,
2710
+ onClick: submit
2711
+ }, busy ? t("policy.saving") : t("action.save"))
2712
+ ];
2713
+ return h(
2714
+ Overlay,
2661
2715
  {
2662
- ref,
2663
- className: "dshsc-policy-pop",
2664
- role: "dialog",
2665
- "aria-label": t("policy.title"),
2666
- style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
2716
+ t,
2717
+ title: t("policy.title"),
2718
+ onCancel: onClose,
2719
+ footer
2667
2720
  },
2668
- h(
2669
- "div",
2670
- { className: "dshsc-policy-head" },
2671
- h(
2672
- "div",
2673
- null,
2674
- h("div", { className: "dshsc-policy-title" }, t("policy.title")),
2675
- h("div", { className: "dshsc-policy-sub" }, t("policy.subtitle"))
2676
- ),
2677
- h("button", {
2678
- type: "button",
2679
- className: "dshsc-policy-close",
2680
- "aria-label": t("action.close"),
2681
- onClick: onClose
2682
- }, "×")
2683
- ),
2684
- author ? h("div", { className: "dshsc-policy-note author" }, t("policy.authorHint")) : null,
2685
- row.legacy ? h("div", { className: "dshsc-policy-note" }, t("policy.legacyHint")) : null,
2686
- row.drift ? h("div", { className: "dshsc-policy-note warn" }, t("policy.driftHint")) : null,
2721
+ h("p", { className: "dshsc-sub", key: "sub" }, t("policy.subtitle")),
2722
+ author ? h("div", { className: "dshsc-policy-note author", key: "author" }, t("policy.authorHint")) : null,
2723
+ row.legacy ? h("div", { className: "dshsc-policy-note", key: "legacy" }, t("policy.legacyHint")) : null,
2724
+ row.drift ? h("div", { className: "dshsc-policy-note warn", key: "drift" }, t("policy.driftHint")) : null,
2687
2725
  hidden ? h(
2688
2726
  "div",
2689
- { className: "dshsc-policy-hidden" },
2727
+ { className: "dshsc-policy-hidden", key: "hidden" },
2690
2728
  h(
2691
2729
  "div",
2692
- { className: "dshsc-policyopt active static" },
2693
- h("span", { className: "dshsc-policyopt-dot" }),
2730
+ { className: "dshsc-panel" },
2694
2731
  h(
2695
- "span",
2696
- { className: "dshsc-policyopt-body" },
2697
- h("b", null, t("policy.hidden")),
2698
- h("small", null, t("policy.hiddenDesc"))
2732
+ "div",
2733
+ { className: "dshsc-policyopt active static", role: "radio", "aria-checked": "true" },
2734
+ h("span", { className: "dshsc-policyopt-dot", "aria-hidden": "true" }),
2735
+ h(
2736
+ "span",
2737
+ { className: "dshsc-policyopt-body" },
2738
+ h("b", null, t("policy.hidden")),
2739
+ h("small", null, t("policy.hiddenDesc"))
2740
+ )
2699
2741
  )
2700
2742
  ),
2701
- h("button", {
2702
- type: "button",
2703
- className: "dshsc-btn primary dshsc-policy-restore",
2704
- disabled: busy,
2705
- onClick: onUnpark
2706
- }, t("policy.unpark")),
2707
- h("p", { className: "dshsc-policy-sub" }, t("policy.hiddenHint"))
2743
+ h("p", { className: "dshsc-sub" }, t("policy.hiddenHint"))
2708
2744
  ) : h(
2709
2745
  "div",
2710
- { className: "dshsc-policy-options" },
2746
+ { className: "dshsc-panel dshsc-policy-options", key: "options", role: "radiogroup", "aria-label": t("policy.title") },
2711
2747
  option("auto", t("policy.auto"), t("policy.autoDesc")),
2712
2748
  option("manual", t("policy.manual"), t("policy.manualDesc")),
2713
2749
  option("hidden", t("policy.hidden"), t("policy.hiddenDesc"))
2714
2750
  ),
2715
2751
  confirming ? h(
2716
2752
  "div",
2717
- { className: "dshsc-policy-confirm" },
2753
+ { className: "dshsc-policy-confirm", key: "confirm" },
2718
2754
  h("b", null, t("policy.hiddenConfirmTitle")),
2719
- h("p", null, t("policy.hiddenConfirmBody")),
2720
- h(
2721
- "div",
2722
- { className: "dshsc-policy-foot" },
2723
- h("button", {
2724
- type: "button",
2725
- className: "dshsc-btn ghost",
2726
- disabled: busy,
2727
- onClick: () => setConfirming(false)
2728
- }, t("action.cancel")),
2729
- h("button", {
2730
- type: "button",
2731
- className: "dshsc-btn danger",
2732
- disabled: busy,
2733
- onClick: () => onSave("hidden")
2734
- }, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
2735
- )
2736
- ) : h(
2737
- "div",
2738
- { className: "dshsc-policy-foot" },
2739
- h("button", { type: "button", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2740
- hidden ? null : h("button", {
2741
- type: "button",
2742
- className: "dshsc-btn primary",
2743
- disabled: busy || !dirty || flat,
2744
- onClick: submit
2745
- }, busy ? t("policy.saving") : t("action.save"))
2746
- )
2747
- ), overlayHost());
2755
+ h("p", null, t("policy.hiddenConfirmBody"))
2756
+ ) : null
2757
+ );
2748
2758
  }
2749
2759
 
2750
2760
  // client/views/installed.js
@@ -2788,16 +2798,10 @@ function InstalledTab({
2788
2798
  anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
2789
2799
  });
2790
2800
  }
2791
- function openPolicy(event, row) {
2801
+ function openPolicy(row) {
2792
2802
  if (busy !== null || row.kind === "file") return;
2793
2803
  if (row.mode === "manual" && row.origin === "author") return;
2794
- const el = event.currentTarget;
2795
- const rect = el.getBoundingClientRect();
2796
- setPolicy({
2797
- row,
2798
- anchorEl: el,
2799
- anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
2800
- });
2804
+ setPolicy({ row });
2801
2805
  }
2802
2806
  function policyChip(row) {
2803
2807
  if (row.status === "unmanaged") {
@@ -2814,7 +2818,7 @@ function InstalledTab({
2814
2818
  disabled: busy !== null,
2815
2819
  title: t("policy.hiddenHint"),
2816
2820
  "aria-haspopup": "dialog",
2817
- onClick: (event) => openPolicy(event, row)
2821
+ onClick: () => openPolicy(row)
2818
2822
  }, t("policy.hidden"));
2819
2823
  }
2820
2824
  if (mode === "manual" && row.origin === "author") {
@@ -2830,7 +2834,7 @@ function InstalledTab({
2830
2834
  disabled: busy !== null,
2831
2835
  title: t("policy.driftHint"),
2832
2836
  "aria-haspopup": "dialog",
2833
- onClick: (event) => openPolicy(event, row)
2837
+ onClick: () => openPolicy(row)
2834
2838
  }, t("policy.drift"));
2835
2839
  }
2836
2840
  if (row.legacy === "dsh-off") {
@@ -2840,7 +2844,7 @@ function InstalledTab({
2840
2844
  disabled: busy !== null,
2841
2845
  title: t("policy.legacyHint"),
2842
2846
  "aria-haspopup": "dialog",
2843
- onClick: (event) => openPolicy(event, row)
2847
+ onClick: () => openPolicy(row)
2844
2848
  }, t("policy.legacy"));
2845
2849
  }
2846
2850
  if (mode === "manual") {
@@ -2850,7 +2854,7 @@ function InstalledTab({
2850
2854
  disabled: busy !== null,
2851
2855
  title: t("policy.manualOnlyHint"),
2852
2856
  "aria-haspopup": "dialog",
2853
- onClick: (event) => openPolicy(event, row)
2857
+ onClick: () => openPolicy(row)
2854
2858
  }, t("policy.manual"));
2855
2859
  }
2856
2860
  return h("button", {
@@ -2859,7 +2863,7 @@ function InstalledTab({
2859
2863
  disabled: busy !== null,
2860
2864
  title: t("policy.open"),
2861
2865
  "aria-haspopup": "dialog",
2862
- onClick: (event) => openPolicy(event, row)
2866
+ onClick: () => openPolicy(row)
2863
2867
  }, t("policy.auto"));
2864
2868
  }
2865
2869
  function statusTag(row) {
@@ -3256,11 +3260,9 @@ function InstalledTab({
3256
3260
  actions.onView(target);
3257
3261
  }
3258
3262
  }) : null;
3259
- const popover = policy ? h(PolicyPopover, {
3263
+ const policyModal = policy ? h(PolicyModal, {
3260
3264
  t,
3261
3265
  row: policy.row,
3262
- anchor: policy.anchor,
3263
- anchorEl: policy.anchorEl,
3264
3266
  busy: busy !== null,
3265
3267
  onClose: () => setPolicy(null),
3266
3268
  onSave: (mode) => {
@@ -3274,7 +3276,7 @@ function InstalledTab({
3274
3276
  actions.onRequestUnpark(target);
3275
3277
  }
3276
3278
  }) : null;
3277
- return h(import_react5.default.Fragment, null, content(), popover, introPopover);
3279
+ return h(import_react5.default.Fragment, null, content(), policyModal, introPopover);
3278
3280
  }
3279
3281
 
3280
3282
  // client/views/agent-tip.js
@@ -3511,7 +3513,6 @@ function SubscriptionsTab({
3511
3513
  h(
3512
3514
  "span",
3513
3515
  { className: `dshsc-state${installedAlready ? " ok" : ""}` },
3514
- installedAlready ? "✓ " : "",
3515
3516
  installedAlready ? t("installed") : t("notInstalled")
3516
3517
  ),
3517
3518
  h(
@@ -3539,34 +3540,6 @@ var import_react8 = __toESM(require("react"), 1);
3539
3540
 
3540
3541
  // client/views/add-skill.js
3541
3542
  var import_react7 = __toESM(require("react"), 1);
3542
-
3543
- // client/views/overlay.js
3544
- function Overlay({ t, title, children, footer, onCancel }) {
3545
- useOverlayLayer(true, onCancel);
3546
- return portalize(h(
3547
- "div",
3548
- { className: "dshsc-overlay", onClick: (event) => {
3549
- if (event.target === event.currentTarget) onCancel?.();
3550
- } },
3551
- h(
3552
- "div",
3553
- { className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
3554
- h("button", {
3555
- className: "dshsc-modal-close",
3556
- "aria-label": t("action.cancel"),
3557
- onClick: (event) => {
3558
- event.stopPropagation();
3559
- onCancel?.();
3560
- }
3561
- }, "×"),
3562
- h("h3", null, title),
3563
- children,
3564
- h("div", { className: "dshsc-modal-foot" }, footer)
3565
- )
3566
- ), overlayHost());
3567
- }
3568
-
3569
- // client/views/add-skill.js
3570
3543
  function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
3571
3544
  const [source, setSource] = import_react7.default.useState("");
3572
3545
  const [phase, setPhase] = import_react7.default.useState("idle");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-skills-console",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "统一管理本地技能的安装、更新、删除与接入,让同一份技能在多个 Agent 间复用。A skill manager for DeepSeek Harness: install, update, remove, link and toggle agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "morehao <morehao@qq.com>",