@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.
- package/README.en.md +12 -5
- package/README.md +11 -4
- package/THIRD_PARTY_NOTICES.md +36 -0
- package/lib/client.js +377 -906
- package/lib/index.mjs +885 -1087
- package/lib/{scrapers-mhvFrvjH.mjs → scrapers-DSArdJ6N.mjs} +1 -1
- package/lib/types/client/ToolPlusSection.d.ts +3 -3
- package/lib/types/client/forms.d.ts +6 -6
- package/lib/types/client/index.d.ts +29 -12
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/preset-panel.d.ts +54 -42
- package/lib/types/config/settings.d.ts +202 -21
- package/lib/types/host/browser-probe-rpc.d.ts +2 -1
- package/lib/types/host/preset-host.d.ts +103 -0
- package/lib/types/host/preset-rpc.d.ts +37 -72
- package/lib/types/presets/compare.d.ts +14 -37
- package/lib/types/presets/conflicts.d.ts +59 -0
- package/lib/types/presets/delta.d.ts +23 -18
- package/lib/types/presets/plan.d.ts +55 -0
- package/lib/types/presets/rows.d.ts +84 -0
- package/lib/types/presets/status.d.ts +56 -47
- package/lib/types/tools/bash/background.d.ts +20 -5
- package/lib/types/tools/bash/streaming-output.d.ts +4 -2
- package/lib/types/tools/shared/browser-rpc-channel.d.ts +84 -42
- package/lib/types/web/client/labels.d.ts +1 -1
- package/lib/types/web/client/row-utils.d.ts +26 -1
- package/lib/{utils-eiUz9n8I.mjs → utils-DA8R1_Q8.mjs} +1 -1
- package/package.json +166 -151
- package/presets/tool-plus-ptc.patch.yml +164 -0
- package/presets/tool-plus-standard.patch.yml +158 -0
- package/lib/types/client/BashPlusCard.d.ts +0 -53
- package/lib/types/presets/install.d.ts +0 -44
- package/lib/types/presets/paths.d.ts +0 -41
- package/lib/types/presets/rewrite.d.ts +0 -86
- package/presets/install-presets.mjs +0 -143
- package/presets/package.json +0 -42
- package/presets/tool-plus-ptc/agent.cordis.yml +0 -284
- package/presets/tool-plus-ptc/preset.yml +0 -3
- package/presets/tool-plus-standard/agent.cordis.yml +0 -269
- 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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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.
|
|
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
|
|
1944
|
-
* @param scope -
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2181
|
-
*
|
|
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
|
|
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
|
-
*
|
|
2289
|
-
* `tool-plus-ptc
|
|
2290
|
-
* `tool-plus-standard`,别按目录名排序撞上 ptc
|
|
2291
|
-
*
|
|
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
|
-
* -
|
|
2320
|
-
*
|
|
2321
|
-
* -
|
|
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.
|
|
2327
|
-
|
|
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
|
-
|
|
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 === "
|
|
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
|
-
*
|
|
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 (
|
|
2373
|
-
|
|
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
|
-
*
|
|
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 (
|
|
2384
|
-
if (status.
|
|
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` 时返回逐行改动(`
|
|
2395
|
-
* `changes`
|
|
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
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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 === "
|
|
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
|
|
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.
|
|
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
|
-
|
|
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("
|
|
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,
|
|
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: () =>
|
|
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
|
-
|
|
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,
|
|
3122
|
-
const form = useToolForm(
|
|
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(
|
|
2547
|
+
const before = Boolean(configForm.getSnapshot().value?.rmSafe ?? true);
|
|
3133
2548
|
await form.actions.save();
|
|
3134
|
-
const after = Boolean(
|
|
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
|
-
|
|
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.
|
|
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: "
|
|
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: "
|
|
3634
|
-
presetActionUpdateNothing: "
|
|
3635
|
-
|
|
3636
|
-
|
|
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
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
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
|
-
|
|
3649
|
-
|
|
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: "
|
|
3857
|
-
presetNoteUnrecognized: "
|
|
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: "
|
|
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});
|
|
3880
|
-
presetActionUpdateNothing: "No tool rows need
|
|
3881
|
-
|
|
3882
|
-
|
|
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
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
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
|
-
|
|
3895
|
-
|
|
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
|
|
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.
|
|
5042
|
-
read: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
5043
|
-
edit: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
5044
|
-
search: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
5045
|
-
others: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5804
|
+
const form = ctx.configForms.get(SETTINGS_NS);
|
|
6327
5805
|
ctx.effect(() => {
|
|
6328
5806
|
const sync = () => {
|
|
6329
|
-
if (cardsEnabled(
|
|
5807
|
+
if (cardsEnabled(form.getSnapshot().value)) install();
|
|
6330
5808
|
else uninstall();
|
|
6331
5809
|
};
|
|
6332
|
-
const stop =
|
|
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
|
|
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
|
|
5866
|
+
/** Required services (cordis fiber inject): slots + locale + the shared configuration forms. */
|
|
6389
5867
|
const inject = [
|
|
6390
5868
|
"slots",
|
|
6391
5869
|
"locale",
|
|
6392
|
-
"
|
|
5870
|
+
"configForms"
|
|
6393
5871
|
];
|
|
6394
5872
|
/**
|
|
6395
|
-
* Mount the tool-plus settings
|
|
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:
|
|
6403
|
-
const
|
|
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
|
-
|
|
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: () => ({
|
|
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;
|