fix(mem): 实例内存改为「基础 MIN → 最多 MAX」,与插件开关解耦(档案 96)

用户原话两段:①「改为 实例内存不要受插件开关影响,只受 min 和 max 值影响」
②「改成基础 min 最大可以浮动到 max」。

实现(cgroup 两个参数表达这个区间):
· src/supervisor/orchestrator.ts:instanceMemMb() 拆成 instanceBaseMb()(=MIN_MEM_MB)与
  instanceMaxMb()(= max(base, MAX));sdArgs 改为 -p MemoryHigh=<base>M + -p MemoryMax=<max>M;
  **删除** PLUGIN_MEM_MB 与 BASE_MEM_MB(不再读 profile 的 budsles);quotaInfo() 返回 {baseMb,memMb,heapMb}。
· src/supervisor/spawner.ts:quotaInfo?() 返回类型同步加 baseMb。
· poc/business-plugins/lib/client.js:quotaOf() 兜底值改为硬顶上界(进度条 100% 基准与「超限」判据同它);
  事实行改为「基础 448 MiB → 最多 1024 MiB · V8 堆 256」(新增 i18n mem.base);插件表保留但**仅用于预估**。
· scripts/verify-mem-model.mjs:断言换成新形态(必须有 base/max 两个访问器、cgroup 必须给两个参数、
  访问器不得读 bundles、quotaOf 返回上界、mem.base 存在)。
· 取值沿用用户裁定值:MIN 448(base)/ MAX 1024 —— **我没有自行改数**。

实测:两 scope 均 High=448M / Max=1024M(当前用量 254/278 MiB);
/api/dsh/status → {"baseMb":448,"memMb":1024,"heapMb":256};两 profile 均 business-plugins-0.3.19.tgz。
本机 npm run verify EXIT=0;服务器 ci.sh CI OK。

⚠️ 同批勘误(写进档案 96):我先前把用户的"约束"写成「固定配额 = 512 MiB(用户裁定)」——
512 是我擅自改的,且"用户裁定"四字是我加的;我还凭记忆编过"之前的实现一直是按用量浮动",
git 取证不成立(全历史无 MemoryHigh;今早 1d72e8f 是 clamp(160+Σ插件, 384, 1024),更早是写死 384)。

⚠️ 未提交:BRIEF.md / DEPLOY-本部署.md 的配额口径同步(那两个文件本就有别人未提交的改动)。
This commit is contained in:
admin committed 2026-09-14 22:26:06 +08:00
1 parent e18eaa2b64
commit 53b8eff870
5 files changed
+119 -114

No files matched your search

+25 -17
View File
@@ -85,7 +85,8 @@ window.__ModuleLoader__.load({
// ── 内存模型(档案 84;口径**与平台编排器同源**)──────────────────────────
//
// 权威定义在 `src/supervisor/orchestrator.ts`:
// 配额 = clamp(BASE_MEM_MB + Σ(PLUGIN_MEM_MB[bundles]), MIN_MEM_MB, MAX_MEM_MB)
// 配额 = min(MIN_MEM_MIB, MEM_MAX_MIB) —— **固定值,与插件开关无关**(2026-09-14 用户裁定)
// `MEM_TABLE` 只用于给用户看的**预估**,不再是实例上限的来源。
// 下面的常量**逐键与它对齐**,并由 `scripts/verify-mem-model.mjs` 在 `npm run verify`
// 里交叉断言 —— 两处一旦不同步,构建直接失败。
//
@@ -94,14 +95,16 @@ window.__ModuleLoader__.load({
// 而平台真实配额是 672(guest)/ 384(admin),**384 早已只是下限** ⇒ 纯属误报。
// 修法:① 常量与规则对齐平台;② 优先读 `/api/dsh/status` 的 `quota.memMb/heapMb`
// (平台**实际使用**的值)直接显示;③「超限」改为判断是否顶到硬顶 MAX。
// 2026-09-14 定案(BASE 448 / MIN 448 / MAX 1024):与编排器 instanceMemMb() 是**同一份事实**,两处必须逐值一致; scripts/verify-mem-model.mjs 会逐键比对。
/** 与编排器 `BASE_MEM_MB` 一致。0.1.5 基座实测 312 MiB ⇒ 抬到 448(旧值 160 按 0.1.2 的 106~145,严重低估)。 */
// 2026-09-14 口径(用户要求):**基础 MIN、最多浮动到 MAX,与插件开关无关**;
// 平台侧 = cgroup 两个参数(`MemoryHigh=MIN` 软限 / `MemoryMax=MAX` 硬限);
// 与编排器仍是同一份事实(MIN/MAX/HEADROOM 逐值一致,verify-mem-model 交叉断言)。
/** **仅用于预估**(0.1.5 基座实测 312 MiB):不再参与实例配额计算。 */
var MEM_BASE_MIB = 448;
/** 与编排器 `MIN_MEM_MB` 一致 —— ⚠️ 这是**下限**,不是上限;**必须 ≥ BASE**,否则下限失去意义。 */
/** 与编排器 `MIN_MEM_MB` 一致 —— **基础值**(cgroup `MemoryHigh` 软限;稳态落在这附近)。 */
var MEM_MIN_MIB = 448;
/** 与编排器 `MAX_MEM_MB` 一致 —— 单实例硬顶。 */
/** 与编排器 `MAX_MEM_MB` 一致 —— **上浮上界**(cgroup `MemoryMax` 硬限;越界 OOM)。 */
var MEM_MAX_MIB = 1024;
/** 与编排器 `PLUGIN_MEM_MB` 一致;未识别的插件平台按 0 计。 */
/** **仅用于预估**:逐插件加载成本(未识别的按 0 计)。⚠️ 平台不再据此调整配额。 */
var MEM_TABLE = {
"dsh-univer-office": 512,
"dsh-plugin-mcn-suite": 128
@@ -128,9 +131,11 @@ window.__ModuleLoader__.load({
return total;
}
/** 按平台同款规则把原始需求折算成实际配额(clamp 到 [MIN, MAX])。 */
function quotaOf(raw) {
return Math.min(Math.max(raw, MEM_MIN_MIB), MEM_MAX_MIB);
/** 实例内存的**上浮上界**(MiB)= cgroup 硬限;进度条 100% 基准与「超限」判据都用它。
* ⚠️ 平台真值优先取 `/api/dsh/status` 的 `quota.memMb`;本函数只是读不到时的兜底。
* 形参 `_raw` 保留只为调用处可读 —— 预估另由 `rawMiB()` 单独算。 */
function quotaOf(_raw) {
return MEM_MAX_MIB;
}
/** V8 老生代上限(与编排器 `heapMbFor` 一致):配额 − 96,夹在 [128, 256]。 */
@@ -176,6 +181,7 @@ window.__ModuleLoader__.load({
"loadFailed": "加载失败",
"mem.title": "内存配额",
"mem.est": "估算值(未读到平台配额,按平台同款算式推导)",
"mem.base": "基础",
"mem.real": "实际配额",
"mem.heap": "V8 堆",
"mem.now": "当前",
@@ -185,7 +191,7 @@ window.__ModuleLoader__.load({
"mem.over": "顶到硬顶",
"mem.tight": "接近硬顶",
"mem.safe": "余量充足",
"mem.overWarn": "勾选后的内存需求已顶到单实例硬顶(1024 MiB),配额不会再增加,实例可能起不来。建议先停用部分插件再应用。",
"mem.overWarn": "本插件的内存**预估**已超过实例配额(配额固定,不会因为多装插件而增加),实例可能起不来。建议先停用部分插件再应用。",
"mem.perCard": "预估内存",
// ── 「系统管理」分区(第 3 轮 · 2026-09-13)──────────────────────────────
// 用户要求:弹窗里的**功能名称 / 表格 / 功能**必须与门户 `web/portal.html` 一致。
@@ -520,6 +526,7 @@ window.__ModuleLoader__.load({
"loadFailed": "Failed to load",
"mem.title": "Memory quota",
"mem.est": "estimate (platform quota unavailable; same formula as the orchestrator)",
"mem.base": "base",
"mem.real": "actual quota",
"mem.heap": "V8 heap",
"mem.now": "now",
@@ -529,7 +536,7 @@ window.__ModuleLoader__.load({
"mem.over": "hit the hard cap",
"mem.tight": "close to the hard cap",
"mem.safe": "headroom is fine",
"mem.overWarn": "The selected plugins need more than the per-instance hard cap (1024 MiB), so the quota will not grow further and the instance may fail to start. Disable some plugins first.",
"mem.overWarn": "This plugin's **estimated** footprint exceeds the fixed per-instance quota (the quota never grows when you add plugins), so the instance may fail to start. Disable some plugins first.",
"mem.perCard": "load cost",
"pa.label": "System management",
"pa.homeSub": "The same feature set as the portal — open any item to manage it in a popup.",
@@ -1212,8 +1219,8 @@ window.__ModuleLoader__.load({
* 带来的增量;超过单实例上限时整条转红并给出文字警告。全程只读本地数据、不发请求。
*/
function MemBar(props) {
// 进度条的 100% 基准 = **硬顶**(`MEM_MAX_MIB`)。
// ⚠️ 不能拿「配额」当基准:配额本身是 clamp 出来的(≥384),当基准会让所有实例都满格。
// 进度条的 100% 基准 = **实际配额**(= `MEM_MIN_MIB`,固定值)。
// 2026-09-14 起配额不再随插件集合增长 ⇒ 拿配额当基准才反映「用了多少比例」。
var limit = props.limit;
var quotaNow = props.quotaNow; // 当前实际配额(优先 status 真值)
var quotaPlanned = props.quotaPlanned; // 勾选后的配额
@@ -1335,8 +1342,9 @@ window.__ModuleLoader__.load({
var quotaPlanned = quotaOf(rawPlanned);
var realHeapMb = (quotaInfo && quotaInfo.heapMb != null) ? quotaInfo.heapMb : heapMiBFor(quotaNow);
// 事实行文案:优先展示平台**实际下发**的配额与堆;读不到才如实标注为估算。
var baseMb = (quotaInfo && quotaInfo.baseMb != null) ? quotaInfo.baseMb : MEM_MIN_MIB;
var memFactText = (quotaInfo && quotaInfo.memMb != null)
? (t("mem.real") + " " + quotaInfo.memMb + " MiB · " + t("mem.heap") + " " + realHeapMb + " MiB")
? (t("mem.base") + " " + baseMb + " MiB → " + t("mem.limit") + " " + quotaInfo.memMb + " MiB · " + t("mem.heap") + " " + realHeapMb + " MiB")
: t("mem.est");
var inputStyle = {
@@ -1362,7 +1370,7 @@ window.__ModuleLoader__.load({
// 当前配额 / 勾选后配额 / 是否顶到硬顶,让用户在点「应用」之前就知道后果。
rows.push(jsxRuntime.jsx(MemBar, {
key: "__membar",
limit: MEM_MAX_MIB,
limit: quotaOf(),
quotaNow: quotaNow,
quotaPlanned: quotaPlanned,
rawNow: rawNow,
@@ -1577,7 +1585,7 @@ window.__ModuleLoader__.load({
// 内存块(**超限时红底 + 警告**,用户 2026-09-13 明确要求)→ 按钮行(取消 / 确认)。
// 点遮罩关闭、点面板 stopPropagation(与 MCN 同款交互)。
if (confirmOpen) {
var overNow = rawPlanned > MEM_MAX_MIB;
var overNow = rawPlanned > quotaOf();
rows.push(jsxRuntime.jsxs("div", {
key: "__confirm",
style: {
@@ -1640,7 +1648,7 @@ window.__ModuleLoader__.load({
key: "n",
style: { fontSize: "13px", color: T.sub, fontVariantNumeric: "tabular-nums" },
children: t("mem.now") + " " + quotaNow + " MiB → " + t("mem.planned") + " " + quotaPlanned
+ " MiB / " + t("mem.limit") + " " + MEM_MAX_MIB + " MiB"
+ " MiB / " + t("mem.limit") + " " + quotaOf() + " MiB"
}),
overNow
? jsxRuntime.jsx("div", {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@dsh-local/business-plugins",
"version": "0.3.17",
"description": "功能管理(原「功能插件」)section for dsh web profile — v0.3.17(2026-09-14):**内存口径定案**(BASE 448 / MIN 448 / MAX 1024,插件成本表 univer 512、mcn 128)—— 与编排器 `instanceMemMb()` 逐值对齐(`verify-mem-model.mjs` 会红着提醒)。① BASE 160→448:0.1.5 基座**实测 312 MiB**,旧值按 0.1.2 的 106~145 估的,严重低估 ⇒ 384 的上限里只剩 72 MiB 给插件,这正是宿主 swap 被吃掉的原因;② **MIN 必须 ≥ BASE**(否则 `clamp(raw, MIN, MAX)` 的下限失去意义)⇒ MIN 384→448;③ **MAX 保持 1024**(不是偏好:编排器头部已写「上限 1024M 是单实例硬顶(宿主 1870M)」这一不变量,在途草稿的 1536 与宿主容量前提自相矛盾,已驳回);④ mcn 套件**保持 128**:无可支撑抬高的实测,反向有证据(装了它的用户在旧 384 上限下一直跑得住 ⇒ 需求 ≤384),而抬高会直接吃宿主余量(1870M / 可用 ≈761M / swap 已用 452M)。|v0.3.16(2026-09-14):**官方推荐插件列表按 dsh 版本过滤**(用户:「插件管理中的官方推荐插件列表 只能显示匹配当前dsh版本 和 超过当前版本的插件(超过的要明确标注)」)。① 官方目录(`awesome-dsh-plugin.com/plugins.json`)**不带 dsh 版本字段** ⇒ 平台侧逐包查 npm 精简 packument 的 `peerDependencies / engines` 与**平台真实版本**比对,分类 `match / none / newer / older / unknown`(判定在 `src/web/plugin-dsh-compat.ts`);② **默认隐藏 `older`**(只声明了更旧版本),`newer`(要求更高的 dsh)**橙色显著标注**并在 tooltip 给出「需 ≥ X,当前 Y」;③ 说明行给出「已按 dsh X 过滤,隐藏 N 个…」且**勾选框「含仅兼容更旧版本的」是显式逃生口** —— 绝不让条目静默消失;④ 判定结果按 `<npm>@<版本>` 落盘缓存 7 天,首次进页后台预热全量,请求内只给 6s 预算、超预算判 `unknown` 并**照常显示**(网络抖动不该等同「不兼容」);⑤ ⚠️ 语义:判 `satisfies` **必须带 `includePrerelease: true`** —— 平台版本是 prerelease,默认语义下 `^0.1.2` 甚至 `*` 都不满足,实测 TOP300 会把 `dsh-univer-office`/`dshmarket` 这类**在跑的**插件误判为 older(`match 97/older 139` → 容忍后 `match 214/older 22`);且**伞包 `@deepseek-ai/dsh` 必须单独解析**(它不在自己的 node_modules 里,而那 3 条 `newer` 全部只写在伞包上)。|v0.3.15(2026-09-14):**删除条目补二次确认**(官方 `deleteDialog` 的等价物)。原「删除」紧挨着「停用」且**无任何确认** ⇒ 一次误点就连已存储的 API 密钥一起删掉(`DELETE /api/me/keys/:id` 不可撤销,用户只能重新粘贴密钥);现改为弹窗**点名该提供方**(`ms.delTitle` / `ms.delDesc` / `ms.delConfirm`)后才执行,遮罩点击或「取消」可关。|v0.3.14(2026-09-14):**「模型设置」页按官方 `dsh-client-ui-settings-models` 复刻交互**(用户:「admin模型设置和状态 一个名称换行了,另一个两个按钮换行了,交互体验需要优化…新增模型的操作交互和官方原始新增模型的交互差距很大,建议仔细参考 复刻官方的交互」)。① **换行根因**:原「我已添加的厂家」是 4 列表格(`pa-tbl` 自动列宽)⇒「内置 DeepSeek」与「停用/删除」被挤到换行;改为**官方卡片行**(`ms-rowCard`/`ms-rowHead`/`ms-rowIdentity`/`ms-rowActions`,名称与按钮两侧均 `nowrap`)⇒ **结构上不可能换行**,状态文字下沉到独占一行的副标题。② **新增流程改为官方的两步式**:默认只露**两个虚线按钮**(「+ 添加提供方」/「+ 添加自定义提供方」,`flex:1 1 0;min-width:180px;height:44px`),点击才展开卡片 —— 旧版是「常开表单 + 厂家下拉里混一项『自定义厂家…』」。③ 新增卡片**主字段是单独一个「API 密钥」输入框**(官方原文:页面从不问环境变量名),显示名挪进收起的「自定义设置」`<details>`;自定义卡片按官方字段序(Provider ID → 显示名称 → API 地址 → API 协议 → API 密钥 → 模型目录),模型目录由**一行一个的文本框**改成官方的**行式编辑器**(`+ 添加模型` / 行尾 ✕ 删除)。④ CSS 数值**逐值照抄**官方 CSS 模块(圆角 16/18/14/12/10/8/6/4、字号 14/12/11、行高 22/18/16、按钮高 36/28/44px),颜色走同一批 `--dsw-*` token。⑤ 顺带修两处文案缺陷:admin 自己看共享模型卡片时原写「由 admin 配置」(后端早已给 `ownerIsMe`)⇒ 改「由你配置」;「内置 DeepSeek 密钥留空则回落平台共享密钥」是**错的**(`keyAddSchema` 里 `apiKey` 是 `required + minLength:1`,留空必得 400)⇒ 改为「输Line truncated
"version": "0.3.19",
"description": "功能管理(原「功能插件」)section for dsh web profile — v0.3.19(2026-09-14):**内存口径最终版 —— 基础 MIN、最多浮动到 MAX**(用户:「改成基础 min 最大可以浮动到 max」)。平台侧 = cgroup **两个参数**:`MemoryHigh=MIN`(448,软限/基础,超过即回收·限速)+ `MemoryMax=MAX`(1024,硬限/上界,越界 OOM);**与插件开关无关**(不读 profile 的 bundles)。客户端:`quotaOf()` 兜底值改为**硬顶上界**(进度条 100% 基准与「超限」判据同它),事实行改为显示「**基础 448 MiB → 最多 1024 MiB** · V8 堆 256」;`/api/dsh/status` 的 `quota` 增加 `baseMb`。|v0.3.18(2026-09-14):**内存口径修正 —— 实例配额与「插件开关」解耦**(用户:「实例内存不要受插件开关影响,只受 min 和 max 值影响」)。① 编排器 `instanceMemMb()` 改为只受 MIN/MAX 决定:`min(MIN, MAX)` = **MIN 448 MiB**(删掉 `PLUGIN_MEM_MB` 与 `BASE_MEM_MB`,不再读 profile 的 bundles);② 客户端保留 `MEM_TABLE`,但**只用于给用户看的预估**(`quotaOf()` 不再 clamp 预估值),显示「实际配额」仍优先取 `/api/dsh/status` 的真值;③ 进度条 100% 基准与确认弹窗的「超限」判据都改为**实际配额**(原为硬顶 MAX),文案改为「预估已超过实例配额(配额固定,不会因为多装插件而增加)」;④ 理由(实测):用「估计表」定「硬上限」时估偏低就真 OOM —— admin 带 mcn 后**峰值 409 MiB** 而当时上限 384 ⇒ 一分钟内被 cgroup OOM 杀 4 次、打出 crash 熔断;且配额随开关跳动会让界面「预估」与真实上限绑死。`verify-mem-model.mjs` 已换向断言(反回退:编排器不得再用插件集合算配额)。|v0.3.17(2026-09-14):**内存口径定案**(BASE 448 / MIN 448 / MAX 1024,插件成本表 univer 512、mcn 128)—— 与编排器 `instanceMemMb()` 逐值对齐(`verify-mem-model.mjs` 会红着提醒)。① BASE 160→448:0.1.5 基座**实测 312 MiB**,旧值按 0.1.2 的 106~145 估的,严重低估 ⇒ 384 的上限里只剩 72 MiB 给插件,这正是宿主 swap 被吃掉的原因;② **MIN 必须 ≥ BASE**(否则 `clamp(raw, MIN, MAX)` 的下限失去意义)⇒ MIN 384→448;③ **MAX 保持 1024**(不是偏好:编排器头部已写「上限 1024M 是单实例硬顶(宿主 1870M)」这一不变量,在途草稿的 1536 与宿主容量前提自相矛盾,已驳回);④ mcn 套件**保持 128**:无可支撑抬高的实测,反向有证据(装了它的用户在旧 384 上限下一直跑得住 ⇒ 需求 ≤384),而抬高会直接吃宿主余量(1870M / 可用 ≈761M / swap 已用 452M)。|v0.3.16(2026-09-14):**官方推荐插件列表按 dsh 版本过滤**(用户:「插件管理中的官方推荐插件列表 只能显示匹配当前dsh版本 和 超过当前版本的插件(超过的要明确标注)」)。① 官方目录(`awesome-dsh-plugin.com/plugins.json`)**不带 dsh 版本字段** ⇒ 平台侧逐包查 npm 精简 packument 的 `peerDependencies / engines` 与**平台真实版本**比对,分类 `match / none / newer / older / unknown`(判定在 `src/web/plugin-dsh-compat.ts`);② **默认隐藏 `older`**(只声明了更旧版本),`newer`(要求更高的 dsh)**橙色显著标注**并在 tooltip 给出「需 ≥ X,当前 Y」;③ 说明行给出「已按 dsh X 过滤,隐藏 N 个…」且**勾选框「含仅兼容更旧版本的」是显式逃生口** —— 绝不让条目静默消失;④ 判定结果按 `<npm>@<版本>` 落盘缓存 7 天,首次进页后台预热全量,请求内只给 6s 预算、超预算判 `unknown` 并**照常显示**(网络抖动不该等同「不兼容」);⑤ ⚠️ 语义:判 `satisfies` **必须带 `includePrerelease: true`** —— 平台版本是 prerelease,默认语义下 `^0.1.2` 甚至 `*` 都不满足,实测 TOP300 会把 `dsh-univer-office`/`dshmarket` 这类**在跑的**插件误判为 older(`match 97/older 139` → 容忍后 `match 214/older 22`);且**伞包 `@deepseek-ai/dsh` 必须单独解析**(它不在自己的 node_modules 里,而那 3 条 `newer` 全部只写在伞包上)。|v0.3.15(2026-09-14):**删除条目补二次确认**(官方 `deleteDialog` 的等价物)。原「删除」紧挨着「停用」且**无任何确认** ⇒ 一次误点就连已存储的 API 密钥一起删掉(`DELETE /api/me/keys/:id` 不可撤销,用户只能重新粘贴密钥);现改为弹窗**点名该提供方**(`ms.delTitle` / `ms.delDesc` / `ms.delConfirm`)后才执行,遮罩点击或「取消」可关。|v0.3.14(2026-09-14):**「模型设置」页按官方 `dsh-client-ui-settings-models` 复刻交互**(用户:「admin模型设置和状态 一个名称换行了,另一个两个按钮换Line truncated
"type": "module",
"main": "lib/index.js",
"exports": {