@xiaoso/dsh-tool-plus 0.1.9 → 0.1.10-beta.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.
Files changed (40) hide show
  1. package/README.en.md +12 -5
  2. package/README.md +11 -4
  3. package/THIRD_PARTY_NOTICES.md +36 -0
  4. package/lib/client.js +272 -181
  5. package/lib/index.mjs +875 -1081
  6. package/lib/{scrapers-mhvFrvjH.mjs → scrapers-DODCPcZW.mjs} +1 -1
  7. package/lib/types/client/BashPlusCard.d.ts +6 -6
  8. package/lib/types/client/ToolPlusSection.d.ts +3 -3
  9. package/lib/types/client/forms.d.ts +6 -6
  10. package/lib/types/client/index.d.ts +25 -12
  11. package/lib/types/client/locales.d.ts +1 -1
  12. package/lib/types/client/preset-panel.d.ts +54 -42
  13. package/lib/types/config/settings.d.ts +202 -21
  14. package/lib/types/host/browser-probe-rpc.d.ts +2 -1
  15. package/lib/types/host/preset-host.d.ts +103 -0
  16. package/lib/types/host/preset-rpc.d.ts +37 -72
  17. package/lib/types/presets/compare.d.ts +14 -37
  18. package/lib/types/presets/conflicts.d.ts +59 -0
  19. package/lib/types/presets/delta.d.ts +23 -18
  20. package/lib/types/presets/plan.d.ts +55 -0
  21. package/lib/types/presets/rows.d.ts +84 -0
  22. package/lib/types/presets/status.d.ts +56 -47
  23. package/lib/types/tools/bash/background.d.ts +20 -5
  24. package/lib/types/tools/bash/streaming-output.d.ts +4 -2
  25. package/lib/types/tools/shared/browser-rpc-channel.d.ts +84 -42
  26. package/lib/types/web/client/labels.d.ts +1 -1
  27. package/lib/types/web/client/row-utils.d.ts +26 -1
  28. package/lib/{utils-eiUz9n8I.mjs → utils-D80w8vry.mjs} +1 -1
  29. package/package.json +61 -46
  30. package/presets/tool-plus-ptc.patch.yml +164 -0
  31. package/presets/tool-plus-standard.patch.yml +158 -0
  32. package/lib/types/presets/install.d.ts +0 -44
  33. package/lib/types/presets/paths.d.ts +0 -41
  34. package/lib/types/presets/rewrite.d.ts +0 -86
  35. package/presets/install-presets.mjs +0 -143
  36. package/presets/package.json +0 -42
  37. package/presets/tool-plus-ptc/agent.cordis.yml +0 -284
  38. package/presets/tool-plus-ptc/preset.yml +0 -3
  39. package/presets/tool-plus-standard/agent.cordis.yml +0 -269
  40. package/presets/tool-plus-standard/preset.yml +0 -3
package/lib/client.js CHANGED
@@ -992,7 +992,7 @@ window.__ModuleLoader__.load({
992
992
  "aria-expanded": open,
993
993
  disabled,
994
994
  onClick: () => onOpenChange(open ? null : control.field),
995
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: current ? t(current.labelKey) : control.value }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "tp-selectChevron" })]
995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: current ? t(current.labelKey) : control.value }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: "tp-selectChevron" })]
996
996
  })
997
997
  })
998
998
  ]
@@ -1092,7 +1092,7 @@ window.__ModuleLoader__.load({
1092
1092
  * (`locale: 'tool-plus'`), which re-renders on a locale switch.
1093
1093
  *
1094
1094
  * The card edits the FLAT settings surface (OMP `bash*`-style scalar keys) —
1095
- * the client scope only writes scalar fields — grouped as timeouts,
1095
+ * the client form only writes scalar fields — grouped as timeouts,
1096
1096
  * backgrounding, output, completion truncation (subdivided into byte and line
1097
1097
  * settings), and behavior. Field definitions, defaults, and copy keys come
1098
1098
  * from the single-source field table (`src/config/fields.ts`), shared with
@@ -1209,7 +1209,7 @@ window.__ModuleLoader__.load({
1209
1209
  if (hasStaged) return (staged ?? "").trim() === "" ? String(revertValue$1(snap, field) ?? "") : staged ?? "";
1210
1210
  return resolved === void 0 || typeof resolved === "boolean" ? "" : String(resolved);
1211
1211
  }
1212
- /** Stage card edits over the bound scope; only a Save writes the document. */
1212
+ /** Stage card edits over the shared configuration form; only a Save writes the document. */
1213
1213
  function useBashPlusForm(scope) {
1214
1214
  const [snap, setSnap] = (0, react.useState)(() => scope.getSnapshot());
1215
1215
  const [numbers, setNumbers] = (0, react.useState)({});
@@ -1464,8 +1464,8 @@ window.__ModuleLoader__.load({
1464
1464
  };
1465
1465
  }
1466
1466
  function BashPlusCard(props) {
1467
- const { t, scope } = props;
1468
- const form = useBashPlusForm(scope);
1467
+ const { t, form: configForm } = props;
1468
+ const form = useBashPlusForm(configForm);
1469
1469
  const [open, setOpen] = (0, react.useState)(false);
1470
1470
  const [openSelect, setOpenSelect] = (0, react.useState)(null);
1471
1471
  (0, react.useEffect)(() => {
@@ -1474,7 +1474,7 @@ window.__ModuleLoader__.load({
1474
1474
  }, []);
1475
1475
  if (!form.shell.available) return null;
1476
1476
  const blocked = !form.shell.dirty || form.shell.invalid || form.shell.saving;
1477
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1477
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1478
1478
  "data-plugin-settings": "tool-plus",
1479
1479
  className: "tp-card" + (open ? " tp-open" : ""),
1480
1480
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -1703,15 +1703,19 @@ window.__ModuleLoader__.load({
1703
1703
  /** Endpoint that lists agent presets with their status (roster + conflicts + template diff). */
1704
1704
  const PRESET_STATUS_ENDPOINT = "presets/status";
1705
1705
  /**
1706
- * The preset ids this plugin ships a template for. Single source of truth for
1707
- * both halves: the host uses it for the startup bootstrap and the template
1708
- * lookup, the panel uses it to synthesize the "not installed" rows for a
1709
- * deployment whose roster does not carry them yet.
1706
+ * The preset ids this plugin **declares**. Single source of truth for both
1707
+ * halves: the host picks its own declarations out of the profile's composed
1708
+ * configuration, and the panel uses the list to tell "ours" from everything else.
1709
+ *
1710
+ * They ship as `@deepseek-ai/dsh-agent-preset` declaration rows in this
1711
+ * package's own bundle patches (`presets/<id>.patch.yml`), so installing the
1712
+ * plugin as a profile bundle declares them: no install step, no file copy, and
1713
+ * nothing written at startup.
1710
1714
  */
1711
1715
  const BUNDLED_PRESET_IDS = ["tool-plus-standard", "tool-plus-ptc"];
1712
- /** Endpoint that applies one preset action to a user-root preset. */
1716
+ /** Endpoint that applies one preset action to the active profile. */
1713
1717
  const PRESET_ACTION_ENDPOINT = "presets/apply";
1714
- /** Endpoint that compares one preset against one of our templates (read-only). */
1718
+ /** Endpoint that compares one preset against one of our declarations (read-only). */
1715
1719
  const PRESET_COMPARE_ENDPOINT = "presets/compare";
1716
1720
  //#endregion
1717
1721
  //#region src/client/web-connection-rpc.ts
@@ -1940,8 +1944,8 @@ window.__ModuleLoader__.load({
1940
1944
  return false;
1941
1945
  }
1942
1946
  /**
1943
- * Stage edits over the bound scope; only a Save writes the document.
1944
- * @param scope - bound settings scope for the `tool-plus` namespace.
1947
+ * Stage edits over the shared configuration form; only a Save writes the document.
1948
+ * @param scope - the `tool-plus` entry's shared configuration form (`ctx.configForms`).
1945
1949
  * @param fields - the fields this surface edits (single-source table slice).
1946
1950
  * @returns the form state and its write actions.
1947
1951
  */
@@ -2123,11 +2127,18 @@ window.__ModuleLoader__.load({
2123
2127
  //#endregion
2124
2128
  //#region src/client/preset-panel.ts
2125
2129
  /**
2126
- * 预设面板的纯映射层:把 `presets/status` / `presets/apply` 的返回值翻成本地化
2127
- * 文案、可用动作与差异摘要。零 React、零 DOM、零 I/O —— 由
2128
- * `tests/unit/preset-panel.spec.ts` 单独覆盖,UI(`PresetPanel.tsx`)只做渲染。
2130
+ * 预设面板的纯映射层:把 `presets/status` / `presets/apply` / `presets/compare`
2131
+ * 的返回值翻成本地化文案、可用动作与差异摘要。零 React、零 DOM、零 I/O ——
2132
+ * 由 `tests/unit/preset-panel.spec.ts` 单独覆盖,UI(`PresetPanel.tsx`)只做渲染。
2133
+ *
2134
+ * 语义基线(dsh 0.1.7 起,别再按旧目录机制理解):
2135
+ * - 预设是 profile 配置里的一条 `@deepseek-ai/dsh-agent-preset` 声明行;
2136
+ * - "没被改动过" = 这一行在 profile 补丁里**没有** `config`(`customized === false`);
2137
+ * - 三个动作:`upgrade` 只禁冲突行、`align` 用所选模板整份替换 plugins、
2138
+ * `revert` 删掉覆盖回落到随包声明;
2139
+ * - 本插件只**声明**两个预设({@link BUNDLED_PRESET_IDS});别的 preset 归别人,
2140
+ * 面板最多替它修冲突行,不改它的内容。
2129
2141
  *
2130
- * 契约来源:`docs/superpowers/specs/2026-09-17-preset-upgrade-design.md` §4.2/§7。
2131
2142
  * 类型与端点名直接取自共享契约模块 `src/tools/shared/browser-rpc-channel.ts`
2132
2143
  * (两半同一个来源,客户端这一侧不复制一份会漂移的定义)。
2133
2144
  * @module @xiaoso/dsh-tool-plus/client/preset-panel
@@ -2143,33 +2154,6 @@ window.__ModuleLoader__.load({
2143
2154
  "tool-fs-search": "presetEntrySearch"
2144
2155
  };
2145
2156
  /**
2146
- * 组合文件读不到但形状没问题:`conflicts` 为空、`clean` 为假,且既没被 roster
2147
- * 标 `broken` 也不是形状无法识别 —— 唯一解释是文件不存在/读不了(例如目录在、
2148
- * `agent.cordis.yml` 缺)。这不是"与模板一致",要对用户说「未安装」。
2149
- */
2150
- function compositionUnreadable(status) {
2151
- return status.broken === void 0 && !status.unrecognized && !status.clean && status.conflicts.length === 0;
2152
- }
2153
- /**
2154
- * roster 缺席我们随包的两份时补出「未安装」行(不改变 roster 原有顺序,
2155
- * 补出的行按 {@link BUNDLED_PRESET_IDS} 顺序排在前面)。
2156
- */
2157
- function mergeBundledPresets(presets) {
2158
- const present = new Set(presets.map((preset) => preset.id));
2159
- const missing = BUNDLED_PRESET_IDS.filter((id) => !present.has(id)).map((id) => ({
2160
- id,
2161
- source: "ours",
2162
- path: "",
2163
- conflicts: [],
2164
- clean: false,
2165
- unrecognized: false,
2166
- installed: false,
2167
- templatePresent: true,
2168
- templateDiffers: false
2169
- }));
2170
- return missing.length === 0 ? [...presets] : [...missing, ...presets];
2171
- }
2172
- /**
2173
2157
  * 下拉里一次只看一个预设时的默认选中项:随包的那份({@link BUNDLED_PRESET_IDS}
2174
2158
  * 的第一项)在场就选它,否则退清单第一项;空清单返回 undefined。
2175
2159
  */
@@ -2177,14 +2161,14 @@ window.__ModuleLoader__.load({
2177
2161
  return BUNDLED_PRESET_IDS.find((id) => presets.some((preset) => preset.id === id)) ?? presets[0]?.id;
2178
2162
  }
2179
2163
  /**
2180
- * 当前选中项在重拉状态后可能已经消失(预设被删、改名、roster 变化):仍在场就
2181
- * 保留,否则回落到 {@link initialPresetId},避免下拉指向一个不存在的选项。
2164
+ * 当前选中项在重拉状态后可能已经消失(声明被移除、roster 变化):仍在场就保留,
2165
+ * 否则回落到 {@link initialPresetId},避免下拉指向一个不存在的选项。
2182
2166
  */
2183
2167
  function resolveSelectedPresetId(presets, current) {
2184
2168
  if (current !== void 0 && presets.some((preset) => preset.id === current)) return current;
2185
2169
  return initialPresetId(presets);
2186
2170
  }
2187
- /** 下拉选项文案:优先展示 name(preset.yml 的显示名),缺了就退 id。 */
2171
+ /** 下拉选项文案:优先展示 name(声明里的显示名),缺了就退 id。 */
2188
2172
  function presetOptionLabel(preset) {
2189
2173
  return preset.name !== void 0 && preset.name !== "" ? preset.name : preset.id;
2190
2174
  }
@@ -2215,7 +2199,7 @@ window.__ModuleLoader__.load({
2215
2199
  }
2216
2200
  /**
2217
2201
  * 状态点 ②(完整比较):这份预设的内容与我们选中的模板差多少 ——
2218
- * 不只是工具开关,整份组合文件都算。
2202
+ * 不只是工具开关,整份插件行列表都算。
2219
2203
  * @param t - 文案表。
2220
2204
  * @param compare - 当前比较结果;尚未返回时给"正在比较"的黄点。
2221
2205
  * @returns 状态点。
@@ -2239,7 +2223,7 @@ window.__ModuleLoader__.load({
2239
2223
  };
2240
2224
  }
2241
2225
  /**
2242
- * 差异弹窗的行:把比较结果翻成"路径 / 两份预设各自的值"三列。
2226
+ * 差异弹窗的行:把比较结果翻成"路径 / 两份各自的值"三列。
2243
2227
  * 值缺失或整行差异都给出占位词,让用户一眼看出哪边没有。
2244
2228
  * @param t - 文案表。
2245
2229
  * @param compare - 当前比较结果。
@@ -2285,10 +2269,10 @@ window.__ModuleLoader__.load({
2285
2269
  return template.name !== void 0 && template.name !== "" ? template.name : template.id;
2286
2270
  }
2287
2271
  /**
2288
- * 选中的模板:①选中的预设自己有同名模板就用它(`tool-plus-ptc` 就该比
2289
- * `tool-plus-ptc`);②否则退随包的第一份模板(`BUNDLED_PRESET_IDS[0]`,即
2290
- * `tool-plus-standard`,别按目录名排序撞上 ptc);③再不行才退清单第一项。
2291
- * 清单为空返回 undefined。
2272
+ * 选中的模板:①手动选过且仍在场就用它;②否则选与当前预设**同名**的那份
2273
+ * (`tool-plus-ptc` 就该比 `tool-plus-ptc`);③再退随包的第一份
2274
+ * (`BUNDLED_PRESET_IDS[0]`,即 `tool-plus-standard`,别按目录名排序撞上 ptc);
2275
+ * ④最后退清单第一项。清单为空返回 undefined。
2292
2276
  * @param templates - 可选模板清单。
2293
2277
  * @param current - 当前选中的模板 id。
2294
2278
  * @param presetId - 当前选中的预设 id(同名模板优先)。
@@ -2300,35 +2284,54 @@ window.__ModuleLoader__.load({
2300
2284
  return BUNDLED_PRESET_IDS.find((id) => templates.some((template) => template.id === id)) ?? templates[0]?.id;
2301
2285
  }
2302
2286
  /**
2303
- * 补充说明行:读取失败原因、无法识别的内容。差异不再在这里铺开 ——
2304
- * 它进了「查看差异」弹窗,正文只留两个状态点。
2287
+ * 补充说明行:为什么读不到、这个 preset 归谁、以及"你改过它"这件事。
2288
+ * 差异不在这里铺开 —— 它进了「查看差异」弹窗,正文只留状态点与必要解释。
2305
2289
  * @param t - 文案表。
2306
2290
  * @param status - 该预设的状态。
2291
+ * @param writable - 本部署有没有可编辑的 profile。
2307
2292
  * @returns 要渲染的说明行(可能为空)。
2308
2293
  */
2309
- function presetNotes(t, status) {
2294
+ function presetNotes(t, status, writable = true) {
2310
2295
  const notes = [];
2296
+ if (!writable) notes.push(t("presetNoteNoProfile"));
2311
2297
  if (status.broken !== void 0) notes.push(t("presetNoteBroken").replace("{reason}", status.broken));
2312
2298
  else if (status.unrecognized) notes.push(t("presetNoteUnrecognized"));
2299
+ if (status.source === "ours" && writable && status.entryId === void 0) notes.push(t("presetNoteNoRow"));
2300
+ if (status.source === "other") notes.push(t("presetNoteOther"));
2301
+ else if (status.customized) notes.push(t("presetNoteCustomized"));
2313
2302
  return notes;
2314
2303
  }
2315
2304
  /**
2316
2305
  * 该预设可执行的动作,按渲染顺序。
2317
2306
  *
2318
- * - 官方随附预设一律无动作(写不了,它的只读语义由"没有按钮"表达,不再用徽标);
2319
- * - 其余预设都给两个动作:`upgrade`(最小更新:只禁用冲突行)与 `reset`
2320
- * (用**所选模板**整份覆盖);
2321
- * - 读不了、无法识别时只剩 `reset` —— 整份覆盖是唯一能修的办法,最小更新会拒绝。
2307
+ * - 本部署没有可编辑 profile(`writable === false`)→ 一个动作都不给;
2308
+ * - 这一行不可寻址(`entryId` 缺席)→ 一个动作都不给(写了也没地方落);
2309
+ * - **别人的** preset → 只给 `upgrade`(我们只该修冲突行,不该替别人重写内容);
2310
+ * - **我们的** preset → `upgrade` + `align`;有覆盖时再加 `revert`;
2311
+ * - 内容不可识别时 `upgrade` 没有意义(它要逐行判定),只给 `align` / `revert`。
2322
2312
  * @param status - 该预设的状态。
2313
+ * @param writable - 本部署有没有可编辑的 profile。
2323
2314
  * @returns 动作清单。
2324
2315
  */
2325
- function presetActions(status) {
2326
- if (status.source === "shipped") return [];
2327
- return status.broken !== void 0 || status.unrecognized || !status.installed || compositionUnreadable(status) ? ["reset"] : ["upgrade", "reset"];
2328
- }
2329
- /** 动作按钮文案:两个动作都面向所有可写预设,文案不再区分来源。 */
2316
+ function presetActions(status, writable = true) {
2317
+ if (!writable || status.entryId === void 0) return [];
2318
+ if (status.source === "other") return status.unrecognized ? [] : ["upgrade"];
2319
+ if (status.unrecognized) return status.customized ? ["align", "revert"] : ["align"];
2320
+ return status.customized ? [
2321
+ "upgrade",
2322
+ "align",
2323
+ "revert"
2324
+ ] : ["upgrade", "align"];
2325
+ }
2326
+ /** 动作按钮文案。 */
2330
2327
  function presetActionText(t, action) {
2331
- return action === "reset" ? t("presetActionReset") : t("presetActionUpdate");
2328
+ if (action === "align") return t("presetActionAlign");
2329
+ if (action === "revert") return t("presetActionRevert");
2330
+ return t("presetActionUpdate");
2331
+ }
2332
+ /** 需要二次确认的动作:两个会动到用户已有内容的动作。 */
2333
+ function presetActionNeedsConfirm(action) {
2334
+ return action === "align" || action === "revert";
2332
2335
  }
2333
2336
  /**
2334
2337
  * 动作按钮的悬浮说明:讲清这个按钮**具体会做什么**,以及当前状态下有没有意义。
@@ -2336,7 +2339,7 @@ window.__ModuleLoader__.load({
2336
2339
  * @param t - 文案表。
2337
2340
  * @param action - 该按钮的动作。
2338
2341
  * @param status - 该预设状态(决定"要不要更新")。
2339
- * @param templateLabel - 当前所选模板的显示名(重置提示里点名它是哪份)。
2342
+ * @param templateLabel - 当前所选模板的显示名(对齐提示里点名它是哪份)。
2340
2343
  * @returns 悬浮提示文案。
2341
2344
  */
2342
2345
  function presetActionHint(t, action, status, templateLabel) {
@@ -2345,7 +2348,8 @@ window.__ModuleLoader__.load({
2345
2348
  for (const [name, value] of Object.entries(values)) text = text.replaceAll(`{${name}}`, value);
2346
2349
  return text;
2347
2350
  };
2348
- if (action === "reset") return fill("presetActionResetHint", { template: templateLabel });
2351
+ if (action === "align") return fill("presetActionAlignHint", { template: templateLabel });
2352
+ if (action === "revert") return t("presetActionRevertHint");
2349
2353
  if (status.conflicts.length === 0) return t("presetActionUpdateNothing");
2350
2354
  return fill("presetActionUpdateHint", {
2351
2355
  count: String(status.conflicts.length),
@@ -2360,54 +2364,59 @@ window.__ModuleLoader__.load({
2360
2364
  });
2361
2365
  }
2362
2366
  /**
2363
- * 重置的二次确认文案。还没安装(含 roster 缺席补出的行)时没有本地改动可丢,
2364
- * 不能拿"会覆盖本地改动"吓人;其余情况按覆盖风险如实提示。两种情况都点名
2365
- * 用的是**哪份模板**:重置的来源是用户在「对比模板」里选的那份。
2367
+ * 二次确认文案。两个动作都会动到用户可能已经改过的内容,所以必须说清后果,
2368
+ * 并且**点名写入位置**——这是本插件唯一会改用户 profile 配置的地方。
2366
2369
  * @param t - 文案表。
2367
2370
  * @param status - 该预设状态。
2371
+ * @param action - 待确认的动作。
2368
2372
  * @param templateLabel - 所选模板的显示名。
2369
2373
  * @returns 确认文案。
2370
2374
  */
2371
- function presetConfirmText(t, status, templateLabel) {
2372
- if (!status.installed || compositionUnreadable(status)) return t("presetConfirmInstall").replace("{template}", templateLabel);
2373
- return t("presetConfirmReset").replace("{template}", templateLabel);
2375
+ function presetConfirmText(t, status, action, templateLabel) {
2376
+ if (action === "revert") return t("presetConfirmRevert");
2377
+ const lines = [t("presetConfirmAlign").replace("{template}", templateLabel)];
2378
+ if (status.customized) lines.push(t("presetConfirmAlignEdits"));
2379
+ return lines.join("\n");
2374
2380
  }
2375
2381
  /**
2376
- * 执行前摘要:将要改哪几行 / 将写入什么。**没事可做时返回 null**(不渲染这一行)——
2377
- * 用户明确要求去掉「工具行都已禁用,无需调整。」这种"没有信息量的状态句"。
2382
+ * 执行前摘要:**当前状态**下有什么需要处理的。没事可做时返回 null
2383
+ * (不渲染这一行)——用户明确要求去掉"工具行都已禁用,无需调整。"这种没有
2384
+ * 信息量的状态句。
2378
2385
  * @param t - 文案表。
2379
2386
  * @param status - 该预设状态。
2380
2387
  * @returns 摘要文案,或 null(无需渲染)。
2381
2388
  */
2382
2389
  function presetPendingText(t, status) {
2383
- if (presetActions(status).length === 0) return null;
2384
- if (status.broken !== void 0 || status.unrecognized) return t("presetPendingReset");
2385
- if (!status.installed || compositionUnreadable(status)) return t("presetPendingInstall");
2390
+ if (status.source === "ours" && status.entryId === void 0) return null;
2391
+ if (status.unrecognized) return t("presetPendingBroken");
2386
2392
  if (status.conflicts.length === 0) return null;
2387
2393
  const names = presetConflictNames(t, status.conflicts);
2388
2394
  return t("presetPendingRows").replace("{count}", String(status.conflicts.length)).replace("{rows}", names.join(t("presetListSeparator")));
2389
2395
  }
2390
2396
  /**
2391
- * 执行后结果:成功(改了几处、改了哪几处、是否留了备份)/ 无变化 / 失败原因。
2397
+ * 执行后结果:成功(改了几处、改了哪几处)/ 无变化 / 失败原因。
2392
2398
  * 多行文本,`\n` 分段;调用方的结果行按 `white-space: pre-wrap` 渲染。
2393
2399
  *
2394
- * `action` 必须传:宿主只在 `upgrade` 时返回逐行改动(`reset` 是整份覆盖,
2395
- * `changes` 恒为空),所以重置不能套用"调整了 N 行"的句式 —— 否则会渲染成
2396
- * "调整了 0 处"这种假话。
2400
+ * `action` 必须传:宿主只在 `upgrade` 时返回逐行改动(`align` 是整份替换、
2401
+ * `revert` 是删覆盖,两者的 `changes` 恒为空),所以它们不能套用"调整了 N 行"
2402
+ * 的句式 —— 否则会渲染成"调整了 0 处"这种假话。
2403
+ * @param t - 文案表。
2404
+ * @param result - 宿主返回的执行结果。
2405
+ * @param action - 刚执行的动作。
2406
+ * @returns 结果文案。
2397
2407
  */
2398
2408
  function presetResultText(t, result, action) {
2399
2409
  if (!result.ok) return t("presetResultFailed").replace("{reason}", result.reason ?? t("presetFailUnknown"));
2400
- if (action === "reset") {
2401
- if (!result.changed) return t("presetResultResetNoChange");
2402
- const lines = [t("presetResultResetDone")];
2403
- if (result.backupPath !== void 0) lines.push(t("presetResultBackup"));
2404
- return lines.join("\n");
2405
- }
2406
- if (!result.changed) return t("presetResultNoChange");
2407
- const lines = [t("presetResultDone").replace("{count}", String(result.changes.length))];
2408
- if (result.changes.length > 0) {
2409
- const names = presetConflictNames(t, result.changes.map((change) => change.id));
2410
- lines.push(t("presetResultRows").replace("{rows}", names.join(t("presetListSeparator"))));
2410
+ const lines = [];
2411
+ if (action === "align") lines.push(result.changed ? t("presetResultAlignDone") : t("presetResultAlignNoChange"));
2412
+ else if (action === "revert") lines.push(result.changed ? t("presetResultRevertDone") : t("presetResultRevertNoChange"));
2413
+ else if (!result.changed) lines.push(t("presetResultNoChange"));
2414
+ else {
2415
+ lines.push(t("presetResultDone").replace("{count}", String(result.changes.length)));
2416
+ if (result.changes.length > 0) {
2417
+ const names = presetConflictNames(t, result.changes.map((change) => change.id));
2418
+ lines.push(t("presetResultRows").replace("{rows}", names.join(t("presetListSeparator"))));
2419
+ }
2411
2420
  }
2412
2421
  if (result.backupPath !== void 0) lines.push(t("presetResultBackup"));
2413
2422
  return lines.join("\n");
@@ -2496,6 +2505,16 @@ window.__ModuleLoader__.load({
2496
2505
  const [phase, setPhase] = (0, react.useState)("loading");
2497
2506
  const [presets, setPresets] = (0, react.useState)([]);
2498
2507
  const [templates, setTemplates] = (0, react.useState)([]);
2508
+ /**
2509
+ * 本部署有没有可编辑的 profile(宿主 `configEditor` 是否在场)。没有时面板
2510
+ * 只读 —— 一个动作都不给,说明行会讲清原因。
2511
+ */
2512
+ const [writable, setWritable] = (0, react.useState)(true);
2513
+ /**
2514
+ * 旧版目录机制留下的 `~/.dsh/.agent-presets`(0.1.7 起没有任何代码读它)。
2515
+ * 只作为"可以安全删除"的提示展示,不参与任何读写。
2516
+ */
2517
+ const [legacyRoot, setLegacyRoot] = (0, react.useState)(void 0);
2499
2518
  const [presetId, setPresetId] = (0, react.useState)(void 0);
2500
2519
  const [templateId, setTemplateId] = (0, react.useState)(void 0);
2501
2520
  /** 用户手动挑过模板之后就不再自动跟随预设(否则他刚选的会被覆盖掉)。 */
@@ -2505,7 +2524,8 @@ window.__ModuleLoader__.load({
2505
2524
  const [openPicker, setOpenPicker] = (0, react.useState)(null);
2506
2525
  const [busyId, setBusyId] = (0, react.useState)(null);
2507
2526
  const [results, setResults] = (0, react.useState)({});
2508
- const [confirmingId, setConfirmingId] = (0, react.useState)(null);
2527
+ /** 待二次确认的动作(动作 + 目标预设);`align` / `revert` 会动到已有内容。 */
2528
+ const [confirming, setConfirming] = (0, react.useState)(null);
2509
2529
  /**
2510
2530
  * 悬浮说明:portal 到 body 的 fixed 浮层 + 按按钮实时矩形算出的坐标。
2511
2531
  * 位置不能像之前那样用绝对定位挂在卡片里:设置面板的滚动容器是 `overflow-y:auto`,
@@ -2539,8 +2559,10 @@ window.__ModuleLoader__.load({
2539
2559
  setPhase("unavailable");
2540
2560
  return;
2541
2561
  }
2542
- setPresets(mergeBundledPresets(value.presets));
2562
+ setPresets(value.presets);
2543
2563
  setTemplates(Array.isArray(value.templates) ? value.templates : []);
2564
+ setWritable(value.writable !== false);
2565
+ setLegacyRoot(typeof value.legacyPresetRoot === "string" && value.legacyPresetRoot.length > 0 ? value.legacyPresetRoot : void 0);
2544
2566
  setPhase("ready");
2545
2567
  } catch {
2546
2568
  setPhase("unavailable");
@@ -2588,7 +2610,7 @@ window.__ModuleLoader__.load({
2588
2610
  ]);
2589
2611
  /** 执行一个动作;成功后重新拉状态与比较(结果行保留,直到下一次动作)。 */
2590
2612
  const runAction = (0, react.useCallback)((preset, action) => {
2591
- setConfirmingId(null);
2613
+ setConfirming(null);
2592
2614
  setBusyId(preset.id);
2593
2615
  setResults((prev) => ({
2594
2616
  ...prev,
@@ -2679,14 +2701,14 @@ window.__ModuleLoader__.load({
2679
2701
  "aria-haspopup": "menu",
2680
2702
  "aria-expanded": openPicker === key,
2681
2703
  onClick: () => setOpenPicker((open) => open === key ? null : key),
2682
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: current }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "tp-selectChevron" })]
2704
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: current }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: "tp-selectChevron" })]
2683
2705
  })
2684
2706
  })]
2685
2707
  });
2686
2708
  /** 选中那份的详情:名称/来源 → 动作 → 说明 → 待改 → 二次确认 → 结果。 */
2687
2709
  const detail = (preset) => {
2688
- const notes = presetNotes(t, preset);
2689
- const actions = presetActions(preset);
2710
+ const notes = presetNotes(t, preset, writable);
2711
+ const actions = presetActions(preset, writable);
2690
2712
  const pending = presetPendingText(t, preset);
2691
2713
  const result = results[preset.id] ?? null;
2692
2714
  const running = busyId === preset.id;
@@ -2710,20 +2732,20 @@ window.__ModuleLoader__.load({
2710
2732
  onMouseLeave: () => setHinted(null),
2711
2733
  children: actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2712
2734
  type: "button",
2713
- className: "tp-actionButton tpp-actionButton" + (action === "reset" ? " tpp-danger" : ""),
2735
+ className: "tp-actionButton tpp-actionButton" + (action === "align" ? " tpp-danger" : ""),
2714
2736
  disabled: running,
2715
2737
  "aria-description": presetActionHint(t, action, preset, templateLabel),
2716
2738
  onMouseEnter: (event) => showHint(action, event.currentTarget),
2717
2739
  onFocus: (event) => showHint(action, event.currentTarget),
2718
2740
  onBlur: () => setHinted(null),
2719
2741
  onClick: () => {
2720
- if (action === "reset") setConfirmingId(preset.id);
2742
+ if (presetActionNeedsConfirm(action)) setConfirming({
2743
+ id: preset.id,
2744
+ action
2745
+ });
2721
2746
  else runAction(preset, action);
2722
2747
  },
2723
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: running ? t("presetActionWorking") : presetActionText(t, action) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, {
2724
- className: "tpp-helpIcon",
2725
- "aria-hidden": "true"
2726
- })]
2748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: running ? t("presetActionWorking") : presetActionText(t, action) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutlineRegular, { className: "tpp-helpIcon" })]
2727
2749
  }, action))
2728
2750
  }) : null]
2729
2751
  }),
@@ -2741,24 +2763,24 @@ window.__ModuleLoader__.load({
2741
2763
  className: "tpp-state",
2742
2764
  children: pending
2743
2765
  }) : null,
2744
- confirmingId === preset.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2766
+ confirming !== null && confirming.id === preset.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2745
2767
  className: "tpp-confirm",
2746
2768
  role: "group",
2747
- "aria-label": t("presetActionReset"),
2769
+ "aria-label": t("presetConfirmTitle"),
2748
2770
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2749
2771
  className: "tpp-confirmText",
2750
- children: presetConfirmText(t, preset, selectedTemplateLabel)
2772
+ children: presetConfirmText(t, preset, confirming.action, selectedTemplateLabel)
2751
2773
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2752
2774
  className: "tpp-confirmActions",
2753
2775
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2754
2776
  type: "button",
2755
2777
  className: "tp-actionButton tpp-danger",
2756
- onClick: () => runAction(preset, "reset"),
2778
+ onClick: () => runAction(preset, confirming.action),
2757
2779
  children: t("presetConfirmYes")
2758
2780
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2759
2781
  type: "button",
2760
2782
  className: "tp-actionButton",
2761
- onClick: () => setConfirmingId(null),
2783
+ onClick: () => setConfirming(null),
2762
2784
  children: t("presetConfirmNo")
2763
2785
  })]
2764
2786
  })]
@@ -2805,7 +2827,7 @@ window.__ModuleLoader__.load({
2805
2827
  label: presetOptionLabel(preset)
2806
2828
  })), (id) => {
2807
2829
  setPresetId(id);
2808
- setConfirmingId(null);
2830
+ setConfirming(null);
2809
2831
  setDiffOpen(false);
2810
2832
  }),
2811
2833
  templates.length > 0 ? picker("template", t("presetPickTemplate"), selectedTemplate === void 0 ? t("presetEmpty") : presetTemplateLabel(selectedTemplate), templates.map((template) => ({
@@ -2845,7 +2867,11 @@ window.__ModuleLoader__.load({
2845
2867
  }) : null
2846
2868
  ]
2847
2869
  }),
2848
- selected !== void 0 ? detail(selected) : null
2870
+ selected !== void 0 ? detail(selected) : null,
2871
+ legacyRoot !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2872
+ className: "tpp-state",
2873
+ children: t("presetNoteLegacy").replace("{path}", legacyRoot)
2874
+ }) : null
2849
2875
  ] }) : null
2850
2876
  ]
2851
2877
  }),
@@ -3118,8 +3144,8 @@ window.__ModuleLoader__.load({
3118
3144
  }
3119
3145
  /** Render the Tool Plus settings section. */
3120
3146
  function ToolPlusSection(props) {
3121
- const { t, scope } = props;
3122
- const form = useToolForm(scope, ALL_FIELDS);
3147
+ const { t, form: configForm } = props;
3148
+ const form = useToolForm(configForm, ALL_FIELDS);
3123
3149
  const sectionRef = (0, react.useRef)(null);
3124
3150
  const tabsId = (0, react.useRef)(`tps-${Math.random().toString(36).slice(2, 8)}`);
3125
3151
  const tabRefs = (0, react.useRef)([]);
@@ -3129,9 +3155,9 @@ window.__ModuleLoader__.load({
3129
3155
  const [actionResults, setActionResults] = (0, react.useState)({});
3130
3156
  const [rmSafeToast, setRmSafeToast] = (0, react.useState)(null);
3131
3157
  const handleSave = (0, react.useCallback)(async () => {
3132
- const before = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
3158
+ const before = Boolean(configForm.getSnapshot().value?.rmSafe ?? true);
3133
3159
  await form.actions.save();
3134
- const after = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
3160
+ const after = Boolean(configForm.getSnapshot().value?.rmSafe ?? true);
3135
3161
  if (before === after || !after) return;
3136
3162
  try {
3137
3163
  const result = await createWebConnectionRpc().call(TOOL_PLUS_RPC_CHANNEL, RM_SAFE_STATUS_ENDPOINT, {});
@@ -3144,7 +3170,7 @@ window.__ModuleLoader__.load({
3144
3170
  });
3145
3171
  } catch {}
3146
3172
  }, [
3147
- scope,
3173
+ configForm,
3148
3174
  form,
3149
3175
  t
3150
3176
  ]);
@@ -3403,7 +3429,7 @@ window.__ModuleLoader__.load({
3403
3429
  }),
3404
3430
  rmSafeToast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
3405
3431
  text: rmSafeToast.text,
3406
- icon: rmSafeToast.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}),
3432
+ icon: rmSafeToast.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
3407
3433
  onDone: () => {
3408
3434
  setRmSafeToast(null);
3409
3435
  }
@@ -3607,13 +3633,18 @@ window.__ModuleLoader__.load({
3607
3633
  invalidNumber: "请填写数字;留空表示使用默认值。",
3608
3634
  appliesTo: "保存后对新调用生效。",
3609
3635
  groupPresets: "预设",
3610
- presetNoteBroken: "读取失败:{reason}",
3611
- presetNoteUnrecognized: "无法识别内容,未做任何改动。",
3636
+ presetNoteBroken: "无法挂载:{reason}",
3637
+ presetNoteUnrecognized: "插件行列表读不出来,未做任何改动。",
3638
+ presetNoteNoRow: "这个 profile 里没有本插件的声明行——插件不是以 profile bundle 装的。",
3639
+ presetNoteOther: "这个预设由别处声明,本面板只替它修冲突的工具行。",
3640
+ presetNoteCustomized: "这一行在你的 profile 配置里有覆盖;「恢复随包」可撤销。",
3641
+ presetNoteNoProfile: "本部署没有可编辑的 profile,只能查看。",
3642
+ presetNoteLegacy: "检测到旧版预设目录 {path}:0.1.7 起没有任何代码读它,可以安全删除。",
3612
3643
  presetPickPreset: "预设",
3613
3644
  presetPickTemplate: "对比模板",
3614
3645
  presetToolOk: "已接入本插件工具",
3615
3646
  presetToolWarn: "还有 {count} 个工具行未处理",
3616
- presetToolError: "读不到这份预设",
3647
+ presetToolError: "读不到这份预设的插件行",
3617
3648
  presetToolLoading: "正在检查…",
3618
3649
  presetCompareOk: "与所选模板一致",
3619
3650
  presetCompareWarn: "与所选模板有 {count} 处不同",
@@ -3630,25 +3661,30 @@ window.__ModuleLoader__.load({
3630
3661
  presetDiffWholeRow: "(整行)",
3631
3662
  presetDiffEmpty: "内容一致,没有可显示的差异。",
3632
3663
  presetActionUpdate: "最小更新",
3633
- presetActionUpdateHint: "只禁用这 {count} 个工具行({rows}),其他内容保持不动。",
3634
- presetActionUpdateNothing: "当前没有需要更新的工具行。",
3635
- presetActionReset: "重置为对比模板",
3636
- presetActionResetHint: "用所选模板「{template}」整份覆盖这份预设。原文件会先备份,本地改动会丢失。",
3664
+ presetActionUpdateHint: "只把这 {count} 个工具行({rows})关掉,其他内容一个字节都不动。",
3665
+ presetActionUpdateNothing: "当前没有需要关掉的工具行。",
3666
+ presetActionAlign: "对齐模板",
3667
+ presetActionAlignHint: "用所选模板「{template}」整份替换这一行的插件列表。",
3668
+ presetActionRevert: "恢复随包",
3669
+ presetActionRevertHint: "删掉你写在本 profile 配置里的这份预设的覆盖,回落到随包声明。",
3637
3670
  presetActionWorking: "处理中…",
3638
- presetPendingReset: "将用所选模板整份覆盖这份预设,本地改动会被覆盖。",
3639
- presetPendingInstall: "将写入这份预设:它当前尚未安装。",
3640
3671
  presetPendingRows: "工具行将调整这 {count} 处:{rows}",
3641
- presetConfirmReset: "用模板「{template}」整份覆盖会丢掉本地改动(原文件会先备份一份)。确认重置?",
3642
- presetConfirmInstall: "将从模板「{template}」写入这份预设;当前没有会被覆盖的内容。",
3643
- presetConfirmYes: "确认重置",
3672
+ presetPendingBroken: "插件行列表读不出来;「对齐模板」可以整份修好它。",
3673
+ presetConfirmTitle: "确认动作",
3674
+ presetConfirmAlign: "将用模板「{template}」整份替换这份预设的插件列表,并写入本 profile 的配置(cordis.patch.yml)。",
3675
+ presetConfirmAlignEdits: "你在这一行上的改动会被覆盖。",
3676
+ presetConfirmRevert: "将删掉你写在本 profile 配置里的这份预设的覆盖,回落到随包声明。你的改动会丢失。",
3677
+ presetConfirmYes: "确认",
3644
3678
  presetConfirmNo: "取消",
3645
3679
  presetResultDone: "已调整 {count} 处。",
3646
3680
  presetResultRows: "涉及:{rows}",
3647
3681
  presetResultNoChange: "没有需要改动的行。",
3648
- presetResultResetDone: "已重置为所选模板。",
3649
- presetResultResetNoChange: "已经与所选模板一致,没有改动。",
3682
+ presetResultAlignDone: "已按所选模板对齐,并写入本 profile 的配置。",
3683
+ presetResultAlignNoChange: "已经与所选模板一致,没有改动。",
3684
+ presetResultRevertDone: "已删掉覆盖,回落到随包声明。",
3685
+ presetResultRevertNoChange: "这一行本来就没有覆盖,没有改动。",
3686
+ presetResultBackup: "改动前的内容已备份(与本 profile 的补丁同目录,.bak-<版本>)。",
3650
3687
  presetResultFailed: "操作失败:{reason}",
3651
- presetResultBackup: "原文件已备份。",
3652
3688
  presetFailUnknown: "未知原因",
3653
3689
  presetEntryCommand: "命令执行",
3654
3690
  presetEntryPwsh: "PowerShell",
@@ -3853,13 +3889,18 @@ window.__ModuleLoader__.load({
3853
3889
  invalidNumber: "Enter a number, or leave blank to use the default.",
3854
3890
  appliesTo: "Values apply to new calls.",
3855
3891
  groupPresets: "Presets",
3856
- presetNoteBroken: "Could not be read: {reason}",
3857
- presetNoteUnrecognized: "Content not recognized; nothing was changed.",
3892
+ presetNoteBroken: "Cannot be mounted: {reason}",
3893
+ presetNoteUnrecognized: "The plugin row list cannot be read; nothing was changed.",
3894
+ presetNoteNoRow: "This profile has no declaration row for the plugin — it is not installed as a profile bundle.",
3895
+ presetNoteOther: "Another bundle declares this preset; this panel only fixes its conflicting tool rows.",
3896
+ presetNoteCustomized: "This row carries an override in your profile configuration; \"Revert to bundled\" removes it.",
3897
+ presetNoteNoProfile: "This deployment has no editable profile, so the panel is read-only.",
3898
+ presetNoteLegacy: "A legacy preset directory was found at {path}: nothing reads it since 0.1.7, so it is safe to delete.",
3858
3899
  presetPickPreset: "Preset",
3859
3900
  presetPickTemplate: "Compare with template",
3860
3901
  presetToolOk: "Plugin tools wired in",
3861
3902
  presetToolWarn: "{count} tool rows left",
3862
- presetToolError: "This preset cannot be read",
3903
+ presetToolError: "Plugin rows for this preset cannot be read",
3863
3904
  presetToolLoading: "Checking…",
3864
3905
  presetCompareOk: "Matches the selected template",
3865
3906
  presetCompareWarn: "{count} differences vs the selected template",
@@ -3876,25 +3917,30 @@ window.__ModuleLoader__.load({
3876
3917
  presetDiffWholeRow: "(whole row)",
3877
3918
  presetDiffEmpty: "Content matches; nothing to show.",
3878
3919
  presetActionUpdate: "Minimal update",
3879
- presetActionUpdateHint: "Disables only these {count} tool rows ({rows}); everything else stays as it is.",
3880
- presetActionUpdateNothing: "No tool rows need updating.",
3881
- presetActionReset: "Reset to the compared template",
3882
- presetActionResetHint: "Overwrites this preset with the selected template \"{template}\". The original is backed up first and local edits are lost.",
3920
+ presetActionUpdateHint: "Disables only these {count} tool rows ({rows}); nothing else changes at all.",
3921
+ presetActionUpdateNothing: "No tool rows need disabling.",
3922
+ presetActionAlign: "Align to template",
3923
+ presetActionAlignHint: "Replaces this row whole plugin list with the selected template \"{template}\".",
3924
+ presetActionRevert: "Revert to bundled",
3925
+ presetActionRevertHint: "Deletes the override written in this profile configuration, falling back to the bundled declaration.",
3883
3926
  presetActionWorking: "Working…",
3884
- presetPendingReset: "The whole preset will be replaced with the selected template; local changes will be lost.",
3885
- presetPendingInstall: "This preset will be written: it is not installed yet.",
3886
3927
  presetPendingRows: "Tool rows to adjust: {count} ({rows})",
3887
- presetConfirmReset: "Overwriting with template \"{template}\" discards local changes (the original file is backed up first). Reset?",
3888
- presetConfirmInstall: "This preset will be written from template \"{template}\"; nothing will be overwritten.",
3889
- presetConfirmYes: "Reset",
3928
+ presetPendingBroken: "The plugin row list cannot be read; \"Align to template\" replaces it with a good one.",
3929
+ presetConfirmTitle: "Confirm action",
3930
+ presetConfirmAlign: "The whole plugin list will be replaced with template \"{template}\" and written to this profile configuration (cordis.patch.yml).",
3931
+ presetConfirmAlignEdits: "Your edits to this row will be overwritten.",
3932
+ presetConfirmRevert: "The override written in this profile configuration will be deleted, falling back to the bundled declaration. Your edits will be lost.",
3933
+ presetConfirmYes: "Confirm",
3890
3934
  presetConfirmNo: "Cancel",
3891
3935
  presetResultDone: "Adjusted {count} entries.",
3892
3936
  presetResultRows: "Entries: {rows}",
3893
3937
  presetResultNoChange: "Nothing needed changing.",
3894
- presetResultResetDone: "Reset to the selected template.",
3895
- presetResultResetNoChange: "Already matches the selected template; nothing changed.",
3938
+ presetResultAlignDone: "Aligned to the selected template and written to this profile configuration.",
3939
+ presetResultAlignNoChange: "Already matches the selected template; nothing changed.",
3940
+ presetResultRevertDone: "Override removed; the preset falls back to its bundled declaration.",
3941
+ presetResultRevertNoChange: "This row carried no override; nothing changed.",
3942
+ presetResultBackup: "The previous content was backed up beside this profile patch (.bak-<version>).",
3896
3943
  presetResultFailed: "Action failed: {reason}",
3897
- presetResultBackup: "The original file was backed up.",
3898
3944
  presetFailUnknown: "unknown reason",
3899
3945
  presetEntryCommand: "Command execution",
3900
3946
  presetEntryPwsh: "PowerShell",
@@ -4237,6 +4283,9 @@ window.__ModuleLoader__.load({
4237
4283
  expandRest: "… 其余 {count} 行",
4238
4284
  "files.one": "{count} 个文件",
4239
4285
  "files.other": "{count} 个文件",
4286
+ "codeBlock.title": "代码块",
4287
+ "codeBlock.wrap": "自动换行",
4288
+ "codeBlock.unwrap": "取消自动换行",
4240
4289
  running: "运行中",
4241
4290
  done: "完成",
4242
4291
  failed: "失败",
@@ -4278,6 +4327,7 @@ window.__ModuleLoader__.load({
4278
4327
  "terminal.failed": "失败",
4279
4328
  "terminal.done": "已完成",
4280
4329
  "terminal.noOutput": "无输出",
4330
+ "terminal.noExitCode": "未正常退出",
4281
4331
  "bash.background": "后台任务 {jobId}",
4282
4332
  "image.loading": "加载中",
4283
4333
  "image.loadFailed": "加载失败",
@@ -4301,6 +4351,9 @@ window.__ModuleLoader__.load({
4301
4351
  expandRest: "… {count} more lines",
4302
4352
  "files.one": "{count} file",
4303
4353
  "files.other": "{count} files",
4354
+ "codeBlock.title": "Code block",
4355
+ "codeBlock.wrap": "Wrap lines",
4356
+ "codeBlock.unwrap": "Do not wrap lines",
4304
4357
  running: "Running",
4305
4358
  done: "Done",
4306
4359
  failed: "Failed",
@@ -4342,6 +4395,7 @@ window.__ModuleLoader__.load({
4342
4395
  "terminal.failed": "Failed",
4343
4396
  "terminal.done": "Done",
4344
4397
  "terminal.noOutput": "No output",
4398
+ "terminal.noExitCode": "no exit code",
4345
4399
  "bash.background": "Background job {jobId}",
4346
4400
  "image.loading": "Loading",
4347
4401
  "image.loadFailed": "Load failed",
@@ -4372,6 +4426,22 @@ window.__ModuleLoader__.load({
4372
4426
  return block.kind === "tool-result";
4373
4427
  }
4374
4428
  /**
4429
+ * Whether a call already landed as a failed result.
4430
+ *
4431
+ * Only a settled result can be wrong about its work: the union's error state is
4432
+ * `ToolResultNode.isError` alone, and the shipped row model reads it the same
4433
+ * way — settle on the `kind` discriminant (`'kind' in block`) and only then look
4434
+ * at `error`, `isError`. A preparing or dispatched call has no result yet, so it
4435
+ * is never an error; the rows that fall back to the generic shell keep exactly
4436
+ * the criterion they have always had (`isError === true`), stated where the
4437
+ * union still allows it.
4438
+ * @param block - the frozen running-or-settled call node.
4439
+ * @returns whether the call settled as a failure.
4440
+ */
4441
+ function isErrorResult(block) {
4442
+ return isSettled(block) && block.isError === true;
4443
+ }
4444
+ /**
4375
4445
  * The call head paired with a block: a running call is its own head; a settled
4376
4446
  * result carries the head backfilled from the in-window `tool/call`, which is
4377
4447
  * `null` when window truncation left that event outside.
@@ -4444,7 +4514,7 @@ window.__ModuleLoader__.load({
4444
4514
  function cardState(block) {
4445
4515
  if (!isSettled(block)) return "running";
4446
4516
  if (isRecord$2(block.error) && block.error.code === "interrupted") return "warning";
4447
- if (block.isError === true) return "error";
4517
+ if (isErrorResult(block)) return "error";
4448
4518
  const terminal = narrowTerminalCardMeta(cardMeta(block));
4449
4519
  if (terminal !== null && terminal.mode === "foreground" && (terminal.timedOut || terminal.aborted)) return "warning";
4450
4520
  return "ok";
@@ -4626,21 +4696,39 @@ window.__ModuleLoader__.load({
4626
4696
  timedOut
4627
4697
  };
4628
4698
  }
4699
+ /**
4700
+ * Bind the card dictionary to the shared code-card toolbar labels.
4701
+ *
4702
+ * The toolbar rides every code surface the primitives draw (the diff block, the
4703
+ * read block), and the host's own adapter for it reads the base locale's
4704
+ * `codeBlock.*` wording; this dictionary carries the same three strings, so a
4705
+ * takeover's code cards read exactly as the shipped ones do.
4706
+ * @param t - the card translate seat.
4707
+ * @returns the language fallback and wrapping actions.
4708
+ */
4709
+ function codeToolbarLabels(t) {
4710
+ return {
4711
+ codeLabel: t("codeBlock.title"),
4712
+ wrapLabel: t("codeBlock.wrap"),
4713
+ unwrapLabel: t("codeBlock.unwrap")
4714
+ };
4715
+ }
4629
4716
  /** Bind the card dictionary to the diff primitive's chrome labels. */
4630
4717
  function diffBlockLabels(t) {
4631
4718
  return {
4719
+ ...codeToolbarLabels(t),
4632
4720
  copy: t("copy"),
4633
4721
  copied: t("copied"),
4634
4722
  collapseAria: t("collapseAria"),
4635
4723
  expandAria: (count) => t("expandAria", { count }),
4636
4724
  collapse: t("collapse"),
4637
- expand: (count) => t("expandRest", { count }),
4638
- files: (count) => t(count === 1 ? "files.one" : "files.other", { count })
4725
+ expand: (count) => t("expandRest", { count })
4639
4726
  };
4640
4727
  }
4641
4728
  /** Bind the card dictionary to the read primitive's chrome labels. */
4642
4729
  function readBlockLabels(t) {
4643
4730
  return {
4731
+ ...codeToolbarLabels(t),
4644
4732
  window: (shown, total) => t("read.window", {
4645
4733
  shown,
4646
4734
  total
@@ -4679,6 +4767,7 @@ window.__ModuleLoader__.load({
4679
4767
  return {
4680
4768
  signal: (signal) => t("terminal.signal", { signal }),
4681
4769
  exitCode: (exitCode) => t("terminal.exitCode", { code: exitCode }),
4770
+ noExitCode: t("terminal.noExitCode"),
4682
4771
  running: t("terminal.running"),
4683
4772
  failed: t("terminal.failed"),
4684
4773
  done: t("terminal.done"),
@@ -5038,11 +5127,11 @@ window.__ModuleLoader__.load({
5038
5127
  */
5039
5128
  /** Leading glyphs (the shipped table): every glyph renders at 14 inside the 16px leading box. */
5040
5129
  const LEADING_GLYPHS = {
5041
- bash: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, { size: 14 }),
5042
- read: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
5043
- edit: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
5044
- search: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
5045
- others: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
5130
+ bash: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutlineRegular, { size: 14 }),
5131
+ read: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutlineRegular, { size: 14 }),
5132
+ edit: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, { size: 14 }),
5133
+ search: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 14 }),
5134
+ others: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkleRegular, { size: 14 })
5046
5135
  };
5047
5136
  /**
5048
5137
  * The leading mark of one row: the tool's glyph while the call is clean or
@@ -5363,7 +5452,7 @@ window.__ModuleLoader__.load({
5363
5452
  const { t, block, cwd, inspect, openFile, title, toolName } = props;
5364
5453
  const args = parseCardArgs(block);
5365
5454
  const meta = narrowAstEditCardMeta(cardMeta(block));
5366
- if (isSubCall(block) || block.isError === true || meta === null || meta.preview === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5455
+ if (isSubCall(block) || isErrorResult(block) || meta === null || meta.preview === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5367
5456
  t,
5368
5457
  title,
5369
5458
  block,
@@ -5450,7 +5539,7 @@ window.__ModuleLoader__.load({
5450
5539
  const args = parseCardArgs(block);
5451
5540
  const meta = narrowEditCardMeta(cardMeta(block));
5452
5541
  const replaceForm = replaceHunks(args);
5453
- if (isSubCall(block) || block.isError === true || meta === null && replaceForm === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5542
+ if (isSubCall(block) || isErrorResult(block) || meta === null && replaceForm === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5454
5543
  t,
5455
5544
  title,
5456
5545
  block,
@@ -5665,7 +5754,7 @@ window.__ModuleLoader__.load({
5665
5754
  const args = parseCardArgs(block);
5666
5755
  const meta = narrowReadCardMeta(cardMeta(block));
5667
5756
  const material = (0, react.useMemo)(() => imageCardMaterial(block, cwd), [block, cwd]);
5668
- if (isSubCall(block) || block.isError === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5757
+ if (isSubCall(block) || isErrorResult(block)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5669
5758
  t,
5670
5759
  title,
5671
5760
  block,
@@ -5948,7 +6037,7 @@ window.__ModuleLoader__.load({
5948
6037
  className: "twc-lightboxClose",
5949
6038
  "aria-label": t("image.close"),
5950
6039
  onClick: onClose,
5951
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 16 })
6040
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 16 })
5952
6041
  })
5953
6042
  ]
5954
6043
  }), document.body);
@@ -6030,7 +6119,7 @@ window.__ModuleLoader__.load({
6030
6119
  const { t, block, cwd, inspect, title, toolName } = props;
6031
6120
  const args = parseCardArgs(block);
6032
6121
  const meta = narrowSearchCardMeta(cardMeta(block));
6033
- if (isSubCall(block) || block.isError === true || meta === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6122
+ if (isSubCall(block) || isErrorResult(block) || meta === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6034
6123
  t,
6035
6124
  title,
6036
6125
  block,
@@ -6109,7 +6198,7 @@ window.__ModuleLoader__.load({
6109
6198
  const meta = narrowTerminalCardMeta(cardMeta(block));
6110
6199
  const command = argText(args, "command");
6111
6200
  const state = cardState(block);
6112
- if (block.isError === true || command === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6201
+ if (isErrorResult(block) || command === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6113
6202
  t,
6114
6203
  title,
6115
6204
  block,
@@ -6197,7 +6286,7 @@ window.__ModuleLoader__.load({
6197
6286
  const meta = narrowWriteCardMeta(cardMeta(block));
6198
6287
  const content = argText(args, "content");
6199
6288
  const path = meta?.path ?? argText(args, "path");
6200
- if (isSubCall(block) || block.isError === true || content === null || path === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6289
+ if (isSubCall(block) || isErrorResult(block) || content === null || path === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6201
6290
  t,
6202
6291
  title,
6203
6292
  block,
@@ -6323,13 +6412,13 @@ window.__ModuleLoader__.load({
6323
6412
  disposeEntries?.();
6324
6413
  disposeEntries = void 0;
6325
6414
  };
6326
- const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NS });
6415
+ const form = ctx.configForms.get(SETTINGS_NS);
6327
6416
  ctx.effect(() => {
6328
6417
  const sync = () => {
6329
- if (cardsEnabled(scope.getSnapshot().value)) install();
6418
+ if (cardsEnabled(form.getSnapshot().value)) install();
6330
6419
  else uninstall();
6331
6420
  };
6332
- const stop = scope.subscribe(sync);
6421
+ const stop = form.subscribe(sync);
6333
6422
  sync();
6334
6423
  return stop;
6335
6424
  }, "tool-plus: web card switch");
@@ -6385,14 +6474,14 @@ window.__ModuleLoader__.load({
6385
6474
  const BASH_PLUS_CLIENT_NS = "tool-plus";
6386
6475
  /** Locale dictionary namespace of this package's card copy. */
6387
6476
  const BASH_PLUS_LOCALE_NS = "tool-plus";
6388
- /** Required services (cordis fiber inject): slots + locale + the settings scope binder. */
6477
+ /** Required services (cordis fiber inject): slots + locale + the shared configuration forms. */
6389
6478
  const inject = [
6390
6479
  "slots",
6391
6480
  "locale",
6392
- "settingsScope"
6481
+ "configForms"
6393
6482
  ];
6394
6483
  /**
6395
- * Mount the tool-plus settings card.
6484
+ * Mount the tool-plus settings surfaces.
6396
6485
  * @param ctx - the browser plugin context.
6397
6486
  */
6398
6487
  function apply(ctx) {
@@ -6400,23 +6489,25 @@ window.__ModuleLoader__.load({
6400
6489
  zh: zh$1,
6401
6490
  en: en$1
6402
6491
  }), "tool-plus: card dictionaries");
6403
- const scope = ctx.settingsScope.bind({ namespace: BASH_PLUS_CLIENT_NS });
6404
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
6405
- name: "settings.plugin.item",
6406
- key: BASH_PLUS_CLIENT_NS,
6492
+ const cardForm = ctx.configForms.get(BASH_PLUS_CLIENT_NS);
6493
+ const sectionForm = ctx.configForms.get(BASH_PLUS_CLIENT_NS);
6494
+ ctx.effect(() => ctx.configForms.whileServed([BASH_PLUS_CLIENT_NS], () => ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
6495
+ name: "settings.plugins.tab",
6496
+ id: BASH_PLUS_CLIENT_NS,
6497
+ order: 20,
6498
+ label: () => ctx.locale.bind(BASH_PLUS_LOCALE_NS)("title"),
6407
6499
  locale: BASH_PLUS_LOCALE_NS,
6408
- inject: () => ({ scope })
6409
- }, BashPlusCard));
6500
+ inject: () => ({ form: cardForm })
6501
+ }, BashPlusCard))), "tool-plus: plugins page");
6410
6502
  registerToolCards(ctx);
6411
- const sectionScope = ctx.settingsScope.bind({ namespace: BASH_PLUS_CLIENT_NS });
6412
- ctx.slots.inject("settings.section", () => ctx.slots.register({
6503
+ ctx.effect(() => ctx.configForms.whileServed([BASH_PLUS_CLIENT_NS], () => ctx.slots.inject("settings.section", () => ctx.slots.register({
6413
6504
  name: "settings.section",
6414
6505
  id: "tool-plus",
6415
6506
  order: 16,
6416
6507
  label: () => ctx.locale.bind(BASH_PLUS_LOCALE_NS)("nav"),
6417
6508
  locale: BASH_PLUS_LOCALE_NS,
6418
- inject: () => ({ scope: sectionScope })
6419
- }, ToolPlusSection));
6509
+ inject: () => ({ form: sectionForm })
6510
+ }, ToolPlusSection))), "tool-plus: settings page");
6420
6511
  }
6421
6512
  //#endregion
6422
6513
  exports.BASH_PLUS_CLIENT_NS = BASH_PLUS_CLIENT_NS;