@dsh-plus/lifeboat 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +423 -29
- package/lib/index.d.ts +0 -3
- package/lib/index.js +230 -77
- package/package.json +15 -8
- package/src/client/client.ts +17 -0
- package/src/client/health-styles.ts +53 -0
- package/src/client/health-tab.tsx +303 -0
- package/src/config.ts +4 -2
- package/src/health-api.ts +120 -0
- package/src/index.ts +8 -0
- package/src/patch-file.ts +54 -0
- package/src/quarantine.ts +22 -6
package/lib/client.js
CHANGED
|
@@ -3,37 +3,431 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const GUARD_RE = /^dsh-plus-[a-z0-9-]+$/;
|
|
15
|
-
function apply(ctx) {
|
|
16
|
-
try {
|
|
17
|
-
const reported = /* @__PURE__ */ new Set();
|
|
18
|
-
ctx.root.on("internal/status", (fiber) => {
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __esmMin = (fn, res, err) => () => {
|
|
13
|
+
if (err) throw err[0];
|
|
19
14
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
return fn && (res = fn(fn = 0)), res;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw err = [e], e;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var __exportAll = (all, no_symbols) => {
|
|
21
|
+
let target = {};
|
|
22
|
+
for (var name in all) __defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true
|
|
25
|
+
});
|
|
26
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
var __copyProps = (to, from, except, desc) => {
|
|
30
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
31
|
+
key = keys[i];
|
|
32
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
33
|
+
get: ((k) => from[k]).bind(null, key),
|
|
34
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return to;
|
|
38
|
+
};
|
|
39
|
+
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
|
+
//#endregion
|
|
41
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
42
|
+
let react = require("react");
|
|
43
|
+
//#region ../shared/src/client/card.tsx
|
|
44
|
+
var init_card = __esmMin((() => {}));
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region ../shared/src/client/fetch.ts
|
|
47
|
+
/**
|
|
48
|
+
* 同源自定义端点的最小 fetch 封装(各插件 client/api.ts 的公共收编版)。
|
|
49
|
+
* 仅同源(credentials: same-origin),错误统一抛 Error(body.error)。
|
|
50
|
+
* @module @dsh-plus/shared/client/fetch
|
|
51
|
+
*/
|
|
52
|
+
async function parse(res) {
|
|
53
|
+
const body = await res.json();
|
|
54
|
+
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
55
|
+
return body;
|
|
56
|
+
}
|
|
57
|
+
/** GET 同源 JSON 端点。 */
|
|
58
|
+
async function getJson(route) {
|
|
59
|
+
return parse(await fetch(route, { credentials: "same-origin" }));
|
|
60
|
+
}
|
|
61
|
+
/** POST 同源 JSON 端点(body 可省略)。 */
|
|
62
|
+
async function postJson(route, body) {
|
|
63
|
+
return parse(await fetch(route, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
credentials: "same-origin",
|
|
66
|
+
headers: { "content-type": "application/json" },
|
|
67
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
var init_fetch = __esmMin((() => {}));
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region ../shared/src/client/fields.tsx
|
|
73
|
+
var init_fields = __esmMin((() => {}));
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region ../shared/src/client/styles.ts
|
|
76
|
+
/**
|
|
77
|
+
* 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
|
|
78
|
+
* 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
|
|
79
|
+
*/
|
|
80
|
+
function injectCardStyle(pluginId, css) {
|
|
81
|
+
if (typeof document === "undefined") return null;
|
|
82
|
+
const tagId = `${pluginId}/card.css`;
|
|
83
|
+
if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
|
|
84
|
+
const tag = document.createElement("style");
|
|
85
|
+
tag.dataset.plugin = pluginId;
|
|
86
|
+
tag.dataset.pluginCss = tagId;
|
|
87
|
+
tag.textContent = css;
|
|
88
|
+
document.head.appendChild(tag);
|
|
89
|
+
return tag;
|
|
90
|
+
}
|
|
91
|
+
var init_styles = __esmMin((() => {}));
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region ../shared/src/client/index.ts
|
|
94
|
+
var init_client = __esmMin((() => {
|
|
95
|
+
init_card();
|
|
96
|
+
init_fetch();
|
|
97
|
+
init_fields();
|
|
98
|
+
init_styles();
|
|
99
|
+
}));
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/client/health-styles.ts
|
|
102
|
+
/** 幂等注入健康页样式标签。 */
|
|
103
|
+
function injectHealthStyle() {
|
|
104
|
+
return injectCardStyle(PLUGIN_ID, healthCss);
|
|
105
|
+
}
|
|
106
|
+
var PLUGIN_ID, healthCss;
|
|
107
|
+
var init_health_styles = __esmMin((() => {
|
|
108
|
+
init_client();
|
|
109
|
+
PLUGIN_ID = "@dsh-plus/lifeboat";
|
|
110
|
+
healthCss = `
|
|
111
|
+
.dlb-tab{display:flex;flex-direction:column;gap:14px;min-width:0}
|
|
112
|
+
.dlb-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;margin:4px 0}
|
|
113
|
+
.dlb-title{color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:600;margin:10px 0 0}
|
|
114
|
+
.dlb-notice{color:var(--dsw-alias-label-secondary);font-size:12px;margin:0}
|
|
115
|
+
.dlb-fallback{border:1px solid var(--dsw-alias-label-warning,#e0a03c);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:6px;background:var(--dsw-alias-bg-layer-3)}
|
|
116
|
+
.dlb-fallbackTitle{color:var(--dsw-alias-label-warning,#e0a03c);font-size:13px;font-weight:600;margin:0}
|
|
117
|
+
.dlb-kv{display:flex;align-items:center;gap:8px;font-size:12px;flex-wrap:wrap}
|
|
118
|
+
.dlb-kv span{color:var(--dsw-alias-label-tertiary);flex:none}
|
|
119
|
+
.dlb-kv code{color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;word-break:break-all}
|
|
120
|
+
.dlb-cards{display:flex;flex-direction:column;gap:8px}
|
|
121
|
+
.dlb-quarantineCard{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--dsw-alias-bg-layer-3)}
|
|
122
|
+
.dlb-pluginName{color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;font-weight:600;flex:1;min-width:0;word-break:break-all}
|
|
123
|
+
.dlb-confirm{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex-basis:100%}
|
|
124
|
+
.dlb-confirmText{color:var(--dsw-alias-label-secondary);font-size:12px;flex:1;min-width:160px;line-height:1.5}
|
|
125
|
+
.dlb-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;flex:none}
|
|
126
|
+
.dlb-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
|
|
127
|
+
.dlb-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
128
|
+
.dlb-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
129
|
+
.dlb-btn:disabled{opacity:.4;cursor:default}
|
|
130
|
+
.dlb-journal{display:flex;flex-direction:column;gap:10px}
|
|
131
|
+
.dlb-journalDay{display:flex;flex-direction:column;gap:2px}
|
|
132
|
+
.dlb-dayLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:600;margin-bottom:2px}
|
|
133
|
+
.dlb-entry{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.6;flex-wrap:wrap}
|
|
134
|
+
.dlb-kind{flex:none;border-radius:999px;padding:0 8px;font-size:10px;font-weight:600;line-height:17px;white-space:nowrap}
|
|
135
|
+
.dlb-kindInfo{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
136
|
+
.dlb-kindAlert{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base)}
|
|
137
|
+
.dlb-kindQuarantine{background:var(--dsw-alias-label-warning,#e0a03c);color:var(--dsw-alias-bg-base)}
|
|
138
|
+
.dlb-kindRestore{background:var(--dsw-alias-state-success,#3d9a50);color:var(--dsw-alias-bg-base)}
|
|
139
|
+
.dlb-kindFallback{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
140
|
+
.dlb-entryAt{color:var(--dsw-alias-label-tertiary);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}
|
|
141
|
+
.dlb-entryDetail{color:var(--dsw-alias-label-primary);min-width:0;word-break:break-word;white-space:pre-wrap;flex:1;flex-basis:320px}
|
|
142
|
+
@media (max-width:767px){
|
|
143
|
+
.dlb-btn{min-height:44px}
|
|
144
|
+
.dlb-quarantineCard{flex-direction:column;align-items:stretch}
|
|
145
|
+
.dlb-confirm{flex-direction:column;align-items:stretch}
|
|
146
|
+
.dlb-entryDetail{flex-basis:100%}
|
|
32
147
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
148
|
+
`;
|
|
149
|
+
}));
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/client/health-tab.tsx
|
|
152
|
+
var health_tab_exports = /* @__PURE__ */ __exportAll({ mountHealthTab: () => mountHealthTab });
|
|
153
|
+
function HealthTab(props) {
|
|
154
|
+
const { t } = props;
|
|
155
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
156
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
157
|
+
const [busyName, setBusyName] = (0, react.useState)(null);
|
|
158
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
159
|
+
const [confirming, setConfirming] = (0, react.useState)(null);
|
|
160
|
+
const load = (0, react.useCallback)(() => {
|
|
161
|
+
getJson(ROUTE_STATUS).then((data) => {
|
|
162
|
+
setStatus(data);
|
|
163
|
+
setFailed(false);
|
|
164
|
+
}).catch(() => setFailed(true));
|
|
165
|
+
}, []);
|
|
166
|
+
(0, react.useEffect)(() => {
|
|
167
|
+
load();
|
|
168
|
+
}, [load]);
|
|
169
|
+
const onRestore = (name) => {
|
|
170
|
+
setBusyName(name);
|
|
171
|
+
setConfirming(null);
|
|
172
|
+
postJson(ROUTE_RESTORE, { name }).then(() => {
|
|
173
|
+
setNotice(t("restored"));
|
|
174
|
+
load();
|
|
175
|
+
}).catch((error) => {
|
|
176
|
+
setNotice(`${t("restoreFailed")}${error instanceof Error ? error.message : ""}`);
|
|
177
|
+
}).finally(() => setBusyName(null));
|
|
178
|
+
};
|
|
179
|
+
const journalGroups = (0, react.useMemo)(() => {
|
|
180
|
+
if (status === null) return [];
|
|
181
|
+
const groups = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const entry of [...status.journal].reverse()) {
|
|
183
|
+
const day = entry.at.slice(0, 10);
|
|
184
|
+
const list = groups.get(day) ?? [];
|
|
185
|
+
list.push(entry);
|
|
186
|
+
groups.set(day, list);
|
|
187
|
+
}
|
|
188
|
+
return [...groups.entries()];
|
|
189
|
+
}, [status]);
|
|
190
|
+
if (status === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
191
|
+
className: "dlb-tab",
|
|
192
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
193
|
+
className: "dlb-empty",
|
|
194
|
+
children: failed ? t("loadFailed") : t("pluginOk") === "OK" ? "…" : "…"
|
|
195
|
+
}), failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
196
|
+
type: "button",
|
|
197
|
+
className: "dlb-btn dlb-btnGhost",
|
|
198
|
+
onClick: load,
|
|
199
|
+
children: t("retry")
|
|
200
|
+
}) : null]
|
|
201
|
+
});
|
|
202
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
203
|
+
className: "dlb-tab",
|
|
204
|
+
children: [
|
|
205
|
+
notice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
206
|
+
className: "dlb-notice",
|
|
207
|
+
role: "status",
|
|
208
|
+
children: notice
|
|
209
|
+
}) : null,
|
|
210
|
+
status.llmFallback?.active === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
211
|
+
className: "dlb-fallback",
|
|
212
|
+
role: "status",
|
|
213
|
+
children: [
|
|
214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
215
|
+
className: "dlb-fallbackTitle",
|
|
216
|
+
children: t("fallbackActive")
|
|
217
|
+
}),
|
|
218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
219
|
+
className: "dlb-kv",
|
|
220
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fallbackFrom") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", { children: [
|
|
221
|
+
status.llmFallback.originalProvider,
|
|
222
|
+
"/",
|
|
223
|
+
status.llmFallback.originalModel
|
|
224
|
+
] })]
|
|
225
|
+
}),
|
|
226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
227
|
+
className: "dlb-kv",
|
|
228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fallbackTo") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: status.llmFallback.fallbackProvider })]
|
|
229
|
+
}),
|
|
230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
231
|
+
className: "dlb-kv",
|
|
232
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fallbackAt") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: status.llmFallback.at.slice(0, 19).replace("T", " ") })]
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
}) : null,
|
|
236
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
237
|
+
className: "dlb-title",
|
|
238
|
+
children: t("quarantined")
|
|
239
|
+
}),
|
|
240
|
+
status.quarantined.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
241
|
+
className: "dlb-empty",
|
|
242
|
+
children: t("emptyQuarantined")
|
|
243
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
244
|
+
className: "dlb-cards",
|
|
245
|
+
children: status.quarantined.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
246
|
+
className: "dlb-quarantineCard",
|
|
247
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
248
|
+
className: "dlb-pluginName",
|
|
249
|
+
children: name
|
|
250
|
+
}), confirming === name ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
251
|
+
className: "dlb-confirm",
|
|
252
|
+
children: [
|
|
253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
254
|
+
className: "dlb-confirmText",
|
|
255
|
+
children: t("restoreConfirm")
|
|
256
|
+
}),
|
|
257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
258
|
+
type: "button",
|
|
259
|
+
className: "dlb-btn dlb-btnGhost",
|
|
260
|
+
disabled: busyName === name,
|
|
261
|
+
onClick: () => setConfirming(null),
|
|
262
|
+
children: "×"
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
265
|
+
type: "button",
|
|
266
|
+
className: "dlb-btn dlb-btnPrimary",
|
|
267
|
+
disabled: busyName === name,
|
|
268
|
+
onClick: () => onRestore(name),
|
|
269
|
+
children: busyName === name ? t("restoring") : t("restore")
|
|
270
|
+
})
|
|
271
|
+
]
|
|
272
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
273
|
+
type: "button",
|
|
274
|
+
className: "dlb-btn dlb-btnPrimary",
|
|
275
|
+
disabled: busyName === name,
|
|
276
|
+
onClick: () => setConfirming(name),
|
|
277
|
+
children: busyName === name ? t("restoring") : t("restore")
|
|
278
|
+
})]
|
|
279
|
+
}, name))
|
|
280
|
+
}),
|
|
281
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
282
|
+
className: "dlb-title",
|
|
283
|
+
children: t("journalTitle")
|
|
284
|
+
}),
|
|
285
|
+
journalGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
286
|
+
className: "dlb-empty",
|
|
287
|
+
children: t("emptyJournal")
|
|
288
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
289
|
+
className: "dlb-journal",
|
|
290
|
+
children: journalGroups.map(([day, entries]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
291
|
+
className: "dlb-journalDay",
|
|
292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
293
|
+
className: "dlb-dayLabel",
|
|
294
|
+
children: day
|
|
295
|
+
}), entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
296
|
+
className: "dlb-entry",
|
|
297
|
+
children: [
|
|
298
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
299
|
+
className: `dlb-kind ${KIND_STYLES[entry.kind] ?? "dlb-kindInfo"}`,
|
|
300
|
+
children: entry.kind
|
|
301
|
+
}),
|
|
302
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
|
+
className: "dlb-entryAt",
|
|
304
|
+
children: entry.at.slice(11, 19)
|
|
305
|
+
}),
|
|
306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
307
|
+
className: "dlb-entryDetail",
|
|
308
|
+
children: entry.detail
|
|
309
|
+
})
|
|
310
|
+
]
|
|
311
|
+
}, `${entry.at}-${entry.kind}`))]
|
|
312
|
+
}, day))
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/** 挂载健康页(由 client.ts 延迟 require;任何异常由调用方捕获)。 */
|
|
318
|
+
function mountHealthTab(ctx) {
|
|
319
|
+
const c = ctx;
|
|
320
|
+
const tag = injectHealthStyle();
|
|
321
|
+
c.effect(() => () => {
|
|
322
|
+
tag?.remove();
|
|
323
|
+
}, "lifeboat: health style");
|
|
324
|
+
c.effect(() => c.locale.register(NS, {
|
|
325
|
+
zh: zhDict,
|
|
326
|
+
en: enDict
|
|
327
|
+
}), "lifeboat: locale");
|
|
328
|
+
const t = c.locale.bind(NS);
|
|
329
|
+
c.slots.inject("settings.plugins.tab", () => c.slots.register({
|
|
330
|
+
name: "settings.plugins.tab",
|
|
331
|
+
id: "dsh-plus-lifeboat",
|
|
332
|
+
order: 20,
|
|
333
|
+
label: () => t("tab"),
|
|
334
|
+
inject: () => ({ t })
|
|
335
|
+
}, HealthTab));
|
|
336
|
+
}
|
|
337
|
+
var NS, ROUTE_STATUS, ROUTE_RESTORE, KIND_STYLES, zhDict, enDict;
|
|
338
|
+
var init_health_tab = __esmMin((() => {
|
|
339
|
+
init_client();
|
|
340
|
+
init_health_styles();
|
|
341
|
+
NS = "dsh-plus-lifeboat";
|
|
342
|
+
ROUTE_STATUS = "/dsh-plus/lifeboat/status";
|
|
343
|
+
ROUTE_RESTORE = "/dsh-plus/lifeboat/restore";
|
|
344
|
+
KIND_STYLES = {
|
|
345
|
+
alert: "dlb-kindAlert",
|
|
346
|
+
quarantine: "dlb-kindQuarantine",
|
|
347
|
+
"quarantine-restore": "dlb-kindRestore",
|
|
348
|
+
"llm-fallback": "dlb-kindFallback",
|
|
349
|
+
"llm-fallback-revert": "dlb-kindRestore"
|
|
350
|
+
};
|
|
351
|
+
zhDict = {
|
|
352
|
+
tab: "救生艇",
|
|
353
|
+
fallbackActive: "LLM 应急翻译进行中",
|
|
354
|
+
fallbackFrom: "源路由",
|
|
355
|
+
fallbackTo: "临时路由",
|
|
356
|
+
fallbackAt: "启用时间",
|
|
357
|
+
quarantined: "已隔离插件",
|
|
358
|
+
restore: "恢复",
|
|
359
|
+
restoring: "恢复中…",
|
|
360
|
+
restoreConfirm: "确认恢复该插件?将移除用户 patch 层的禁用覆盖,热应用即时生效。",
|
|
361
|
+
journalTitle: "操作日志",
|
|
362
|
+
pluginsTitle: "dsh-plus 插件状态",
|
|
363
|
+
pluginFailed: "FAILED",
|
|
364
|
+
pluginOk: "正常",
|
|
365
|
+
pluginDisabled: "已禁用",
|
|
366
|
+
emptyJournal: "暂无记录。",
|
|
367
|
+
emptyQuarantined: "当前无隔离插件。",
|
|
368
|
+
loadFailed: "状态加载失败:",
|
|
369
|
+
retry: "重试",
|
|
370
|
+
restored: "已恢复,热应用即时生效。",
|
|
371
|
+
restoreFailed: "恢复失败:"
|
|
372
|
+
};
|
|
373
|
+
enDict = {
|
|
374
|
+
tab: "Lifeboat",
|
|
375
|
+
fallbackActive: "LLM emergency translation active",
|
|
376
|
+
fallbackFrom: "Source route",
|
|
377
|
+
fallbackTo: "Fallback route",
|
|
378
|
+
fallbackAt: "Since",
|
|
379
|
+
quarantined: "Quarantined plugins",
|
|
380
|
+
restore: "Restore",
|
|
381
|
+
restoring: "Restoring…",
|
|
382
|
+
restoreConfirm: "Restore this plugin? The disable override in the user patch layer will be removed and hot-applied.",
|
|
383
|
+
journalTitle: "Journal",
|
|
384
|
+
pluginsTitle: "dsh-plus plugin status",
|
|
385
|
+
pluginFailed: "FAILED",
|
|
386
|
+
pluginOk: "OK",
|
|
387
|
+
pluginDisabled: "Disabled",
|
|
388
|
+
emptyJournal: "No entries yet.",
|
|
389
|
+
emptyQuarantined: "No quarantined plugins.",
|
|
390
|
+
loadFailed: "Failed to load: ",
|
|
391
|
+
retry: "Retry",
|
|
392
|
+
restored: "Restored; hot-applied immediately.",
|
|
393
|
+
restoreFailed: "Restore failed: "
|
|
394
|
+
};
|
|
395
|
+
}));
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/client/client.ts
|
|
398
|
+
const name = "dsh-plus-lifeboat";
|
|
399
|
+
/** 健康页需要的 cordis 服务 key(哨兵段只用 root ctx 原生面,不依赖这些)。 */
|
|
400
|
+
const inject = ["slots", "locale"];
|
|
401
|
+
/** Runtime mirror: FiberState 是 cordis 跨包 const enum(FAILED = 3,cordis 4.0.1)。 */
|
|
402
|
+
const FIBER_FAILED = 3;
|
|
403
|
+
/** 永不回报的条目:救生艇自身与聚合 bundle。 */
|
|
404
|
+
const EXCLUDED = /* @__PURE__ */ new Set(["dsh-plus-lifeboat", "dsh-plus-bundle-main"]);
|
|
405
|
+
const GUARD_RE = /^dsh-plus-[a-z0-9-]+$/;
|
|
406
|
+
function apply(ctx) {
|
|
407
|
+
try {
|
|
408
|
+
const reported = /* @__PURE__ */ new Set();
|
|
409
|
+
ctx.root.on("internal/status", (fiber) => {
|
|
410
|
+
try {
|
|
411
|
+
if (fiber.state !== FIBER_FAILED) return;
|
|
412
|
+
const plugin = fiber.name;
|
|
413
|
+
if (!GUARD_RE.test(plugin) || EXCLUDED.has(plugin) || reported.has(plugin)) return;
|
|
414
|
+
reported.add(plugin);
|
|
415
|
+
fetch("/dsh-plus/lifeboat/quarantine", {
|
|
416
|
+
method: "POST",
|
|
417
|
+
headers: { "content-type": "application/json" },
|
|
418
|
+
body: JSON.stringify({ name: plugin })
|
|
419
|
+
}).catch(() => {});
|
|
420
|
+
} catch {}
|
|
421
|
+
});
|
|
422
|
+
} catch {}
|
|
423
|
+
try {
|
|
424
|
+
(init_health_tab(), __toCommonJS(health_tab_exports)).mountHealthTab(ctx);
|
|
425
|
+
} catch {}
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
exports.apply = apply;
|
|
429
|
+
exports.inject = inject;
|
|
430
|
+
exports.name = name;
|
|
37
431
|
return module.exports;
|
|
38
432
|
}
|
|
39
433
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import "@deepseek-ai/dsh-settings";
|
|
2
1
|
import z from "@deepseek-ai/schemastery";
|
|
3
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
-
|
|
5
3
|
//#region src/config.d.ts
|
|
6
|
-
|
|
7
4
|
/** 行级配置:全部有默认值,正常部署零配置。 */
|
|
8
5
|
declare const Config: z<Schemastery.ObjectS<{
|
|
9
6
|
enabled: z<boolean, boolean>;
|