dsh-recency-guard 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -8
- package/dist/client.js +123 -43
- package/dist/index.js +214 -19
- package/locale/en.json +7 -3
- package/locale/zh.json +7 -3
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -65,6 +65,67 @@
|
|
|
65
65
|
|
|
66
66
|
当某个插件把 DSH 拖垮时,本插件会在崩溃的那一刻记录下"谁最可疑",并在**下一次启动时、在崩溃插件的代码被加载之前**把它挡在门外。反复崩溃时,关闭的插件会逐个增加(第 1 次关最近更新的 1 个,仍崩溃则关最近更新的 2 个)。稳定之后,除首要嫌疑外,其余被关闭的配置会自动恢复。
|
|
67
67
|
|
|
68
|
+
## v0.11.1 紧急修复(宿主兼容 + 启动崩溃 · 2026-11-08)
|
|
69
|
+
|
|
70
|
+
**事故背景**:桌面端 DSH 自动升级到 `dsh 0.2.2-rc.1` 后连续崩溃(5 次/3 分钟),
|
|
71
|
+
守护插件被兼容闸门移出 profile、不再保护;重新挂载后又因遗留写锁把宿主拖进
|
|
72
|
+
`fatal load failure`。本版本修复三处,全部是「守护不能拖垮宿主」的底线问题。
|
|
73
|
+
|
|
74
|
+
- **宿主预发布版本兼容(P0)**:`engines.dsh` 与全部 `@deepseek-ai/dsh-*`
|
|
75
|
+
peer 范围改为显式预发布分支
|
|
76
|
+
`>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0`。
|
|
77
|
+
原因:node-semver 只放行「与范围中某个比较符 **major.minor.patch 元组相同**
|
|
78
|
+
且自身带预发布标签」的预发布版本 —— 旧范围 `>=0.2.1-rc.5 <0.3.0-0` 会
|
|
79
|
+
静默排除 `0.2.2-rc.1`,插件被兼容闸门判为不兼容并从 profile 移除。
|
|
80
|
+
已在 `dsh 0.2.2-rc.1` 干净沙箱(sb-rg-v0111)实测安装 + 启动 + 守护激活通过。
|
|
81
|
+
- **启动绝不抛出(P0)**:`boot()` 失败改为**降级留痕**(步骤记失败 + 警告
|
|
82
|
+
日志),不再 rethrow。此前一个可恢复的写锁超时会把宿主变成
|
|
83
|
+
`dsh: fatal load failure`(退出码 1)—— 观测/恢复设施不能拖垮主流程。
|
|
84
|
+
- **失效写锁自愈(P0)**:新增 `cleanStaleLocks`(Store.init 启动时清理 +
|
|
85
|
+
`updateState` 超时后清理重试一次)。判据:锁内容为空/非正整数 PID(不完整
|
|
86
|
+
记录,atomic-write 永远无法接管)/ PID 对应进程已死,且文件年龄 > 5s
|
|
87
|
+
(避开锁创建与写 PID 之间的零字节窗口,绝不抢活锁)。
|
|
88
|
+
真机事故:崩溃恰好发生在锁创建与写 PID 之间,遗留 0 字节
|
|
89
|
+
`state.json.lock`,此后每次启动都等满超时并致命。
|
|
90
|
+
- **连续崩溃兜底升级(P0)**:`assessConfidence` 新增安全网 —— 累计崩溃
|
|
91
|
+
`crashSeq ≥ 3` 时即使概率计数未达 `confirmRounds` 也强制行动;同时
|
|
92
|
+
`readCrashHistory` 跳过启动时的「无嫌疑 crash-detected」记录。实测该记录
|
|
93
|
+
每轮打断连续计数,导致 crashSeq 涨到 3 仍停在「观察中 1/2 轮」,崩溃循环
|
|
94
|
+
永远无法自愈。
|
|
95
|
+
- 测试:新增 `tests/unit/store-lock.test.ts`(锁自愈 11 断言)与契约层
|
|
96
|
+
预发布范围守卫;14/14 全绿(Core 7 / Host 2 / Client 4 / Contract 1)。
|
|
97
|
+
|
|
98
|
+
## v0.11 新增(数据面安全 + 图谱图例一致性 + 马尔可夫去向可见 + 市场准备)
|
|
99
|
+
|
|
100
|
+
- **动作路由方法守卫(P0 安全修复)**:`/action/archive|reenable|restore|recover`
|
|
101
|
+
此前不检查 HTTP 方法 —— `GET /recency-guard/api/action/archive` 也会执行归档重置
|
|
102
|
+
(浏览器预取/爬虫/误点链接都可能触发)。现在非 POST 一律 405 + `allow: POST`。
|
|
103
|
+
- **`?partition=` 真正生效(P0)**:客户端一直拼分区查询参数,但数据面此前从不读它
|
|
104
|
+
—— 跨分区查看永远静默返回当前分区。现在快照路由在宿主侧实现分区感知:
|
|
105
|
+
请求其它分区返回**只读磁盘投影**(状态/日志/决策/备份),运行时字段如实标注
|
|
106
|
+
归属,未知分区返回 `available:false`(绝不假绿)。
|
|
107
|
+
- **面板动作面补齐**:新增 `/action/restore` 路由 + 面板「恢复非嫌疑插件」按钮
|
|
108
|
+
(工具早有此动作,路由此前缺失);暂停轮询时 footer 显示「数据已停留 N 分钟」。
|
|
109
|
+
- **图谱图例一致性(P0)**:图例颜色此前固定语义色调色板,切到「按类型」口径时
|
|
110
|
+
与节点实际颜色不一致;现在图例色**跟随当前着色口径**(legendColorOf),并直接
|
|
111
|
+
绘制节点形状(形状=第二编码通道,图例不展示它就只对知道映射的人可读)。
|
|
112
|
+
- **搜索循环跳转**:多匹配时回车依次遍历(第 n/m 指示可见),不再永远停在第一个;
|
|
113
|
+
「重置」按钮现在同时清掉拖拽偏移(此前节点拖飞后只能刷新页面)。
|
|
114
|
+
- **马尔可夫跨阶段去向(P0)**:矩阵卡此前只渲染自环概率条 —— `monitoring → 崩溃
|
|
115
|
+
40%` 这类核心去向分布(README 宣称的)在界面上根本看不到。现在每行带 top-2
|
|
116
|
+
跨阶段去向徽标。
|
|
117
|
+
- **事件流展开**:默认 12 条 + 「展开其余 N 条」显式计数(与嫌疑排名同款纪律,
|
|
118
|
+
数据层给全量、显示层管截断)。
|
|
119
|
+
- **测试盲区补齐**:新增 `tests/client/graph-model.test.ts`(过滤共源/布局确定性/
|
|
120
|
+
图例同源/平行边曲率)与 `tests/host/routes.test.ts`(方法守卫/partition/参数
|
|
121
|
+
校验/请假语义)—— 文件头「已由测试钉死」的注释如今成真。13/13 全绿。
|
|
122
|
+
- **参考插件取长补短**:系统研究 dsh-ops(输出 notice 三要素)、dsh-compat-guardian
|
|
123
|
+
(reconcile 自愈/原子写/乐观锁)、dsh-context(工作预算/资源自适应)、
|
|
124
|
+
dsh-cookie-guard(独立救援 CLI)后按审计清单采纳落地;研究报告见
|
|
125
|
+
`.cycles/cycles/cycle-6/参考插件研究报告.md`。
|
|
126
|
+
- **插件市场准备**:仓库 topics(dsh-plugin 等)补齐、256px 图标、screenshots.json
|
|
127
|
+
声明、投稿 YAML 按 awesome-dsh-plugin contributing.md 形态定稿。
|
|
128
|
+
|
|
68
129
|
## v0.7 新增(崩溃级修复 + 可交互图谱 + 可观测性白盒化)
|
|
69
130
|
|
|
70
131
|
**先说最要紧的一条:v0.6 会把 profile 写崩。**
|
|
@@ -267,6 +328,10 @@ npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上
|
|
|
267
328
|
includeExperimental: true # 是否把 experimental/beta 命名的包算作嫌疑
|
|
268
329
|
maxAutoDisable: 2 # 单次恢复最多关闭几个嫌疑插件
|
|
269
330
|
autoRestore: true # 稳定后自动恢复非首要嫌疑的配置
|
|
331
|
+
confirmRounds: 2 # 概率性证据的行动阈值(唯一嫌疑不受限)
|
|
332
|
+
versionedBackups: true # 插件更新时写带版本的时间线备份
|
|
333
|
+
backupRetain: 5 # 每插件保留几份时间线备份
|
|
334
|
+
backupRoot: '' # 备份自定义目录(留空=profile 内分区目录)
|
|
270
335
|
```
|
|
271
336
|
|
|
272
337
|
| 配置 | 默认 | 说明 |
|
|
@@ -275,6 +340,10 @@ npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上
|
|
|
275
340
|
| `includeExperimental` | `true` | 实验性/beta 命名的包是否算嫌疑 |
|
|
276
341
|
| `maxAutoDisable` | `2` | 单次恢复最多关闭几个。崩溃次数递增,但封顶于此值 |
|
|
277
342
|
| `autoRestore` | `true` | 稳定启动后自动重新启用非首要嫌疑 |
|
|
343
|
+
| `confirmRounds` | `2` | 多嫌疑时同一头号嫌疑连续 N 轮崩溃才自动关闭(唯一嫌疑第 1 轮即行动) |
|
|
344
|
+
| `versionedBackups` | `true` | 插件更新时写「带版本的时间线备份」,可逐版对账 |
|
|
345
|
+
| `backupRetain` | `5` | 每个插件保留的备份份数(超出删最旧) |
|
|
346
|
+
| `backupRoot` | `''` | 备份自定义目录;环境变量 `RECENCY_GUARD_BACKUP_ROOT` 优先 |
|
|
278
347
|
|
|
279
348
|
---
|
|
280
349
|
|
|
@@ -305,12 +374,18 @@ npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上
|
|
|
305
374
|
|
|
306
375
|
```
|
|
307
376
|
<profile>/.recency-guard/
|
|
308
|
-
├──
|
|
309
|
-
├──
|
|
310
|
-
├──
|
|
311
|
-
├──
|
|
312
|
-
├──
|
|
313
|
-
|
|
377
|
+
├── current.json # 指针文件:当前分区键(守护行的 disabled 表达式经它搭桥)
|
|
378
|
+
├── partitions.json # 分区索引:项目 → 分区键(多项目隔离的登记处)
|
|
379
|
+
├── projects/<分区键>/ # 每个项目的分区目录(状态/日志/决策完全独立)
|
|
380
|
+
│ ├── journal.jsonl # 只追加的事件日志:install / update / crash-detected / disable / restore …
|
|
381
|
+
│ ├── snapshot.json # 上次观测到的 { 插件名: 版本 },用于识别更新
|
|
382
|
+
│ ├── state.json # 崩溃计数、被自动关闭的插件、已恢复的插件
|
|
383
|
+
│ ├── decision.json # 本轮要拦截的插件(守护行的表达式读它)
|
|
384
|
+
│ ├── crashed # 崩溃哨兵:非零退出码时写下
|
|
385
|
+
│ ├── observation-*.json # 观测快照(启动事实,量化指标的数据源)
|
|
386
|
+
│ ├── archive/ # 本分区的历史归档(搬走+留档)
|
|
387
|
+
│ └── backups/ # 关闭前备份的配置(versionedBackups 时带版本)
|
|
388
|
+
└── …
|
|
314
389
|
```
|
|
315
390
|
|
|
316
391
|
---
|
|
@@ -349,9 +424,9 @@ npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上
|
|
|
349
424
|
|
|
350
425
|
```bash
|
|
351
426
|
npm install
|
|
352
|
-
npm run build # esbuild
|
|
427
|
+
npm run build # esbuild 双产物:dist/index.js(宿主)+ dist/client.js(客户端)
|
|
353
428
|
npm run typecheck # tsc --noEmit
|
|
354
|
-
npm test #
|
|
429
|
+
npm test # 分层测试(Core/Host/Client/Contract)
|
|
355
430
|
```
|
|
356
431
|
|
|
357
432
|
工具行为测试需要 DSH 运行时包(harness 由应用提供,不在 profile 的 node_modules 中):
|
package/dist/client.js
CHANGED
|
@@ -56,7 +56,9 @@ var zh_default = {
|
|
|
56
56
|
emptyHint: "\u5B88\u62A4\u63D2\u4EF6\u5C1A\u672A\u8BB0\u5F55\u4EFB\u4F55\u542F\u52A8\u4E8B\u5B9E\u3002",
|
|
57
57
|
snapshotAt: "\u5FEB\u7167\u65F6\u523B {time}",
|
|
58
58
|
pollingOn: "\u6BCF {seconds} \u79D2\u8F6E\u8BE2",
|
|
59
|
-
pollingPaused: "\u8F6E\u8BE2\u5DF2\u6682\u505C\uFF08\u753B\u9762\u505C\u5728\u6682\u505C\u65F6\u523B\uFF09"
|
|
59
|
+
pollingPaused: "\u8F6E\u8BE2\u5DF2\u6682\u505C\uFF08\u753B\u9762\u505C\u5728\u6682\u505C\u65F6\u523B\uFF09",
|
|
60
|
+
restore: "\u6062\u590D\u975E\u5ACC\u7591\u63D2\u4EF6",
|
|
61
|
+
stale: "\u6570\u636E\u5DF2\u505C\u7559 {minutes} \u5206\u949F\uFF08\u8F6E\u8BE2\u5DF2\u6682\u505C\uFF09"
|
|
60
62
|
},
|
|
61
63
|
sections: {
|
|
62
64
|
backupPolicy: "\u5907\u4EFD\u7B56\u7565",
|
|
@@ -267,11 +269,13 @@ var zh_default = {
|
|
|
267
269
|
metric: "\u6307\u6807",
|
|
268
270
|
bundle: "\u63D2\u4EF6",
|
|
269
271
|
gap: "\u7F3A\u53E3"
|
|
270
|
-
}
|
|
272
|
+
},
|
|
273
|
+
matchIndex: "\u7B2C {index}/{total} \u4E2A"
|
|
271
274
|
},
|
|
272
275
|
events: {
|
|
273
276
|
title: "\u4E8B\u4EF6\u6D41",
|
|
274
|
-
count: "\u6700\u8FD1 {count} \u6761"
|
|
277
|
+
count: "\u6700\u8FD1 {count} \u6761",
|
|
278
|
+
more: "\u5C55\u5F00\u5176\u4F59 {count} \u6761"
|
|
275
279
|
},
|
|
276
280
|
partitions: {
|
|
277
281
|
title: "\u9879\u76EE\u5206\u533A",
|
|
@@ -328,7 +332,9 @@ var en_default = {
|
|
|
328
332
|
emptyHint: "The guard has not recorded any startup facts yet.",
|
|
329
333
|
snapshotAt: "Snapshot at {time}",
|
|
330
334
|
pollingOn: "polling every {seconds}s",
|
|
331
|
-
pollingPaused: "polling paused (the view is frozen at the pause moment)"
|
|
335
|
+
pollingPaused: "polling paused (the view is frozen at the pause moment)",
|
|
336
|
+
restore: "Restore non-suspects",
|
|
337
|
+
stale: "Data is {minutes} min old (polling paused)"
|
|
332
338
|
},
|
|
333
339
|
sections: {
|
|
334
340
|
backupPolicy: "Backup policy",
|
|
@@ -539,11 +545,13 @@ var en_default = {
|
|
|
539
545
|
metric: "Metric",
|
|
540
546
|
bundle: "Bundle",
|
|
541
547
|
gap: "Gap"
|
|
542
|
-
}
|
|
548
|
+
},
|
|
549
|
+
matchIndex: "Match {index}/{total}"
|
|
543
550
|
},
|
|
544
551
|
events: {
|
|
545
552
|
title: "Event stream",
|
|
546
|
-
count: "last {count}"
|
|
553
|
+
count: "last {count}",
|
|
554
|
+
more: "Show {count} more"
|
|
547
555
|
},
|
|
548
556
|
partitions: {
|
|
549
557
|
title: "Project partitions",
|
|
@@ -967,6 +975,33 @@ function kindCounts(nodes) {
|
|
|
967
975
|
for (const node of nodes) counts[node.kind] = (counts[node.kind] ?? 0) + 1;
|
|
968
976
|
return counts;
|
|
969
977
|
}
|
|
978
|
+
function legendColorOf(kind, mode) {
|
|
979
|
+
if (mode === "kind") return KIND_COLOR[kind];
|
|
980
|
+
return TONE_COLOR[LEGEND_TONE[kind]];
|
|
981
|
+
}
|
|
982
|
+
var LEGEND_TONE = {
|
|
983
|
+
capability: "info",
|
|
984
|
+
stage: "neutral",
|
|
985
|
+
metric: "warn",
|
|
986
|
+
bundle: "neutral",
|
|
987
|
+
gap: "error"
|
|
988
|
+
};
|
|
989
|
+
function legendShapeOf(kind, nodes) {
|
|
990
|
+
const tally = /* @__PURE__ */ new Map();
|
|
991
|
+
for (const node of nodes) {
|
|
992
|
+
if (node.kind !== kind) continue;
|
|
993
|
+
tally.set(node.shape, (tally.get(node.shape) ?? 0) + 1);
|
|
994
|
+
}
|
|
995
|
+
let best = null;
|
|
996
|
+
let bestCount = 0;
|
|
997
|
+
for (const [shape, count] of tally) {
|
|
998
|
+
if (count > bestCount) {
|
|
999
|
+
best = shape;
|
|
1000
|
+
bestCount = count;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return best ?? "circle";
|
|
1004
|
+
}
|
|
970
1005
|
var RADIAL_GAP = 28;
|
|
971
1006
|
var RADIAL_RING_GAP = NODE_HEIGHT + 40;
|
|
972
1007
|
function radialPositions(nodes) {
|
|
@@ -1065,17 +1100,30 @@ function GraphToolbar(props) {
|
|
|
1065
1100
|
placeholder: t("graph.search"),
|
|
1066
1101
|
onChange: (event) => props.onQuery(event.target.value),
|
|
1067
1102
|
onKeyDown: (event) => {
|
|
1068
|
-
if (event.key === "Enter" && props.matches.length > 0)
|
|
1103
|
+
if (event.key === "Enter" && props.matches.length > 0) {
|
|
1104
|
+
event.preventDefault?.();
|
|
1105
|
+
props.onMatchNext();
|
|
1106
|
+
}
|
|
1069
1107
|
if (event.key === "Escape") props.onQuery("");
|
|
1070
1108
|
event.stopPropagation();
|
|
1071
1109
|
},
|
|
1072
|
-
"aria-label": t("graph.search")
|
|
1110
|
+
"aria-label": t("graph.search"),
|
|
1111
|
+
"aria-keyshortcuts": "Enter"
|
|
1073
1112
|
}
|
|
1074
1113
|
),
|
|
1075
|
-
props.query.trim() !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "rg-search-results", role: "listbox", "aria-label": t("graph.searchResults"), children: props.matches.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "rg-search-noresult", children: t("graph.noResults") }) : props.matches.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1114
|
+
props.query.trim() !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "rg-search-results", role: "listbox", "aria-label": t("graph.searchResults"), children: props.matches.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "rg-search-noresult", children: t("graph.noResults") }) : props.matches.map((node, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1115
|
+
"button",
|
|
1116
|
+
{
|
|
1117
|
+
type: "button",
|
|
1118
|
+
onClick: () => props.onLocate(node.id),
|
|
1119
|
+
"aria-current": index === props.matchIndex,
|
|
1120
|
+
children: [
|
|
1121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `rg-dot rg-dot-${node.tone}`, "aria-hidden": true }),
|
|
1122
|
+
node.label
|
|
1123
|
+
]
|
|
1124
|
+
}
|
|
1125
|
+
) }, node.id)) }),
|
|
1126
|
+
props.query.trim() !== "" && props.matches.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rg-search-index", role: "status", children: t("graph.matchIndex", { index: props.matchIndex + 1, total: props.matches.length }) })
|
|
1079
1127
|
] }),
|
|
1080
1128
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1081
1129
|
Segmented,
|
|
@@ -1127,7 +1175,7 @@ function GraphToolbar(props) {
|
|
|
1127
1175
|
"aria-pressed": !props.hidden.has(kind),
|
|
1128
1176
|
title: t("graph.legendToggle", { kind: t(kindKey(kind)) }),
|
|
1129
1177
|
children: [
|
|
1130
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
1178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "rg-legend-shape", viewBox: "-8 -8 16 16", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: shapePath(legendShapeOf(kind, props.allNodes), 7, 0), fill: legendColorOf(kind, props.colorMode) }) }),
|
|
1131
1179
|
t(kindKey(kind)),
|
|
1132
1180
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rg-legend-count", children: props.counts[kind] ?? 0 })
|
|
1133
1181
|
]
|
|
@@ -1136,21 +1184,6 @@ function GraphToolbar(props) {
|
|
|
1136
1184
|
)) })
|
|
1137
1185
|
] });
|
|
1138
1186
|
}
|
|
1139
|
-
function dotToneOf(kind) {
|
|
1140
|
-
switch (kind) {
|
|
1141
|
-
case "capability":
|
|
1142
|
-
return "info";
|
|
1143
|
-
case "stage":
|
|
1144
|
-
return "neutral";
|
|
1145
|
-
case "metric":
|
|
1146
|
-
return "warn";
|
|
1147
|
-
case "bundle":
|
|
1148
|
-
return "neutral";
|
|
1149
|
-
case "gap":
|
|
1150
|
-
default:
|
|
1151
|
-
return "error";
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
1187
|
function Segmented({
|
|
1155
1188
|
label,
|
|
1156
1189
|
options,
|
|
@@ -1242,6 +1275,7 @@ function GuardGraph({ graph, height = VIEWPORT_HEIGHT_WIDE, focusId, onFocusChan
|
|
|
1242
1275
|
const [hidden, setHidden] = (0, import_react.useState)(/* @__PURE__ */ new Set());
|
|
1243
1276
|
const [size, setSize] = (0, import_react.useState)({ width: 0, height });
|
|
1244
1277
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
1278
|
+
const [matchIndex, setMatchIndex] = (0, import_react.useState)(0);
|
|
1245
1279
|
const [layoutMode, setLayoutMode] = (0, import_react.useState)("layered");
|
|
1246
1280
|
const [colorMode, setColorMode] = (0, import_react.useState)("tone");
|
|
1247
1281
|
const [onlyConnected, setOnlyConnected] = (0, import_react.useState)(false);
|
|
@@ -1419,9 +1453,13 @@ function GuardGraph({ graph, height = VIEWPORT_HEIGHT_WIDE, focusId, onFocusChan
|
|
|
1419
1453
|
apply2(centerOn(viewport, size, centreOfNode(node)));
|
|
1420
1454
|
onFocusChange(nodeId);
|
|
1421
1455
|
setQuery("");
|
|
1456
|
+
setMatchIndex(0);
|
|
1422
1457
|
},
|
|
1423
1458
|
[apply2, viewport, size, positionedById, centreOfNode, onFocusChange]
|
|
1424
1459
|
);
|
|
1460
|
+
(0, import_react.useEffect)(() => {
|
|
1461
|
+
setMatchIndex(0);
|
|
1462
|
+
}, [query]);
|
|
1425
1463
|
(0, import_react.useEffect)(() => {
|
|
1426
1464
|
if (locateRequest === null) return;
|
|
1427
1465
|
if (locateRequest.nonce === lastLocateNonceRef.current) return;
|
|
@@ -1461,6 +1499,15 @@ function GuardGraph({ graph, height = VIEWPORT_HEIGHT_WIDE, focusId, onFocusChan
|
|
|
1461
1499
|
return traceFrom(focusId, visibleEdges, "both");
|
|
1462
1500
|
}, [focusId, visibleEdges]);
|
|
1463
1501
|
const matches = (0, import_react.useMemo)(() => matchNodes(visibleNodes, query), [visibleNodes, query]);
|
|
1502
|
+
const locateMatchNext = (0, import_react.useCallback)(() => {
|
|
1503
|
+
if (matches.length === 0) return;
|
|
1504
|
+
const next = (matchIndex + 1) % matches.length;
|
|
1505
|
+
setMatchIndex(next);
|
|
1506
|
+
const node = matches[next];
|
|
1507
|
+
if (!node) return;
|
|
1508
|
+
apply2(centerOn(viewport, size, centreOfNode(node)));
|
|
1509
|
+
onFocusChange(node.id);
|
|
1510
|
+
}, [matches, matchIndex, apply2, viewport, size, centreOfNode, onFocusChange]);
|
|
1464
1511
|
const matchIds = (0, import_react.useMemo)(() => new Set(matches.map((node) => node.id)), [matches]);
|
|
1465
1512
|
const focusNode = focusId === null ? void 0 : nodeById.get(focusId);
|
|
1466
1513
|
const focusDegree = (0, import_react.useMemo)(() => focusId === null ? { in: 0, out: 0 } : degreeOf(focusId, visibleEdges), [focusId, visibleEdges]);
|
|
@@ -1503,14 +1550,18 @@ function GuardGraph({ graph, height = VIEWPORT_HEIGHT_WIDE, focusId, onFocusChan
|
|
|
1503
1550
|
onFocusChange(null);
|
|
1504
1551
|
setQuery("");
|
|
1505
1552
|
setHoverId(null);
|
|
1553
|
+
setOffsets({});
|
|
1506
1554
|
},
|
|
1507
|
-
canReset: focusId !== null || query !== "",
|
|
1555
|
+
canReset: focusId !== null || query !== "" || Object.keys(offsets).length > 0,
|
|
1508
1556
|
query,
|
|
1509
1557
|
onQuery: setQuery,
|
|
1510
1558
|
matches,
|
|
1511
1559
|
onLocate: locateNode,
|
|
1560
|
+
matchIndex,
|
|
1561
|
+
onMatchNext: locateMatchNext,
|
|
1512
1562
|
counts,
|
|
1513
1563
|
hidden,
|
|
1564
|
+
allNodes: visibleNodes,
|
|
1514
1565
|
onToggleKind: (kind) => setHidden((current) => {
|
|
1515
1566
|
const next = new Set(current);
|
|
1516
1567
|
if (next.has(kind)) next.delete(kind);
|
|
@@ -1833,15 +1884,18 @@ function metricRows(snapshot) {
|
|
|
1833
1884
|
);
|
|
1834
1885
|
return rows;
|
|
1835
1886
|
}
|
|
1887
|
+
var MARKOV_EXITS_LIMIT = 2;
|
|
1836
1888
|
function markovView(snapshot) {
|
|
1837
1889
|
const markov = snapshot.markov;
|
|
1838
1890
|
const rows = markov.rows.map((row) => {
|
|
1839
1891
|
const selfLoop = row.probabilities?.[row.stage];
|
|
1892
|
+
const exits = row.probabilities === null ? null : Object.entries(row.probabilities).filter(([stage, p]) => stage !== row.stage && p > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MARKOV_EXITS_LIMIT).map(([stage, p]) => ({ stage, percent: Math.round(p * 100) }));
|
|
1840
1893
|
return {
|
|
1841
1894
|
stage: row.stage,
|
|
1842
1895
|
label: row.label,
|
|
1843
1896
|
selfLoop: typeof selfLoop === "number" ? selfLoop : null,
|
|
1844
|
-
barPercent: typeof selfLoop === "number" ? Math.round(selfLoop * 100) : null
|
|
1897
|
+
barPercent: typeof selfLoop === "number" ? Math.round(selfLoop * 100) : null,
|
|
1898
|
+
exits: exits !== null && exits.length > 0 ? exits : null
|
|
1845
1899
|
};
|
|
1846
1900
|
});
|
|
1847
1901
|
const stationary = markov.stationary === null ? [] : Object.entries(markov.stationary).map(([stage, share]) => ({ stage, share, percent: Math.round(share * 100) })).sort((a, b) => b.share - a.share || a.stage.localeCompare(b.stage));
|
|
@@ -2336,18 +2390,26 @@ function PartitionRow({
|
|
|
2336
2390
|
!option.current && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "rg-button rg-button-small", onClick: () => onSelect(option.key), children: t("partitions.inspect") })
|
|
2337
2391
|
] });
|
|
2338
2392
|
}
|
|
2393
|
+
var EVENTS_INITIAL = 12;
|
|
2339
2394
|
function EventsBody({ events }) {
|
|
2395
|
+
const [showAll, setShowAll] = (0, import_react2.useState)(false);
|
|
2340
2396
|
if (events.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "rg-empty", children: t("panel.emptyHint") });
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
event.detail
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2397
|
+
const ordered = [...events].reverse();
|
|
2398
|
+
const visible = showAll ? ordered : ordered.slice(0, EVENTS_INITIAL);
|
|
2399
|
+
const omitted = ordered.length - visible.length;
|
|
2400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2401
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "rg-events", children: visible.map((event, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "rg-event", children: [
|
|
2402
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("time", { className: "rg-event-time", title: event.t, children: formatClock(event.t) }),
|
|
2403
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `rg-badge rg-badge-${eventTone(event.kind, event.detail)}`, children: event.kind }),
|
|
2404
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "rg-event-detail", title: event.detail ?? event.bundle ?? "", children: [
|
|
2405
|
+
event.bundle !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: event.bundle }),
|
|
2406
|
+
event.bundle !== void 0 && event.detail !== void 0 && " \xB7 ",
|
|
2407
|
+
event.detail ?? ""
|
|
2408
|
+
] }),
|
|
2409
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "rg-event-ago", children: agoLabel(event.t) })
|
|
2410
|
+
] }, `${event.t}-${index}`)) }),
|
|
2411
|
+
omitted > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "rg-link rg-risks-expand", onClick: () => setShowAll(true), children: t("events.more", { count: omitted }) })
|
|
2412
|
+
] });
|
|
2351
2413
|
}
|
|
2352
2414
|
function eventTone(kind, detail) {
|
|
2353
2415
|
if (kind === "crash-detected") return "error";
|
|
@@ -2430,7 +2492,14 @@ function MarkovBody({ snapshot }) {
|
|
|
2430
2492
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "rg-markov", "aria-label": t("markov.title"), children: view.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "rg-markov-row", title: row.label, children: [
|
|
2431
2493
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-markov-stage", children: stageLabelOf(row.stage, row.stage) }),
|
|
2432
2494
|
row.barPercent !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-bar", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-bar-fill", style: { width: `${row.barPercent}%` } }) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-markov-nodata", children: t("metrics.undefined") }),
|
|
2433
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-markov-value", children: row.selfLoop !== null ? `${Math.round(row.selfLoop * 100)}%` : "\u2014" })
|
|
2495
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-markov-value", children: row.selfLoop !== null ? `${Math.round(row.selfLoop * 100)}%` : "\u2014" }),
|
|
2496
|
+
row.exits !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rg-markov-exits", children: row.exits.map((exit) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "rg-chip rg-chip-exit", title: row.label, children: [
|
|
2497
|
+
"\u2192 ",
|
|
2498
|
+
stageLabelOf(exit.stage, exit.stage),
|
|
2499
|
+
" ",
|
|
2500
|
+
exit.percent,
|
|
2501
|
+
"%"
|
|
2502
|
+
] }, exit.stage)) })
|
|
2434
2503
|
] }, row.stage)) }),
|
|
2435
2504
|
view.stationary.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rg-markov-stationary", children: [
|
|
2436
2505
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { className: "rg-subhead", children: t("markov.stationary") }),
|
|
@@ -2592,6 +2661,16 @@ function GuardPanel(props) {
|
|
|
2592
2661
|
}, [selectedNodeId, graph]);
|
|
2593
2662
|
const unavailable = failure !== null && snapshot === null;
|
|
2594
2663
|
const loading = snapshot === null;
|
|
2664
|
+
const [nowTick, setNowTick] = (0, import_react3.useState)(() => Date.now());
|
|
2665
|
+
(0, import_react3.useEffect)(() => {
|
|
2666
|
+
if (polling) return;
|
|
2667
|
+
const timer = setInterval(() => setNowTick(Date.now()), 6e4);
|
|
2668
|
+
return () => clearInterval(timer);
|
|
2669
|
+
}, [polling]);
|
|
2670
|
+
const staleMinutes = (0, import_react3.useMemo)(() => {
|
|
2671
|
+
if (polling || snapshot === null) return 0;
|
|
2672
|
+
return Math.max(0, Math.round((nowTick - Date.parse(snapshot.at)) / 6e4));
|
|
2673
|
+
}, [polling, snapshot, nowTick]);
|
|
2595
2674
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "rg-panel", "data-form": form, "data-recency-guard-panel": "", ref: rootRef, "aria-busy": loading || busy, children: [
|
|
2596
2675
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "rg-header", children: [
|
|
2597
2676
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rg-header-main", children: [
|
|
@@ -2620,6 +2699,7 @@ function GuardPanel(props) {
|
|
|
2620
2699
|
),
|
|
2621
2700
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "rg-button", onClick: () => void load(requestedKey), disabled: busy, children: t("panel.refresh") }),
|
|
2622
2701
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "rg-button", onClick: () => void runAction("recover"), disabled: busy, children: t("panel.recover") }),
|
|
2702
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "rg-button", onClick: () => void runAction("restore"), disabled: busy, children: t("panel.restore") }),
|
|
2623
2703
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2624
2704
|
"button",
|
|
2625
2705
|
{
|
|
@@ -2824,7 +2904,7 @@ function GuardPanel(props) {
|
|
|
2824
2904
|
failure !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "rg-error", role: "alert", children: failure }),
|
|
2825
2905
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("footer", { className: "rg-foot", children: [
|
|
2826
2906
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: t("panel.snapshotAt", { time: snapshot.at.replace("T", " ").slice(0, 19) }) }),
|
|
2827
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: polling ? t("panel.pollingOn", { seconds: POLL_INTERVAL / 1e3 }) : t("panel.
|
|
2907
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: polling ? t("panel.pollingOn", { seconds: POLL_INTERVAL / 1e3 }) : t("panel.stale", { minutes: staleMinutes }) })
|
|
2828
2908
|
] })
|
|
2829
2909
|
] })
|
|
2830
2910
|
] });
|
|
@@ -3059,7 +3139,7 @@ function openPanel(ctx) {
|
|
|
3059
3139
|
}
|
|
3060
3140
|
|
|
3061
3141
|
// src/client/styles.css
|
|
3062
|
-
var styles_default = "/**\n * \u7BA1\u7406\u9762\u677F\u7684\u6837\u5F0F\u3002\n *\n * ## \u7EAA\u5F8B\n *\n * 1. **\u53EA\u7528\u4E3B\u9898\u53D8\u91CF**\uFF1A\u989C\u8272\u3001\u5706\u89D2\u3001\u5B57\u4F53\u5168\u90E8\u5F15\u7528 `--dsw-alias-*` / `--dsw-radius-*`\n * / `--dsw-shadow-*` \u5BB6\u65CF\u3002\u5BBF\u4E3B\u91CD\u547D\u540D\u4E00\u4E2A\u53D8\u91CF\u65F6\u9762\u677F\u53EA\u662F\u53D8\u4E11\uFF0C\u4E0D\u4F1A\u5D29 \u2014\u2014\n * \u56E0\u6B64\u6BCF\u4E2A var() \u90FD\u5E26\u4E00\u4E2A\u4FDD\u5B88\u7684\u515C\u5E95\u503C\u3002\n * 2. **\u4E24\u79CD\u5F62\u6001\u6309\u5BB9\u5668\u5BBD\u5EA6**\uFF1A`.rg-panel[data-form=\"narrow\"]` \u7531 JS \u6D4B\u51FA\u7684\n * \u9762\u677F\u5BBD\u5EA6\u9A71\u52A8\uFF08\u9762\u677F\u53EF\u80FD\u6302\u5728\u88AB\u62D6\u7A84\u7684\u4FA7\u680F\u91CC\uFF0C\u5A92\u4F53\u67E5\u8BE2\u91CF\u7684\u662F\u7A97\u53E3\uFF0C\u4F1A\u7B54\u9519\uFF09\u3002\n * 3. **\u5C40\u90E8\u52A8\u6548\u4EE4\u724C**\uFF1A\u8FC7\u6E21\u65F6\u957F\u4E0E\u7F13\u52A8\u5B9A\u4E49\u5728 `.rg-panel` \u4E0A\u7684 `--rg-*`\uFF0C\n * \u4E0D\u4F9D\u8D56\u672A\u7ECF\u9A8C\u8BC1\u7684\u5168\u5C40 `--ds-*`\uFF08\u751F\u6001\u63D2\u4EF6\u540C\u6837\u81EA\u6301\u8FD9\u5957\u4EE4\u724C\uFF09\u3002\n * 4. **\u4E0D\u5199\u6B7B\u5BB9\u5668\u5BBD\u5EA6**\uFF1A\u9762\u677F\u5360\u6EE1\u5BBF\u4E3B\u7ED9\u7684\u4E2D\u592E\u9762\u677F\u533A\u57DF\u3002\n * 5. **\u5BF9\u6BD4\u5EA6**\uFF1A\u6B63\u6587 `--dsw-alias-label-primary`\uFF0C\u6B21\u8981 `--dsw-alias-label-secondary`\uFF0C\n * \u63D0\u793A `--dsw-alias-label-tertiary`\uFF08\u4EAE/\u6697\u4E3B\u9898\u90FD\u7531\u5BBF\u4E3B\u4FDD\u8BC1\u53EF\u8BFB\uFF09\u3002\n */\n\n.rg-panel {\n /* \u5C40\u90E8\u4EE4\u724C\uFF1A\u63A7\u4EF6\u9AD8\u5EA6\u3001\u52A8\u6548\u3001\u95F4\u8DDD\u3002\u6539\u4E00\u5904\uFF0C\u6574\u5757\u9762\u677F\u8DDF\u7740\u4E00\u81F4\u3002 */\n --rg-control-h: 28px;\n --rg-motion-fast: 120ms;\n --rg-motion-medium: 200ms;\n --rg-ease: cubic-bezier(0.2, 0, 0, 1);\n --rg-gap: 16px;\n\n display: flex;\n flex-direction: column;\n gap: var(--rg-gap);\n padding: 20px 24px 32px;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font-family: var(--dsw-font-family);\n font-size: var(--dsw-font-s-14, 14px);\n line-height: var(--dsw-font-s-14-line-height, 22px);\n\n /**\n * \u9762\u677F\u5FC5\u987B**\u81EA\u5DF1**\u662F\u6EDA\u52A8\u5BB9\u5668\u3002\n *\n * \u4E3A\u4EC0\u4E48\u4E0D\u80FD\u6307\u671B\u5BBF\u4E3B\uFF1A\u5B9E\u6D4B\uFF08\u6C99\u7BB1 sb-rg-c5\uFF09\u5BBF\u4E3B\u69FD\u4F4D\u662F\u8FD9\u6837\u4E00\u4E32\n * .centerCol { overflow-y: hidden } \u2190 \u7956\u5148\u628A\u6EA2\u51FA\u88C1\u6389\n * .mainPanel { height: 714px; overflow: visible }\n * .rg-panel { height: 3965px } \u2190 \u5185\u5BB9\u81EA\u7136\u9AD8\u5EA6\uFF0C\u8D85\u51FA\u540E\u76F4\u63A5\u88AB\u88C1\n * \u6587\u6863\u672C\u8EAB\u4E0D\u6EDA\uFF08docScrollH == clientH == 714\uFF09\uFF0C\u56E0\u6B64**\u4E0B\u65B9\u6570\u636E\u9875\u6C38\u8FDC\u770B\u4E0D\u5230**\u3002\n *\n * \u4E09\u4E2A\u5C5E\u6027\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n * - `height: 100%` + `min-height: 0`\uFF1A\u6491\u6EE1\u5BBF\u4E3B\u7ED9\u7684\u786E\u5B9A\u9AD8\u5EA6\uFF0C\u4E14\u5141\u8BB8\u6536\u7F29\u5230\u53EF\u6EDA\u52A8\uFF1B\n * \u53EA\u5199 overflow \u800C\u4E0D\u9650\u5236\u9AD8\u5EA6\uFF0Cflex \u5B50\u9879\u4ECD\u4F1A\u6309\u5185\u5BB9\u6491\u5F00\uFF0C\u6EDA\u4E0D\u8D77\u6765\u3002\n * - `overflow-y: auto`\uFF1A\u9762\u677F\u81EA\u5DF1\u6EDA\u3002\n * - `overscroll-behavior: contain`\uFF1A\u6EDA\u5230\u5C3D\u5934\u4E0D\u628A\u6EDA\u52A8\u4F20\u7ED9\u5BBF\u4E3B\uFF0C\u5426\u5219\u4F1A\u628A\u6574\u9875/\u4FA7\u680F\n * \u5E26\u7740\u4E00\u8D77\u6EDA\uFF0C\u89C6\u89C9\u4E0A\u50CF\"\u9762\u677F\u5728\u4E71\u8DF3\"\u3002\n */\n height: 100%;\n max-height: 100%;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n overscroll-behavior: contain;\n}\n\n/* \u7A84\u5F62\u6001\uFF08\u4FA7\u680F / \u5206\u5C4F\uFF09\uFF1A\u6536\u7D27\u8FB9\u8DDD\u4E0E\u95F4\u8DDD\uFF0C\u628A\u7EB5\u5411\u7A7A\u95F4\u8BA9\u7ED9\u5185\u5BB9\u3002 */\n.rg-panel[data-form='narrow'] {\n --rg-gap: 12px;\n padding: 14px 14px 24px;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n/* ---------- \u5934\u90E8 ---------- */\n\n.rg-header {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n\n.rg-header-main {\n min-width: 0;\n flex: 1 1 260px;\n}\n\n.rg-title {\n margin: 0;\n font-size: var(--dsw-font-l-20, 20px);\n font-weight: var(--dsw-font-l-20-font-weight, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-panel[data-form='narrow'] .rg-title {\n font-size: var(--dsw-font-m-18, 18px);\n}\n\n.rg-subtitle {\n margin: 4px 0 0;\n font-size: var(--dsw-font-xs-13, 13px);\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n\n.rg-project-name {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-partition-workspace-full {\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n direction: ltr;\n}\n\n.rg-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* ---------- \u63A7\u4EF6 ---------- */\n\n.rg-button {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n padding: 4px 12px;\n font: inherit;\n font-size: var(--dsw-font-xs-13, 13px);\n cursor: pointer;\n transition:\n background var(--rg-motion-fast) var(--rg-ease),\n border-color var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n.rg-button:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.rg-button-small {\n min-height: 24px;\n padding: 2px 9px;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-button-icon {\n min-width: 28px;\n padding: 2px 6px;\n justify-content: center;\n font-size: var(--dsw-font-s-14, 14px);\n line-height: 1;\n}\n\n.rg-button-danger {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.rg-button-active {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* \u8F6E\u8BE2\u6682\u505C\u6001\u6309\u94AE\uFF08\u6682\u505C = \u9700\u8981\u88AB\u770B\u89C1\u7684\u72B6\u6001\uFF0C\u4E0D\u80FD\u53EA\u9760 aria-pressed\uFF09\u3002 */\n.rg-button-paused {\n border-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-link {\n appearance: none;\n border: none;\n background: none;\n padding: 0 2px;\n font: inherit;\n font-size: inherit;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n text-decoration: underline;\n}\n\n/* \u5206\u6BB5\u63A7\u4EF6\uFF08\u5E03\u5C40 / \u7740\u8272\u53E3\u5F84\uFF09\u3002 */\n.rg-segmented {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.rg-segment {\n appearance: none;\n border: none;\n border-right: 1px solid var(--dsw-alias-border-l1);\n background: none;\n color: var(--dsw-alias-label-secondary);\n padding: 3px 9px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n cursor: pointer;\n transition: background var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-segment:last-child {\n border-right: none;\n}\n\n.rg-segment:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-segment[aria-pressed='true'] {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n}\n\n.rg-segment:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.rg-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.rg-field-grow {\n flex: 1 1 160px;\n}\n\n.rg-field-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-input,\n.rg-select {\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n padding: 2px 8px;\n font: inherit;\n font-size: var(--dsw-font-xs-13, 13px);\n max-width: 100%;\n}\n\n.rg-input:focus-visible,\n.rg-select:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* ---------- \u63D0\u793A\u6761 ---------- */\n\n.rg-notice {\n margin: 0;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-error {\n margin: 0;\n padding: 10px 12px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 1px solid var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-empty {\n margin: 0;\n padding: 8px 0;\n font-size: var(--dsw-font-xs-13, 13px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-muted {\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-strong {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n}\n\n.rg-block {\n display: block;\n}\n\n.rg-ellipsis {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 16px;\n margin-left: 6px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n/* \u52A0\u8F7D / \u4E0D\u53EF\u7528\u72B6\u6001\u5757\uFF1A\u5360\u4F4D\u9AD8\u5EA6\u7A33\u5B9A\uFF0C\u907F\u514D\u8F6E\u8BE2\u65F6\u9875\u9762\u8DF3\u52A8\u3002 */\n.rg-state-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 24px 0;\n}\n\n.rg-state-title {\n margin: 0;\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n/* ---------- \u5361\u7247\u7F51\u683C ---------- */\n\n.rg-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n}\n\n.rg-panel[data-form='narrow'] .rg-grid {\n grid-template-columns: 1fr;\n}\n\n.rg-card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n min-height: 0;\n}\n\n/**\n * \u5361\u7247\u5185\u5BB9\u533A\u56FA\u5B9A\u9AD8\u5EA6 + \u5185\u90E8\u6EDA\u52A8\uFF08v0.9.1 \u7528\u6237\u9700\u6C42\uFF09\u3002\n *\n * \u4E3A\u4EC0\u4E48\u9650\u9AD8\u800C\u4E0D\u662F\u8BA9\u5185\u5BB9\u81EA\u7136\u4F38\u5C55\uFF1A\u5C55\u5F00\u7684\u5361\u7247\u591A\u4E86\u4E4B\u540E\uFF0C\u9762\u677F\u88AB\u5355\u4E2A\n * \u5361\u7247\u6491\u5230\u51E0\u5343\u50CF\u7D20\uFF0C\u4E0B\u9762\u7684\u5361\u7247\u8981\u6EDA\u5F88\u4E45\u624D\u770B\u5F97\u5230 \u2014\u2014 \u5747\u5300\u7684\u89C6\u53E3\u5BC6\u5EA6\n * \u6BD4\u5355\u5361\u7684\u5B8C\u6574\u6027\u66F4\u91CD\u8981\uFF08\u8FD9\u6B63\u662F\u7528\u6237\u539F\u59CB\u8BC9\u6C42\uFF09\u3002\n *\n * - \u9ED8\u8BA4 `--rg-card-body-h: 240px`\uFF1A\u8DB3\u591F\u770B\u7EA6 10 \u884C\u5185\u5BB9\uFF0C\u8D85\u51FA\u5185\u90E8\u6EDA\u52A8\uFF1B\n * - `overscroll-behavior: contain`\uFF1A\u6EDA\u5230\u5C3D\u5934\u4E0D\u628A\u5916\u5C42\u9762\u677F\u5E26\u7740\u6EDA\uFF1B\n * - `.rg-card-enlarged` \u89E3\u9664\u9650\u9AD8\uFF08\u2922 \u6309\u94AE\uFF09\uFF0C\u644A\u5F00\u9605\u8BFB\u957F\u6E05\u5355\uFF1B\n * - \u7A84\u5F62\u6001\u628A\u9650\u9AD8\u964D\u5230 180px\uFF08\u4FA7\u680F\u7A7A\u95F4\u5C11\uFF0C\u5BC6\u5EA6\u4F18\u5148\uFF09\u3002\n */\n.rg-card-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n height: var(--rg-card-body-h, 240px);\n overflow-y: auto;\n overscroll-behavior: contain;\n min-height: 0;\n}\n\n/* \u6269\u5927\u6001\uFF1A\u89E3\u9664\u9650\u9AD8\uFF0C\u5361\u7247\u6309\u5185\u5BB9\u81EA\u7136\u4F38\u5C55\uFF08\u9762\u677F\u81EA\u8EAB\u4ECD\u662F\u6EDA\u52A8\u5BB9\u5668\uFF09\u3002 */\n.rg-card-enlarged .rg-card-body {\n height: auto;\n max-height: none;\n overflow-y: visible;\n}\n\n/* \u6269\u5927\u6309\u94AE\uFF1A\u5934\u90E8\u5185\u7684\u7B2C\u4E8C\u4E2A\u52A8\u4F5C\uFF0C\u5C0F\u5C3A\u5BF8\u3001\u4F4E\u5BF9\u6BD4\uFF0C\u4E0D\u4E0E\u6298\u53E0\u7BAD\u5934\u7ADE\u4E89\u3002 */\n.rg-card-expand {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n margin-left: auto;\n border-radius: var(--dsw-radius-sm, 8px);\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n user-select: none;\n transition: background var(--rg-motion-fast, 120ms) var(--rg-ease, ease);\n}\n.rg-card-expand:hover,\n.rg-card-expand:focus-visible {\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n.rg-card-enlarged .rg-card-expand {\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-card-title {\n margin: 0;\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n\n/* \u7A84\u5F62\u6001\uFF1A\u4FA7\u680F\u7A7A\u95F4\u5C11\uFF0C\u5BC6\u5EA6\u4F18\u5148 \u2014\u2014 \u5185\u5BB9\u533A\u9650\u9AD8\u964D\u4E3A 180px\u3002 */\n.rg-panel[data-form='narrow'] .rg-card-body {\n --rg-card-body-h: 180px;\n}\n\n.rg-card-title {\n margin: 0;\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n\n.rg-card-subtitle {\n margin: 4px 0 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-card-note {\n margin: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u53EF\u6298\u53E0\u5361\u7247\uFF1A\u6807\u9898\u884C\u662F\u6309\u94AE\uFF0C\u6574\u4E2A\u5934\u90E8\u53EF\u70B9\u3002 */\n.rg-card-head {\n appearance: none;\n border: none;\n background: none;\n padding: 0;\n margin: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n cursor: pointer;\n font: inherit;\n color: inherit;\n text-align: left;\n}\n\n.rg-card-head:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-card-head-hint {\n margin-left: auto;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-card-chevron {\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-card-collapsed {\n padding-bottom: 10px;\n}\n\n.rg-card-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- \u5FBD\u6807 / \u5706\u70B9 / \u8BA1\u6570\u7247 ---------- */\n\n.rg-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 8px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 1px solid var(--dsw-alias-border-l2);\n font-size: var(--dsw-font-xxs-12, 12px);\n white-space: nowrap;\n}\n\n.rg-badge-idle,\n.rg-badge-monitoring {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.rg-badge-crashed,\n.rg-badge-error {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.rg-badge-deciding,\n.rg-badge-worsening,\n.rg-badge-warn {\n border-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-badge-stabilizing,\n.rg-badge-recovered,\n.rg-badge-improving,\n.rg-badge-success {\n border-color: var(--dsw-alias-state-success-primary);\n color: var(--dsw-alias-state-success-primary);\n}\n\n.rg-badge-stable,\n.rg-badge-neutral {\n border-color: var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-chip {\n display: inline-flex;\n align-items: center;\n padding: 0 6px;\n border-radius: var(--dsw-radius-xs, 4px);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxxs-11, 11px);\n white-space: nowrap;\n}\n\n.rg-chip-warn {\n border: 1px solid var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n background: none;\n}\n\n.rg-chip-more {\n font-variant-numeric: tabular-nums;\n}\n\n.rg-names {\n display: inline-flex;\n flex-wrap: wrap;\n gap: 3px;\n min-width: 0;\n}\n\n.rg-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-secondary);\n flex: none;\n}\n\n.rg-dot-info {\n background: var(--dsw-alias-brand-primary);\n}\n\n.rg-dot-success {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.rg-dot-warn {\n background: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-dot-error {\n background: var(--dsw-alias-state-error-primary);\n}\n\n/* ---------- \u8FDB\u5EA6\u6761 ---------- */\n\n.rg-bar {\n display: inline-block;\n width: 72px;\n height: 4px;\n border-radius: 2px;\n background: var(--dsw-alias-bg-layer-2);\n overflow: hidden;\n flex: none;\n}\n\n.rg-bar-wide {\n width: 100%;\n}\n\n.rg-bar-fill {\n display: block;\n height: 100%;\n background: var(--dsw-alias-brand-primary);\n transition: width var(--rg-motion-medium) var(--rg-ease);\n}\n\n/* ---------- KPI \u6458\u8981\u6761 ---------- */\n\n.rg-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 10px;\n}\n\n.rg-kpi {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n}\n\n.rg-kpi-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-kpi-value {\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n font-variant-numeric: tabular-nums;\n}\n\n.rg-kpi-big {\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n.rg-kpi-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.rg-kpi-meta {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.rg-ring {\n flex: none;\n}\n\n.rg-ring-text {\n fill: var(--dsw-alias-label-primary);\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n\n/* ---------- \u63CF\u8FF0\u5217\u8868 / \u9636\u6BB5\u8F68\u9053 ---------- */\n\n.rg-dl {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 4px 12px;\n margin: 0;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-dl dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-dl dd {\n margin: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex-wrap: wrap;\n}\n\n.rg-dl-compact {\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-mono {\n font-family: var(--dsw-font-mono, var(--dsw-font-family-mono, ui-monospace, monospace));\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow-wrap: anywhere;\n}\n\n/* \u9636\u6BB5\u8F68\u9053\uFF1A\u72B6\u6001\u673A\u4E0D\u662F\u8FDB\u5EA6\u6761\uFF0Cvisited \u4E0E current \u7528\u4E0D\u540C\u901A\u9053\u8868\u8FBE\u3002 */\n.rg-stage-rail {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 10px;\n}\n\n.rg-stage-cell {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-stage-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n border: 1px solid var(--dsw-alias-border-l2);\n background: none;\n flex: none;\n}\n\n.rg-stage-visited {\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-stage-visited .rg-stage-dot {\n background: var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-color: var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n}\n\n.rg-stage-current {\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-xxxs-strong-11, 600);\n}\n\n.rg-stage-current .rg-stage-dot {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n box-shadow: 0 0 0 2px var(--dsw-alias-bg-layer-2);\n}\n\n/* ---------- \u6307\u6807 ---------- */\n\n.rg-metrics {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.rg-metric {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.rg-metric-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-metric-value {\n font-size: var(--dsw-font-xs-13, 13px);\n font-variant-numeric: tabular-nums;\n}\n\n.rg-metric-undefined {\n color: var(--dsw-alias-label-secondary);\n font-style: italic;\n}\n\n.rg-metric-reason {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-metric-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-trend {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* ---------- \u8FC1\u79FB / \u51B3\u7B56 / \u4E8B\u4EF6\u65F6\u95F4\u7EBF ---------- */\n\n.rg-transitions,\n.rg-decisions,\n.rg-events {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n\n.rg-transition,\n.rg-decision,\n.rg-event {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-transition-reason,\n.rg-decision-detail,\n.rg-event-detail {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-decision-bundle {\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-transition-time,\n.rg-decision-time,\n.rg-event-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n.rg-event-ago {\n flex: none;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n/* ---------- \u5ACC\u7591\u6392\u540D ---------- */\n\n.rg-risks {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* \u300C\u5C55\u5F00\u5176\u4F59 N \u4E2A\u300D\u6309\u94AE\uFF1A\u5DE6\u5BF9\u9F50\u7684\u94FE\u63A5\u5F62\u6001\uFF0C\u4E0E\u5361\u7247\u5185\u7684\u5176\u5B83\u64CD\u4F5C\u533A\u5206\u3002 */\n.rg-risks-expand {\n align-self: flex-start;\n margin-top: 2px;\n}\n\n.rg-risk {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.rg-risk:last-child {\n border-bottom: none;\n padding-bottom: 0;\n}\n\n.rg-risk-main {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.rg-risk-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-risk-score {\n font-variant-numeric: tabular-nums;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-risk-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-risk-actions {\n display: flex;\n gap: 6px;\n}\n\n/* ---------- \u80FD\u529B\u6E05\u5355\u8868 ---------- */\n\n.rg-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--dsw-font-xxs-12, 12px);\n table-layout: fixed;\n}\n\n.rg-table th,\n.rg-table td {\n text-align: left;\n vertical-align: top;\n padding: 5px 6px;\n border-bottom: 1px solid var(--dsw-alias-hairline, var(--dsw-alias-border-l1));\n overflow-wrap: anywhere;\n}\n\n.rg-table thead th {\n color: var(--dsw-alias-label-secondary);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n white-space: nowrap;\n border-bottom-color: var(--dsw-alias-border-l2);\n}\n\n.rg-table tbody tr {\n cursor: pointer;\n}\n\n.rg-table tbody tr:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n/* \u9009\u4E2D\u884C\uFF1A\u5DE6\u4FA7 2px \u7AD6\u6761 + \u5E95\u8272\uFF08\u53EA\u9760\u5E95\u8272\u5728\u6697\u8272\u4E3B\u9898\u4E0B\u4E0D\u591F\u663E\u773C\uFF09\u3002 */\n.rg-row-selected {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary);\n}\n\n/* \u7A84\u5F62\u6001\u4E0B\u8868\u683C\u4F1A\u6324\u6210\u4E00\u56E2 \u2014\u2014 \u5217\u5BBD\u6309\u300C\u80FD\u529B\u540D / \u5173\u7CFB\u300D\u4E24\u680F\u91CD\u6392\u3002 */\n.rg-panel[data-form='narrow'] .rg-table {\n table-layout: auto;\n}\n\n.rg-panel[data-form='narrow'] .rg-table th:nth-child(2),\n.rg-panel[data-form='narrow'] .rg-table td:nth-child(2),\n.rg-panel[data-form='narrow'] .rg-table th:nth-child(3),\n.rg-panel[data-form='narrow'] .rg-table td:nth-child(3) {\n display: none;\n}\n\n.rg-gaps {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-gap {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-gap-detail {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* ---------- \u9879\u76EE\u5206\u533A ---------- */\n\n.rg-partitions {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-partition {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: var(--dsw-font-xxs-12, 12px);\n padding: 3px 4px;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-partition-selected {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary);\n}\n\n.rg-partition-label {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-partition-workspace {\n flex: 1 1 140px;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n text-align: left;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-partition-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u5F52\u6863 ---------- */\n\n.rg-archive-run {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.rg-archives {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-archive {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm, 8px);\n}\n\n.rg-archive-dir {\n font-family: var(--dsw-font-mono, var(--dsw-font-family-mono, ui-monospace, monospace));\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow-wrap: anywhere;\n}\n\n.rg-archive-meta,\n.rg-archive-summary {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u533A\u5757 / \u9875\u811A ---------- */\n\n.rg-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.rg-section-head {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.rg-section-title {\n margin: 0;\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n.rg-selection-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n padding: 2px 8px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n}\n\n.rg-foot {\n display: flex;\n flex-wrap: wrap;\n gap: 4px 16px;\n border-top: 1px solid var(--dsw-alias-hairline, var(--dsw-alias-border-l1));\n padding-top: 8px;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u56FE\u8C31 ---------- */\n\n.rg-graph {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n padding: 10px;\n min-width: 0;\n}\n\n.rg-graph-toolbar {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.rg-graph-zoom {\n display: inline-flex;\n gap: 4px;\n}\n\n.rg-graph-hint {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-graph-viewport {\n position: relative;\n overflow: hidden;\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-2);\n cursor: grab;\n touch-action: none;\n}\n\n.rg-graph-viewport:active {\n cursor: grabbing;\n}\n\n.rg-graph-viewport:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n.rg-graph-node:focus-visible {\n outline: none;\n}\n\n/* \u952E\u76D8\u805A\u7126\u7684\u8282\u70B9\u5FC5\u987B\u80FD\u770B\u89C1\u7126\u70B9\uFF08\u5426\u5219 Tab \u8D70\u4E00\u5708\u4E0D\u77E5\u9053\u505C\u5728\u54EA\uFF09\u3002 */\n.rg-graph-node:focus-visible .rg-graph-node-bg {\n stroke: var(--dsw-alias-brand-primary);\n stroke-width: 3px;\n}\n\n.rg-graph-node-label {\n fill: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 600;\n}\n\n.rg-graph-node-detail {\n fill: var(--dsw-alias-label-secondary);\n font-size: 11px;\n}\n\n.rg-graph-column-title {\n fill: var(--dsw-alias-label-secondary);\n font-size: 12px;\n font-weight: 600;\n}\n\n/* \u66F2\u7EBF\u8FB9\u7684\u8BED\u4E49\u6807\u7B7E\uFF08\u4EC5\u9AD8\u4EAE\u8FB9\u663E\u793A\uFF0C\u907F\u514D\u566A\u58F0\uFF09\u3002 */\n.rg-graph-edge-label {\n fill: var(--dsw-alias-brand-primary);\n font-size: 10px;\n font-weight: 600;\n}\n\n/* \u56FE\u4F8B\uFF08\u7C7B\u578B\u8FC7\u6EE4 chips\uFF0C\u5E26\u8BA1\u6570\uFF09\u3002 */\n.rg-graph-legend {\n display: inline-flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-left: 4px;\n}\n\n.rg-legend-chip {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: 999px;\n padding: 2px 10px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n cursor: pointer;\n transition: background var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-legend-chip:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-legend-chip-off {\n opacity: 0.45;\n text-decoration: line-through;\n}\n\n.rg-legend-chip:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.rg-legend-count {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* \u56FE\u8C31\u4E3B\u4F53\uFF1A\u89C6\u53E3 + \u8BE6\u60C5\u4FA7\u680F\u4E24\u680F\uFF08\u7A84\u5BB9\u5668\u7EB5\u5411\u5806\u53E0\uFF09\u3002 */\n.rg-graph-body {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: 10px;\n}\n\n@media (min-width: 960px) {\n .rg-graph-body:has(.rg-graph-aside) {\n grid-template-columns: minmax(0, 1fr) 260px;\n }\n}\n\n.rg-graph-status {\n display: flex;\n align-items: center;\n gap: 6px 14px;\n flex-wrap: wrap;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-graph-zoom-label {\n font-variant-numeric: tabular-nums;\n}\n\n.rg-graph-status-selected {\n color: var(--dsw-alias-label-primary);\n}\n\n/* \u60AC\u505C\u6D6E\u5C42\uFF1A\u6D6E\u52A8\u5361\u7247\uFF08\u552F\u4E00\u5E26\u9634\u5F71\u7684\u8868\u9762\uFF09\u3002 */\n.rg-graph-tooltip {\n position: absolute;\n z-index: 5;\n transform: translate(-50%, -100%);\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-width: 260px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-shadow-lv3, var(--dsw-shadow-lv1, none));\n pointer-events: none;\n}\n\n.rg-tooltip-title {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-tooltip-meta,\n.rg-tooltip-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow-wrap: anywhere;\n}\n\n/* \u56FE\u8C31\u641C\u7D22\u6846\u4E0E\u7ED3\u679C\u4E0B\u62C9\u3002 */\n.rg-graph-search {\n position: relative;\n display: inline-flex;\n}\n\n.rg-search-input {\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n padding: 2px 9px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n min-width: 150px;\n}\n\n.rg-search-input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.rg-search-input::placeholder {\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-search-results {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 10;\n margin: 0;\n padding: 4px;\n list-style: none;\n min-width: 200px;\n max-height: 220px;\n overflow: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-shadow-lv3, var(--dsw-shadow-lv1, none));\n}\n\n.rg-search-results button {\n appearance: none;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 5px 8px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n text-align: left;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-search-results button:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-search-noresult {\n padding: 5px 8px;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u8BE6\u60C5\u4FA7\u680F\u3002 */\n.rg-graph-aside {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n align-self: start;\n}\n\n.rg-graph-aside-header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.rg-graph-aside-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsw-font-s-14, 14px);\n}\n\n.rg-graph-aside-detail {\n margin: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-graph-aside-actions {\n display: flex;\n gap: 6px;\n}\n\n/* \u90BB\u5C45\u5BFC\u822A\uFF08\u8BE6\u60C5\u4FA7\u680F\uFF09\uFF1A\u4E0A\u6E38/\u4E0B\u6E38\u5206\u7EC4\u5217\u8868\uFF0C\u70B9\u51FB\u5373\u8DF3\u8F6C\u5B9A\u4F4D\u3002 */\n.rg-graph-neighbors {\n display: flex;\n flex-direction: column;\n gap: 6px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.rg-graph-neighbor-title {\n margin: 0;\n display: flex;\n align-items: center;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-graph-neighbor-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.rg-graph-neighbor-link {\n appearance: none;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 6px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n text-align: left;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-graph-neighbor-link:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-graph-neighbor-link:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* \u52A8\u6548\u4E0E\u504F\u597D\uFF1A\u5C0A\u91CD\u300C\u51CF\u5C11\u52A8\u6001\u6548\u679C\u300D\uFF08\u524D\u5EAD\u5931\u8C03\u7528\u6237\u4F1A\u56E0\u8FC7\u6E21\u4E0D\u9002\uFF09\u3002 */\n@media (prefers-reduced-motion: reduce) {\n .rg-panel *,\n .rg-panel *::before,\n .rg-panel *::after {\n transition-duration: 0.01ms !important;\n animation-duration: 0.01ms !important;\n }\n}\n\n/* ---------- \u6062\u590D\u5224\u5B9A\u6761\uFF08v0.9.1\uFF09 ---------- */\n\n.rg-verdict {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin: 0;\n padding: 8px 12px;\n border-radius: var(--dsw-radius-sm, 8px);\n font-size: var(--dsw-font-xs-13, 13px);\n}\n/* act\uFF08\u5173\u95ED\u4E86\u63D2\u4EF6\uFF09\u4E0E watch\uFF08\u89C2\u5BDF\u8F6E\uFF09\u7528**\u8FB9\u6846+\u6807\u7B7E**\u533A\u5206\u800C\u4E0D\u662F\u53EA\u9760\u989C\u8272\uFF1A\n \u7070\u5EA6\u6253\u5370/\u8272\u5F31\u7528\u6237\u4E5F\u80FD\u5206\u8FA8\u3002 */\n.rg-verdict-act {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n}\n.rg-verdict-watch {\n border: 1px dashed var(--dsw-alias-border-l1);\n background: transparent;\n}\n.rg-verdict-tag {\n padding: 1px 8px;\n border-radius: var(--dsw-radius-sm, 8px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n background: var(--dsw-alias-bg-layer-2);\n}\n.rg-verdict-meta {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---------- v0.10 \u6D1E\u5BDF\u5361\uFF1A\u9A6C\u5C14\u53EF\u592B / \u6570\u636E\u6D41 / \u7CFB\u7EDF\u8BCA\u65AD ---------- */\n\n/* \u5361\u7247\u9876\u90E8\u7684\u4E00\u884C\u6458\u8981\uFF08\u6837\u672C\u6570\u3001\u6536\u655B\u6027\u7B49\u767D\u76D2\u5143\u4FE1\u606F\uFF09\u3002 */\n.rg-card-note {\n margin: 0 0 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* \u9A6C\u5C14\u53EF\u592B\u884C\uFF1A\u9636\u6BB5\u540D | \u81EA\u73AF\u6982\u7387\u6761 | \u767E\u5206\u6BD4\u3002 */\n.rg-markov {\n list-style: none;\n margin: 0;\n padding: 0;\n display: grid;\n gap: 6px;\n}\n.rg-markov-row {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.rg-markov-stage {\n width: 84px;\n flex: none;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.rg-markov-row .rg-bar {\n flex: 1 1 auto;\n width: auto;\n}\n.rg-markov-value {\n width: 44px;\n flex: none;\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n}\n.rg-markov-nodata {\n flex: 1 1 auto;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n.rg-markov-stationary {\n margin-top: 10px;\n}\n.rg-subhead {\n margin: 0 0 6px;\n font-size: 12px;\n font-weight: var(--dsw-font-s-strong-14, 600);\n color: var(--dsw-alias-label-secondary);\n}\n.rg-chip-row {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* \u6570\u636E\u6D41\u94FE\u8DEF\uFF1A\u5E8F\u53F7 | \u540D\u79F0+\u8BF4\u660E | \u8BA1\u6570\u3002 */\n.rg-dataflow {\n list-style: none;\n margin: 0;\n padding: 0;\n display: grid;\n gap: 8px;\n counter-reset: rg-flow;\n}\n.rg-dataflow-step {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n}\n.rg-dataflow-index {\n flex: none;\n width: 20px;\n height: 20px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--dsw-radius-full, 999px);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n.rg-dataflow-main {\n min-width: 0;\n display: grid;\n gap: 2px;\n}\n.rg-dataflow-label {\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-s-strong-14, 600);\n font-size: 13px;\n}\n.rg-dataflow-detail {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n.rg-dataflow-count {\n flex: none;\n margin-left: auto;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u7CFB\u7EDF\u8BCA\u65AD\u5361\uFF1A\u7D27\u51D1\u952E\u503C\u5BF9 + \u53CD\u9988\u6309\u94AE\u3002 */\n.rg-issues-button {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n margin-top: 10px;\n text-decoration: none;\n color: var(--dsw-alias-brand-primary);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n.rg-issues-button:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n";
|
|
3142
|
+
var styles_default = "/**\n * \u7BA1\u7406\u9762\u677F\u7684\u6837\u5F0F\u3002\n *\n * ## \u7EAA\u5F8B\n *\n * 1. **\u53EA\u7528\u4E3B\u9898\u53D8\u91CF**\uFF1A\u989C\u8272\u3001\u5706\u89D2\u3001\u5B57\u4F53\u5168\u90E8\u5F15\u7528 `--dsw-alias-*` / `--dsw-radius-*`\n * / `--dsw-shadow-*` \u5BB6\u65CF\u3002\u5BBF\u4E3B\u91CD\u547D\u540D\u4E00\u4E2A\u53D8\u91CF\u65F6\u9762\u677F\u53EA\u662F\u53D8\u4E11\uFF0C\u4E0D\u4F1A\u5D29 \u2014\u2014\n * \u56E0\u6B64\u6BCF\u4E2A var() \u90FD\u5E26\u4E00\u4E2A\u4FDD\u5B88\u7684\u515C\u5E95\u503C\u3002\n * 2. **\u4E24\u79CD\u5F62\u6001\u6309\u5BB9\u5668\u5BBD\u5EA6**\uFF1A`.rg-panel[data-form=\"narrow\"]` \u7531 JS \u6D4B\u51FA\u7684\n * \u9762\u677F\u5BBD\u5EA6\u9A71\u52A8\uFF08\u9762\u677F\u53EF\u80FD\u6302\u5728\u88AB\u62D6\u7A84\u7684\u4FA7\u680F\u91CC\uFF0C\u5A92\u4F53\u67E5\u8BE2\u91CF\u7684\u662F\u7A97\u53E3\uFF0C\u4F1A\u7B54\u9519\uFF09\u3002\n * 3. **\u5C40\u90E8\u52A8\u6548\u4EE4\u724C**\uFF1A\u8FC7\u6E21\u65F6\u957F\u4E0E\u7F13\u52A8\u5B9A\u4E49\u5728 `.rg-panel` \u4E0A\u7684 `--rg-*`\uFF0C\n * \u4E0D\u4F9D\u8D56\u672A\u7ECF\u9A8C\u8BC1\u7684\u5168\u5C40 `--ds-*`\uFF08\u751F\u6001\u63D2\u4EF6\u540C\u6837\u81EA\u6301\u8FD9\u5957\u4EE4\u724C\uFF09\u3002\n * 4. **\u4E0D\u5199\u6B7B\u5BB9\u5668\u5BBD\u5EA6**\uFF1A\u9762\u677F\u5360\u6EE1\u5BBF\u4E3B\u7ED9\u7684\u4E2D\u592E\u9762\u677F\u533A\u57DF\u3002\n * 5. **\u5BF9\u6BD4\u5EA6**\uFF1A\u6B63\u6587 `--dsw-alias-label-primary`\uFF0C\u6B21\u8981 `--dsw-alias-label-secondary`\uFF0C\n * \u63D0\u793A `--dsw-alias-label-tertiary`\uFF08\u4EAE/\u6697\u4E3B\u9898\u90FD\u7531\u5BBF\u4E3B\u4FDD\u8BC1\u53EF\u8BFB\uFF09\u3002\n */\n\n.rg-panel {\n /* \u5C40\u90E8\u4EE4\u724C\uFF1A\u63A7\u4EF6\u9AD8\u5EA6\u3001\u52A8\u6548\u3001\u95F4\u8DDD\u3002\u6539\u4E00\u5904\uFF0C\u6574\u5757\u9762\u677F\u8DDF\u7740\u4E00\u81F4\u3002 */\n --rg-control-h: 28px;\n --rg-motion-fast: 120ms;\n --rg-motion-medium: 200ms;\n --rg-ease: cubic-bezier(0.2, 0, 0, 1);\n --rg-gap: 16px;\n\n display: flex;\n flex-direction: column;\n gap: var(--rg-gap);\n padding: 20px 24px 32px;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font-family: var(--dsw-font-family);\n font-size: var(--dsw-font-s-14, 14px);\n line-height: var(--dsw-font-s-14-line-height, 22px);\n\n /**\n * \u9762\u677F\u5FC5\u987B**\u81EA\u5DF1**\u662F\u6EDA\u52A8\u5BB9\u5668\u3002\n *\n * \u4E3A\u4EC0\u4E48\u4E0D\u80FD\u6307\u671B\u5BBF\u4E3B\uFF1A\u5B9E\u6D4B\uFF08\u6C99\u7BB1 sb-rg-c5\uFF09\u5BBF\u4E3B\u69FD\u4F4D\u662F\u8FD9\u6837\u4E00\u4E32\n * .centerCol { overflow-y: hidden } \u2190 \u7956\u5148\u628A\u6EA2\u51FA\u88C1\u6389\n * .mainPanel { height: 714px; overflow: visible }\n * .rg-panel { height: 3965px } \u2190 \u5185\u5BB9\u81EA\u7136\u9AD8\u5EA6\uFF0C\u8D85\u51FA\u540E\u76F4\u63A5\u88AB\u88C1\n * \u6587\u6863\u672C\u8EAB\u4E0D\u6EDA\uFF08docScrollH == clientH == 714\uFF09\uFF0C\u56E0\u6B64**\u4E0B\u65B9\u6570\u636E\u9875\u6C38\u8FDC\u770B\u4E0D\u5230**\u3002\n *\n * \u4E09\u4E2A\u5C5E\u6027\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n * - `height: 100%` + `min-height: 0`\uFF1A\u6491\u6EE1\u5BBF\u4E3B\u7ED9\u7684\u786E\u5B9A\u9AD8\u5EA6\uFF0C\u4E14\u5141\u8BB8\u6536\u7F29\u5230\u53EF\u6EDA\u52A8\uFF1B\n * \u53EA\u5199 overflow \u800C\u4E0D\u9650\u5236\u9AD8\u5EA6\uFF0Cflex \u5B50\u9879\u4ECD\u4F1A\u6309\u5185\u5BB9\u6491\u5F00\uFF0C\u6EDA\u4E0D\u8D77\u6765\u3002\n * - `overflow-y: auto`\uFF1A\u9762\u677F\u81EA\u5DF1\u6EDA\u3002\n * - `overscroll-behavior: contain`\uFF1A\u6EDA\u5230\u5C3D\u5934\u4E0D\u628A\u6EDA\u52A8\u4F20\u7ED9\u5BBF\u4E3B\uFF0C\u5426\u5219\u4F1A\u628A\u6574\u9875/\u4FA7\u680F\n * \u5E26\u7740\u4E00\u8D77\u6EDA\uFF0C\u89C6\u89C9\u4E0A\u50CF\"\u9762\u677F\u5728\u4E71\u8DF3\"\u3002\n */\n height: 100%;\n max-height: 100%;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n overscroll-behavior: contain;\n}\n\n/* \u7A84\u5F62\u6001\uFF08\u4FA7\u680F / \u5206\u5C4F\uFF09\uFF1A\u6536\u7D27\u8FB9\u8DDD\u4E0E\u95F4\u8DDD\uFF0C\u628A\u7EB5\u5411\u7A7A\u95F4\u8BA9\u7ED9\u5185\u5BB9\u3002 */\n.rg-panel[data-form='narrow'] {\n --rg-gap: 12px;\n padding: 14px 14px 24px;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n/* ---------- \u5934\u90E8 ---------- */\n\n.rg-header {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n\n.rg-header-main {\n min-width: 0;\n flex: 1 1 260px;\n}\n\n.rg-title {\n margin: 0;\n font-size: var(--dsw-font-l-20, 20px);\n font-weight: var(--dsw-font-l-20-font-weight, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-panel[data-form='narrow'] .rg-title {\n font-size: var(--dsw-font-m-18, 18px);\n}\n\n.rg-subtitle {\n margin: 4px 0 0;\n font-size: var(--dsw-font-xs-13, 13px);\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n\n.rg-project-name {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-partition-workspace-full {\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n direction: ltr;\n}\n\n.rg-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* ---------- \u63A7\u4EF6 ---------- */\n\n.rg-button {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n padding: 4px 12px;\n font: inherit;\n font-size: var(--dsw-font-xs-13, 13px);\n cursor: pointer;\n transition:\n background var(--rg-motion-fast) var(--rg-ease),\n border-color var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n.rg-button:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.rg-button-small {\n min-height: 24px;\n padding: 2px 9px;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-button-icon {\n min-width: 28px;\n padding: 2px 6px;\n justify-content: center;\n font-size: var(--dsw-font-s-14, 14px);\n line-height: 1;\n}\n\n.rg-button-danger {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.rg-button-active {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* \u8F6E\u8BE2\u6682\u505C\u6001\u6309\u94AE\uFF08\u6682\u505C = \u9700\u8981\u88AB\u770B\u89C1\u7684\u72B6\u6001\uFF0C\u4E0D\u80FD\u53EA\u9760 aria-pressed\uFF09\u3002 */\n.rg-button-paused {\n border-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-link {\n appearance: none;\n border: none;\n background: none;\n padding: 0 2px;\n font: inherit;\n font-size: inherit;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n text-decoration: underline;\n}\n\n/* \u5206\u6BB5\u63A7\u4EF6\uFF08\u5E03\u5C40 / \u7740\u8272\u53E3\u5F84\uFF09\u3002 */\n.rg-segmented {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.rg-segment {\n appearance: none;\n border: none;\n border-right: 1px solid var(--dsw-alias-border-l1);\n background: none;\n color: var(--dsw-alias-label-secondary);\n padding: 3px 9px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n cursor: pointer;\n transition: background var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-segment:last-child {\n border-right: none;\n}\n\n.rg-segment:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-segment[aria-pressed='true'] {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n}\n\n.rg-segment:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.rg-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.rg-field-grow {\n flex: 1 1 160px;\n}\n\n.rg-field-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-input,\n.rg-select {\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n padding: 2px 8px;\n font: inherit;\n font-size: var(--dsw-font-xs-13, 13px);\n max-width: 100%;\n}\n\n.rg-input:focus-visible,\n.rg-select:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* ---------- \u63D0\u793A\u6761 ---------- */\n\n.rg-notice {\n margin: 0;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-error {\n margin: 0;\n padding: 10px 12px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 1px solid var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-empty {\n margin: 0;\n padding: 8px 0;\n font-size: var(--dsw-font-xs-13, 13px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-muted {\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-strong {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n}\n\n.rg-block {\n display: block;\n}\n\n.rg-ellipsis {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 16px;\n margin-left: 6px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n/* \u52A0\u8F7D / \u4E0D\u53EF\u7528\u72B6\u6001\u5757\uFF1A\u5360\u4F4D\u9AD8\u5EA6\u7A33\u5B9A\uFF0C\u907F\u514D\u8F6E\u8BE2\u65F6\u9875\u9762\u8DF3\u52A8\u3002 */\n.rg-state-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 24px 0;\n}\n\n.rg-state-title {\n margin: 0;\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n/* ---------- \u5361\u7247\u7F51\u683C ---------- */\n\n.rg-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n}\n\n.rg-panel[data-form='narrow'] .rg-grid {\n grid-template-columns: 1fr;\n}\n\n.rg-card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n min-height: 0;\n}\n\n/**\n * \u5361\u7247\u5185\u5BB9\u533A\u56FA\u5B9A\u9AD8\u5EA6 + \u5185\u90E8\u6EDA\u52A8\uFF08v0.9.1 \u7528\u6237\u9700\u6C42\uFF09\u3002\n *\n * \u4E3A\u4EC0\u4E48\u9650\u9AD8\u800C\u4E0D\u662F\u8BA9\u5185\u5BB9\u81EA\u7136\u4F38\u5C55\uFF1A\u5C55\u5F00\u7684\u5361\u7247\u591A\u4E86\u4E4B\u540E\uFF0C\u9762\u677F\u88AB\u5355\u4E2A\n * \u5361\u7247\u6491\u5230\u51E0\u5343\u50CF\u7D20\uFF0C\u4E0B\u9762\u7684\u5361\u7247\u8981\u6EDA\u5F88\u4E45\u624D\u770B\u5F97\u5230 \u2014\u2014 \u5747\u5300\u7684\u89C6\u53E3\u5BC6\u5EA6\n * \u6BD4\u5355\u5361\u7684\u5B8C\u6574\u6027\u66F4\u91CD\u8981\uFF08\u8FD9\u6B63\u662F\u7528\u6237\u539F\u59CB\u8BC9\u6C42\uFF09\u3002\n *\n * - \u9ED8\u8BA4 `--rg-card-body-h: 240px`\uFF1A\u8DB3\u591F\u770B\u7EA6 10 \u884C\u5185\u5BB9\uFF0C\u8D85\u51FA\u5185\u90E8\u6EDA\u52A8\uFF1B\n * - `overscroll-behavior: contain`\uFF1A\u6EDA\u5230\u5C3D\u5934\u4E0D\u628A\u5916\u5C42\u9762\u677F\u5E26\u7740\u6EDA\uFF1B\n * - `.rg-card-enlarged` \u89E3\u9664\u9650\u9AD8\uFF08\u2922 \u6309\u94AE\uFF09\uFF0C\u644A\u5F00\u9605\u8BFB\u957F\u6E05\u5355\uFF1B\n * - \u7A84\u5F62\u6001\u628A\u9650\u9AD8\u964D\u5230 180px\uFF08\u4FA7\u680F\u7A7A\u95F4\u5C11\uFF0C\u5BC6\u5EA6\u4F18\u5148\uFF09\u3002\n */\n.rg-card-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n height: var(--rg-card-body-h, 240px);\n overflow-y: auto;\n overscroll-behavior: contain;\n min-height: 0;\n}\n\n/* \u6269\u5927\u6001\uFF1A\u89E3\u9664\u9650\u9AD8\uFF0C\u5361\u7247\u6309\u5185\u5BB9\u81EA\u7136\u4F38\u5C55\uFF08\u9762\u677F\u81EA\u8EAB\u4ECD\u662F\u6EDA\u52A8\u5BB9\u5668\uFF09\u3002 */\n.rg-card-enlarged .rg-card-body {\n height: auto;\n max-height: none;\n overflow-y: visible;\n}\n\n/* \u6269\u5927\u6309\u94AE\uFF1A\u5934\u90E8\u5185\u7684\u7B2C\u4E8C\u4E2A\u52A8\u4F5C\uFF0C\u5C0F\u5C3A\u5BF8\u3001\u4F4E\u5BF9\u6BD4\uFF0C\u4E0D\u4E0E\u6298\u53E0\u7BAD\u5934\u7ADE\u4E89\u3002 */\n.rg-card-expand {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n margin-left: auto;\n border-radius: var(--dsw-radius-sm, 8px);\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n user-select: none;\n transition: background var(--rg-motion-fast, 120ms) var(--rg-ease, ease);\n}\n.rg-card-expand:hover,\n.rg-card-expand:focus-visible {\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n.rg-card-enlarged .rg-card-expand {\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-card-title {\n margin: 0;\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n\n/* \u7A84\u5F62\u6001\uFF1A\u4FA7\u680F\u7A7A\u95F4\u5C11\uFF0C\u5BC6\u5EA6\u4F18\u5148 \u2014\u2014 \u5185\u5BB9\u533A\u9650\u9AD8\u964D\u4E3A 180px\u3002 */\n.rg-panel[data-form='narrow'] .rg-card-body {\n --rg-card-body-h: 180px;\n}\n\n.rg-card-title {\n margin: 0;\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n\n.rg-card-subtitle {\n margin: 4px 0 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-card-note {\n margin: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u53EF\u6298\u53E0\u5361\u7247\uFF1A\u6807\u9898\u884C\u662F\u6309\u94AE\uFF0C\u6574\u4E2A\u5934\u90E8\u53EF\u70B9\u3002 */\n.rg-card-head {\n appearance: none;\n border: none;\n background: none;\n padding: 0;\n margin: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n cursor: pointer;\n font: inherit;\n color: inherit;\n text-align: left;\n}\n\n.rg-card-head:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-card-head-hint {\n margin-left: auto;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-card-chevron {\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-card-collapsed {\n padding-bottom: 10px;\n}\n\n.rg-card-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- \u5FBD\u6807 / \u5706\u70B9 / \u8BA1\u6570\u7247 ---------- */\n\n.rg-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 8px;\n border-radius: var(--dsw-radius-sm, 8px);\n border: 1px solid var(--dsw-alias-border-l2);\n font-size: var(--dsw-font-xxs-12, 12px);\n white-space: nowrap;\n}\n\n.rg-badge-idle,\n.rg-badge-monitoring {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.rg-badge-crashed,\n.rg-badge-error {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.rg-badge-deciding,\n.rg-badge-worsening,\n.rg-badge-warn {\n border-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-badge-stabilizing,\n.rg-badge-recovered,\n.rg-badge-improving,\n.rg-badge-success {\n border-color: var(--dsw-alias-state-success-primary);\n color: var(--dsw-alias-state-success-primary);\n}\n\n.rg-badge-stable,\n.rg-badge-neutral {\n border-color: var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-chip {\n display: inline-flex;\n align-items: center;\n padding: 0 6px;\n border-radius: var(--dsw-radius-xs, 4px);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: var(--dsw-font-xxxs-11, 11px);\n white-space: nowrap;\n}\n\n.rg-chip-warn {\n border: 1px solid var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-state-warn-primary);\n background: none;\n}\n\n.rg-chip-more {\n font-variant-numeric: tabular-nums;\n}\n\n.rg-names {\n display: inline-flex;\n flex-wrap: wrap;\n gap: 3px;\n min-width: 0;\n}\n\n.rg-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-secondary);\n flex: none;\n}\n\n.rg-dot-info {\n background: var(--dsw-alias-brand-primary);\n}\n\n.rg-dot-success {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.rg-dot-warn {\n background: var(--dsw-alias-state-warn-primary);\n}\n\n.rg-dot-error {\n background: var(--dsw-alias-state-error-primary);\n}\n\n/* ---------- \u8FDB\u5EA6\u6761 ---------- */\n\n.rg-bar {\n display: inline-block;\n width: 72px;\n height: 4px;\n border-radius: 2px;\n background: var(--dsw-alias-bg-layer-2);\n overflow: hidden;\n flex: none;\n}\n\n.rg-bar-wide {\n width: 100%;\n}\n\n.rg-bar-fill {\n display: block;\n height: 100%;\n background: var(--dsw-alias-brand-primary);\n transition: width var(--rg-motion-medium) var(--rg-ease);\n}\n\n/* ---------- KPI \u6458\u8981\u6761 ---------- */\n\n.rg-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 10px;\n}\n\n.rg-kpi {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n}\n\n.rg-kpi-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-kpi-value {\n font-size: var(--dsw-font-s-14, 14px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n font-variant-numeric: tabular-nums;\n}\n\n.rg-kpi-big {\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n.rg-kpi-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.rg-kpi-meta {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.rg-ring {\n flex: none;\n}\n\n.rg-ring-text {\n fill: var(--dsw-alias-label-primary);\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n\n/* ---------- \u63CF\u8FF0\u5217\u8868 / \u9636\u6BB5\u8F68\u9053 ---------- */\n\n.rg-dl {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 4px 12px;\n margin: 0;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-dl dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-dl dd {\n margin: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex-wrap: wrap;\n}\n\n.rg-dl-compact {\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-mono {\n font-family: var(--dsw-font-mono, var(--dsw-font-family-mono, ui-monospace, monospace));\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow-wrap: anywhere;\n}\n\n/* \u9636\u6BB5\u8F68\u9053\uFF1A\u72B6\u6001\u673A\u4E0D\u662F\u8FDB\u5EA6\u6761\uFF0Cvisited \u4E0E current \u7528\u4E0D\u540C\u901A\u9053\u8868\u8FBE\u3002 */\n.rg-stage-rail {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 10px;\n}\n\n.rg-stage-cell {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-stage-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n border: 1px solid var(--dsw-alias-border-l2);\n background: none;\n flex: none;\n}\n\n.rg-stage-visited {\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-stage-visited .rg-stage-dot {\n background: var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-color: var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n}\n\n.rg-stage-current {\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-xxxs-strong-11, 600);\n}\n\n.rg-stage-current .rg-stage-dot {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n box-shadow: 0 0 0 2px var(--dsw-alias-bg-layer-2);\n}\n\n/* ---------- \u6307\u6807 ---------- */\n\n.rg-metrics {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.rg-metric {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.rg-metric-label {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-metric-value {\n font-size: var(--dsw-font-xs-13, 13px);\n font-variant-numeric: tabular-nums;\n}\n\n.rg-metric-undefined {\n color: var(--dsw-alias-label-secondary);\n font-style: italic;\n}\n\n.rg-metric-reason {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-metric-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-trend {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* ---------- \u8FC1\u79FB / \u51B3\u7B56 / \u4E8B\u4EF6\u65F6\u95F4\u7EBF ---------- */\n\n.rg-transitions,\n.rg-decisions,\n.rg-events {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n\n.rg-transition,\n.rg-decision,\n.rg-event {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-transition-reason,\n.rg-decision-detail,\n.rg-event-detail {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-decision-bundle {\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-transition-time,\n.rg-decision-time,\n.rg-event-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n.rg-event-ago {\n flex: none;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: var(--dsw-font-xxxs-11, 11px);\n}\n\n/* ---------- \u5ACC\u7591\u6392\u540D ---------- */\n\n.rg-risks {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* \u300C\u5C55\u5F00\u5176\u4F59 N \u4E2A\u300D\u6309\u94AE\uFF1A\u5DE6\u5BF9\u9F50\u7684\u94FE\u63A5\u5F62\u6001\uFF0C\u4E0E\u5361\u7247\u5185\u7684\u5176\u5B83\u64CD\u4F5C\u533A\u5206\u3002 */\n.rg-risks-expand {\n align-self: flex-start;\n margin-top: 2px;\n}\n\n.rg-risk {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.rg-risk:last-child {\n border-bottom: none;\n padding-bottom: 0;\n}\n\n.rg-risk-main {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.rg-risk-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-risk-score {\n font-variant-numeric: tabular-nums;\n font-size: var(--dsw-font-xs-13, 13px);\n}\n\n.rg-risk-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-risk-actions {\n display: flex;\n gap: 6px;\n}\n\n/* ---------- \u80FD\u529B\u6E05\u5355\u8868 ---------- */\n\n.rg-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--dsw-font-xxs-12, 12px);\n table-layout: fixed;\n}\n\n.rg-table th,\n.rg-table td {\n text-align: left;\n vertical-align: top;\n padding: 5px 6px;\n border-bottom: 1px solid var(--dsw-alias-hairline, var(--dsw-alias-border-l1));\n overflow-wrap: anywhere;\n}\n\n.rg-table thead th {\n color: var(--dsw-alias-label-secondary);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n white-space: nowrap;\n border-bottom-color: var(--dsw-alias-border-l2);\n}\n\n.rg-table tbody tr {\n cursor: pointer;\n}\n\n.rg-table tbody tr:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n/* \u9009\u4E2D\u884C\uFF1A\u5DE6\u4FA7 2px \u7AD6\u6761 + \u5E95\u8272\uFF08\u53EA\u9760\u5E95\u8272\u5728\u6697\u8272\u4E3B\u9898\u4E0B\u4E0D\u591F\u663E\u773C\uFF09\u3002 */\n.rg-row-selected {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary);\n}\n\n/* \u7A84\u5F62\u6001\u4E0B\u8868\u683C\u4F1A\u6324\u6210\u4E00\u56E2 \u2014\u2014 \u5217\u5BBD\u6309\u300C\u80FD\u529B\u540D / \u5173\u7CFB\u300D\u4E24\u680F\u91CD\u6392\u3002 */\n.rg-panel[data-form='narrow'] .rg-table {\n table-layout: auto;\n}\n\n.rg-panel[data-form='narrow'] .rg-table th:nth-child(2),\n.rg-panel[data-form='narrow'] .rg-table td:nth-child(2),\n.rg-panel[data-form='narrow'] .rg-table th:nth-child(3),\n.rg-panel[data-form='narrow'] .rg-table td:nth-child(3) {\n display: none;\n}\n\n.rg-gaps {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-gap {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--dsw-font-xxs-12, 12px);\n}\n\n.rg-gap-detail {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* ---------- \u9879\u76EE\u5206\u533A ---------- */\n\n.rg-partitions {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-partition {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: var(--dsw-font-xxs-12, 12px);\n padding: 3px 4px;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-partition-selected {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\n box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary);\n}\n\n.rg-partition-label {\n font-weight: var(--dsw-font-xs-strong-13, 600);\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rg-partition-workspace {\n flex: 1 1 140px;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n text-align: left;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-partition-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u5F52\u6863 ---------- */\n\n.rg-archive-run {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.rg-archives {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.rg-archive {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm, 8px);\n}\n\n.rg-archive-dir {\n font-family: var(--dsw-font-mono, var(--dsw-font-family-mono, ui-monospace, monospace));\n font-size: var(--dsw-font-xxs-12, 12px);\n overflow-wrap: anywhere;\n}\n\n.rg-archive-meta,\n.rg-archive-summary {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u533A\u5757 / \u9875\u811A ---------- */\n\n.rg-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.rg-section-head {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.rg-section-title {\n margin: 0;\n font-size: var(--dsw-font-m-18, 18px);\n font-weight: var(--dsw-font-m-18-font-weight, 600);\n}\n\n.rg-selection-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n padding: 2px 8px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n}\n\n.rg-foot {\n display: flex;\n flex-wrap: wrap;\n gap: 4px 16px;\n border-top: 1px solid var(--dsw-alias-hairline, var(--dsw-alias-border-l1));\n padding-top: 8px;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* ---------- \u56FE\u8C31 ---------- */\n\n.rg-graph {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n padding: 10px;\n min-width: 0;\n}\n\n.rg-graph-toolbar {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.rg-graph-zoom {\n display: inline-flex;\n gap: 4px;\n}\n\n.rg-graph-hint {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-graph-viewport {\n position: relative;\n overflow: hidden;\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-2);\n cursor: grab;\n touch-action: none;\n}\n\n.rg-graph-viewport:active {\n cursor: grabbing;\n}\n\n.rg-graph-viewport:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n.rg-graph-node:focus-visible {\n outline: none;\n}\n\n/* \u952E\u76D8\u805A\u7126\u7684\u8282\u70B9\u5FC5\u987B\u80FD\u770B\u89C1\u7126\u70B9\uFF08\u5426\u5219 Tab \u8D70\u4E00\u5708\u4E0D\u77E5\u9053\u505C\u5728\u54EA\uFF09\u3002 */\n.rg-graph-node:focus-visible .rg-graph-node-bg {\n stroke: var(--dsw-alias-brand-primary);\n stroke-width: 3px;\n}\n\n.rg-graph-node-label {\n fill: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 600;\n}\n\n.rg-graph-node-detail {\n fill: var(--dsw-alias-label-secondary);\n font-size: 11px;\n}\n\n.rg-graph-column-title {\n fill: var(--dsw-alias-label-secondary);\n font-size: 12px;\n font-weight: 600;\n}\n\n/* \u66F2\u7EBF\u8FB9\u7684\u8BED\u4E49\u6807\u7B7E\uFF08\u4EC5\u9AD8\u4EAE\u8FB9\u663E\u793A\uFF0C\u907F\u514D\u566A\u58F0\uFF09\u3002 */\n.rg-graph-edge-label {\n fill: var(--dsw-alias-brand-primary);\n font-size: 10px;\n font-weight: 600;\n}\n\n/* \u56FE\u4F8B\uFF08\u7C7B\u578B\u8FC7\u6EE4 chips\uFF0C\u5E26\u8BA1\u6570\uFF09\u3002 */\n.rg-graph-legend {\n display: inline-flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-left: 4px;\n}\n\n.rg-legend-chip {\n appearance: none;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: 999px;\n padding: 2px 10px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n cursor: pointer;\n transition: background var(--rg-motion-fast) var(--rg-ease);\n}\n\n.rg-legend-chip:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-legend-chip-off {\n opacity: 0.45;\n text-decoration: line-through;\n}\n\n.rg-legend-chip:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.rg-legend-count {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n/* \u5F62\u72B6\u56FE\u4F8B\uFF08v0.11\uFF09\uFF1A\u56FE\u4F8B\u91CC\u76F4\u63A5\u753B\u8282\u70B9\u5F62\u72B6 \u2014\u2014 \u5F62\u72B6\u662F\u989C\u8272\u4E4B\u5916\u7684\u7B2C\u4E8C\u7F16\u7801\u901A\u9053\uFF0C\n \u56FE\u4F8B\u4E0D\u5C55\u793A\u5B83\uFF0C\u8272\u76F2/\u7070\u5EA6\u6253\u5370\u7528\u6237\u5C31\u6C38\u8FDC\u4E0D\u77E5\u9053\u65B9\u6846=\u5448\u73B0\u3001\u4E09\u89D2=\u7F3A\u53E3\u3002 */\n.rg-legend-shape {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n/* \u641C\u7D22\u904D\u5386\u6307\u793A\uFF08v0.11\uFF09\uFF1A\u7B2C n/m \u4E2A\u5339\u914D\uFF0C\u56DE\u8F66\u5FAA\u73AF\u8DF3\u8F6C\u65F6\u544A\u8BC9\u7528\u6237\u4F4D\u7F6E\u3002 */\n.rg-search-index {\n font-size: var(--dsw-font-xxs-12, 12px);\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n/* \u9A6C\u5C14\u53EF\u592B\u8DE8\u9636\u6BB5\u53BB\u5411\u5FBD\u6807\uFF08v0.11\uFF09\uFF1A`\u2192 \u5D29\u6E83 40%` \u9AD8\u5371\u8FC1\u79FB\u5FC5\u987B\u4E00\u773C\u53EF\u89C1\u3002 */\n.rg-markov-exits {\n display: inline-flex;\n gap: 4px;\n margin-left: 6px;\n flex-wrap: wrap;\n}\n\n.rg-chip-exit {\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n border: 1px dashed var(--dsw-alias-border-l2);\n background: transparent;\n white-space: nowrap;\n}\n\n/* \u56FE\u8C31\u4E3B\u4F53\uFF1A\u89C6\u53E3 + \u8BE6\u60C5\u4FA7\u680F\u4E24\u680F\uFF08\u7A84\u5BB9\u5668\u7EB5\u5411\u5806\u53E0\uFF09\u3002 */\n.rg-graph-body {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: 10px;\n}\n\n@media (min-width: 960px) {\n .rg-graph-body:has(.rg-graph-aside) {\n grid-template-columns: minmax(0, 1fr) 260px;\n }\n}\n\n.rg-graph-status {\n display: flex;\n align-items: center;\n gap: 6px 14px;\n flex-wrap: wrap;\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-graph-zoom-label {\n font-variant-numeric: tabular-nums;\n}\n\n.rg-graph-status-selected {\n color: var(--dsw-alias-label-primary);\n}\n\n/* \u60AC\u505C\u6D6E\u5C42\uFF1A\u6D6E\u52A8\u5361\u7247\uFF08\u552F\u4E00\u5E26\u9634\u5F71\u7684\u8868\u9762\uFF09\u3002 */\n.rg-graph-tooltip {\n position: absolute;\n z-index: 5;\n transform: translate(-50%, -100%);\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-width: 260px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-shadow-lv3, var(--dsw-shadow-lv1, none));\n pointer-events: none;\n}\n\n.rg-tooltip-title {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-primary);\n}\n\n.rg-tooltip-meta,\n.rg-tooltip-detail {\n font-size: var(--dsw-font-xxxs-11, 11px);\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n overflow-wrap: anywhere;\n}\n\n/* \u56FE\u8C31\u641C\u7D22\u6846\u4E0E\u7ED3\u679C\u4E0B\u62C9\u3002 */\n.rg-graph-search {\n position: relative;\n display: inline-flex;\n}\n\n.rg-search-input {\n min-height: var(--rg-control-h);\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n border-radius: var(--dsw-radius-sm, 8px);\n padding: 2px 9px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n min-width: 150px;\n}\n\n.rg-search-input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.rg-search-input::placeholder {\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n}\n\n.rg-search-results {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 10;\n margin: 0;\n padding: 4px;\n list-style: none;\n min-width: 200px;\n max-height: 220px;\n overflow: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm, 8px);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-shadow-lv3, var(--dsw-shadow-lv1, none));\n}\n\n.rg-search-results button {\n appearance: none;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 5px 8px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n text-align: left;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-search-results button:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-search-noresult {\n padding: 5px 8px;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u8BE6\u60C5\u4FA7\u680F\u3002 */\n.rg-graph-aside {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n min-width: 0;\n align-self: start;\n}\n\n.rg-graph-aside-header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.rg-graph-aside-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsw-font-s-14, 14px);\n}\n\n.rg-graph-aside-detail {\n margin: 0;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-graph-aside-actions {\n display: flex;\n gap: 6px;\n}\n\n/* \u90BB\u5C45\u5BFC\u822A\uFF08\u8BE6\u60C5\u4FA7\u680F\uFF09\uFF1A\u4E0A\u6E38/\u4E0B\u6E38\u5206\u7EC4\u5217\u8868\uFF0C\u70B9\u51FB\u5373\u8DF3\u8F6C\u5B9A\u4F4D\u3002 */\n.rg-graph-neighbors {\n display: flex;\n flex-direction: column;\n gap: 6px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.rg-graph-neighbor-title {\n margin: 0;\n display: flex;\n align-items: center;\n font-size: var(--dsw-font-xxs-12, 12px);\n font-weight: var(--dsw-font-xxs-strong-12, 600);\n color: var(--dsw-alias-label-secondary);\n}\n\n.rg-graph-neighbor-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.rg-graph-neighbor-link {\n appearance: none;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 6px;\n font: inherit;\n font-size: var(--dsw-font-xxs-12, 12px);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n text-align: left;\n border-radius: var(--dsw-radius-xs, 4px);\n}\n\n.rg-graph-neighbor-link:hover {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\n}\n\n.rg-graph-neighbor-link:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* \u52A8\u6548\u4E0E\u504F\u597D\uFF1A\u5C0A\u91CD\u300C\u51CF\u5C11\u52A8\u6001\u6548\u679C\u300D\uFF08\u524D\u5EAD\u5931\u8C03\u7528\u6237\u4F1A\u56E0\u8FC7\u6E21\u4E0D\u9002\uFF09\u3002 */\n@media (prefers-reduced-motion: reduce) {\n .rg-panel *,\n .rg-panel *::before,\n .rg-panel *::after {\n transition-duration: 0.01ms !important;\n animation-duration: 0.01ms !important;\n }\n}\n\n/* ---------- \u6062\u590D\u5224\u5B9A\u6761\uFF08v0.9.1\uFF09 ---------- */\n\n.rg-verdict {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin: 0;\n padding: 8px 12px;\n border-radius: var(--dsw-radius-sm, 8px);\n font-size: var(--dsw-font-xs-13, 13px);\n}\n/* act\uFF08\u5173\u95ED\u4E86\u63D2\u4EF6\uFF09\u4E0E watch\uFF08\u89C2\u5BDF\u8F6E\uFF09\u7528**\u8FB9\u6846+\u6807\u7B7E**\u533A\u5206\u800C\u4E0D\u662F\u53EA\u9760\u989C\u8272\uFF1A\n \u7070\u5EA6\u6253\u5370/\u8272\u5F31\u7528\u6237\u4E5F\u80FD\u5206\u8FA8\u3002 */\n.rg-verdict-act {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n}\n.rg-verdict-watch {\n border: 1px dashed var(--dsw-alias-border-l1);\n background: transparent;\n}\n.rg-verdict-tag {\n padding: 1px 8px;\n border-radius: var(--dsw-radius-sm, 8px);\n font-weight: var(--dsw-font-s-strong-14, 600);\n background: var(--dsw-alias-bg-layer-2);\n}\n.rg-verdict-meta {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---------- v0.10 \u6D1E\u5BDF\u5361\uFF1A\u9A6C\u5C14\u53EF\u592B / \u6570\u636E\u6D41 / \u7CFB\u7EDF\u8BCA\u65AD ---------- */\n\n/* \u5361\u7247\u9876\u90E8\u7684\u4E00\u884C\u6458\u8981\uFF08\u6837\u672C\u6570\u3001\u6536\u655B\u6027\u7B49\u767D\u76D2\u5143\u4FE1\u606F\uFF09\u3002 */\n.rg-card-note {\n margin: 0 0 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* \u9A6C\u5C14\u53EF\u592B\u884C\uFF1A\u9636\u6BB5\u540D | \u81EA\u73AF\u6982\u7387\u6761 | \u767E\u5206\u6BD4\u3002 */\n.rg-markov {\n list-style: none;\n margin: 0;\n padding: 0;\n display: grid;\n gap: 6px;\n}\n.rg-markov-row {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.rg-markov-stage {\n width: 84px;\n flex: none;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.rg-markov-row .rg-bar {\n flex: 1 1 auto;\n width: auto;\n}\n.rg-markov-value {\n width: 44px;\n flex: none;\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n}\n.rg-markov-nodata {\n flex: 1 1 auto;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n.rg-markov-stationary {\n margin-top: 10px;\n}\n.rg-subhead {\n margin: 0 0 6px;\n font-size: 12px;\n font-weight: var(--dsw-font-s-strong-14, 600);\n color: var(--dsw-alias-label-secondary);\n}\n.rg-chip-row {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* \u6570\u636E\u6D41\u94FE\u8DEF\uFF1A\u5E8F\u53F7 | \u540D\u79F0+\u8BF4\u660E | \u8BA1\u6570\u3002 */\n.rg-dataflow {\n list-style: none;\n margin: 0;\n padding: 0;\n display: grid;\n gap: 8px;\n counter-reset: rg-flow;\n}\n.rg-dataflow-step {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n}\n.rg-dataflow-index {\n flex: none;\n width: 20px;\n height: 20px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--dsw-radius-full, 999px);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-weight: var(--dsw-font-s-strong-14, 600);\n}\n\n/* \u6D41\u52A8\u8109\u51B2\uFF08v0.11\u300C\u53EF\u89C6\u5316\u6D41\u52A8\u300D\uFF09\uFF1A\u56DB\u73AF\u94FE\u8DEF\u7684\u5E8F\u53F7\u70B9\u6309\u94FE\u8DEF\u987A\u5E8F\u547C\u5438 \u2014\u2014\n \u6570\u636E\u4ECE journal \u6D41\u5411\u753B\u9762\u7684\u65B9\u5411\u611F\u7528\u5EF6\u8FDF\u9519\u76F8\u8868\u8FBE\uFF08\u6BCF\u73AF\u5EF6\u8FDF 0.3s\uFF09\u3002\n \u52A8\u753B\u53EA\u52A8 opacity\uFF08\u5408\u6210\u5C42\u5C5E\u6027\uFF0C\u4E0D\u89E6\u53D1\u91CD\u6392\uFF09\uFF1B\u7CFB\u7EDF\u300C\u51CF\u5C11\u52A8\u6001\u300D\u65F6\u505C\u7528\uFF0C\n \u9759\u6001\u5E8F\u53F7\u4ECD\u7136\u5B8C\u6574\u627F\u8F7D\u4FE1\u606F\uFF08\u52A8\u753B\u662F\u589E\u5F3A\uFF0C\u4E0D\u662F\u4FE1\u606F\u8F7D\u4F53\uFF09\u3002 */\n.rg-dataflow-step .rg-dataflow-index {\n animation: rg-flow-pulse 1.8s var(--rg-ease, ease) infinite;\n}\n.rg-dataflow-step:nth-child(2) .rg-dataflow-index {\n animation-delay: 0.3s;\n}\n.rg-dataflow-step:nth-child(3) .rg-dataflow-index {\n animation-delay: 0.6s;\n}\n.rg-dataflow-step:nth-child(4) .rg-dataflow-index {\n animation-delay: 0.9s;\n}\n@keyframes rg-flow-pulse {\n 0%,\n 100% {\n opacity: 1;\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-brand-primary) 0%, transparent);\n }\n 50% {\n opacity: 0.55;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent);\n }\n}\n.rg-dataflow-main {\n min-width: 0;\n display: grid;\n gap: 2px;\n}\n.rg-dataflow-label {\n color: var(--dsw-alias-label-primary);\n font-weight: var(--dsw-font-s-strong-14, 600);\n font-size: 13px;\n}\n.rg-dataflow-detail {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n.rg-dataflow-count {\n flex: none;\n margin-left: auto;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* \u7CFB\u7EDF\u8BCA\u65AD\u5361\uFF1A\u7D27\u51D1\u952E\u503C\u5BF9 + \u53CD\u9988\u6309\u94AE\u3002 */\n.rg-issues-button {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n margin-top: 10px;\n text-decoration: none;\n color: var(--dsw-alias-brand-primary);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n.rg-issues-button:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n";
|
|
3063
3143
|
|
|
3064
3144
|
// src/client/styles.ts
|
|
3065
3145
|
var STYLE_ELEMENT_ID = "dsh-recency-guard-styles";
|
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ function readHostVersion() {
|
|
|
62
62
|
|
|
63
63
|
// src/store.ts
|
|
64
64
|
import { mkdir, readFile, readdir, stat, unlink } from "node:fs/promises";
|
|
65
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
65
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
66
66
|
import { isAbsolute, join, resolve } from "node:path";
|
|
67
67
|
import { writeFileAtomic, withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
68
68
|
var STATE_DIRNAME = ".recency-guard";
|
|
@@ -70,6 +70,55 @@ var JOURNAL_MAX_LINES = 1e3;
|
|
|
70
70
|
function emptyState() {
|
|
71
71
|
return { bootMarker: null, crashSeq: 0, autoDisabled: [], stabilized: false, restored: [] };
|
|
72
72
|
}
|
|
73
|
+
var STALE_LOCK_MIN_AGE_MS = 5e3;
|
|
74
|
+
function processAlive(pid) {
|
|
75
|
+
try {
|
|
76
|
+
process.kill(pid, 0);
|
|
77
|
+
return true;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return error.code === "EPERM";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function cleanStaleLocks(dir, now = Date.now()) {
|
|
83
|
+
const report = { removed: [], kept: [] };
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = readdirSync(dir);
|
|
87
|
+
} catch {
|
|
88
|
+
return report;
|
|
89
|
+
}
|
|
90
|
+
for (const name2 of entries) {
|
|
91
|
+
if (!name2.endsWith(".lock")) continue;
|
|
92
|
+
const path = join(dir, name2);
|
|
93
|
+
try {
|
|
94
|
+
if (now - statSync(path).mtimeMs < STALE_LOCK_MIN_AGE_MS) {
|
|
95
|
+
report.kept.push(name2);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
let text = "";
|
|
99
|
+
try {
|
|
100
|
+
text = readFileSync(path, "utf8").trim();
|
|
101
|
+
} catch {
|
|
102
|
+
report.kept.push(name2);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const pid = Number(text);
|
|
106
|
+
const incomplete = text === "" || !Number.isInteger(pid) || pid <= 0;
|
|
107
|
+
if (!incomplete && processAlive(pid)) {
|
|
108
|
+
report.kept.push(name2);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
rmSync(path, { force: true });
|
|
112
|
+
report.removed.push(name2);
|
|
113
|
+
} catch {
|
|
114
|
+
report.kept.push(name2);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return report;
|
|
118
|
+
}
|
|
119
|
+
function isLockTimeout(error) {
|
|
120
|
+
return error instanceof Error && error.message.includes("timed out waiting for the writer lock");
|
|
121
|
+
}
|
|
73
122
|
var Store = class {
|
|
74
123
|
/** profile 根目录下的守护目录(分区索引与归档都在这里)。 */
|
|
75
124
|
rootDir;
|
|
@@ -105,6 +154,10 @@ var Store = class {
|
|
|
105
154
|
}
|
|
106
155
|
async init() {
|
|
107
156
|
await mkdir(this.backupsDir, { recursive: true });
|
|
157
|
+
try {
|
|
158
|
+
cleanStaleLocks(this.dir);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
108
161
|
}
|
|
109
162
|
/** 本分区的绝对路径(供面板与日志引用)。 */
|
|
110
163
|
get partitionPath() {
|
|
@@ -246,12 +299,23 @@ var Store = class {
|
|
|
246
299
|
}
|
|
247
300
|
/** Serialized read-modify-write across processes. */
|
|
248
301
|
async updateState(mutate) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
302
|
+
const run = () => withFileLock(
|
|
303
|
+
this.statePath,
|
|
304
|
+
async () => {
|
|
305
|
+
const cur = await this.readState();
|
|
306
|
+
const mutated = await mutate(cur);
|
|
307
|
+
await this.writeState(mutated);
|
|
308
|
+
return mutated;
|
|
309
|
+
},
|
|
310
|
+
{ waitMs: 3e3 }
|
|
311
|
+
);
|
|
312
|
+
try {
|
|
313
|
+
return await run();
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (!isLockTimeout(error)) throw error;
|
|
316
|
+
cleanStaleLocks(this.dir);
|
|
317
|
+
return run();
|
|
318
|
+
}
|
|
255
319
|
}
|
|
256
320
|
/**
|
|
257
321
|
* Re-arm the crash marker synchronously.
|
|
@@ -2391,16 +2455,19 @@ function assessConfidence(history, currentSuspects, options = {}) {
|
|
|
2391
2455
|
let streak = 1;
|
|
2392
2456
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
2393
2457
|
const entry = history[i];
|
|
2458
|
+
if (entry.suspects.length === 0) continue;
|
|
2394
2459
|
if ((entry.suspects[0] ?? "") !== head) break;
|
|
2395
2460
|
streak += 1;
|
|
2396
2461
|
}
|
|
2397
|
-
const
|
|
2462
|
+
const hardEscalateAfter = Math.max(1, options.hardEscalateAfter ?? 3);
|
|
2463
|
+
const escalated = options.crashSeq !== void 0 && options.crashSeq >= hardEscalateAfter;
|
|
2464
|
+
const act = streak >= confirmRounds || escalated;
|
|
2398
2465
|
return {
|
|
2399
2466
|
act,
|
|
2400
2467
|
evidence: "probabilistic",
|
|
2401
2468
|
rounds: streak,
|
|
2402
2469
|
required: confirmRounds,
|
|
2403
|
-
label: act ? `\u5934\u53F7\u5ACC\u7591 ${head} \u8FDE\u7EED ${streak} \u8F6E\u590D\u73B0\uFF08\u2265 ${confirmRounds}\uFF09\uFF1A\u8FBE\u5230\u884C\u52A8\u9608\u503C` : `\u5934\u53F7\u5ACC\u7591 ${head} \u590D\u73B0 ${streak}/${confirmRounds} \u8F6E\uFF1A\u89C2\u5BDF\u4E2D\uFF0C\u672C\u8F6E\u53EA\u8BB0\u5F55\u4E0D\u5173\u95ED`
|
|
2470
|
+
label: act ? streak >= confirmRounds ? `\u5934\u53F7\u5ACC\u7591 ${head} \u8FDE\u7EED ${streak} \u8F6E\u590D\u73B0\uFF08\u2265 ${confirmRounds}\uFF09\uFF1A\u8FBE\u5230\u884C\u52A8\u9608\u503C` : `\u8FDE\u7EED ${options.crashSeq} \u6B21\u5D29\u6E83\u4ECD\u672A\u6062\u590D\uFF1A\u515C\u5E95\u5347\u7EA7\u4E3A\u884C\u52A8\uFF08\u5B89\u5168\u7F51\uFF09` : `\u5934\u53F7\u5ACC\u7591 ${head} \u590D\u73B0 ${streak}/${confirmRounds} \u8F6E\uFF1A\u89C2\u5BDF\u4E2D\uFF0C\u672C\u8F6E\u53EA\u8BB0\u5F55\u4E0D\u5173\u95ED`
|
|
2404
2471
|
};
|
|
2405
2472
|
}
|
|
2406
2473
|
function environmentChanged(lastEntry, currentRuntime, currentSuspects) {
|
|
@@ -2557,6 +2624,7 @@ var ROUTE_PATHS = {
|
|
|
2557
2624
|
archives: "/recency-guard/api/archives",
|
|
2558
2625
|
archive: "/recency-guard/api/action/archive",
|
|
2559
2626
|
reenable: "/recency-guard/api/action/reenable",
|
|
2627
|
+
restore: "/recency-guard/api/action/restore",
|
|
2560
2628
|
recover: "/recency-guard/api/action/recover"
|
|
2561
2629
|
};
|
|
2562
2630
|
var ROUTE_SURFACE = [
|
|
@@ -2567,6 +2635,7 @@ var ROUTE_SURFACE = [
|
|
|
2567
2635
|
{ capability: "guard.govern.archive", name: "archives", exposedName: "recency-guard.archives", path: ROUTE_PATHS.archives, method: "GET" },
|
|
2568
2636
|
{ capability: "guard.govern.reset", name: "archive", exposedName: "recency-guard.reset", path: ROUTE_PATHS.archive, method: "POST" },
|
|
2569
2637
|
{ capability: "guard.recover.reenable", name: "reenable", exposedName: "recency-guard.reenable", path: ROUTE_PATHS.reenable, method: "POST" },
|
|
2638
|
+
{ capability: "guard.recover.restore", name: "restore", exposedName: "recency-guard.restore", path: ROUTE_PATHS.restore, method: "POST" },
|
|
2570
2639
|
{ capability: "guard.recover.manual", name: "recover", exposedName: "recency-guard.recover", path: ROUTE_PATHS.recover, method: "POST" }
|
|
2571
2640
|
];
|
|
2572
2641
|
function sendJson(res, data, status = 200) {
|
|
@@ -2595,21 +2664,38 @@ async function readBody(req) {
|
|
|
2595
2664
|
return null;
|
|
2596
2665
|
}
|
|
2597
2666
|
}
|
|
2667
|
+
function partitionOf(req) {
|
|
2668
|
+
const url = req.url ?? "";
|
|
2669
|
+
const qIndex = url.indexOf("?");
|
|
2670
|
+
if (qIndex === -1) return void 0;
|
|
2671
|
+
const params = new URLSearchParams(url.slice(qIndex + 1));
|
|
2672
|
+
const value = params.get("partition");
|
|
2673
|
+
return value === null || value === "" ? void 0 : value;
|
|
2674
|
+
}
|
|
2675
|
+
function rejectWrongMethod(req, res) {
|
|
2676
|
+
if ((req.method ?? "GET").toUpperCase() === "POST") return false;
|
|
2677
|
+
res.writeHead(405, { allow: "POST", "content-type": "application/json; charset=utf-8" });
|
|
2678
|
+
res.end(JSON.stringify({ ok: false, reason: `\u52A8\u4F5C\u8DEF\u7531\u53EA\u63A5\u53D7 POST\uFF08\u6536\u5230 ${req.method ?? "GET"}\uFF09` }));
|
|
2679
|
+
return true;
|
|
2680
|
+
}
|
|
2598
2681
|
function handlerTable(ctx) {
|
|
2599
|
-
const read = (load) => async (
|
|
2682
|
+
const read = (load) => async (req, res) => {
|
|
2600
2683
|
try {
|
|
2601
|
-
sendJson(res, await load());
|
|
2684
|
+
sendJson(res, await load(partitionOf(req)));
|
|
2602
2685
|
} catch (error) {
|
|
2603
2686
|
sendJson(res, { available: false, reason: describe(error) }, 500);
|
|
2604
2687
|
}
|
|
2605
2688
|
};
|
|
2606
2689
|
return {
|
|
2607
|
-
snapshot: read(
|
|
2690
|
+
snapshot: read(
|
|
2691
|
+
(partition) => partition === void 0 || partition === ctx.projectKey ? ctx.snapshot() : ctx.snapshotPartition(partition)
|
|
2692
|
+
),
|
|
2608
2693
|
graph: read(() => ctx.graph()),
|
|
2609
2694
|
isolation: read(() => ctx.isolation()),
|
|
2610
2695
|
lifecycle: read(() => ctx.lifecycle()),
|
|
2611
2696
|
archives: read(() => ctx.archives()),
|
|
2612
2697
|
archive: async (req, res) => {
|
|
2698
|
+
if (rejectWrongMethod(req, res)) return;
|
|
2613
2699
|
try {
|
|
2614
2700
|
const body = await readBody(req);
|
|
2615
2701
|
const reason = typeof body?.reason === "string" && body.reason.trim() !== "" ? body.reason : "\u624B\u52A8\u5F52\u6863\u91CD\u7F6E";
|
|
@@ -2619,6 +2705,7 @@ function handlerTable(ctx) {
|
|
|
2619
2705
|
}
|
|
2620
2706
|
},
|
|
2621
2707
|
reenable: async (req, res) => {
|
|
2708
|
+
if (rejectWrongMethod(req, res)) return;
|
|
2622
2709
|
try {
|
|
2623
2710
|
const body = await readBody(req);
|
|
2624
2711
|
const bundle = typeof body?.bundle === "string" ? body.bundle : "";
|
|
@@ -2631,7 +2718,16 @@ function handlerTable(ctx) {
|
|
|
2631
2718
|
sendJson(res, { ok: false, reason: describe(error) }, 500);
|
|
2632
2719
|
}
|
|
2633
2720
|
},
|
|
2634
|
-
|
|
2721
|
+
restore: async (req, res) => {
|
|
2722
|
+
if (rejectWrongMethod(req, res)) return;
|
|
2723
|
+
try {
|
|
2724
|
+
sendJson(res, await ctx.restore());
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
sendJson(res, { ok: false, reason: describe(error) }, 500);
|
|
2727
|
+
}
|
|
2728
|
+
},
|
|
2729
|
+
recover: async (req, res) => {
|
|
2730
|
+
if (rejectWrongMethod(req, res)) return;
|
|
2635
2731
|
try {
|
|
2636
2732
|
sendJson(res, await ctx.recover());
|
|
2637
2733
|
} catch (error) {
|
|
@@ -2833,6 +2929,9 @@ var CAPABILITY_SPECS = [
|
|
|
2833
2929
|
stage: "recovered",
|
|
2834
2930
|
exposed: true,
|
|
2835
2931
|
exposedAs: "recency_guard",
|
|
2932
|
+
// v0.11:restore 同时作为**动作路由**暴露(/action/restore),面板的
|
|
2933
|
+
// 「恢复非嫌疑插件」按钮走这条路由 —— 名称必须声明,契约测试双向核对。
|
|
2934
|
+
exposedAlsoAs: ["recency-guard.restore"],
|
|
2836
2935
|
exposedActions: ["restore"],
|
|
2837
2936
|
hostServices: ["tools", "pluginManager"]
|
|
2838
2937
|
},
|
|
@@ -3505,8 +3604,69 @@ function partitionHealth(index) {
|
|
|
3505
3604
|
}));
|
|
3506
3605
|
}
|
|
3507
3606
|
|
|
3607
|
+
// src/partition-snapshot.ts
|
|
3608
|
+
async function buildPartitionSnapshot(input, key) {
|
|
3609
|
+
const index = readPartitionIndex(input.profileDir);
|
|
3610
|
+
const target = index.partitions.find((p) => p.key === key);
|
|
3611
|
+
if (!target) {
|
|
3612
|
+
return {
|
|
3613
|
+
available: false,
|
|
3614
|
+
reason: `\u672A\u77E5\u5206\u533A ${key}\uFF08\u672C profile \u53EA\u6709 ${index.partitions.length} \u4E2A\u5206\u533A\uFF09`
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3617
|
+
const viewer = new Store(input.profileDir, key);
|
|
3618
|
+
const [state, journal] = await Promise.all([viewer.readState(), viewer.readJournal(40)]);
|
|
3619
|
+
const events = journal.slice(-40).map((e) => ({ t: e.t, kind: e.kind, bundle: e.bundle, detail: e.detail }));
|
|
3620
|
+
return {
|
|
3621
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3622
|
+
profile: input.profileName,
|
|
3623
|
+
projectKey: key,
|
|
3624
|
+
// 运行时字段如实归属:阶段与评分是**当前进程**的状态机事实,
|
|
3625
|
+
// 别的分区没有自己的守护进程在跑,这里不能替它编一个阶段。
|
|
3626
|
+
stage: "idle",
|
|
3627
|
+
stageLabel: "\u8DE8\u5206\u533A\u53EA\u8BFB\u89C6\u56FE\uFF08\u9636\u6BB5\u4E3A\u5F53\u524D\u5206\u533A\u8BED\u4E49\uFF0C\u975E\u5BF9\u65B9\u5B9E\u65F6\u72B6\u6001\uFF09",
|
|
3628
|
+
transitions: [],
|
|
3629
|
+
stabilityScore: null,
|
|
3630
|
+
metrics: null,
|
|
3631
|
+
risks: [],
|
|
3632
|
+
capability: null,
|
|
3633
|
+
graphConnectivity: null,
|
|
3634
|
+
restored: state.restored,
|
|
3635
|
+
autoDisabled: state.autoDisabled.map((d) => ({
|
|
3636
|
+
name: d.name,
|
|
3637
|
+
reason: d.reason,
|
|
3638
|
+
crashSeq: d.crashSeq,
|
|
3639
|
+
disabledAt: d.disabledAt
|
|
3640
|
+
})),
|
|
3641
|
+
lastRecoveryVerdict: state.lastRecoveryVerdict ?? null,
|
|
3642
|
+
partitions: index.partitions.map((p) => ({ ...p })),
|
|
3643
|
+
eventFlow: [],
|
|
3644
|
+
recentEvents: events,
|
|
3645
|
+
markov: null,
|
|
3646
|
+
backupPolicy: {
|
|
3647
|
+
versionedBackups: input.config.versionedBackups,
|
|
3648
|
+
retain: input.config.backupRetain,
|
|
3649
|
+
customRoot: false,
|
|
3650
|
+
root: viewer.backupsRoot,
|
|
3651
|
+
configEditorAvailable: input.config.configEditorAvailable
|
|
3652
|
+
},
|
|
3653
|
+
diagnostics: {
|
|
3654
|
+
pluginVersion: input.selfPackage.version,
|
|
3655
|
+
hostVersion: readHostVersion(),
|
|
3656
|
+
platform: process.platform,
|
|
3657
|
+
nodeVersion: process.version,
|
|
3658
|
+
dataPlane: true,
|
|
3659
|
+
snapshotMs: null,
|
|
3660
|
+
repositoryUrl: input.selfPackage.repositoryUrl,
|
|
3661
|
+
issuesUrl: input.selfPackage.issuesUrl
|
|
3662
|
+
},
|
|
3663
|
+
readonly: true,
|
|
3664
|
+
workspace: target.workspace
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3508
3668
|
// src/archive.ts
|
|
3509
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readdirSync, readFileSync as readFileSync7, renameSync as renameSync4, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3669
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, renameSync as renameSync4, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3510
3670
|
import { join as join7 } from "node:path";
|
|
3511
3671
|
function classify(name2) {
|
|
3512
3672
|
if (name2 === "journal.jsonl") return "journal";
|
|
@@ -3539,7 +3699,7 @@ function archiveAndReset(partitionDir2, workspace, reason, now = /* @__PURE__ */
|
|
|
3539
3699
|
}
|
|
3540
3700
|
if (existsSync7(stateDir)) {
|
|
3541
3701
|
mkdirSync6(archiveDir, { recursive: true });
|
|
3542
|
-
for (const name2 of
|
|
3702
|
+
for (const name2 of readdirSync2(stateDir)) {
|
|
3543
3703
|
if (name2 === "archive") continue;
|
|
3544
3704
|
const source = join7(stateDir, name2);
|
|
3545
3705
|
const target = join7(archiveDir, name2);
|
|
@@ -3584,7 +3744,7 @@ function directoryBytes(dir) {
|
|
|
3584
3744
|
let total = 0;
|
|
3585
3745
|
let names;
|
|
3586
3746
|
try {
|
|
3587
|
-
names =
|
|
3747
|
+
names = readdirSync2(dir);
|
|
3588
3748
|
} catch {
|
|
3589
3749
|
return 0;
|
|
3590
3750
|
}
|
|
@@ -3603,7 +3763,7 @@ function listArchives(partitionDir2) {
|
|
|
3603
3763
|
const archiveRoot = join7(partitionDir2, "archive");
|
|
3604
3764
|
if (!existsSync7(archiveRoot)) return [];
|
|
3605
3765
|
const out = [];
|
|
3606
|
-
for (const name2 of
|
|
3766
|
+
for (const name2 of readdirSync2(archiveRoot)) {
|
|
3607
3767
|
const dir = join7(archiveRoot, name2);
|
|
3608
3768
|
try {
|
|
3609
3769
|
const manifest = JSON.parse(readFileSync7(join7(dir, "manifest.json"), "utf8"));
|
|
@@ -4184,11 +4344,14 @@ function apply(ctx, config) {
|
|
|
4184
4344
|
profileDir: profile.dir,
|
|
4185
4345
|
workspace: partition.workspace,
|
|
4186
4346
|
profileName: profile.name,
|
|
4347
|
+
projectKey: partition.key,
|
|
4187
4348
|
snapshot: () => guard.panelSnapshot(),
|
|
4349
|
+
snapshotPartition: (key) => guard.partitionSnapshot(key),
|
|
4188
4350
|
graph: () => guard.graphData(),
|
|
4189
4351
|
archive: (reason) => guard.archiveReset(reason),
|
|
4190
4352
|
archives: () => guard.archives(),
|
|
4191
4353
|
reenable: (bundle) => guard.reenable(bundle),
|
|
4354
|
+
restore: () => guard.restore(),
|
|
4192
4355
|
recover: () => guard.recoverNow(),
|
|
4193
4356
|
isolation: async () => guard.isolationView(),
|
|
4194
4357
|
lifecycle: async () => guard.lifecycleView(),
|
|
@@ -4481,6 +4644,7 @@ var Guard = class {
|
|
|
4481
4644
|
for (const e of events) {
|
|
4482
4645
|
if (e.kind !== "crash-detected") continue;
|
|
4483
4646
|
const suspects = Array.isArray(e.suspects) ? e.suspects.filter((s) => typeof s === "string") : [];
|
|
4647
|
+
if (suspects.length === 0) continue;
|
|
4484
4648
|
out.push({
|
|
4485
4649
|
at: e.t,
|
|
4486
4650
|
exitCode: typeof e.exitCode === "number" ? e.exitCode : -1,
|
|
@@ -4656,7 +4820,10 @@ var Guard = class {
|
|
|
4656
4820
|
const targets = pickSuspects(ranked, n);
|
|
4657
4821
|
const history = this.readCrashHistory();
|
|
4658
4822
|
const envChanged = environmentChanged(history[history.length - 1], safeVersion(), ranked);
|
|
4659
|
-
const verdict = assessConfidence(envChanged ? [] : history, ranked, {
|
|
4823
|
+
const verdict = assessConfidence(envChanged ? [] : history, ranked, {
|
|
4824
|
+
confirmRounds: this.config.confirmRounds,
|
|
4825
|
+
crashSeq: seq
|
|
4826
|
+
});
|
|
4660
4827
|
this.store.appendSync({
|
|
4661
4828
|
kind: "note",
|
|
4662
4829
|
detail: `\u6062\u590D\u5224\u5B9A\uFF1A${verdict.label}`
|
|
@@ -4750,7 +4917,10 @@ ${error?.stack ?? ""}
|
|
|
4750
4917
|
this.endStep("boot", true, "\u542F\u52A8\u6D41\u7A0B\u5B8C\u6210");
|
|
4751
4918
|
} catch (error) {
|
|
4752
4919
|
this.endStep("boot", false, describeError(error));
|
|
4753
|
-
|
|
4920
|
+
try {
|
|
4921
|
+
this.ctx.logger?.warn?.(`[recency-guard] \u5B88\u62A4\u542F\u52A8\u964D\u7EA7\uFF08\u5BBF\u4E3B\u7EE7\u7EED\u542F\u52A8\uFF09\uFF1A${describeError(error)}`);
|
|
4922
|
+
} catch {
|
|
4923
|
+
}
|
|
4754
4924
|
}
|
|
4755
4925
|
}
|
|
4756
4926
|
/** Boot 的实现体(外层负责步骤计时与失败原因留痕)。 */
|
|
@@ -5080,6 +5250,31 @@ ${error?.stack ?? ""}
|
|
|
5080
5250
|
async graphData() {
|
|
5081
5251
|
return buildGraphDataFor(buildCapabilityGraph());
|
|
5082
5252
|
}
|
|
5253
|
+
/**
|
|
5254
|
+
* 跨分区只读快照(v0.11 P0:数据面真正支持 `?partition=` 查询)。
|
|
5255
|
+
*
|
|
5256
|
+
* 投影实现在 `partition-snapshot.ts`(独立模块:磁盘投影有自己的一条
|
|
5257
|
+
* 隔离边界要讲清楚,且 index.ts 复杂度预算 100 不该被它撑破)。
|
|
5258
|
+
*/
|
|
5259
|
+
async partitionSnapshot(key) {
|
|
5260
|
+
return buildPartitionSnapshot(
|
|
5261
|
+
{
|
|
5262
|
+
profileDir: this.profileDir,
|
|
5263
|
+
profileName: this.profileName,
|
|
5264
|
+
config: {
|
|
5265
|
+
versionedBackups: this.config.versionedBackups,
|
|
5266
|
+
backupRetain: this.config.backupRetain,
|
|
5267
|
+
configEditorAvailable: this.configEditorAvailable
|
|
5268
|
+
},
|
|
5269
|
+
selfPackage: {
|
|
5270
|
+
version: this.selfPackage.version,
|
|
5271
|
+
repositoryUrl: this.selfPackage.repositoryUrl,
|
|
5272
|
+
issuesUrl: this.selfPackage.issuesUrl
|
|
5273
|
+
}
|
|
5274
|
+
},
|
|
5275
|
+
key
|
|
5276
|
+
);
|
|
5277
|
+
}
|
|
5083
5278
|
/**
|
|
5084
5279
|
* 实际注册出去的对外名称。
|
|
5085
5280
|
*
|
package/locale/en.json
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"emptyHint": "The guard has not recorded any startup facts yet.",
|
|
18
18
|
"snapshotAt": "Snapshot at {time}",
|
|
19
19
|
"pollingOn": "polling every {seconds}s",
|
|
20
|
-
"pollingPaused": "polling paused (the view is frozen at the pause moment)"
|
|
20
|
+
"pollingPaused": "polling paused (the view is frozen at the pause moment)",
|
|
21
|
+
"restore": "Restore non-suspects",
|
|
22
|
+
"stale": "Data is {minutes} min old (polling paused)"
|
|
21
23
|
},
|
|
22
24
|
"sections": {
|
|
23
25
|
"backupPolicy": "Backup policy",
|
|
@@ -228,11 +230,13 @@
|
|
|
228
230
|
"metric": "Metric",
|
|
229
231
|
"bundle": "Bundle",
|
|
230
232
|
"gap": "Gap"
|
|
231
|
-
}
|
|
233
|
+
},
|
|
234
|
+
"matchIndex": "Match {index}/{total}"
|
|
232
235
|
},
|
|
233
236
|
"events": {
|
|
234
237
|
"title": "Event stream",
|
|
235
|
-
"count": "last {count}"
|
|
238
|
+
"count": "last {count}",
|
|
239
|
+
"more": "Show {count} more"
|
|
236
240
|
},
|
|
237
241
|
"partitions": {
|
|
238
242
|
"title": "Project partitions",
|
package/locale/zh.json
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"emptyHint": "守护插件尚未记录任何启动事实。",
|
|
18
18
|
"snapshotAt": "快照时刻 {time}",
|
|
19
19
|
"pollingOn": "每 {seconds} 秒轮询",
|
|
20
|
-
"pollingPaused": "轮询已暂停(画面停在暂停时刻)"
|
|
20
|
+
"pollingPaused": "轮询已暂停(画面停在暂停时刻)",
|
|
21
|
+
"restore": "恢复非嫌疑插件",
|
|
22
|
+
"stale": "数据已停留 {minutes} 分钟(轮询已暂停)"
|
|
21
23
|
},
|
|
22
24
|
"sections": {
|
|
23
25
|
"backupPolicy": "备份策略",
|
|
@@ -228,11 +230,13 @@
|
|
|
228
230
|
"metric": "指标",
|
|
229
231
|
"bundle": "插件",
|
|
230
232
|
"gap": "缺口"
|
|
231
|
-
}
|
|
233
|
+
},
|
|
234
|
+
"matchIndex": "第 {index}/{total} 个"
|
|
232
235
|
},
|
|
233
236
|
"events": {
|
|
234
237
|
"title": "事件流",
|
|
235
|
-
"count": "最近 {count} 条"
|
|
238
|
+
"count": "最近 {count} 条",
|
|
239
|
+
"more": "展开其余 {count} 条"
|
|
236
240
|
},
|
|
237
241
|
"partitions": {
|
|
238
242
|
"title": "项目分区",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recency-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Crash-recovery guard for DeepSeek Harness: records third-party/experimental plugin install & update recency, backs up their config, and after a crash auto-disables the most-recently-updated plugin(s) so the profile boots again, then restores stable config.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"extract-runtime": "node scripts/extract-runtime.mjs",
|
|
44
44
|
"repair-patch": "tsx scripts/repair-patch.mjs",
|
|
45
45
|
"test": "tsx tests/run.ts",
|
|
46
|
-
"test:unit": "tsx tests/unit.test.ts",
|
|
47
|
-
"test:
|
|
46
|
+
"test:unit": "tsx tests/unit/core.test.ts",
|
|
47
|
+
"test:routes": "tsx tests/host/routes.test.ts",
|
|
48
48
|
"test:coverage": "tsx tests/run.ts --coverage",
|
|
49
|
-
"test:e2e": "tsx tests/tool.test.ts"
|
|
49
|
+
"test:e2e": "tsx tests/host/tool.test.ts"
|
|
50
50
|
},
|
|
51
51
|
"dsh": {
|
|
52
52
|
"manifestVersion": 1,
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": "~4.0.5",
|
|
70
|
-
"@deepseek-ai/dsh-app-boot": ">=0.2.1-rc.5 <0.3.0-0",
|
|
71
|
-
"@deepseek-ai/dsh-config-editor": ">=0.2.1-rc.5 <0.3.0-0",
|
|
72
|
-
"@deepseek-ai/dsh-plugin-manager": ">=0.2.1-rc.5 <0.3.0-0",
|
|
73
|
-
"@deepseek-ai/dsh-tools": ">=0.2.1-rc.5 <0.3.0-0",
|
|
70
|
+
"@deepseek-ai/dsh-app-boot": ">=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0",
|
|
71
|
+
"@deepseek-ai/dsh-config-editor": ">=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0",
|
|
72
|
+
"@deepseek-ai/dsh-plugin-manager": ">=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0",
|
|
73
|
+
"@deepseek-ai/dsh-tools": ">=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0",
|
|
74
74
|
"@deepseek-ai/schemastery": ">=3.18.0 <4.0.0-0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
@@ -122,6 +122,6 @@
|
|
|
122
122
|
"typescript": "^5.9.0"
|
|
123
123
|
},
|
|
124
124
|
"engines": {
|
|
125
|
-
"dsh": ">=0.2.1-rc.5 <0.3.0-0"
|
|
125
|
+
"dsh": ">=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0"
|
|
126
126
|
}
|
|
127
127
|
}
|