@xiaoso/dsh-tool-plus 0.1.9 → 0.1.10-beta.2

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 +377 -906
  5. package/lib/index.mjs +885 -1087
  6. package/lib/{scrapers-mhvFrvjH.mjs → scrapers-DSArdJ6N.mjs} +1 -1
  7. package/lib/types/client/ToolPlusSection.d.ts +3 -3
  8. package/lib/types/client/forms.d.ts +6 -6
  9. package/lib/types/client/index.d.ts +29 -12
  10. package/lib/types/client/locales.d.ts +1 -1
  11. package/lib/types/client/preset-panel.d.ts +54 -42
  12. package/lib/types/config/settings.d.ts +202 -21
  13. package/lib/types/host/browser-probe-rpc.d.ts +2 -1
  14. package/lib/types/host/preset-host.d.ts +103 -0
  15. package/lib/types/host/preset-rpc.d.ts +37 -72
  16. package/lib/types/presets/compare.d.ts +14 -37
  17. package/lib/types/presets/conflicts.d.ts +59 -0
  18. package/lib/types/presets/delta.d.ts +23 -18
  19. package/lib/types/presets/plan.d.ts +55 -0
  20. package/lib/types/presets/rows.d.ts +84 -0
  21. package/lib/types/presets/status.d.ts +56 -47
  22. package/lib/types/tools/bash/background.d.ts +20 -5
  23. package/lib/types/tools/bash/streaming-output.d.ts +4 -2
  24. package/lib/types/tools/shared/browser-rpc-channel.d.ts +84 -42
  25. package/lib/types/web/client/labels.d.ts +1 -1
  26. package/lib/types/web/client/row-utils.d.ts +26 -1
  27. package/lib/{utils-eiUz9n8I.mjs → utils-DA8R1_Q8.mjs} +1 -1
  28. package/package.json +166 -151
  29. package/presets/tool-plus-ptc.patch.yml +164 -0
  30. package/presets/tool-plus-standard.patch.yml +158 -0
  31. package/lib/types/client/BashPlusCard.d.ts +0 -53
  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
@@ -6,8 +6,8 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
- let react_jsx_runtime = require("react/jsx-runtime");
10
9
  let _deepseek_ai_dsh_client_connection_client = require("@deepseek-ai/dsh-client-connection/client");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let react_dom = require("react-dom");
12
12
  //#region src/config/fields.ts
13
13
  /** Field group ids -> locale key of the group heading. */
@@ -783,15 +783,133 @@ window.__ModuleLoader__.load({
783
783
  fields: TOOL_PLUS_FIELDS.filter((f) => f.tool === "astEdit").map((f) => f.name)
784
784
  }
785
785
  ];
786
- /** Look up one field definition by its schema key. */
787
- function toolPlusField(name) {
788
- return TOOL_PLUS_FIELDS.find((field) => field.name === name);
786
+ TOOL_PLUS_FIELDS.map((field) => field.name);
787
+ //#endregion
788
+ //#region src/tools/shared/browser-rpc-channel.ts
789
+ /**
790
+ * Shared RPC channel constants for the browser-detection round trip between
791
+ * the settings panel (client half) and the host. Both halves import this
792
+ * module so the channel/endpoint never drift.
793
+ *
794
+ * The channel rides the official `@deepseek-ai/dsh-client-connection` generic
795
+ * RPC transport (host registers with `ctx.connection.rpc.handle`; the client
796
+ * calls via `createWebConnectionRpc().call`). Channel names must match
797
+ * `/^\/[A-Za-z0-9._~-]+$/` and `/api` is reserved — `/tool-plus` is ours.
798
+ * @module @xiaoso/dsh-tool-plus/browser-rpc-channel
799
+ */
800
+ /** Logical RPC channel registered by the host half of this plugin. */
801
+ const TOOL_PLUS_RPC_CHANNEL = "/tool-plus";
802
+ /** Endpoint that reports the rmSafe injection status (query = ensure + verify). */
803
+ const RM_SAFE_STATUS_ENDPOINT = "rmSafe/status";
804
+ /** Endpoint that lists agent presets with their status (roster + conflicts + template diff). */
805
+ const PRESET_STATUS_ENDPOINT = "presets/status";
806
+ /**
807
+ * The preset ids this plugin **declares**. Single source of truth for both
808
+ * halves: the host picks its own declarations out of the profile's composed
809
+ * configuration, and the panel uses the list to tell "ours" from everything else.
810
+ *
811
+ * They ship as `@deepseek-ai/dsh-agent-preset` declaration rows in this
812
+ * package's own bundle patches (`presets/<id>.patch.yml`), so installing the
813
+ * plugin as a profile bundle declares them: no install step, no file copy, and
814
+ * nothing written at startup.
815
+ */
816
+ const BUNDLED_PRESET_IDS = ["tool-plus-standard", "tool-plus-ptc"];
817
+ /** Endpoint that applies one preset action to the active profile. */
818
+ const PRESET_ACTION_ENDPOINT = "presets/apply";
819
+ /** Endpoint that compares one preset against one of our declarations (read-only). */
820
+ const PRESET_COMPARE_ENDPOINT = "presets/compare";
821
+ //#endregion
822
+ //#region src/client/web-connection-rpc.ts
823
+ /**
824
+ * Browser caller for the plugin's Connection RPC channel.
825
+ *
826
+ * The official `@deepseek-ai/dsh-client-connection/client` package ships the
827
+ * protocol primitives as types (`RpcFetch`, `ClientConnectionRpc`, `RpcResult`)
828
+ * and the `RpcId` brand, but its published `createWebConnectionRpc` factory is
829
+ * internal to the bundle — not re-exported. This module re-implements that
830
+ * factory against the same public wire contract (docs/api-gateway.md, the
831
+ * client `rpc.ts` source): a `POST {origin}/{channel}/{endpoint}` with a
832
+ * `client-request` JSON envelope, answered by a `server-response` envelope
833
+ * echoing the rpcId. Host-side registration uses the official
834
+ * `ctx.connection.rpc.handle` — this file only mirrors the caller half.
835
+ * @module @xiaoso/dsh-tool-plus/client/web-connection-rpc
836
+ */
837
+ const INTERNAL_BASE = "http://dsh.internal";
838
+ const CHANNEL_PATTERN = /^\/[A-Za-z0-9._~-]+$/;
839
+ const ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
840
+ function randomUuid() {
841
+ const g = globalThis;
842
+ if (g.crypto?.randomUUID !== void 0) return g.crypto.randomUUID();
843
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
844
+ const r = Math.random() * 16 | 0;
845
+ return (c === "x" ? r : r & 3 | 8).toString(16);
846
+ });
789
847
  }
790
- /** Fields of one tool tab, in table order. */
791
- function toolPlusFieldsOf(tool) {
792
- return TOOL_PLUS_FIELDS.filter((field) => field.tool === tool);
848
+ function isRecord$4(value) {
849
+ return typeof value === "object" && value !== null && !Array.isArray(value);
850
+ }
851
+ function parseConnectionResponse(value) {
852
+ if (!isRecord$4(value) || value.type !== "server-response" || typeof value.rpcId !== "string") throw new TypeError("connection: invalid server-response envelope");
853
+ const result = value.result;
854
+ if (!isRecord$4(result)) throw new TypeError("connection: invalid server-response result");
855
+ if (result.ok === true) return {
856
+ rpcId: value.rpcId,
857
+ result: {
858
+ ok: true,
859
+ value: result.value
860
+ }
861
+ };
862
+ if (result.ok !== false || !isRecord$4(result.error)) throw new TypeError("connection: invalid server-response result");
863
+ const error = result.error;
864
+ if (typeof error.code !== "string" || typeof error.message !== "string" || !isRecord$4(error.details)) throw new TypeError("connection: invalid server-response failure");
865
+ return {
866
+ rpcId: value.rpcId,
867
+ result: {
868
+ ok: false,
869
+ error: {
870
+ code: error.code,
871
+ message: error.message,
872
+ details: error.details
873
+ }
874
+ }
875
+ };
876
+ }
877
+ function resolveBase() {
878
+ const location = globalThis.location;
879
+ return location?.origin !== void 0 && location.origin !== "null" ? location.origin : INTERNAL_BASE;
880
+ }
881
+ function assertTarget(channel, endpoint) {
882
+ const segments = endpoint.split("/");
883
+ if (!CHANNEL_PATTERN.test(channel) || segments.some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT_PATTERN.test(segment))) throw new Error(`connection: invalid RPC target ${JSON.stringify(`${channel}/${endpoint}`)}`);
884
+ }
885
+ /**
886
+ * Create the browser-backed generic RPC caller for the plugin's channel
887
+ * (the `/tool-plus` host registration). Wire-compatible with the official
888
+ * ClientConnectionRpc contract; only the transport override differs.
889
+ * @param doFetch - transport override; defaults to the page's global fetch.
890
+ */
891
+ function createWebConnectionRpc(doFetch) {
892
+ const send = doFetch ?? ((input, init) => globalThis.fetch(input, init));
893
+ return { async call(channel, endpoint, payload, signal) {
894
+ assertTarget(channel, endpoint);
895
+ const rpcId = (0, _deepseek_ai_dsh_client_connection_client.RpcId)(randomUuid());
896
+ const response = await send(new URL(`${channel}/${endpoint}`, resolveBase()), {
897
+ method: "POST",
898
+ headers: { "content-type": "application/json" },
899
+ body: JSON.stringify({
900
+ type: "client-request",
901
+ rpcId,
902
+ method: endpoint,
903
+ payload
904
+ }),
905
+ ...signal === void 0 ? {} : { signal }
906
+ });
907
+ if (!response.ok) throw new Error(`transport failure for ${channel}/${endpoint}: HTTP ${response.status}`);
908
+ const full = parseConnectionResponse(await response.json());
909
+ if (full.rpcId !== rpcId) throw new Error(`rpcId mismatch for ${endpoint}: sent ${rpcId}, got ${full.rpcId}`);
910
+ return full.result;
911
+ } };
793
912
  }
794
- TOOL_PLUS_FIELDS.map((field) => field.name);
795
913
  //#endregion
796
914
  //#region src/client/rows.tsx
797
915
  /**
@@ -992,7 +1110,7 @@ window.__ModuleLoader__.load({
992
1110
  "aria-expanded": open,
993
1111
  disabled,
994
1112
  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" })]
1113
+ 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
1114
  })
997
1115
  })
998
1116
  ]
@@ -1081,731 +1199,6 @@ window.__ModuleLoader__.load({
1081
1199
  });
1082
1200
  }
1083
1201
  //#endregion
1084
- //#region src/client/BashPlusCard.tsx
1085
- /**
1086
- * The tool-plus settings card — a collapsible disclosure card in the Plugins
1087
- * settings tab styled against the app's `--dsw-alias-*` design tokens and the
1088
- * official card interaction (header button with unsaved badge + rotating
1089
- * chevron, smooth expand/collapse respecting reduced-motion, staged edits that
1090
- * survive collapsing, one Save write, nothing rendered while the namespace is
1091
- * unavailable). Copy is fully localized through the slot-injected `t` seat
1092
- * (`locale: 'tool-plus'`), which re-renders on a locale switch.
1093
- *
1094
- * The card edits the FLAT settings surface (OMP `bash*`-style scalar keys) —
1095
- * the client scope only writes scalar fields — grouped as timeouts,
1096
- * backgrounding, output, completion truncation (subdivided into byte and line
1097
- * settings), and behavior. Field definitions, defaults, and copy keys come
1098
- * from the single-source field table (`src/config/fields.ts`), shared with
1099
- * the Tool Plus settings section.
1100
- * @module @xiaoso/dsh-tool-plus/client
1101
- */
1102
- /** Bash fields derived from the single-source table, split by subgroup. */
1103
- const BASH_FIELDS = toolPlusFieldsOf("bash");
1104
- const TIMING_FIELDS = BASH_FIELDS.filter((f) => f.group === "timing").map((f) => ({
1105
- field: f.name,
1106
- labelKey: f.labelKey,
1107
- hintKey: f.hintKey
1108
- }));
1109
- const OUTPUT_FIELDS = BASH_FIELDS.filter((f) => f.group === "output").map((f) => ({
1110
- field: f.name,
1111
- labelKey: f.labelKey,
1112
- hintKey: f.hintKey
1113
- }));
1114
- const TRUNCATION_FIELDS = BASH_FIELDS.filter((f) => f.group === "truncation");
1115
- const STRATEGY_SELECT = {
1116
- field: "outputTruncateStrategy",
1117
- labelKey: "outputTruncateStrategy",
1118
- hintKey: "outputTruncateStrategyHint",
1119
- options: toolPlusField("outputTruncateStrategy")?.options ?? []
1120
- };
1121
- const BYTE_SELECT = {
1122
- field: "outputTruncateByteMode",
1123
- labelKey: "outputTruncateByteMode",
1124
- hintKey: "outputTruncateByteModeHint",
1125
- options: toolPlusField("outputTruncateByteMode")?.options ?? []
1126
- };
1127
- const BYTE_NUMBER_FIELDS = TRUNCATION_FIELDS.filter((f) => f.name.startsWith("outputTruncateByte") && f.name !== "outputTruncateByteMode").map((f) => ({
1128
- field: f.name,
1129
- labelKey: f.labelKey,
1130
- hintKey: f.hintKey
1131
- }));
1132
- const LINE_SELECT = {
1133
- field: "outputTruncateLineMode",
1134
- labelKey: "outputTruncateLineMode",
1135
- hintKey: "outputTruncateLineModeHint",
1136
- options: toolPlusField("outputTruncateLineMode")?.options ?? []
1137
- };
1138
- const LINE_NUMBER_FIELDS = TRUNCATION_FIELDS.filter((f) => f.name.startsWith("outputTruncateLine") && f.name !== "outputTruncateLineMode").map((f) => ({
1139
- field: f.name,
1140
- labelKey: f.labelKey,
1141
- hintKey: f.hintKey
1142
- }));
1143
- const TOGGLE_FIELDS = BASH_FIELDS.filter((f) => f.group === "behavior").map((f) => ({
1144
- field: f.name,
1145
- labelKey: f.labelKey,
1146
- hintKey: f.hintKey
1147
- }));
1148
- /** Schema defaults sourced from the single-source field table. */
1149
- const SCHEMA_DEFAULTS = Object.fromEntries(TOOL_PLUS_FIELDS.map((f) => [f.name, f.default]));
1150
- /** Value a cleared field will revert to: the composition base when present, else the schema default. */
1151
- function revertValue$1(snap, field) {
1152
- if (hasOwn(snap.base, field)) return snap.base[field];
1153
- return SCHEMA_DEFAULTS[field];
1154
- }
1155
- /** Card chrome CSS, keyed by `data-plugin-css` and injected once (design-token driven). */
1156
- const CSS$3 = `
1157
- .tp-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);transition:border-color .16s,background .16s;margin:0 0 8px}
1158
- .tp-card:hover{border-color:var(--dsw-alias-label-dimmed)}
1159
- .tp-card.tp-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
1160
- .tp-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}
1161
- .tp-header:active{background:var(--dsw-alias-bg-layer-2)}
1162
- .tp-header:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
1163
- .tp-headText{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
1164
- .tp-name{font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
1165
- .tp-desc{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
1166
- .tp-pending{display:inline-flex;align-items:center;gap:6px;flex:none;border-radius:999px;padding:2px 10px;font-size:11px;line-height:17px;font-weight:500;white-space:nowrap;background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-state-business-primary)}
1167
- .tp-pending::before{content:'';flex:none;width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-business-primary);animation:tp-pulse 1.8s ease-in-out infinite}
1168
- @keyframes tp-pulse{0%,100%{opacity:.35;transform:scale(.85)}50%{opacity:1;transform:scale(1)}}
1169
- .tp-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s;display:inline-flex}
1170
- .tp-chevron.tp-open{transform:rotate(180deg)}
1171
- .tp-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-top:4px;display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .22s cubic-bezier(.22,.61,.36,1),opacity .16s ease}
1172
- .tp-body.tp-open{grid-template-rows:1fr;opacity:1}
1173
- .tp-bodyInner{overflow:hidden}
1174
- .tp-readOnly{margin:12px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
1175
- .tp-group{display:flex;flex-direction:column;gap:0;padding:12px 0 4px}
1176
- .tp-group+.tp-group{border-top:1px solid var(--dsw-alias-border-l2)}
1177
- .tp-groupTitle{font-size:13px;font-weight:600;line-height:20px;color:var(--dsw-alias-label-primary);margin:0 0 4px}
1178
- .tp-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 0 4px;border-top:1px solid var(--dsw-alias-border-l2)}
1179
- .tp-failed{flex:1;min-width:0;margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
1180
- .tp-applies{flex:1;min-width:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
1181
- .tp-discard,.tp-save{appearance:none;border:1px solid transparent;border-radius:8px;padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;cursor:pointer;transition:transform .1s ease}
1182
- .tp-discard:active:not(:disabled),.tp-save:active:not(:disabled){transform:scale(.97)}
1183
- .tp-discard{border-color:var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary)}
1184
- .tp-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
1185
- .tp-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
1186
- .tp-save:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
1187
- .tp-discard:disabled,.tp-save:disabled{opacity:.4;cursor:default}
1188
- .tp-discard:focus-visible,.tp-save:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
1189
- @media (prefers-reduced-motion: reduce){
1190
- .tp-card,.tp-header,.tp-chevron,.tp-body,.tp-thumb,.tp-switchTrack,.tp-discard,.tp-save{transition:none}
1191
- .tp-pending::before{animation:none}
1192
- }
1193
- `;
1194
- /** Inject the card stylesheet once per page; the loader removes plugin-owned style tags on unload. */
1195
- let cssInjected$2 = false;
1196
- function injectCardCss() {
1197
- if (cssInjected$2 || typeof document === "undefined") return;
1198
- cssInjected$2 = true;
1199
- const id = "tool-plus-card";
1200
- if (document.querySelector("style[data-plugin-css=\"tool-plus-card\"]") !== null) return;
1201
- const tag = document.createElement("style");
1202
- tag.dataset.plugin = "@xiaoso/dsh-tool-plus";
1203
- tag.dataset.pluginCss = id;
1204
- tag.textContent = CSS$3;
1205
- document.head.appendChild(tag);
1206
- }
1207
- /** Text of a staged (or resolved) value for the number controls. */
1208
- function numberText$1(staged, hasStaged, resolved, snap, field) {
1209
- if (hasStaged) return (staged ?? "").trim() === "" ? String(revertValue$1(snap, field) ?? "") : staged ?? "";
1210
- return resolved === void 0 || typeof resolved === "boolean" ? "" : String(resolved);
1211
- }
1212
- /** Stage card edits over the bound scope; only a Save writes the document. */
1213
- function useBashPlusForm(scope) {
1214
- const [snap, setSnap] = (0, react.useState)(() => scope.getSnapshot());
1215
- const [numbers, setNumbers] = (0, react.useState)({});
1216
- const [selects, setSelects] = (0, react.useState)({});
1217
- const [toggles, setToggles] = (0, react.useState)({});
1218
- const [saving, setSaving] = (0, react.useState)(false);
1219
- const [failed, setFailed] = (0, react.useState)(false);
1220
- (0, react.useEffect)(() => scope.subscribe(() => {
1221
- setSnap(scope.getSnapshot());
1222
- setFailed(false);
1223
- }), [scope]);
1224
- const value = snap.value ?? {};
1225
- const writable = snap.writable;
1226
- const timing = (0, react.useMemo)(() => TIMING_FIELDS.map((def) => renderNumber$1(def, numbers, value, snap)), [numbers, snap]);
1227
- const output = (0, react.useMemo)(() => OUTPUT_FIELDS.map((def) => renderNumber$1(def, numbers, value, snap)), [numbers, snap]);
1228
- const strategySelect = (0, react.useMemo)(() => renderSelect$1(STRATEGY_SELECT, selects, value, snap), [selects, snap]);
1229
- const byteSelect = (0, react.useMemo)(() => renderSelect$1(BYTE_SELECT, selects, value, snap), [selects, snap]);
1230
- const byteNumbers = (0, react.useMemo)(() => BYTE_NUMBER_FIELDS.map((def) => renderNumber$1(def, numbers, value, snap)), [numbers, snap]);
1231
- const lineSelect = (0, react.useMemo)(() => renderSelect$1(LINE_SELECT, selects, value, snap), [selects, snap]);
1232
- const lineNumbers = (0, react.useMemo)(() => LINE_NUMBER_FIELDS.map((def) => renderNumber$1(def, numbers, value, snap)), [numbers, snap]);
1233
- const togglesView = (0, react.useMemo)(() => TOGGLE_FIELDS.map((def) => {
1234
- const hasStaged = Object.prototype.hasOwnProperty.call(toggles, def.field);
1235
- const staged = toggles[def.field];
1236
- const baseVal = Boolean(revertValue$1(snap, def.field));
1237
- const userStored = hasOwn(snap.user, def.field);
1238
- snap.user?.[def.field];
1239
- const effectiveVal = hasStaged ? staged ?? baseVal : Boolean(value[def.field]);
1240
- let overridden = false;
1241
- if (hasStaged) {
1242
- if (staged === null || staged === baseVal) overridden = false;
1243
- else overridden = true;
1244
- } else overridden = userStored;
1245
- return {
1246
- field: def.field,
1247
- labelKey: def.labelKey,
1248
- hintKey: def.hintKey,
1249
- checked: effectiveVal,
1250
- overridden
1251
- };
1252
- }), [toggles, snap]);
1253
- const invalid = (0, react.useMemo)(() => [
1254
- ...timing,
1255
- ...output,
1256
- ...byteNumbers,
1257
- ...lineNumbers
1258
- ], [
1259
- timing,
1260
- output,
1261
- byteNumbers,
1262
- lineNumbers
1263
- ]).some((c) => c.invalid);
1264
- const isDirty = (0, react.useMemo)(() => {
1265
- for (const [field, staged] of Object.entries(selects)) {
1266
- const baseVal = String(revertValue$1(snap, field) ?? "");
1267
- const userStored = hasOwn(snap.user, field);
1268
- const userVal = snap.user?.[field];
1269
- if (staged === null) {
1270
- if (userStored) return true;
1271
- } else if (staged === baseVal) {
1272
- if (userStored) return true;
1273
- } else if (!userStored || userVal !== staged) return true;
1274
- }
1275
- for (const [field, staged] of Object.entries(numbers)) {
1276
- const baseVal = revertValue$1(snap, field);
1277
- const userStored = hasOwn(snap.user, field);
1278
- const userVal = snap.user?.[field];
1279
- const trimmed = staged.trim();
1280
- if (trimmed === "") {
1281
- if (userStored) return true;
1282
- } else {
1283
- const num = Number(trimmed);
1284
- if (Number.isFinite(num)) {
1285
- if (num === baseVal) {
1286
- if (userStored) return true;
1287
- } else if (!userStored || userVal !== num) return true;
1288
- } else return true;
1289
- }
1290
- }
1291
- for (const [field, staged] of Object.entries(toggles)) {
1292
- const baseVal = Boolean(revertValue$1(snap, field));
1293
- const userStored = hasOwn(snap.user, field);
1294
- const userVal = snap.user?.[field];
1295
- if (staged === null) {
1296
- if (userStored) return true;
1297
- } else if (staged === baseVal) {
1298
- if (userStored) return true;
1299
- } else if (!userStored || userVal !== staged) return true;
1300
- }
1301
- return false;
1302
- }, [
1303
- selects,
1304
- numbers,
1305
- toggles,
1306
- snap
1307
- ]);
1308
- const edit = (0, react.useCallback)((field, text) => {
1309
- setFailed(false);
1310
- setNumbers((prev) => ({
1311
- ...prev,
1312
- [field]: text
1313
- }));
1314
- }, []);
1315
- const editSelect = (0, react.useCallback)((field, value) => {
1316
- setFailed(false);
1317
- setSelects((prev) => ({
1318
- ...prev,
1319
- [field]: value
1320
- }));
1321
- }, []);
1322
- const onToggle = (0, react.useCallback)((field, checked) => {
1323
- setFailed(false);
1324
- setToggles((prev) => ({
1325
- ...prev,
1326
- [field]: checked
1327
- }));
1328
- }, []);
1329
- const resetField = (0, react.useCallback)((field) => {
1330
- setFailed(false);
1331
- setNumbers((prev) => ({
1332
- ...prev,
1333
- [field]: ""
1334
- }));
1335
- setSelects((prev) => ({
1336
- ...prev,
1337
- [field]: null
1338
- }));
1339
- setToggles((prev) => ({
1340
- ...prev,
1341
- [field]: null
1342
- }));
1343
- }, []);
1344
- const save = (0, react.useCallback)(async () => {
1345
- if (!writable || invalid) return;
1346
- setSaving(true);
1347
- setFailed(false);
1348
- try {
1349
- for (const [field, staged] of Object.entries(numbers)) {
1350
- const baseVal = revertValue$1(snap, field);
1351
- const trimmed = staged.trim();
1352
- if (trimmed === "" || Number(trimmed) === baseVal) {
1353
- if (hasOwn(snap.user, field)) await scope.unset(field);
1354
- } else await scope.set(field, Number(trimmed));
1355
- }
1356
- for (const [field, staged] of Object.entries(selects)) {
1357
- const baseVal = String(revertValue$1(snap, field) ?? "");
1358
- if (staged === null || staged === baseVal) {
1359
- if (hasOwn(snap.user, field)) await scope.unset(field);
1360
- } else await scope.set(field, staged);
1361
- }
1362
- for (const [field, staged] of Object.entries(toggles)) {
1363
- const baseVal = Boolean(revertValue$1(snap, field));
1364
- if (staged === null || staged === baseVal) {
1365
- if (hasOwn(snap.user, field)) await scope.unset(field);
1366
- } else await scope.set(field, staged);
1367
- }
1368
- } catch {
1369
- setFailed(true);
1370
- } finally {
1371
- setSaving(false);
1372
- setNumbers({});
1373
- setSelects({});
1374
- setToggles({});
1375
- }
1376
- }, [
1377
- scope,
1378
- writable,
1379
- invalid,
1380
- numbers,
1381
- selects,
1382
- toggles,
1383
- snap
1384
- ]);
1385
- const discard = (0, react.useCallback)(() => {
1386
- setFailed(false);
1387
- setNumbers({});
1388
- setSelects({});
1389
- setToggles({});
1390
- }, []);
1391
- return {
1392
- shell: {
1393
- available: snap.status === "ready",
1394
- writable,
1395
- dirty: isDirty,
1396
- invalid,
1397
- saving,
1398
- failed
1399
- },
1400
- timing,
1401
- output,
1402
- strategySelect,
1403
- byteSelect,
1404
- byteNumbers,
1405
- lineSelect,
1406
- lineNumbers,
1407
- toggles: togglesView,
1408
- actions: {
1409
- edit,
1410
- resetField,
1411
- save,
1412
- discard
1413
- },
1414
- editSelect,
1415
- onToggle
1416
- };
1417
- }
1418
- function renderNumber$1(def, numbers, value, snap) {
1419
- const hasStaged = Object.prototype.hasOwnProperty.call(numbers, def.field);
1420
- const staged = numbers[def.field];
1421
- const resolved = value[def.field];
1422
- const text = numberText$1(staged, hasStaged, typeof resolved === "boolean" ? void 0 : resolved, snap, def.field);
1423
- const baseVal = revertValue$1(snap, def.field);
1424
- const userStored = hasOwn(snap.user, def.field);
1425
- let overridden = false;
1426
- let invalid = false;
1427
- if (hasStaged) {
1428
- const trimmed = (staged ?? "").trim();
1429
- if (trimmed === "") overridden = false;
1430
- else {
1431
- const num = Number(trimmed);
1432
- if (Number.isFinite(num)) overridden = num !== baseVal;
1433
- else invalid = true;
1434
- }
1435
- } else overridden = userStored;
1436
- return {
1437
- field: def.field,
1438
- labelKey: def.labelKey,
1439
- hintKey: def.hintKey,
1440
- text,
1441
- overridden,
1442
- invalid
1443
- };
1444
- }
1445
- function renderSelect$1(def, selects, value, snap) {
1446
- const hasStaged = Object.prototype.hasOwnProperty.call(selects, def.field);
1447
- const staged = selects[def.field];
1448
- const resolved = value[def.field];
1449
- const baseVal = String(revertValue$1(snap, def.field) ?? "");
1450
- const v = hasStaged ? staged ?? baseVal : typeof resolved === "string" ? resolved : baseVal;
1451
- const userStored = hasOwn(snap.user, def.field);
1452
- let overridden = false;
1453
- if (hasStaged) {
1454
- if (staged === null || staged === baseVal) overridden = false;
1455
- else overridden = true;
1456
- } else overridden = userStored;
1457
- return {
1458
- field: def.field,
1459
- labelKey: def.labelKey,
1460
- hintKey: def.hintKey,
1461
- options: def.options,
1462
- value: v,
1463
- overridden
1464
- };
1465
- }
1466
- function BashPlusCard(props) {
1467
- const { t, scope } = props;
1468
- const form = useBashPlusForm(scope);
1469
- const [open, setOpen] = (0, react.useState)(false);
1470
- const [openSelect, setOpenSelect] = (0, react.useState)(null);
1471
- (0, react.useEffect)(() => {
1472
- injectCardCss();
1473
- injectSettingsRowsCss();
1474
- }, []);
1475
- if (!form.shell.available) return null;
1476
- const blocked = !form.shell.dirty || form.shell.invalid || form.shell.saving;
1477
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1478
- "data-plugin-settings": "tool-plus",
1479
- className: "tp-card" + (open ? " tp-open" : ""),
1480
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1481
- type: "button",
1482
- className: "tp-header",
1483
- "aria-expanded": open,
1484
- "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
1485
- onClick: () => setOpen((o) => !o),
1486
- children: [
1487
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1488
- className: "tp-headText",
1489
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1490
- className: "tp-name",
1491
- children: t("title")
1492
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1493
- className: "tp-desc",
1494
- children: t("description")
1495
- })]
1496
- }),
1497
- form.shell.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1498
- className: "tp-pending",
1499
- role: "status",
1500
- children: t("unsaved")
1501
- }) : null,
1502
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1503
- "aria-hidden": "true",
1504
- className: "tp-chevron" + (open ? " tp-open" : ""),
1505
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1506
- width: "14",
1507
- height: "14",
1508
- viewBox: "0 0 14 14",
1509
- fill: "none",
1510
- "aria-hidden": "true",
1511
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1512
- d: "M3.5 5.25 L7 8.75 L10.5 5.25",
1513
- stroke: "currentColor",
1514
- strokeWidth: "1.5",
1515
- strokeLinecap: "round",
1516
- strokeLinejoin: "round"
1517
- })
1518
- })
1519
- })
1520
- ]
1521
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1522
- className: "tp-body" + (open ? " tp-open" : ""),
1523
- "aria-hidden": !open,
1524
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1525
- className: "tp-bodyInner",
1526
- children: [
1527
- !form.shell.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1528
- className: "tp-readOnly",
1529
- role: "status",
1530
- children: t("readOnly")
1531
- }) : null,
1532
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1533
- className: "tp-group",
1534
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1535
- className: "tp-groupTitle",
1536
- children: t("groupBehavior")
1537
- }), form.toggles.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
1538
- t,
1539
- control: c,
1540
- disabled: !form.shell.writable,
1541
- onToggle: form.onToggle,
1542
- onReset: form.actions.resetField
1543
- }, c.field))]
1544
- }),
1545
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1546
- className: "tp-group",
1547
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1548
- className: "tp-groupTitle",
1549
- children: t("groupTiming")
1550
- }), form.timing.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1551
- t,
1552
- control: c,
1553
- disabled: !form.shell.writable,
1554
- onEdit: form.actions.edit,
1555
- onReset: form.actions.resetField
1556
- }, c.field))]
1557
- }),
1558
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1559
- className: "tp-group",
1560
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1561
- className: "tp-groupTitle",
1562
- children: t("groupOutput")
1563
- }), form.output.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1564
- t,
1565
- control: c,
1566
- disabled: !form.shell.writable,
1567
- onEdit: form.actions.edit,
1568
- onReset: form.actions.resetField
1569
- }, c.field))]
1570
- }),
1571
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1572
- className: "tp-group",
1573
- children: [
1574
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1575
- className: "tp-groupTitle",
1576
- children: t("groupTruncation")
1577
- }),
1578
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
1579
- t,
1580
- control: form.strategySelect,
1581
- disabled: !form.shell.writable,
1582
- open: openSelect === form.strategySelect.field,
1583
- onOpenChange: setOpenSelect,
1584
- onEdit: form.editSelect,
1585
- onReset: form.actions.resetField
1586
- }),
1587
- form.strategySelect.value !== "lines" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1588
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1589
- t,
1590
- control: form.byteNumbers[0],
1591
- disabled: !form.shell.writable,
1592
- onEdit: form.actions.edit,
1593
- onReset: form.actions.resetField
1594
- }),
1595
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
1596
- t,
1597
- control: form.byteSelect,
1598
- disabled: !form.shell.writable,
1599
- open: openSelect === form.byteSelect.field,
1600
- onOpenChange: setOpenSelect,
1601
- onEdit: form.editSelect,
1602
- onReset: form.actions.resetField
1603
- }),
1604
- form.byteSelect.value !== "tail" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1605
- t,
1606
- control: form.byteNumbers[1],
1607
- disabled: !form.shell.writable,
1608
- onEdit: form.actions.edit,
1609
- onReset: form.actions.resetField
1610
- }) : null,
1611
- form.byteSelect.value !== "head" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1612
- t,
1613
- control: form.byteNumbers[2],
1614
- disabled: !form.shell.writable,
1615
- onEdit: form.actions.edit,
1616
- onReset: form.actions.resetField
1617
- }) : null
1618
- ] }) : null,
1619
- form.strategySelect.value === "lines" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1620
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1621
- t,
1622
- control: form.lineNumbers[0],
1623
- disabled: !form.shell.writable,
1624
- onEdit: form.actions.edit,
1625
- onReset: form.actions.resetField
1626
- }),
1627
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
1628
- t,
1629
- control: form.lineSelect,
1630
- disabled: !form.shell.writable,
1631
- open: openSelect === form.lineSelect.field,
1632
- onOpenChange: setOpenSelect,
1633
- onEdit: form.editSelect,
1634
- onReset: form.actions.resetField
1635
- }),
1636
- form.lineSelect.value !== "tail" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1637
- t,
1638
- control: form.lineNumbers[1],
1639
- disabled: !form.shell.writable,
1640
- onEdit: form.actions.edit,
1641
- onReset: form.actions.resetField
1642
- }) : null,
1643
- form.lineSelect.value !== "head" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1644
- t,
1645
- control: form.lineNumbers[2],
1646
- disabled: !form.shell.writable,
1647
- onEdit: form.actions.edit,
1648
- onReset: form.actions.resetField
1649
- }) : null
1650
- ] }) : null
1651
- ]
1652
- }),
1653
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1654
- className: "tp-footer",
1655
- children: [
1656
- form.shell.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1657
- className: "tp-failed",
1658
- role: "status",
1659
- children: t("saveFailed")
1660
- }) : null,
1661
- !form.shell.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1662
- className: "tp-applies",
1663
- children: t("appliesTo")
1664
- }) : null,
1665
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1666
- type: "button",
1667
- className: "tp-discard",
1668
- disabled: !form.shell.dirty || form.shell.saving,
1669
- onClick: form.actions.discard,
1670
- children: t("discard")
1671
- }),
1672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1673
- type: "button",
1674
- className: "tp-save",
1675
- disabled: blocked,
1676
- onClick: () => void form.actions.save(),
1677
- children: form.shell.saving ? t("saving") : t("save")
1678
- })
1679
- ]
1680
- })
1681
- ]
1682
- })
1683
- })]
1684
- });
1685
- }
1686
- //#endregion
1687
- //#region src/tools/shared/browser-rpc-channel.ts
1688
- /**
1689
- * Shared RPC channel constants for the browser-detection round trip between
1690
- * the settings panel (client half) and the host. Both halves import this
1691
- * module so the channel/endpoint never drift.
1692
- *
1693
- * The channel rides the official `@deepseek-ai/dsh-client-connection` generic
1694
- * RPC transport (host registers with `ctx.connection.rpc.handle`; the client
1695
- * calls via `createWebConnectionRpc().call`). Channel names must match
1696
- * `/^\/[A-Za-z0-9._~-]+$/` and `/api` is reserved — `/tool-plus` is ours.
1697
- * @module @xiaoso/dsh-tool-plus/browser-rpc-channel
1698
- */
1699
- /** Logical RPC channel registered by the host half of this plugin. */
1700
- const TOOL_PLUS_RPC_CHANNEL = "/tool-plus";
1701
- /** Endpoint that reports the rmSafe injection status (query = ensure + verify). */
1702
- const RM_SAFE_STATUS_ENDPOINT = "rmSafe/status";
1703
- /** Endpoint that lists agent presets with their status (roster + conflicts + template diff). */
1704
- const PRESET_STATUS_ENDPOINT = "presets/status";
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.
1710
- */
1711
- const BUNDLED_PRESET_IDS = ["tool-plus-standard", "tool-plus-ptc"];
1712
- /** Endpoint that applies one preset action to a user-root preset. */
1713
- const PRESET_ACTION_ENDPOINT = "presets/apply";
1714
- /** Endpoint that compares one preset against one of our templates (read-only). */
1715
- const PRESET_COMPARE_ENDPOINT = "presets/compare";
1716
- //#endregion
1717
- //#region src/client/web-connection-rpc.ts
1718
- /**
1719
- * Browser caller for the plugin's Connection RPC channel.
1720
- *
1721
- * The official `@deepseek-ai/dsh-client-connection/client` package ships the
1722
- * protocol primitives as types (`RpcFetch`, `ClientConnectionRpc`, `RpcResult`)
1723
- * and the `RpcId` brand, but its published `createWebConnectionRpc` factory is
1724
- * internal to the bundle — not re-exported. This module re-implements that
1725
- * factory against the same public wire contract (docs/api-gateway.md, the
1726
- * client `rpc.ts` source): a `POST {origin}/{channel}/{endpoint}` with a
1727
- * `client-request` JSON envelope, answered by a `server-response` envelope
1728
- * echoing the rpcId. Host-side registration uses the official
1729
- * `ctx.connection.rpc.handle` — this file only mirrors the caller half.
1730
- * @module @xiaoso/dsh-tool-plus/client/web-connection-rpc
1731
- */
1732
- const INTERNAL_BASE = "http://dsh.internal";
1733
- const CHANNEL_PATTERN = /^\/[A-Za-z0-9._~-]+$/;
1734
- const ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
1735
- function randomUuid() {
1736
- const g = globalThis;
1737
- if (g.crypto?.randomUUID !== void 0) return g.crypto.randomUUID();
1738
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1739
- const r = Math.random() * 16 | 0;
1740
- return (c === "x" ? r : r & 3 | 8).toString(16);
1741
- });
1742
- }
1743
- function isRecord$4(value) {
1744
- return typeof value === "object" && value !== null && !Array.isArray(value);
1745
- }
1746
- function parseConnectionResponse(value) {
1747
- if (!isRecord$4(value) || value.type !== "server-response" || typeof value.rpcId !== "string") throw new TypeError("connection: invalid server-response envelope");
1748
- const result = value.result;
1749
- if (!isRecord$4(result)) throw new TypeError("connection: invalid server-response result");
1750
- if (result.ok === true) return {
1751
- rpcId: value.rpcId,
1752
- result: {
1753
- ok: true,
1754
- value: result.value
1755
- }
1756
- };
1757
- if (result.ok !== false || !isRecord$4(result.error)) throw new TypeError("connection: invalid server-response result");
1758
- const error = result.error;
1759
- if (typeof error.code !== "string" || typeof error.message !== "string" || !isRecord$4(error.details)) throw new TypeError("connection: invalid server-response failure");
1760
- return {
1761
- rpcId: value.rpcId,
1762
- result: {
1763
- ok: false,
1764
- error: {
1765
- code: error.code,
1766
- message: error.message,
1767
- details: error.details
1768
- }
1769
- }
1770
- };
1771
- }
1772
- function resolveBase() {
1773
- const location = globalThis.location;
1774
- return location?.origin !== void 0 && location.origin !== "null" ? location.origin : INTERNAL_BASE;
1775
- }
1776
- function assertTarget(channel, endpoint) {
1777
- const segments = endpoint.split("/");
1778
- if (!CHANNEL_PATTERN.test(channel) || segments.some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT_PATTERN.test(segment))) throw new Error(`connection: invalid RPC target ${JSON.stringify(`${channel}/${endpoint}`)}`);
1779
- }
1780
- /**
1781
- * Create the browser-backed generic RPC caller for the plugin's channel
1782
- * (the `/tool-plus` host registration). Wire-compatible with the official
1783
- * ClientConnectionRpc contract; only the transport override differs.
1784
- * @param doFetch - transport override; defaults to the page's global fetch.
1785
- */
1786
- function createWebConnectionRpc(doFetch) {
1787
- const send = doFetch ?? ((input, init) => globalThis.fetch(input, init));
1788
- return { async call(channel, endpoint, payload, signal) {
1789
- assertTarget(channel, endpoint);
1790
- const rpcId = (0, _deepseek_ai_dsh_client_connection_client.RpcId)(randomUuid());
1791
- const response = await send(new URL(`${channel}/${endpoint}`, resolveBase()), {
1792
- method: "POST",
1793
- headers: { "content-type": "application/json" },
1794
- body: JSON.stringify({
1795
- type: "client-request",
1796
- rpcId,
1797
- method: endpoint,
1798
- payload
1799
- }),
1800
- ...signal === void 0 ? {} : { signal }
1801
- });
1802
- if (!response.ok) throw new Error(`transport failure for ${channel}/${endpoint}: HTTP ${response.status}`);
1803
- const full = parseConnectionResponse(await response.json());
1804
- if (full.rpcId !== rpcId) throw new Error(`rpcId mismatch for ${endpoint}: sent ${rpcId}, got ${full.rpcId}`);
1805
- return full.result;
1806
- } };
1807
- }
1808
- //#endregion
1809
1202
  //#region src/client/forms.ts
1810
1203
  /**
1811
1204
  * Generic staged-form model over the plugin's flat settings namespace,
@@ -1940,8 +1333,8 @@ window.__ModuleLoader__.load({
1940
1333
  return false;
1941
1334
  }
1942
1335
  /**
1943
- * Stage edits over the bound scope; only a Save writes the document.
1944
- * @param scope - bound settings scope for the `tool-plus` namespace.
1336
+ * Stage edits over the shared configuration form; only a Save writes the document.
1337
+ * @param scope - the `tool-plus` entry's shared configuration form (`ctx.configForms`).
1945
1338
  * @param fields - the fields this surface edits (single-source table slice).
1946
1339
  * @returns the form state and its write actions.
1947
1340
  */
@@ -2123,11 +1516,18 @@ window.__ModuleLoader__.load({
2123
1516
  //#endregion
2124
1517
  //#region src/client/preset-panel.ts
2125
1518
  /**
2126
- * 预设面板的纯映射层:把 `presets/status` / `presets/apply` 的返回值翻成本地化
2127
- * 文案、可用动作与差异摘要。零 React、零 DOM、零 I/O —— 由
2128
- * `tests/unit/preset-panel.spec.ts` 单独覆盖,UI(`PresetPanel.tsx`)只做渲染。
1519
+ * 预设面板的纯映射层:把 `presets/status` / `presets/apply` / `presets/compare`
1520
+ * 的返回值翻成本地化文案、可用动作与差异摘要。零 React、零 DOM、零 I/O ——
1521
+ * 由 `tests/unit/preset-panel.spec.ts` 单独覆盖,UI(`PresetPanel.tsx`)只做渲染。
1522
+ *
1523
+ * 语义基线(dsh 0.1.7 起,别再按旧目录机制理解):
1524
+ * - 预设是 profile 配置里的一条 `@deepseek-ai/dsh-agent-preset` 声明行;
1525
+ * - "没被改动过" = 这一行在 profile 补丁里**没有** `config`(`customized === false`);
1526
+ * - 三个动作:`upgrade` 只禁冲突行、`align` 用所选模板整份替换 plugins、
1527
+ * `revert` 删掉覆盖回落到随包声明;
1528
+ * - 本插件只**声明**两个预设({@link BUNDLED_PRESET_IDS});别的 preset 归别人,
1529
+ * 面板最多替它修冲突行,不改它的内容。
2129
1530
  *
2130
- * 契约来源:`docs/superpowers/specs/2026-09-17-preset-upgrade-design.md` §4.2/§7。
2131
1531
  * 类型与端点名直接取自共享契约模块 `src/tools/shared/browser-rpc-channel.ts`
2132
1532
  * (两半同一个来源,客户端这一侧不复制一份会漂移的定义)。
2133
1533
  * @module @xiaoso/dsh-tool-plus/client/preset-panel
@@ -2143,33 +1543,6 @@ window.__ModuleLoader__.load({
2143
1543
  "tool-fs-search": "presetEntrySearch"
2144
1544
  };
2145
1545
  /**
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
1546
  * 下拉里一次只看一个预设时的默认选中项:随包的那份({@link BUNDLED_PRESET_IDS}
2174
1547
  * 的第一项)在场就选它,否则退清单第一项;空清单返回 undefined。
2175
1548
  */
@@ -2177,14 +1550,14 @@ window.__ModuleLoader__.load({
2177
1550
  return BUNDLED_PRESET_IDS.find((id) => presets.some((preset) => preset.id === id)) ?? presets[0]?.id;
2178
1551
  }
2179
1552
  /**
2180
- * 当前选中项在重拉状态后可能已经消失(预设被删、改名、roster 变化):仍在场就
2181
- * 保留,否则回落到 {@link initialPresetId},避免下拉指向一个不存在的选项。
1553
+ * 当前选中项在重拉状态后可能已经消失(声明被移除、roster 变化):仍在场就保留,
1554
+ * 否则回落到 {@link initialPresetId},避免下拉指向一个不存在的选项。
2182
1555
  */
2183
1556
  function resolveSelectedPresetId(presets, current) {
2184
1557
  if (current !== void 0 && presets.some((preset) => preset.id === current)) return current;
2185
1558
  return initialPresetId(presets);
2186
1559
  }
2187
- /** 下拉选项文案:优先展示 name(preset.yml 的显示名),缺了就退 id。 */
1560
+ /** 下拉选项文案:优先展示 name(声明里的显示名),缺了就退 id。 */
2188
1561
  function presetOptionLabel(preset) {
2189
1562
  return preset.name !== void 0 && preset.name !== "" ? preset.name : preset.id;
2190
1563
  }
@@ -2215,7 +1588,7 @@ window.__ModuleLoader__.load({
2215
1588
  }
2216
1589
  /**
2217
1590
  * 状态点 ②(完整比较):这份预设的内容与我们选中的模板差多少 ——
2218
- * 不只是工具开关,整份组合文件都算。
1591
+ * 不只是工具开关,整份插件行列表都算。
2219
1592
  * @param t - 文案表。
2220
1593
  * @param compare - 当前比较结果;尚未返回时给"正在比较"的黄点。
2221
1594
  * @returns 状态点。
@@ -2239,7 +1612,7 @@ window.__ModuleLoader__.load({
2239
1612
  };
2240
1613
  }
2241
1614
  /**
2242
- * 差异弹窗的行:把比较结果翻成"路径 / 两份预设各自的值"三列。
1615
+ * 差异弹窗的行:把比较结果翻成"路径 / 两份各自的值"三列。
2243
1616
  * 值缺失或整行差异都给出占位词,让用户一眼看出哪边没有。
2244
1617
  * @param t - 文案表。
2245
1618
  * @param compare - 当前比较结果。
@@ -2285,10 +1658,10 @@ window.__ModuleLoader__.load({
2285
1658
  return template.name !== void 0 && template.name !== "" ? template.name : template.id;
2286
1659
  }
2287
1660
  /**
2288
- * 选中的模板:①选中的预设自己有同名模板就用它(`tool-plus-ptc` 就该比
2289
- * `tool-plus-ptc`);②否则退随包的第一份模板(`BUNDLED_PRESET_IDS[0]`,即
2290
- * `tool-plus-standard`,别按目录名排序撞上 ptc);③再不行才退清单第一项。
2291
- * 清单为空返回 undefined。
1661
+ * 选中的模板:①手动选过且仍在场就用它;②否则选与当前预设**同名**的那份
1662
+ * (`tool-plus-ptc` 就该比 `tool-plus-ptc`);③再退随包的第一份
1663
+ * (`BUNDLED_PRESET_IDS[0]`,即 `tool-plus-standard`,别按目录名排序撞上 ptc);
1664
+ * ④最后退清单第一项。清单为空返回 undefined。
2292
1665
  * @param templates - 可选模板清单。
2293
1666
  * @param current - 当前选中的模板 id。
2294
1667
  * @param presetId - 当前选中的预设 id(同名模板优先)。
@@ -2300,35 +1673,54 @@ window.__ModuleLoader__.load({
2300
1673
  return BUNDLED_PRESET_IDS.find((id) => templates.some((template) => template.id === id)) ?? templates[0]?.id;
2301
1674
  }
2302
1675
  /**
2303
- * 补充说明行:读取失败原因、无法识别的内容。差异不再在这里铺开 ——
2304
- * 它进了「查看差异」弹窗,正文只留两个状态点。
1676
+ * 补充说明行:为什么读不到、这个 preset 归谁、以及"你改过它"这件事。
1677
+ * 差异不在这里铺开 —— 它进了「查看差异」弹窗,正文只留状态点与必要解释。
2305
1678
  * @param t - 文案表。
2306
1679
  * @param status - 该预设的状态。
1680
+ * @param writable - 本部署有没有可编辑的 profile。
2307
1681
  * @returns 要渲染的说明行(可能为空)。
2308
1682
  */
2309
- function presetNotes(t, status) {
1683
+ function presetNotes(t, status, writable = true) {
2310
1684
  const notes = [];
1685
+ if (!writable) notes.push(t("presetNoteNoProfile"));
2311
1686
  if (status.broken !== void 0) notes.push(t("presetNoteBroken").replace("{reason}", status.broken));
2312
1687
  else if (status.unrecognized) notes.push(t("presetNoteUnrecognized"));
1688
+ if (status.source === "ours" && writable && status.entryId === void 0) notes.push(t("presetNoteNoRow"));
1689
+ if (status.source === "other") notes.push(t("presetNoteOther"));
1690
+ else if (status.customized) notes.push(t("presetNoteCustomized"));
2313
1691
  return notes;
2314
1692
  }
2315
1693
  /**
2316
1694
  * 该预设可执行的动作,按渲染顺序。
2317
1695
  *
2318
- * - 官方随附预设一律无动作(写不了,它的只读语义由"没有按钮"表达,不再用徽标);
2319
- * - 其余预设都给两个动作:`upgrade`(最小更新:只禁用冲突行)与 `reset`
2320
- * (用**所选模板**整份覆盖);
2321
- * - 读不了、无法识别时只剩 `reset` —— 整份覆盖是唯一能修的办法,最小更新会拒绝。
1696
+ * - 本部署没有可编辑 profile(`writable === false`)→ 一个动作都不给;
1697
+ * - 这一行不可寻址(`entryId` 缺席)→ 一个动作都不给(写了也没地方落);
1698
+ * - **别人的** preset → 只给 `upgrade`(我们只该修冲突行,不该替别人重写内容);
1699
+ * - **我们的** preset → `upgrade` + `align`;有覆盖时再加 `revert`;
1700
+ * - 内容不可识别时 `upgrade` 没有意义(它要逐行判定),只给 `align` / `revert`。
2322
1701
  * @param status - 该预设的状态。
1702
+ * @param writable - 本部署有没有可编辑的 profile。
2323
1703
  * @returns 动作清单。
2324
1704
  */
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
- /** 动作按钮文案:两个动作都面向所有可写预设,文案不再区分来源。 */
1705
+ function presetActions(status, writable = true) {
1706
+ if (!writable || status.entryId === void 0) return [];
1707
+ if (status.source === "other") return status.unrecognized ? [] : ["upgrade"];
1708
+ if (status.unrecognized) return status.customized ? ["align", "revert"] : ["align"];
1709
+ return status.customized ? [
1710
+ "upgrade",
1711
+ "align",
1712
+ "revert"
1713
+ ] : ["upgrade", "align"];
1714
+ }
1715
+ /** 动作按钮文案。 */
2330
1716
  function presetActionText(t, action) {
2331
- return action === "reset" ? t("presetActionReset") : t("presetActionUpdate");
1717
+ if (action === "align") return t("presetActionAlign");
1718
+ if (action === "revert") return t("presetActionRevert");
1719
+ return t("presetActionUpdate");
1720
+ }
1721
+ /** 需要二次确认的动作:两个会动到用户已有内容的动作。 */
1722
+ function presetActionNeedsConfirm(action) {
1723
+ return action === "align" || action === "revert";
2332
1724
  }
2333
1725
  /**
2334
1726
  * 动作按钮的悬浮说明:讲清这个按钮**具体会做什么**,以及当前状态下有没有意义。
@@ -2336,7 +1728,7 @@ window.__ModuleLoader__.load({
2336
1728
  * @param t - 文案表。
2337
1729
  * @param action - 该按钮的动作。
2338
1730
  * @param status - 该预设状态(决定"要不要更新")。
2339
- * @param templateLabel - 当前所选模板的显示名(重置提示里点名它是哪份)。
1731
+ * @param templateLabel - 当前所选模板的显示名(对齐提示里点名它是哪份)。
2340
1732
  * @returns 悬浮提示文案。
2341
1733
  */
2342
1734
  function presetActionHint(t, action, status, templateLabel) {
@@ -2345,7 +1737,8 @@ window.__ModuleLoader__.load({
2345
1737
  for (const [name, value] of Object.entries(values)) text = text.replaceAll(`{${name}}`, value);
2346
1738
  return text;
2347
1739
  };
2348
- if (action === "reset") return fill("presetActionResetHint", { template: templateLabel });
1740
+ if (action === "align") return fill("presetActionAlignHint", { template: templateLabel });
1741
+ if (action === "revert") return t("presetActionRevertHint");
2349
1742
  if (status.conflicts.length === 0) return t("presetActionUpdateNothing");
2350
1743
  return fill("presetActionUpdateHint", {
2351
1744
  count: String(status.conflicts.length),
@@ -2360,54 +1753,59 @@ window.__ModuleLoader__.load({
2360
1753
  });
2361
1754
  }
2362
1755
  /**
2363
- * 重置的二次确认文案。还没安装(含 roster 缺席补出的行)时没有本地改动可丢,
2364
- * 不能拿"会覆盖本地改动"吓人;其余情况按覆盖风险如实提示。两种情况都点名
2365
- * 用的是**哪份模板**:重置的来源是用户在「对比模板」里选的那份。
1756
+ * 二次确认文案。两个动作都会动到用户可能已经改过的内容,所以必须说清后果,
1757
+ * 并且**点名写入位置**——这是本插件唯一会改用户 profile 配置的地方。
2366
1758
  * @param t - 文案表。
2367
1759
  * @param status - 该预设状态。
1760
+ * @param action - 待确认的动作。
2368
1761
  * @param templateLabel - 所选模板的显示名。
2369
1762
  * @returns 确认文案。
2370
1763
  */
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);
1764
+ function presetConfirmText(t, status, action, templateLabel) {
1765
+ if (action === "revert") return t("presetConfirmRevert");
1766
+ const lines = [t("presetConfirmAlign").replace("{template}", templateLabel)];
1767
+ if (status.customized) lines.push(t("presetConfirmAlignEdits"));
1768
+ return lines.join("\n");
2374
1769
  }
2375
1770
  /**
2376
- * 执行前摘要:将要改哪几行 / 将写入什么。**没事可做时返回 null**(不渲染这一行)——
2377
- * 用户明确要求去掉「工具行都已禁用,无需调整。」这种"没有信息量的状态句"。
1771
+ * 执行前摘要:**当前状态**下有什么需要处理的。没事可做时返回 null
1772
+ * (不渲染这一行)——用户明确要求去掉"工具行都已禁用,无需调整。"这种没有
1773
+ * 信息量的状态句。
2378
1774
  * @param t - 文案表。
2379
1775
  * @param status - 该预设状态。
2380
1776
  * @returns 摘要文案,或 null(无需渲染)。
2381
1777
  */
2382
1778
  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");
1779
+ if (status.source === "ours" && status.entryId === void 0) return null;
1780
+ if (status.unrecognized) return t("presetPendingBroken");
2386
1781
  if (status.conflicts.length === 0) return null;
2387
1782
  const names = presetConflictNames(t, status.conflicts);
2388
1783
  return t("presetPendingRows").replace("{count}", String(status.conflicts.length)).replace("{rows}", names.join(t("presetListSeparator")));
2389
1784
  }
2390
1785
  /**
2391
- * 执行后结果:成功(改了几处、改了哪几处、是否留了备份)/ 无变化 / 失败原因。
1786
+ * 执行后结果:成功(改了几处、改了哪几处)/ 无变化 / 失败原因。
2392
1787
  * 多行文本,`\n` 分段;调用方的结果行按 `white-space: pre-wrap` 渲染。
2393
1788
  *
2394
- * `action` 必须传:宿主只在 `upgrade` 时返回逐行改动(`reset` 是整份覆盖,
2395
- * `changes` 恒为空),所以重置不能套用"调整了 N 行"的句式 —— 否则会渲染成
2396
- * "调整了 0 处"这种假话。
1789
+ * `action` 必须传:宿主只在 `upgrade` 时返回逐行改动(`align` 是整份替换、
1790
+ * `revert` 是删覆盖,两者的 `changes` 恒为空),所以它们不能套用"调整了 N 行"
1791
+ * 的句式 —— 否则会渲染成"调整了 0 处"这种假话。
1792
+ * @param t - 文案表。
1793
+ * @param result - 宿主返回的执行结果。
1794
+ * @param action - 刚执行的动作。
1795
+ * @returns 结果文案。
2397
1796
  */
2398
1797
  function presetResultText(t, result, action) {
2399
1798
  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"))));
1799
+ const lines = [];
1800
+ if (action === "align") lines.push(result.changed ? t("presetResultAlignDone") : t("presetResultAlignNoChange"));
1801
+ else if (action === "revert") lines.push(result.changed ? t("presetResultRevertDone") : t("presetResultRevertNoChange"));
1802
+ else if (!result.changed) lines.push(t("presetResultNoChange"));
1803
+ else {
1804
+ lines.push(t("presetResultDone").replace("{count}", String(result.changes.length)));
1805
+ if (result.changes.length > 0) {
1806
+ const names = presetConflictNames(t, result.changes.map((change) => change.id));
1807
+ lines.push(t("presetResultRows").replace("{rows}", names.join(t("presetListSeparator"))));
1808
+ }
2411
1809
  }
2412
1810
  if (result.backupPath !== void 0) lines.push(t("presetResultBackup"));
2413
1811
  return lines.join("\n");
@@ -2496,6 +1894,16 @@ window.__ModuleLoader__.load({
2496
1894
  const [phase, setPhase] = (0, react.useState)("loading");
2497
1895
  const [presets, setPresets] = (0, react.useState)([]);
2498
1896
  const [templates, setTemplates] = (0, react.useState)([]);
1897
+ /**
1898
+ * 本部署有没有可编辑的 profile(宿主 `configEditor` 是否在场)。没有时面板
1899
+ * 只读 —— 一个动作都不给,说明行会讲清原因。
1900
+ */
1901
+ const [writable, setWritable] = (0, react.useState)(true);
1902
+ /**
1903
+ * 旧版目录机制留下的 `~/.dsh/.agent-presets`(0.1.7 起没有任何代码读它)。
1904
+ * 只作为"可以安全删除"的提示展示,不参与任何读写。
1905
+ */
1906
+ const [legacyRoot, setLegacyRoot] = (0, react.useState)(void 0);
2499
1907
  const [presetId, setPresetId] = (0, react.useState)(void 0);
2500
1908
  const [templateId, setTemplateId] = (0, react.useState)(void 0);
2501
1909
  /** 用户手动挑过模板之后就不再自动跟随预设(否则他刚选的会被覆盖掉)。 */
@@ -2505,7 +1913,8 @@ window.__ModuleLoader__.load({
2505
1913
  const [openPicker, setOpenPicker] = (0, react.useState)(null);
2506
1914
  const [busyId, setBusyId] = (0, react.useState)(null);
2507
1915
  const [results, setResults] = (0, react.useState)({});
2508
- const [confirmingId, setConfirmingId] = (0, react.useState)(null);
1916
+ /** 待二次确认的动作(动作 + 目标预设);`align` / `revert` 会动到已有内容。 */
1917
+ const [confirming, setConfirming] = (0, react.useState)(null);
2509
1918
  /**
2510
1919
  * 悬浮说明:portal 到 body 的 fixed 浮层 + 按按钮实时矩形算出的坐标。
2511
1920
  * 位置不能像之前那样用绝对定位挂在卡片里:设置面板的滚动容器是 `overflow-y:auto`,
@@ -2539,8 +1948,10 @@ window.__ModuleLoader__.load({
2539
1948
  setPhase("unavailable");
2540
1949
  return;
2541
1950
  }
2542
- setPresets(mergeBundledPresets(value.presets));
1951
+ setPresets(value.presets);
2543
1952
  setTemplates(Array.isArray(value.templates) ? value.templates : []);
1953
+ setWritable(value.writable !== false);
1954
+ setLegacyRoot(typeof value.legacyPresetRoot === "string" && value.legacyPresetRoot.length > 0 ? value.legacyPresetRoot : void 0);
2544
1955
  setPhase("ready");
2545
1956
  } catch {
2546
1957
  setPhase("unavailable");
@@ -2588,7 +1999,7 @@ window.__ModuleLoader__.load({
2588
1999
  ]);
2589
2000
  /** 执行一个动作;成功后重新拉状态与比较(结果行保留,直到下一次动作)。 */
2590
2001
  const runAction = (0, react.useCallback)((preset, action) => {
2591
- setConfirmingId(null);
2002
+ setConfirming(null);
2592
2003
  setBusyId(preset.id);
2593
2004
  setResults((prev) => ({
2594
2005
  ...prev,
@@ -2679,14 +2090,14 @@ window.__ModuleLoader__.load({
2679
2090
  "aria-haspopup": "menu",
2680
2091
  "aria-expanded": openPicker === key,
2681
2092
  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" })]
2093
+ 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
2094
  })
2684
2095
  })]
2685
2096
  });
2686
2097
  /** 选中那份的详情:名称/来源 → 动作 → 说明 → 待改 → 二次确认 → 结果。 */
2687
2098
  const detail = (preset) => {
2688
- const notes = presetNotes(t, preset);
2689
- const actions = presetActions(preset);
2099
+ const notes = presetNotes(t, preset, writable);
2100
+ const actions = presetActions(preset, writable);
2690
2101
  const pending = presetPendingText(t, preset);
2691
2102
  const result = results[preset.id] ?? null;
2692
2103
  const running = busyId === preset.id;
@@ -2710,20 +2121,20 @@ window.__ModuleLoader__.load({
2710
2121
  onMouseLeave: () => setHinted(null),
2711
2122
  children: actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2712
2123
  type: "button",
2713
- className: "tp-actionButton tpp-actionButton" + (action === "reset" ? " tpp-danger" : ""),
2124
+ className: "tp-actionButton tpp-actionButton" + (action === "align" ? " tpp-danger" : ""),
2714
2125
  disabled: running,
2715
2126
  "aria-description": presetActionHint(t, action, preset, templateLabel),
2716
2127
  onMouseEnter: (event) => showHint(action, event.currentTarget),
2717
2128
  onFocus: (event) => showHint(action, event.currentTarget),
2718
2129
  onBlur: () => setHinted(null),
2719
2130
  onClick: () => {
2720
- if (action === "reset") setConfirmingId(preset.id);
2131
+ if (presetActionNeedsConfirm(action)) setConfirming({
2132
+ id: preset.id,
2133
+ action
2134
+ });
2721
2135
  else runAction(preset, action);
2722
2136
  },
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
- })]
2137
+ 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
2138
  }, action))
2728
2139
  }) : null]
2729
2140
  }),
@@ -2741,24 +2152,24 @@ window.__ModuleLoader__.load({
2741
2152
  className: "tpp-state",
2742
2153
  children: pending
2743
2154
  }) : null,
2744
- confirmingId === preset.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2155
+ confirming !== null && confirming.id === preset.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2745
2156
  className: "tpp-confirm",
2746
2157
  role: "group",
2747
- "aria-label": t("presetActionReset"),
2158
+ "aria-label": t("presetConfirmTitle"),
2748
2159
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2749
2160
  className: "tpp-confirmText",
2750
- children: presetConfirmText(t, preset, selectedTemplateLabel)
2161
+ children: presetConfirmText(t, preset, confirming.action, selectedTemplateLabel)
2751
2162
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2752
2163
  className: "tpp-confirmActions",
2753
2164
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2754
2165
  type: "button",
2755
2166
  className: "tp-actionButton tpp-danger",
2756
- onClick: () => runAction(preset, "reset"),
2167
+ onClick: () => runAction(preset, confirming.action),
2757
2168
  children: t("presetConfirmYes")
2758
2169
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2759
2170
  type: "button",
2760
2171
  className: "tp-actionButton",
2761
- onClick: () => setConfirmingId(null),
2172
+ onClick: () => setConfirming(null),
2762
2173
  children: t("presetConfirmNo")
2763
2174
  })]
2764
2175
  })]
@@ -2805,7 +2216,7 @@ window.__ModuleLoader__.load({
2805
2216
  label: presetOptionLabel(preset)
2806
2217
  })), (id) => {
2807
2218
  setPresetId(id);
2808
- setConfirmingId(null);
2219
+ setConfirming(null);
2809
2220
  setDiffOpen(false);
2810
2221
  }),
2811
2222
  templates.length > 0 ? picker("template", t("presetPickTemplate"), selectedTemplate === void 0 ? t("presetEmpty") : presetTemplateLabel(selectedTemplate), templates.map((template) => ({
@@ -2845,7 +2256,11 @@ window.__ModuleLoader__.load({
2845
2256
  }) : null
2846
2257
  ]
2847
2258
  }),
2848
- selected !== void 0 ? detail(selected) : null
2259
+ selected !== void 0 ? detail(selected) : null,
2260
+ legacyRoot !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2261
+ className: "tpp-state",
2262
+ children: t("presetNoteLegacy").replace("{path}", legacyRoot)
2263
+ }) : null
2849
2264
  ] }) : null
2850
2265
  ]
2851
2266
  }),
@@ -3118,8 +2533,8 @@ window.__ModuleLoader__.load({
3118
2533
  }
3119
2534
  /** Render the Tool Plus settings section. */
3120
2535
  function ToolPlusSection(props) {
3121
- const { t, scope } = props;
3122
- const form = useToolForm(scope, ALL_FIELDS);
2536
+ const { t, form: configForm } = props;
2537
+ const form = useToolForm(configForm, ALL_FIELDS);
3123
2538
  const sectionRef = (0, react.useRef)(null);
3124
2539
  const tabsId = (0, react.useRef)(`tps-${Math.random().toString(36).slice(2, 8)}`);
3125
2540
  const tabRefs = (0, react.useRef)([]);
@@ -3129,9 +2544,9 @@ window.__ModuleLoader__.load({
3129
2544
  const [actionResults, setActionResults] = (0, react.useState)({});
3130
2545
  const [rmSafeToast, setRmSafeToast] = (0, react.useState)(null);
3131
2546
  const handleSave = (0, react.useCallback)(async () => {
3132
- const before = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
2547
+ const before = Boolean(configForm.getSnapshot().value?.rmSafe ?? true);
3133
2548
  await form.actions.save();
3134
- const after = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
2549
+ const after = Boolean(configForm.getSnapshot().value?.rmSafe ?? true);
3135
2550
  if (before === after || !after) return;
3136
2551
  try {
3137
2552
  const result = await createWebConnectionRpc().call(TOOL_PLUS_RPC_CHANNEL, RM_SAFE_STATUS_ENDPOINT, {});
@@ -3144,7 +2559,7 @@ window.__ModuleLoader__.load({
3144
2559
  });
3145
2560
  } catch {}
3146
2561
  }, [
3147
- scope,
2562
+ configForm,
3148
2563
  form,
3149
2564
  t
3150
2565
  ]);
@@ -3403,7 +2818,7 @@ window.__ModuleLoader__.load({
3403
2818
  }),
3404
2819
  rmSafeToast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
3405
2820
  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, {}),
2821
+ 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
2822
  onDone: () => {
3408
2823
  setRmSafeToast(null);
3409
2824
  }
@@ -3607,13 +3022,18 @@ window.__ModuleLoader__.load({
3607
3022
  invalidNumber: "请填写数字;留空表示使用默认值。",
3608
3023
  appliesTo: "保存后对新调用生效。",
3609
3024
  groupPresets: "预设",
3610
- presetNoteBroken: "读取失败:{reason}",
3611
- presetNoteUnrecognized: "无法识别内容,未做任何改动。",
3025
+ presetNoteBroken: "无法挂载:{reason}",
3026
+ presetNoteUnrecognized: "插件行列表读不出来,未做任何改动。",
3027
+ presetNoteNoRow: "这个 profile 里没有本插件的声明行——插件不是以 profile bundle 装的。",
3028
+ presetNoteOther: "这个预设由别处声明,本面板只替它修冲突的工具行。",
3029
+ presetNoteCustomized: "这一行在你的 profile 配置里有覆盖;「恢复随包」可撤销。",
3030
+ presetNoteNoProfile: "本部署没有可编辑的 profile,只能查看。",
3031
+ presetNoteLegacy: "检测到旧版预设目录 {path}:0.1.7 起没有任何代码读它,可以安全删除。",
3612
3032
  presetPickPreset: "预设",
3613
3033
  presetPickTemplate: "对比模板",
3614
3034
  presetToolOk: "已接入本插件工具",
3615
3035
  presetToolWarn: "还有 {count} 个工具行未处理",
3616
- presetToolError: "读不到这份预设",
3036
+ presetToolError: "读不到这份预设的插件行",
3617
3037
  presetToolLoading: "正在检查…",
3618
3038
  presetCompareOk: "与所选模板一致",
3619
3039
  presetCompareWarn: "与所选模板有 {count} 处不同",
@@ -3630,25 +3050,30 @@ window.__ModuleLoader__.load({
3630
3050
  presetDiffWholeRow: "(整行)",
3631
3051
  presetDiffEmpty: "内容一致,没有可显示的差异。",
3632
3052
  presetActionUpdate: "最小更新",
3633
- presetActionUpdateHint: "只禁用这 {count} 个工具行({rows}),其他内容保持不动。",
3634
- presetActionUpdateNothing: "当前没有需要更新的工具行。",
3635
- presetActionReset: "重置为对比模板",
3636
- presetActionResetHint: "用所选模板「{template}」整份覆盖这份预设。原文件会先备份,本地改动会丢失。",
3053
+ presetActionUpdateHint: "只把这 {count} 个工具行({rows})关掉,其他内容一个字节都不动。",
3054
+ presetActionUpdateNothing: "当前没有需要关掉的工具行。",
3055
+ presetActionAlign: "对齐模板",
3056
+ presetActionAlignHint: "用所选模板「{template}」整份替换这一行的插件列表。",
3057
+ presetActionRevert: "恢复随包",
3058
+ presetActionRevertHint: "删掉你写在本 profile 配置里的这份预设的覆盖,回落到随包声明。",
3637
3059
  presetActionWorking: "处理中…",
3638
- presetPendingReset: "将用所选模板整份覆盖这份预设,本地改动会被覆盖。",
3639
- presetPendingInstall: "将写入这份预设:它当前尚未安装。",
3640
3060
  presetPendingRows: "工具行将调整这 {count} 处:{rows}",
3641
- presetConfirmReset: "用模板「{template}」整份覆盖会丢掉本地改动(原文件会先备份一份)。确认重置?",
3642
- presetConfirmInstall: "将从模板「{template}」写入这份预设;当前没有会被覆盖的内容。",
3643
- presetConfirmYes: "确认重置",
3061
+ presetPendingBroken: "插件行列表读不出来;「对齐模板」可以整份修好它。",
3062
+ presetConfirmTitle: "确认动作",
3063
+ presetConfirmAlign: "将用模板「{template}」整份替换这份预设的插件列表,并写入本 profile 的配置(cordis.patch.yml)。",
3064
+ presetConfirmAlignEdits: "你在这一行上的改动会被覆盖。",
3065
+ presetConfirmRevert: "将删掉你写在本 profile 配置里的这份预设的覆盖,回落到随包声明。你的改动会丢失。",
3066
+ presetConfirmYes: "确认",
3644
3067
  presetConfirmNo: "取消",
3645
3068
  presetResultDone: "已调整 {count} 处。",
3646
3069
  presetResultRows: "涉及:{rows}",
3647
3070
  presetResultNoChange: "没有需要改动的行。",
3648
- presetResultResetDone: "已重置为所选模板。",
3649
- presetResultResetNoChange: "已经与所选模板一致,没有改动。",
3071
+ presetResultAlignDone: "已按所选模板对齐,并写入本 profile 的配置。",
3072
+ presetResultAlignNoChange: "已经与所选模板一致,没有改动。",
3073
+ presetResultRevertDone: "已删掉覆盖,回落到随包声明。",
3074
+ presetResultRevertNoChange: "这一行本来就没有覆盖,没有改动。",
3075
+ presetResultBackup: "改动前的内容已备份(与本 profile 的补丁同目录,.bak-<版本>)。",
3650
3076
  presetResultFailed: "操作失败:{reason}",
3651
- presetResultBackup: "原文件已备份。",
3652
3077
  presetFailUnknown: "未知原因",
3653
3078
  presetEntryCommand: "命令执行",
3654
3079
  presetEntryPwsh: "PowerShell",
@@ -3853,13 +3278,18 @@ window.__ModuleLoader__.load({
3853
3278
  invalidNumber: "Enter a number, or leave blank to use the default.",
3854
3279
  appliesTo: "Values apply to new calls.",
3855
3280
  groupPresets: "Presets",
3856
- presetNoteBroken: "Could not be read: {reason}",
3857
- presetNoteUnrecognized: "Content not recognized; nothing was changed.",
3281
+ presetNoteBroken: "Cannot be mounted: {reason}",
3282
+ presetNoteUnrecognized: "The plugin row list cannot be read; nothing was changed.",
3283
+ presetNoteNoRow: "This profile has no declaration row for the plugin — it is not installed as a profile bundle.",
3284
+ presetNoteOther: "Another bundle declares this preset; this panel only fixes its conflicting tool rows.",
3285
+ presetNoteCustomized: "This row carries an override in your profile configuration; \"Revert to bundled\" removes it.",
3286
+ presetNoteNoProfile: "This deployment has no editable profile, so the panel is read-only.",
3287
+ presetNoteLegacy: "A legacy preset directory was found at {path}: nothing reads it since 0.1.7, so it is safe to delete.",
3858
3288
  presetPickPreset: "Preset",
3859
3289
  presetPickTemplate: "Compare with template",
3860
3290
  presetToolOk: "Plugin tools wired in",
3861
3291
  presetToolWarn: "{count} tool rows left",
3862
- presetToolError: "This preset cannot be read",
3292
+ presetToolError: "Plugin rows for this preset cannot be read",
3863
3293
  presetToolLoading: "Checking…",
3864
3294
  presetCompareOk: "Matches the selected template",
3865
3295
  presetCompareWarn: "{count} differences vs the selected template",
@@ -3876,25 +3306,30 @@ window.__ModuleLoader__.load({
3876
3306
  presetDiffWholeRow: "(whole row)",
3877
3307
  presetDiffEmpty: "Content matches; nothing to show.",
3878
3308
  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.",
3309
+ presetActionUpdateHint: "Disables only these {count} tool rows ({rows}); nothing else changes at all.",
3310
+ presetActionUpdateNothing: "No tool rows need disabling.",
3311
+ presetActionAlign: "Align to template",
3312
+ presetActionAlignHint: "Replaces this row whole plugin list with the selected template \"{template}\".",
3313
+ presetActionRevert: "Revert to bundled",
3314
+ presetActionRevertHint: "Deletes the override written in this profile configuration, falling back to the bundled declaration.",
3883
3315
  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
3316
  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",
3317
+ presetPendingBroken: "The plugin row list cannot be read; \"Align to template\" replaces it with a good one.",
3318
+ presetConfirmTitle: "Confirm action",
3319
+ presetConfirmAlign: "The whole plugin list will be replaced with template \"{template}\" and written to this profile configuration (cordis.patch.yml).",
3320
+ presetConfirmAlignEdits: "Your edits to this row will be overwritten.",
3321
+ presetConfirmRevert: "The override written in this profile configuration will be deleted, falling back to the bundled declaration. Your edits will be lost.",
3322
+ presetConfirmYes: "Confirm",
3890
3323
  presetConfirmNo: "Cancel",
3891
3324
  presetResultDone: "Adjusted {count} entries.",
3892
3325
  presetResultRows: "Entries: {rows}",
3893
3326
  presetResultNoChange: "Nothing needed changing.",
3894
- presetResultResetDone: "Reset to the selected template.",
3895
- presetResultResetNoChange: "Already matches the selected template; nothing changed.",
3327
+ presetResultAlignDone: "Aligned to the selected template and written to this profile configuration.",
3328
+ presetResultAlignNoChange: "Already matches the selected template; nothing changed.",
3329
+ presetResultRevertDone: "Override removed; the preset falls back to its bundled declaration.",
3330
+ presetResultRevertNoChange: "This row carried no override; nothing changed.",
3331
+ presetResultBackup: "The previous content was backed up beside this profile patch (.bak-<version>).",
3896
3332
  presetResultFailed: "Action failed: {reason}",
3897
- presetResultBackup: "The original file was backed up.",
3898
3333
  presetFailUnknown: "unknown reason",
3899
3334
  presetEntryCommand: "Command execution",
3900
3335
  presetEntryPwsh: "PowerShell",
@@ -4237,6 +3672,9 @@ window.__ModuleLoader__.load({
4237
3672
  expandRest: "… 其余 {count} 行",
4238
3673
  "files.one": "{count} 个文件",
4239
3674
  "files.other": "{count} 个文件",
3675
+ "codeBlock.title": "代码块",
3676
+ "codeBlock.wrap": "自动换行",
3677
+ "codeBlock.unwrap": "取消自动换行",
4240
3678
  running: "运行中",
4241
3679
  done: "完成",
4242
3680
  failed: "失败",
@@ -4278,6 +3716,7 @@ window.__ModuleLoader__.load({
4278
3716
  "terminal.failed": "失败",
4279
3717
  "terminal.done": "已完成",
4280
3718
  "terminal.noOutput": "无输出",
3719
+ "terminal.noExitCode": "未正常退出",
4281
3720
  "bash.background": "后台任务 {jobId}",
4282
3721
  "image.loading": "加载中",
4283
3722
  "image.loadFailed": "加载失败",
@@ -4301,6 +3740,9 @@ window.__ModuleLoader__.load({
4301
3740
  expandRest: "… {count} more lines",
4302
3741
  "files.one": "{count} file",
4303
3742
  "files.other": "{count} files",
3743
+ "codeBlock.title": "Code block",
3744
+ "codeBlock.wrap": "Wrap lines",
3745
+ "codeBlock.unwrap": "Do not wrap lines",
4304
3746
  running: "Running",
4305
3747
  done: "Done",
4306
3748
  failed: "Failed",
@@ -4342,6 +3784,7 @@ window.__ModuleLoader__.load({
4342
3784
  "terminal.failed": "Failed",
4343
3785
  "terminal.done": "Done",
4344
3786
  "terminal.noOutput": "No output",
3787
+ "terminal.noExitCode": "no exit code",
4345
3788
  "bash.background": "Background job {jobId}",
4346
3789
  "image.loading": "Loading",
4347
3790
  "image.loadFailed": "Load failed",
@@ -4372,6 +3815,22 @@ window.__ModuleLoader__.load({
4372
3815
  return block.kind === "tool-result";
4373
3816
  }
4374
3817
  /**
3818
+ * Whether a call already landed as a failed result.
3819
+ *
3820
+ * Only a settled result can be wrong about its work: the union's error state is
3821
+ * `ToolResultNode.isError` alone, and the shipped row model reads it the same
3822
+ * way — settle on the `kind` discriminant (`'kind' in block`) and only then look
3823
+ * at `error`, `isError`. A preparing or dispatched call has no result yet, so it
3824
+ * is never an error; the rows that fall back to the generic shell keep exactly
3825
+ * the criterion they have always had (`isError === true`), stated where the
3826
+ * union still allows it.
3827
+ * @param block - the frozen running-or-settled call node.
3828
+ * @returns whether the call settled as a failure.
3829
+ */
3830
+ function isErrorResult(block) {
3831
+ return isSettled(block) && block.isError === true;
3832
+ }
3833
+ /**
4375
3834
  * The call head paired with a block: a running call is its own head; a settled
4376
3835
  * result carries the head backfilled from the in-window `tool/call`, which is
4377
3836
  * `null` when window truncation left that event outside.
@@ -4444,7 +3903,7 @@ window.__ModuleLoader__.load({
4444
3903
  function cardState(block) {
4445
3904
  if (!isSettled(block)) return "running";
4446
3905
  if (isRecord$2(block.error) && block.error.code === "interrupted") return "warning";
4447
- if (block.isError === true) return "error";
3906
+ if (isErrorResult(block)) return "error";
4448
3907
  const terminal = narrowTerminalCardMeta(cardMeta(block));
4449
3908
  if (terminal !== null && terminal.mode === "foreground" && (terminal.timedOut || terminal.aborted)) return "warning";
4450
3909
  return "ok";
@@ -4626,21 +4085,39 @@ window.__ModuleLoader__.load({
4626
4085
  timedOut
4627
4086
  };
4628
4087
  }
4088
+ /**
4089
+ * Bind the card dictionary to the shared code-card toolbar labels.
4090
+ *
4091
+ * The toolbar rides every code surface the primitives draw (the diff block, the
4092
+ * read block), and the host's own adapter for it reads the base locale's
4093
+ * `codeBlock.*` wording; this dictionary carries the same three strings, so a
4094
+ * takeover's code cards read exactly as the shipped ones do.
4095
+ * @param t - the card translate seat.
4096
+ * @returns the language fallback and wrapping actions.
4097
+ */
4098
+ function codeToolbarLabels(t) {
4099
+ return {
4100
+ codeLabel: t("codeBlock.title"),
4101
+ wrapLabel: t("codeBlock.wrap"),
4102
+ unwrapLabel: t("codeBlock.unwrap")
4103
+ };
4104
+ }
4629
4105
  /** Bind the card dictionary to the diff primitive's chrome labels. */
4630
4106
  function diffBlockLabels(t) {
4631
4107
  return {
4108
+ ...codeToolbarLabels(t),
4632
4109
  copy: t("copy"),
4633
4110
  copied: t("copied"),
4634
4111
  collapseAria: t("collapseAria"),
4635
4112
  expandAria: (count) => t("expandAria", { count }),
4636
4113
  collapse: t("collapse"),
4637
- expand: (count) => t("expandRest", { count }),
4638
- files: (count) => t(count === 1 ? "files.one" : "files.other", { count })
4114
+ expand: (count) => t("expandRest", { count })
4639
4115
  };
4640
4116
  }
4641
4117
  /** Bind the card dictionary to the read primitive's chrome labels. */
4642
4118
  function readBlockLabels(t) {
4643
4119
  return {
4120
+ ...codeToolbarLabels(t),
4644
4121
  window: (shown, total) => t("read.window", {
4645
4122
  shown,
4646
4123
  total
@@ -4679,6 +4156,7 @@ window.__ModuleLoader__.load({
4679
4156
  return {
4680
4157
  signal: (signal) => t("terminal.signal", { signal }),
4681
4158
  exitCode: (exitCode) => t("terminal.exitCode", { code: exitCode }),
4159
+ noExitCode: t("terminal.noExitCode"),
4682
4160
  running: t("terminal.running"),
4683
4161
  failed: t("terminal.failed"),
4684
4162
  done: t("terminal.done"),
@@ -5038,11 +4516,11 @@ window.__ModuleLoader__.load({
5038
4516
  */
5039
4517
  /** Leading glyphs (the shipped table): every glyph renders at 14 inside the 16px leading box. */
5040
4518
  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 })
4519
+ bash: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutlineRegular, { size: 14 }),
4520
+ read: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutlineRegular, { size: 14 }),
4521
+ edit: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, { size: 14 }),
4522
+ search: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 14 }),
4523
+ others: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkleRegular, { size: 14 })
5046
4524
  };
5047
4525
  /**
5048
4526
  * The leading mark of one row: the tool's glyph while the call is clean or
@@ -5363,7 +4841,7 @@ window.__ModuleLoader__.load({
5363
4841
  const { t, block, cwd, inspect, openFile, title, toolName } = props;
5364
4842
  const args = parseCardArgs(block);
5365
4843
  const meta = narrowAstEditCardMeta(cardMeta(block));
5366
- if (isSubCall(block) || block.isError === true || meta === null || meta.preview === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
4844
+ if (isSubCall(block) || isErrorResult(block) || meta === null || meta.preview === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5367
4845
  t,
5368
4846
  title,
5369
4847
  block,
@@ -5450,7 +4928,7 @@ window.__ModuleLoader__.load({
5450
4928
  const args = parseCardArgs(block);
5451
4929
  const meta = narrowEditCardMeta(cardMeta(block));
5452
4930
  const replaceForm = replaceHunks(args);
5453
- if (isSubCall(block) || block.isError === true || meta === null && replaceForm === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
4931
+ if (isSubCall(block) || isErrorResult(block) || meta === null && replaceForm === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5454
4932
  t,
5455
4933
  title,
5456
4934
  block,
@@ -5665,7 +5143,7 @@ window.__ModuleLoader__.load({
5665
5143
  const args = parseCardArgs(block);
5666
5144
  const meta = narrowReadCardMeta(cardMeta(block));
5667
5145
  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, {
5146
+ if (isSubCall(block) || isErrorResult(block)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5669
5147
  t,
5670
5148
  title,
5671
5149
  block,
@@ -5948,7 +5426,7 @@ window.__ModuleLoader__.load({
5948
5426
  className: "twc-lightboxClose",
5949
5427
  "aria-label": t("image.close"),
5950
5428
  onClick: onClose,
5951
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 16 })
5429
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 16 })
5952
5430
  })
5953
5431
  ]
5954
5432
  }), document.body);
@@ -6030,7 +5508,7 @@ window.__ModuleLoader__.load({
6030
5508
  const { t, block, cwd, inspect, title, toolName } = props;
6031
5509
  const args = parseCardArgs(block);
6032
5510
  const meta = narrowSearchCardMeta(cardMeta(block));
6033
- if (isSubCall(block) || block.isError === true || meta === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5511
+ if (isSubCall(block) || isErrorResult(block) || meta === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6034
5512
  t,
6035
5513
  title,
6036
5514
  block,
@@ -6109,7 +5587,7 @@ window.__ModuleLoader__.load({
6109
5587
  const meta = narrowTerminalCardMeta(cardMeta(block));
6110
5588
  const command = argText(args, "command");
6111
5589
  const state = cardState(block);
6112
- if (block.isError === true || command === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
5590
+ if (isErrorResult(block) || command === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6113
5591
  t,
6114
5592
  title,
6115
5593
  block,
@@ -6197,7 +5675,7 @@ window.__ModuleLoader__.load({
6197
5675
  const meta = narrowWriteCardMeta(cardMeta(block));
6198
5676
  const content = argText(args, "content");
6199
5677
  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, {
5678
+ if (isSubCall(block) || isErrorResult(block) || content === null || path === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
6201
5679
  t,
6202
5680
  title,
6203
5681
  block,
@@ -6323,13 +5801,13 @@ window.__ModuleLoader__.load({
6323
5801
  disposeEntries?.();
6324
5802
  disposeEntries = void 0;
6325
5803
  };
6326
- const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NS });
5804
+ const form = ctx.configForms.get(SETTINGS_NS);
6327
5805
  ctx.effect(() => {
6328
5806
  const sync = () => {
6329
- if (cardsEnabled(scope.getSnapshot().value)) install();
5807
+ if (cardsEnabled(form.getSnapshot().value)) install();
6330
5808
  else uninstall();
6331
5809
  };
6332
- const stop = scope.subscribe(sync);
5810
+ const stop = form.subscribe(sync);
6333
5811
  sync();
6334
5812
  return stop;
6335
5813
  }, "tool-plus: web card switch");
@@ -6383,40 +5861,33 @@ window.__ModuleLoader__.load({
6383
5861
  * spells the same value the Host plugin registers (src/config/settings.ts).
6384
5862
  */
6385
5863
  const BASH_PLUS_CLIENT_NS = "tool-plus";
6386
- /** Locale dictionary namespace of this package's card copy. */
5864
+ /** Locale dictionary namespace of this package's page copy. */
6387
5865
  const BASH_PLUS_LOCALE_NS = "tool-plus";
6388
- /** Required services (cordis fiber inject): slots + locale + the settings scope binder. */
5866
+ /** Required services (cordis fiber inject): slots + locale + the shared configuration forms. */
6389
5867
  const inject = [
6390
5868
  "slots",
6391
5869
  "locale",
6392
- "settingsScope"
5870
+ "configForms"
6393
5871
  ];
6394
5872
  /**
6395
- * Mount the tool-plus settings card.
5873
+ * Mount the tool-plus settings page.
6396
5874
  * @param ctx - the browser plugin context.
6397
5875
  */
6398
5876
  function apply(ctx) {
6399
5877
  ctx.effect(() => ctx.locale.register(BASH_PLUS_LOCALE_NS, {
6400
5878
  zh: zh$1,
6401
5879
  en: en$1
6402
- }), "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,
6407
- locale: BASH_PLUS_LOCALE_NS,
6408
- inject: () => ({ scope })
6409
- }, BashPlusCard));
5880
+ }), "tool-plus: page dictionaries");
5881
+ const form = ctx.configForms.get(BASH_PLUS_CLIENT_NS);
6410
5882
  registerToolCards(ctx);
6411
- const sectionScope = ctx.settingsScope.bind({ namespace: BASH_PLUS_CLIENT_NS });
6412
- ctx.slots.inject("settings.section", () => ctx.slots.register({
5883
+ ctx.effect(() => ctx.configForms.whileServed([BASH_PLUS_CLIENT_NS], () => ctx.slots.inject("settings.section", () => ctx.slots.register({
6413
5884
  name: "settings.section",
6414
5885
  id: "tool-plus",
6415
5886
  order: 16,
6416
5887
  label: () => ctx.locale.bind(BASH_PLUS_LOCALE_NS)("nav"),
6417
5888
  locale: BASH_PLUS_LOCALE_NS,
6418
- inject: () => ({ scope: sectionScope })
6419
- }, ToolPlusSection));
5889
+ inject: () => ({ form })
5890
+ }, ToolPlusSection))), "tool-plus: settings page");
6420
5891
  }
6421
5892
  //#endregion
6422
5893
  exports.BASH_PLUS_CLIENT_NS = BASH_PLUS_CLIENT_NS;