@dsh-plus/lifeboat 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,9 +5,11 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
8
9
  var __defProp = Object.defineProperty;
9
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
11
  var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
11
13
  var __hasOwnProp = Object.prototype.hasOwnProperty;
12
14
  var __esmMin = (fn, res, err) => () => {
13
15
  if (err) throw err[0];
@@ -36,19 +38,118 @@ window.__ModuleLoader__.load({
36
38
  }
37
39
  return to;
38
40
  };
41
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
42
+ value: mod,
43
+ enumerable: true
44
+ }) : target, mod));
39
45
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
46
  //#endregion
41
- let react_jsx_runtime = require("react/jsx-runtime");
42
47
  let react = require("react");
48
+ react = __toESM(react, 1);
49
+ let react_jsx_runtime = require("react/jsx-runtime");
50
+ require("@deepseek-ai/schemastery");
43
51
  require("@deepseek-ai/dsh-client-ui-primitives");
44
52
  //#region ../shared/src/client/fields.tsx
45
53
  var init_fields = __esmMin((() => {}));
46
54
  //#endregion
47
55
  //#region ../shared/src/client/card.tsx
56
+ /**
57
+ * 配置卡片外壳与排版件:`ConfigShell`(卡片级无边框分节)、`Section`(卡片内二级
58
+ * 分组)、`FieldGrid`(字段排布)、`Note`(只读说明)、`ListEditor`(数组型资源
59
+ * 统一编辑)、`CardLoading`(取值未到达占位)。
60
+ *
61
+ * 宿主页面(`plugins.row.config` / `plugins.bundle.config` 的 page 视图)自己画
62
+ * 标题、图标、面包屑与页面级内边距,视图只填内容区——所以这里不再自绘带边框
63
+ * 卡片(会形成卡片套卡片,把移动端可用宽度逐层扣掉),也不做折叠:page 视图
64
+ * 本身就是表单落地页。度量对齐官方设置页(dsh-client-ui-primitives 的
65
+ * settings-form):小节标题 13px/600、字段区零横向内边距、页脚非 sticky。
66
+ *
67
+ * 各插件 card.tsx 只填 children 字段区与 actions;类名前缀与样式由
68
+ * cardCss(prefix) 配套。
69
+ * @module @dsh-plus/shared/client/card
70
+ */
48
71
  var init_card = __esmMin((() => {
49
72
  init_fields();
50
73
  }));
51
74
  //#endregion
75
+ //#region ../shared/src/client/code-validate.ts
76
+ /**
77
+ * 配置源码编辑的模式校验:用宿主 `describe` 回传的 schema JSON 在浏览器本地
78
+ * 重建同一套 schemastery 校验(库 23KB 纯 JS,随 client bundle 打苞——浏览器端
79
+ * 没有宿主实例,不存在跨实例符号身份问题,见 docs/reference/官方机制参照.md
80
+ * 「客户端平台」)。
81
+ *
82
+ * 校验与宿主写入路径同源:`new Schema(json)` 后按同一份 volatile 投影调用,
83
+ * 因此本地拦下的错误与宿主 `settings/rejected` 的判据一致。
84
+ * @module @dsh-plus/shared/client/code-validate
85
+ */
86
+ var init_code_validate = __esmMin((() => {}));
87
+ //#endregion
88
+ //#region ../shared/src/client/styles.ts
89
+ /**
90
+ * 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
91
+ * 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
92
+ */
93
+ function injectCardStyle(pluginId, css) {
94
+ if (typeof document === "undefined") return null;
95
+ const tagId = `${pluginId}/card.css`;
96
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
97
+ const tag = document.createElement("style");
98
+ tag.dataset.plugin = pluginId;
99
+ tag.dataset.pluginCss = tagId;
100
+ tag.textContent = css;
101
+ document.head.appendChild(tag);
102
+ return tag;
103
+ }
104
+ var init_styles = __esmMin((() => {}));
105
+ //#endregion
106
+ //#region ../shared/src/client/code-section.ts
107
+ /**
108
+ * 配置源码编辑节(自动追加到每张插件配置卡片页)。
109
+ *
110
+ * 形态:无边框分节,与 ConfigShell/Section 的小节度量一致;文档=宿主 `describe` 回传的
111
+ * volatile 投影(JSON,两空格缩进),校验=本地 JSON 语法 + schemastery 模式,
112
+ * 写回=官方 `settings.mutate` 路径 ops(密钥字段不进文档,也不会被写坏)。
113
+ *
114
+ * 本文件刻意不写 JSX:`@dsh-plus/shared` 的 client 纯逻辑测试用 `node --test`
115
+ * 直跑 .ts(Node 原生类型擦除不支持 JSX),而 `config-slots.ts` 会被测试导入,
116
+ * 因此这条导入链上的模块一律用 `createElement`。React 只经默认导入取用
117
+ * (react 是 CJS,Node ESM 下具名导入不保证可解析)。
118
+ * @module @dsh-plus/shared/client/code-section
119
+ */
120
+ var h$1, useEffect$3, useMemo$2, useState$3, useSyncExternalStore$1;
121
+ var init_code_section = __esmMin((() => {
122
+ init_code_validate();
123
+ ({createElement: h$1, useEffect: useEffect$3, useMemo: useMemo$2, useState: useState$3, useSyncExternalStore: useSyncExternalStore$1} = react.default);
124
+ }));
125
+ //#endregion
126
+ //#region ../shared/src/client/config-slots.ts
127
+ /**
128
+ * 配置卡片的槽位注册助手(目标宿主:dsh 0.2.1-alpha.2)。
129
+ *
130
+ * 官方插件页(`dsh-client-ui-plugin-manager`)为第三方配置视图声明两个 keyed 槽位:
131
+ * - `plugins.row.config`(key = `<bundle 包名>#<row id>`):bundle 详情页对应行出现
132
+ * 「配置」入口,行配置页渲染 `view: 'summary'`(一行简介)与 `view: 'page'`
133
+ * (带保存控件的表单);
134
+ * - `plugins.bundle.config`(key = 包名):包详情页本体配置,仅渲染 `view: 'page'`。
135
+ *
136
+ * 页面自己画标题、图标、面包屑与页面级内边距(`detailSections` 内无边框容器),
137
+ * 故 page 视图输出无边框分节(见 card.tsx),summary 视图只返回一行文本。
138
+ * 旧 `settings.plugin.item` 槽位已随官方插件页迁移删除,不再注册。
139
+ *
140
+ * **源码编辑节自动追加**:注册时把插件组件包一层,page 视图在原卡片之后追加
141
+ * `ConfigCodeSection`(复用同一份 scope/api),summary 视图原样透传。因此「只要
142
+ * 插件存在可配置项」就有源码编辑入口,插件侧零接线;命名空间没有 volatile 字段时
143
+ * 它本身不出现在 describe 视图里,该节自然显示为不可用。
144
+ *
145
+ * @module @dsh-plus/shared/client/config-slots
146
+ */
147
+ var h, Fragment;
148
+ var init_config_slots = __esmMin((() => {
149
+ init_code_section();
150
+ ({createElement: h, Fragment} = react.default);
151
+ }));
152
+ //#endregion
52
153
  //#region ../shared/src/client/credentials.tsx
53
154
  /**
54
155
  * 凭据面(共享件):官方 settings-controller 的 `remote.credentials` 命名空间
@@ -103,27 +204,12 @@ window.__ModuleLoader__.load({
103
204
  //#region ../shared/src/client/icons.tsx
104
205
  var init_icons = __esmMin((() => {}));
105
206
  //#endregion
106
- //#region ../shared/src/client/styles.ts
107
- /**
108
- * 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
109
- * 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
110
- */
111
- function injectCardStyle(pluginId, css) {
112
- if (typeof document === "undefined") return null;
113
- const tagId = `${pluginId}/card.css`;
114
- if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
115
- const tag = document.createElement("style");
116
- tag.dataset.plugin = pluginId;
117
- tag.dataset.pluginCss = tagId;
118
- tag.textContent = css;
119
- document.head.appendChild(tag);
120
- return tag;
121
- }
122
- var init_styles = __esmMin((() => {}));
123
- //#endregion
124
207
  //#region ../shared/src/client/index.ts
125
208
  var init_client = __esmMin((() => {
126
209
  init_card();
210
+ init_code_section();
211
+ init_code_validate();
212
+ init_config_slots();
127
213
  init_credentials();
128
214
  init_draft();
129
215
  init_fetch();
package/lib/index.d.ts CHANGED
@@ -3,11 +3,9 @@ import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/config.d.ts
4
4
  /** 行级配置:全部有默认值,正常部署零配置。 */
5
5
  declare const Config: z<Schemastery.ObjectS<NoInfer<{
6
- enabled: z<boolean, boolean, "defined">;
7
6
  patchFile: z<string, string, "defined">;
8
7
  alertCooldownMs: z<number, number, "defined">;
9
8
  }>>, Schemastery.ObjectT<NoInfer<{
10
- enabled: z<boolean, boolean, "defined">;
11
9
  patchFile: z<string, string, "defined">;
12
10
  alertCooldownMs: z<number, number, "defined">;
13
11
  }>>, "plain">;
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { copyFile, readFile, stat } from "node:fs/promises";
3
- import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
3
+ import { pluginDataPath } from "@dsh-plus/shared";
4
4
  import { isSeq, parse, parseDocument } from "yaml";
5
5
  import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
6
+ import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
6
7
  //#region src/config.ts
7
8
  /**
8
9
  * 配置单一事实源。
@@ -13,29 +14,30 @@ import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
13
14
  */
14
15
  /** 行级配置:全部有默认值,正常部署零配置。 */
15
16
  const Config = z.object({
16
- enabled: z.boolean().description("故障隔离总开关").default(true),
17
17
  patchFile: z.string().description("隔离写入的 profile 用户 patch 文件绝对路径;留空自动取当前 profile 的 cordis.patch.yml(profileContext.patchPath,CLI 与桌面端 desktop profile 同源)").default(""),
18
18
  alertCooldownMs: z.natural().description("同一插件重复告警的最小间隔毫秒数").default(3e5)
19
19
  });
20
20
  //#endregion
21
21
  //#region src/official-copy-status.ts
22
22
  /**
23
- * 官方应急副本的只读状态读取($DSH_HOME/llm-pi.official-patch.yaml)。
23
+ * 官方应急副本的只读状态读取($DSH_HOME/dsh-plus/llm-pi/official-patch.yaml)。
24
24
  *
25
25
  * 文件契约与 @dsh-plus/llm-pi 同路径:对方常备副本、本包只读消费——零
26
- * dsh-plus 内部依赖铁律下不 import 对方实现,路径按契约文件名同源派生。
26
+ * dsh-plus 内部依赖铁律下不 import 对方实现,路径按插件数据目录规范同源派生。
27
27
  * 读取一切失败都降级为结构化状态(面板展示与告警文案的输入,绝不抛出)。
28
28
  * @module lifeboat/official-copy-status
29
29
  */
30
- /** 副本文件名(跨包文件契约:llm-pi 写、本包读,改名须两侧同步)。 */
31
- const OFFICIAL_COPY_FILE = "llm-pi.official-patch.yaml";
30
+ /** 副本所在插件(跨包文件契约:llm-pi 写、本包读,改名须两侧同步)。 */
31
+ const OFFICIAL_COPY_PLUGIN = "llm-pi";
32
+ /** 副本文件名(同上,两侧同步)。 */
33
+ const OFFICIAL_COPY_FILE = "official-patch.yaml";
32
34
  /** 解析上限:超出只报存在性与时间,不读内容(面板/告警不得被大文件拖垮)。 */
33
35
  const MAX_BYTES$1 = 2097152;
34
36
  /** 头注释里的生成警告行前缀(llm-pi 渲染约定)。 */
35
37
  const WARNING_PREFIX = "# 警告:";
36
38
  /** 副本契约路径(默认读取点)。 */
37
39
  function officialCopyPath() {
38
- return dshHomePath(OFFICIAL_COPY_FILE);
40
+ return pluginDataPath(OFFICIAL_COPY_PLUGIN, OFFICIAL_COPY_FILE);
39
41
  }
40
42
  /** 解析副本内容(纯函数):头注释警告 + llm-pi-ai 行的 providers 数。 */
41
43
  function parseCopyText(text) {
@@ -654,29 +656,28 @@ function apply(ctx, config) {
654
656
  profile: ctx.get("profileContext")?.name,
655
657
  cooldownMs: config.alertCooldownMs
656
658
  });
657
- if (config.enabled) {
658
- const patchFile = resolvePatchFile(ctx, config);
659
- if (patchFile === void 0) warn("故障隔离停用:当前组合树无 profileContext,无法定位 profile 用户 patch 层");
660
- else {
661
- const quarantine = createQuarantine(ctx, {
662
- patchFile,
663
- alertCooldownMs: config.alertCooldownMs,
664
- journal,
665
- alert
666
- });
667
- installHostWatch(ctx, quarantine);
668
- ctx.inject(["webServer"], (webCtx) => {
669
- registerQuarantineApi(webCtx, quarantine);
670
- registerHealthApi(webCtx, {
671
- patchFile,
672
- journal,
673
- alert,
674
- readJournal: () => doc.journal,
675
- readCopy: () => readOfficialCopyStatus()
676
- });
677
- });
678
- }
659
+ const patchFile = resolvePatchFile(ctx, config);
660
+ if (patchFile === void 0) {
661
+ warn("故障隔离停用:当前组合树无 profileContext,无法定位 profile 用户 patch 层");
662
+ return;
679
663
  }
664
+ const quarantine = createQuarantine(ctx, {
665
+ patchFile,
666
+ alertCooldownMs: config.alertCooldownMs,
667
+ journal,
668
+ alert
669
+ });
670
+ installHostWatch(ctx, quarantine);
671
+ ctx.inject(["webServer"], (webCtx) => {
672
+ registerQuarantineApi(webCtx, quarantine);
673
+ registerHealthApi(webCtx, {
674
+ patchFile,
675
+ journal,
676
+ alert,
677
+ readJournal: () => doc.journal,
678
+ readCopy: () => readOfficialCopyStatus()
679
+ });
680
+ });
680
681
  }
681
682
  //#endregion
682
683
  export { Config, apply, inject, name, resolvePatchFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/lifeboat",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "dsh-plus service plugin: 故障救生艇——兄弟插件崩溃时自动隔离(写用户 patch 层禁用),llm-pi 缺席时把其配置临时翻译为官方 llm-pi-ai 路由,恢复后自动还原",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "yaml": "^2.9.0",
34
- "@dsh-plus/shared": "0.1.43"
34
+ "@dsh-plus/shared": "0.1.44"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/cordis": "^4.0.5",
package/src/config.ts CHANGED
@@ -10,7 +10,6 @@ import z from '@deepseek-ai/schemastery'
10
10
 
11
11
  /** 行级配置:全部有默认值,正常部署零配置。 */
12
12
  export const Config = z.object({
13
- enabled: z.boolean().description('故障隔离总开关').default(true),
14
13
  patchFile: z
15
14
  .string()
16
15
  .description(
package/src/index.ts CHANGED
@@ -77,30 +77,30 @@ export function apply(ctx: Context, config: LifeboatConfig): void {
77
77
  cooldownMs: config.alertCooldownMs,
78
78
  })
79
79
 
80
- if (config.enabled) {
81
- const patchFile = resolvePatchFile(ctx, config)
82
- if (patchFile === undefined) {
83
- // 无 profileContext 的组合树(非 dsh 启动):不猜路径、不写任何 patch,
84
- // 隔离停用但告警/journal 照常。
85
- warn('故障隔离停用:当前组合树无 profileContext,无法定位 profile 用户 patch 层')
86
- } else {
87
- const quarantine = createQuarantine(ctx, {
88
- patchFile,
89
- alertCooldownMs: config.alertCooldownMs,
90
- journal,
91
- alert,
92
- })
93
- installHostWatch(ctx, quarantine)
94
- ctx.inject(['webServer'], (webCtx) => {
95
- registerQuarantineApi(webCtx, quarantine)
96
- registerHealthApi(webCtx, {
97
- patchFile,
98
- journal,
99
- alert,
100
- readJournal: () => doc.journal,
101
- readCopy: () => readOfficialCopyStatus(),
102
- })
103
- })
104
- }
80
+ // 故障隔离是常驻能力:整行开关由 Loader 的 profile patch `disabled` 提供,
81
+ // 插件内部不再持有第二套总开关(见 docs/repo/插件存储规范.md)。
82
+ const patchFile = resolvePatchFile(ctx, config)
83
+ if (patchFile === undefined) {
84
+ // 无 profileContext 的组合树(非 dsh 启动):不猜路径、不写任何 patch,
85
+ // 隔离停用但告警/journal 照常。
86
+ warn('故障隔离停用:当前组合树无 profileContext,无法定位 profile 用户 patch 层')
87
+ return
105
88
  }
89
+ const quarantine = createQuarantine(ctx, {
90
+ patchFile,
91
+ alertCooldownMs: config.alertCooldownMs,
92
+ journal,
93
+ alert,
94
+ })
95
+ installHostWatch(ctx, quarantine)
96
+ ctx.inject(['webServer'], (webCtx) => {
97
+ registerQuarantineApi(webCtx, quarantine)
98
+ registerHealthApi(webCtx, {
99
+ patchFile,
100
+ journal,
101
+ alert,
102
+ readJournal: () => doc.journal,
103
+ readCopy: () => readOfficialCopyStatus(),
104
+ })
105
+ })
106
106
  }
@@ -1,18 +1,19 @@
1
1
  /**
2
- * 官方应急副本的只读状态读取($DSH_HOME/llm-pi.official-patch.yaml)。
2
+ * 官方应急副本的只读状态读取($DSH_HOME/dsh-plus/llm-pi/official-patch.yaml)。
3
3
  *
4
4
  * 文件契约与 @dsh-plus/llm-pi 同路径:对方常备副本、本包只读消费——零
5
- * dsh-plus 内部依赖铁律下不 import 对方实现,路径按契约文件名同源派生。
5
+ * dsh-plus 内部依赖铁律下不 import 对方实现,路径按插件数据目录规范同源派生。
6
6
  * 读取一切失败都降级为结构化状态(面板展示与告警文案的输入,绝不抛出)。
7
7
  * @module lifeboat/official-copy-status
8
8
  */
9
9
  import { readFile, stat } from 'node:fs/promises'
10
-
11
- import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
10
+ import { pluginDataPath } from '@dsh-plus/shared'
12
11
  import { parse } from 'yaml'
13
12
 
14
- /** 副本文件名(跨包文件契约:llm-pi 写、本包读,改名须两侧同步)。 */
15
- export const OFFICIAL_COPY_FILE = 'llm-pi.official-patch.yaml'
13
+ /** 副本所在插件(跨包文件契约:llm-pi 写、本包读,改名须两侧同步)。 */
14
+ const OFFICIAL_COPY_PLUGIN = 'llm-pi'
15
+ /** 副本文件名(同上,两侧同步)。 */
16
+ export const OFFICIAL_COPY_FILE = 'official-patch.yaml'
16
17
 
17
18
  /** 解析上限:超出只报存在性与时间,不读内容(面板/告警不得被大文件拖垮)。 */
18
19
  const MAX_BYTES = 2 * 1024 * 1024
@@ -35,7 +36,7 @@ export interface OfficialCopyStatus {
35
36
 
36
37
  /** 副本契约路径(默认读取点)。 */
37
38
  export function officialCopyPath(): string {
38
- return dshHomePath(OFFICIAL_COPY_FILE)
39
+ return pluginDataPath(OFFICIAL_COPY_PLUGIN, OFFICIAL_COPY_FILE)
39
40
  }
40
41
 
41
42
  /** 解析副本内容(纯函数):头注释警告 + llm-pi-ai 行的 providers 数。 */