@xiaoso/dsh-tool-plus 0.1.10-beta.1 → 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/lib/client.js +131 -751
- package/lib/index.mjs +12 -8
- package/lib/{scrapers-DODCPcZW.mjs → scrapers-DSArdJ6N.mjs} +1 -1
- package/lib/types/client/index.d.ts +18 -14
- package/lib/{utils-D80w8vry.mjs → utils-DA8R1_Q8.mjs} +1 -1
- package/package.json +166 -166
- package/lib/types/client/BashPlusCard.d.ts +0 -53
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
|
/**
|
|
@@ -1081,735 +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 form 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 shared configuration form; 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, form: configForm } = props;
|
|
1468
|
-
const form = useBashPlusForm(configForm);
|
|
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)("div", {
|
|
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 **declares**. Single source of truth for both
|
|
1707
|
-
* halves: the host picks its own declarations out of the profile's composed
|
|
1708
|
-
* configuration, and the panel uses the list to tell "ours" from everything else.
|
|
1709
|
-
*
|
|
1710
|
-
* They ship as `@deepseek-ai/dsh-agent-preset` declaration rows in this
|
|
1711
|
-
* package's own bundle patches (`presets/<id>.patch.yml`), so installing the
|
|
1712
|
-
* plugin as a profile bundle declares them: no install step, no file copy, and
|
|
1713
|
-
* nothing written at startup.
|
|
1714
|
-
*/
|
|
1715
|
-
const BUNDLED_PRESET_IDS = ["tool-plus-standard", "tool-plus-ptc"];
|
|
1716
|
-
/** Endpoint that applies one preset action to the active profile. */
|
|
1717
|
-
const PRESET_ACTION_ENDPOINT = "presets/apply";
|
|
1718
|
-
/** Endpoint that compares one preset against one of our declarations (read-only). */
|
|
1719
|
-
const PRESET_COMPARE_ENDPOINT = "presets/compare";
|
|
1720
|
-
//#endregion
|
|
1721
|
-
//#region src/client/web-connection-rpc.ts
|
|
1722
|
-
/**
|
|
1723
|
-
* Browser caller for the plugin's Connection RPC channel.
|
|
1724
|
-
*
|
|
1725
|
-
* The official `@deepseek-ai/dsh-client-connection/client` package ships the
|
|
1726
|
-
* protocol primitives as types (`RpcFetch`, `ClientConnectionRpc`, `RpcResult`)
|
|
1727
|
-
* and the `RpcId` brand, but its published `createWebConnectionRpc` factory is
|
|
1728
|
-
* internal to the bundle — not re-exported. This module re-implements that
|
|
1729
|
-
* factory against the same public wire contract (docs/api-gateway.md, the
|
|
1730
|
-
* client `rpc.ts` source): a `POST {origin}/{channel}/{endpoint}` with a
|
|
1731
|
-
* `client-request` JSON envelope, answered by a `server-response` envelope
|
|
1732
|
-
* echoing the rpcId. Host-side registration uses the official
|
|
1733
|
-
* `ctx.connection.rpc.handle` — this file only mirrors the caller half.
|
|
1734
|
-
* @module @xiaoso/dsh-tool-plus/client/web-connection-rpc
|
|
1735
|
-
*/
|
|
1736
|
-
const INTERNAL_BASE = "http://dsh.internal";
|
|
1737
|
-
const CHANNEL_PATTERN = /^\/[A-Za-z0-9._~-]+$/;
|
|
1738
|
-
const ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
1739
|
-
function randomUuid() {
|
|
1740
|
-
const g = globalThis;
|
|
1741
|
-
if (g.crypto?.randomUUID !== void 0) return g.crypto.randomUUID();
|
|
1742
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1743
|
-
const r = Math.random() * 16 | 0;
|
|
1744
|
-
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
function isRecord$4(value) {
|
|
1748
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1749
|
-
}
|
|
1750
|
-
function parseConnectionResponse(value) {
|
|
1751
|
-
if (!isRecord$4(value) || value.type !== "server-response" || typeof value.rpcId !== "string") throw new TypeError("connection: invalid server-response envelope");
|
|
1752
|
-
const result = value.result;
|
|
1753
|
-
if (!isRecord$4(result)) throw new TypeError("connection: invalid server-response result");
|
|
1754
|
-
if (result.ok === true) return {
|
|
1755
|
-
rpcId: value.rpcId,
|
|
1756
|
-
result: {
|
|
1757
|
-
ok: true,
|
|
1758
|
-
value: result.value
|
|
1759
|
-
}
|
|
1760
|
-
};
|
|
1761
|
-
if (result.ok !== false || !isRecord$4(result.error)) throw new TypeError("connection: invalid server-response result");
|
|
1762
|
-
const error = result.error;
|
|
1763
|
-
if (typeof error.code !== "string" || typeof error.message !== "string" || !isRecord$4(error.details)) throw new TypeError("connection: invalid server-response failure");
|
|
1764
|
-
return {
|
|
1765
|
-
rpcId: value.rpcId,
|
|
1766
|
-
result: {
|
|
1767
|
-
ok: false,
|
|
1768
|
-
error: {
|
|
1769
|
-
code: error.code,
|
|
1770
|
-
message: error.message,
|
|
1771
|
-
details: error.details
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
1776
|
-
function resolveBase() {
|
|
1777
|
-
const location = globalThis.location;
|
|
1778
|
-
return location?.origin !== void 0 && location.origin !== "null" ? location.origin : INTERNAL_BASE;
|
|
1779
|
-
}
|
|
1780
|
-
function assertTarget(channel, endpoint) {
|
|
1781
|
-
const segments = endpoint.split("/");
|
|
1782
|
-
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}`)}`);
|
|
1783
|
-
}
|
|
1784
|
-
/**
|
|
1785
|
-
* Create the browser-backed generic RPC caller for the plugin's channel
|
|
1786
|
-
* (the `/tool-plus` host registration). Wire-compatible with the official
|
|
1787
|
-
* ClientConnectionRpc contract; only the transport override differs.
|
|
1788
|
-
* @param doFetch - transport override; defaults to the page's global fetch.
|
|
1789
|
-
*/
|
|
1790
|
-
function createWebConnectionRpc(doFetch) {
|
|
1791
|
-
const send = doFetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
1792
|
-
return { async call(channel, endpoint, payload, signal) {
|
|
1793
|
-
assertTarget(channel, endpoint);
|
|
1794
|
-
const rpcId = (0, _deepseek_ai_dsh_client_connection_client.RpcId)(randomUuid());
|
|
1795
|
-
const response = await send(new URL(`${channel}/${endpoint}`, resolveBase()), {
|
|
1796
|
-
method: "POST",
|
|
1797
|
-
headers: { "content-type": "application/json" },
|
|
1798
|
-
body: JSON.stringify({
|
|
1799
|
-
type: "client-request",
|
|
1800
|
-
rpcId,
|
|
1801
|
-
method: endpoint,
|
|
1802
|
-
payload
|
|
1803
|
-
}),
|
|
1804
|
-
...signal === void 0 ? {} : { signal }
|
|
1805
|
-
});
|
|
1806
|
-
if (!response.ok) throw new Error(`transport failure for ${channel}/${endpoint}: HTTP ${response.status}`);
|
|
1807
|
-
const full = parseConnectionResponse(await response.json());
|
|
1808
|
-
if (full.rpcId !== rpcId) throw new Error(`rpcId mismatch for ${endpoint}: sent ${rpcId}, got ${full.rpcId}`);
|
|
1809
|
-
return full.result;
|
|
1810
|
-
} };
|
|
1811
|
-
}
|
|
1812
|
-
//#endregion
|
|
1813
1202
|
//#region src/client/forms.ts
|
|
1814
1203
|
/**
|
|
1815
1204
|
* Generic staged-form model over the plugin's flat settings namespace,
|
|
@@ -6472,7 +5861,7 @@ window.__ModuleLoader__.load({
|
|
|
6472
5861
|
* spells the same value the Host plugin registers (src/config/settings.ts).
|
|
6473
5862
|
*/
|
|
6474
5863
|
const BASH_PLUS_CLIENT_NS = "tool-plus";
|
|
6475
|
-
/** Locale dictionary namespace of this package's
|
|
5864
|
+
/** Locale dictionary namespace of this package's page copy. */
|
|
6476
5865
|
const BASH_PLUS_LOCALE_NS = "tool-plus";
|
|
6477
5866
|
/** Required services (cordis fiber inject): slots + locale + the shared configuration forms. */
|
|
6478
5867
|
const inject = [
|
|
@@ -6481,24 +5870,15 @@ window.__ModuleLoader__.load({
|
|
|
6481
5870
|
"configForms"
|
|
6482
5871
|
];
|
|
6483
5872
|
/**
|
|
6484
|
-
* Mount the tool-plus settings
|
|
5873
|
+
* Mount the tool-plus settings page.
|
|
6485
5874
|
* @param ctx - the browser plugin context.
|
|
6486
5875
|
*/
|
|
6487
5876
|
function apply(ctx) {
|
|
6488
5877
|
ctx.effect(() => ctx.locale.register(BASH_PLUS_LOCALE_NS, {
|
|
6489
5878
|
zh: zh$1,
|
|
6490
5879
|
en: en$1
|
|
6491
|
-
}), "tool-plus:
|
|
6492
|
-
const
|
|
6493
|
-
const sectionForm = ctx.configForms.get(BASH_PLUS_CLIENT_NS);
|
|
6494
|
-
ctx.effect(() => ctx.configForms.whileServed([BASH_PLUS_CLIENT_NS], () => ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
6495
|
-
name: "settings.plugins.tab",
|
|
6496
|
-
id: BASH_PLUS_CLIENT_NS,
|
|
6497
|
-
order: 20,
|
|
6498
|
-
label: () => ctx.locale.bind(BASH_PLUS_LOCALE_NS)("title"),
|
|
6499
|
-
locale: BASH_PLUS_LOCALE_NS,
|
|
6500
|
-
inject: () => ({ form: cardForm })
|
|
6501
|
-
}, BashPlusCard))), "tool-plus: plugins page");
|
|
5880
|
+
}), "tool-plus: page dictionaries");
|
|
5881
|
+
const form = ctx.configForms.get(BASH_PLUS_CLIENT_NS);
|
|
6502
5882
|
registerToolCards(ctx);
|
|
6503
5883
|
ctx.effect(() => ctx.configForms.whileServed([BASH_PLUS_CLIENT_NS], () => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
6504
5884
|
name: "settings.section",
|
|
@@ -6506,7 +5886,7 @@ window.__ModuleLoader__.load({
|
|
|
6506
5886
|
order: 16,
|
|
6507
5887
|
label: () => ctx.locale.bind(BASH_PLUS_LOCALE_NS)("nav"),
|
|
6508
5888
|
locale: BASH_PLUS_LOCALE_NS,
|
|
6509
|
-
inject: () => ({ form
|
|
5889
|
+
inject: () => ({ form })
|
|
6510
5890
|
}, ToolPlusSection))), "tool-plus: settings page");
|
|
6511
5891
|
}
|
|
6512
5892
|
//#endregion
|