@dsh-workbench/core 0.1.17 → 0.2.0
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/client.js +1026 -554
- package/dist/board.css +31 -0
- package/dist/client/client.body.js +1026 -554
- package/dist/client/index.js +1027 -555
- package/dist/host/index.js +1175 -146
- package/dist/types/app/client.d.ts +111 -69
- package/dist/types/app/layouts/market/__tests__/add-module-stages.test.d.ts +1 -0
- package/dist/types/app/layouts/market/add-module-text.d.ts +16 -1
- package/dist/types/app/layouts/market/use-add-module.d.ts +2 -8
- package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +5 -1
- package/dist/types/app/layouts/rightbar/ModulesPage.d.ts +4 -2
- package/dist/types/app/layouts/rightbar/PanelModules.d.ts +2 -2
- package/dist/types/app/layouts/rightbar/PanelPluginRows.d.ts +9 -0
- package/dist/types/app/layouts/rightbar/PanelRowControls.d.ts +14 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-row-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-section-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-workbench.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-plugin-rows.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-scope-ui.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/board-selection.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/market-act.d.ts +21 -0
- package/dist/types/app/layouts/rightbar/market-freshness.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-mock.d.ts +6 -1
- package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +3 -1
- package/dist/types/app/layouts/rightbar/market-row-icon.d.ts +7 -0
- package/dist/types/app/layouts/rightbar/market-row-types.d.ts +35 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +8 -35
- package/dist/types/app/layouts/rightbar/market-section-icon.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-section.d.ts +18 -0
- package/dist/types/app/layouts/rightbar/market-view-text.d.ts +7 -1
- package/dist/types/app/layouts/rightbar/market-workbench-row.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/panel-mock.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/use-panels.d.ts +15 -1
- package/dist/types/app/units/registry.d.ts +1 -1
- package/dist/types/modules/panels/__tests__/plugin-panel-market-parity.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-panel.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-tool-units.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-machine.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-support.d.ts +51 -0
- package/dist/types/modules/panels/model/config.d.ts +19 -0
- package/dist/types/modules/panels/model/market-dispatch.d.ts +4 -0
- package/dist/types/modules/panels/model/market-registry.d.ts +12 -0
- package/dist/types/modules/panels/model/market-registry.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-types.d.ts +1 -0
- package/dist/types/modules/panels/model/market.d.ts +3 -2
- package/dist/types/modules/panels/model/plugin-panel.d.ts +3 -0
- package/dist/types/modules/panels/model/prefs.d.ts +3 -1
- package/dist/types/modules/panels/model/service.d.ts +5 -1
- package/dist/types/shared/api/bundle-install.d.ts +15 -1
- package/dist/types/shared/api/host/__tests__/plugin-catalog.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-login-hint.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-tools.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-inspect-kind.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-routes-self.test.d.ts +1 -0
- package/dist/types/shared/api/host/installed-module.d.ts +10 -0
- package/dist/types/shared/api/host/plugin-catalog.d.ts +60 -0
- package/dist/types/shared/api/host/plugin-login-hint.d.ts +2 -0
- package/dist/types/shared/api/host/plugin-tools.d.ts +10 -0
- package/dist/types/shared/api/host/plugin-units.d.ts +47 -0
- package/dist/types/shared/api/host/unit-registry.d.ts +3 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/REVIEW.md +2 -2
- package/skills/workbench-module-diy/TOOLS.md +4 -1
package/dist/host/index.js
CHANGED
|
@@ -67,7 +67,12 @@ var DECLARATION_REQUIRED_KEYS = [
|
|
|
67
67
|
"host"
|
|
68
68
|
];
|
|
69
69
|
var CARD_KEYS = ["titleKey", "order"];
|
|
70
|
-
var TOOL_KEYS = [
|
|
70
|
+
var TOOL_KEYS = [
|
|
71
|
+
"name",
|
|
72
|
+
"writes",
|
|
73
|
+
"key"
|
|
74
|
+
];
|
|
75
|
+
var TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
|
|
71
76
|
var HOST_KEYS = ["inject", "optional"];
|
|
72
77
|
function text$1(value) {
|
|
73
78
|
return typeof value === "string" ? value.trim() : "";
|
|
@@ -109,6 +114,7 @@ function declarationProblemsOf(subject, raw) {
|
|
|
109
114
|
if (!Array.isArray(tools)) problems.push(`${subject}: dsh.workbench.tools 要是一串声明,现在是 ${JSON.stringify(tools)}`);
|
|
110
115
|
else {
|
|
111
116
|
const seen = /* @__PURE__ */ new Set();
|
|
117
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
112
118
|
tools.forEach((tool, index) => {
|
|
113
119
|
const at = `dsh.workbench.tools[${index}]`;
|
|
114
120
|
if (!isRecord(tool)) {
|
|
@@ -122,6 +128,12 @@ function declarationProblemsOf(subject, raw) {
|
|
|
122
128
|
else if (seen.has(name)) problems.push(`${subject}: ${at}.name「${name}」与前面那条撞了 —— 工具名会进 dsh 的工具表,不许重`);
|
|
123
129
|
else seen.add(name);
|
|
124
130
|
if (typeof tool.writes !== "boolean") problems.push(`${subject}: ${at}.writes 要是 true / false(它是"这会写"的唯一声明,缺了审批无从认领)`);
|
|
131
|
+
if (tool.key !== void 0) {
|
|
132
|
+
const key = text$1(tool.key);
|
|
133
|
+
if (!TOOL_KEY_SHAPE.test(key)) problems.push(`${subject}: ${at}.key 要是 lowerCamelCase 的标识符(如 \`sync\` / \`podLogs\`),现在是 ${JSON.stringify(tool.key)} —— 它会变成生成物里的键名,所以不引号、不许带点或横线`);
|
|
134
|
+
else if (seenKeys.has(key)) problems.push(`${subject}: ${at}.key「${key}」与前面那条撞了 —— 生成物里的键只能有一个`);
|
|
135
|
+
else seenKeys.add(key);
|
|
136
|
+
}
|
|
125
137
|
});
|
|
126
138
|
}
|
|
127
139
|
}
|
|
@@ -201,6 +213,71 @@ function parseJsonLoose(text) {
|
|
|
201
213
|
}
|
|
202
214
|
}
|
|
203
215
|
//#endregion
|
|
216
|
+
//#region ../shared/dist/host/cli-error.js
|
|
217
|
+
function unquote(raw) {
|
|
218
|
+
const value = raw.trim();
|
|
219
|
+
if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
function joinBlock(previous, line, fold) {
|
|
223
|
+
if (previous === "") return line;
|
|
224
|
+
return fold ? `${previous} ${line}` : `${previous}\n${line}`;
|
|
225
|
+
}
|
|
226
|
+
function parseCliErrorEnvelope(text) {
|
|
227
|
+
if (text === void 0 || text.trim() === "") return void 0;
|
|
228
|
+
const lines = text.split("\n");
|
|
229
|
+
const start = lines.findIndex((line) => /^error:\s*$/.test(line.trim()) || /^error:\s*\S/.test(line.trim()));
|
|
230
|
+
if (start === -1) return void 0;
|
|
231
|
+
const scalars = {};
|
|
232
|
+
let current;
|
|
233
|
+
let block;
|
|
234
|
+
const errorIndent = lines[start]?.search(/\S/) ?? 0;
|
|
235
|
+
for (const line of lines.slice(start + 1)) {
|
|
236
|
+
if (line.trim() === "") continue;
|
|
237
|
+
const indent = line.search(/\S/);
|
|
238
|
+
if (block !== void 0 && indent > block.indent) {
|
|
239
|
+
scalars[block.key] = joinBlock(scalars[block.key] ?? "", line.trim(), block.fold);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
block = void 0;
|
|
243
|
+
if (indent <= errorIndent) break;
|
|
244
|
+
const match = /^\s*(code|message|help|exitCode):\s*(.*)$/.exec(line);
|
|
245
|
+
if (match === null) {
|
|
246
|
+
if (current !== void 0) scalars[current] = `${scalars[current] ?? ""} ${line.trim()}`.trim();
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const [, key, value] = match;
|
|
250
|
+
if (key === void 0) continue;
|
|
251
|
+
const raw = (value ?? "").trim();
|
|
252
|
+
const marker = /^([|>])[0-9]*([+-]?)$/.exec(raw);
|
|
253
|
+
if (marker !== null) {
|
|
254
|
+
scalars[key] = "";
|
|
255
|
+
block = {
|
|
256
|
+
key,
|
|
257
|
+
fold: marker[1] === ">",
|
|
258
|
+
indent
|
|
259
|
+
};
|
|
260
|
+
current = key;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
scalars[key] = unquote(raw);
|
|
264
|
+
current = key;
|
|
265
|
+
}
|
|
266
|
+
const code = scalars["code"];
|
|
267
|
+
const message = scalars["message"];
|
|
268
|
+
if (code === void 0 || code === "") return void 0;
|
|
269
|
+
const exitCode = scalars["exitCode"];
|
|
270
|
+
return {
|
|
271
|
+
code,
|
|
272
|
+
message: message ?? code,
|
|
273
|
+
...scalars["help"] === void 0 || scalars["help"] === "" ? {} : { help: scalars["help"] },
|
|
274
|
+
...exitCode === void 0 || Number.isNaN(Number(exitCode)) ? {} : { exitCode: Number(exitCode) }
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function helpUrlOf(envelope) {
|
|
278
|
+
return /https?:\/\/[^\s"'`]+/.exec(envelope.help ?? "")?.[0];
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
204
281
|
//#region ../shared/dist/host/data-root.js
|
|
205
282
|
function dshHome() {
|
|
206
283
|
const configured = process.env.DSH_HOME?.trim();
|
|
@@ -270,20 +347,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
270
347
|
function asRecord$9(value) {
|
|
271
348
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
272
349
|
}
|
|
273
|
-
function textOf$
|
|
350
|
+
function textOf$5(value) {
|
|
274
351
|
return typeof value === "string" ? value.trim() : "";
|
|
275
352
|
}
|
|
276
353
|
function codeOf(page) {
|
|
277
354
|
const record = asRecord$9(page);
|
|
278
|
-
return record === void 0 ? "" : textOf$
|
|
355
|
+
return record === void 0 ? "" : textOf$5(record["code"]);
|
|
279
356
|
}
|
|
280
|
-
function messageOf$
|
|
357
|
+
function messageOf$2(page) {
|
|
281
358
|
const record = asRecord$9(page);
|
|
282
|
-
return record === void 0 ? "" : textOf$
|
|
359
|
+
return record === void 0 ? "" : textOf$5(record["message"]);
|
|
283
360
|
}
|
|
284
361
|
function hintOf$1(page) {
|
|
285
362
|
const record = asRecord$9(page);
|
|
286
|
-
const hint = record === void 0 ? "" : textOf$
|
|
363
|
+
const hint = record === void 0 ? "" : textOf$5(record["hint"]);
|
|
287
364
|
return hint === "" ? void 0 : hint;
|
|
288
365
|
}
|
|
289
366
|
function pageFailed(page) {
|
|
@@ -472,7 +549,7 @@ async function browserRead(options) {
|
|
|
472
549
|
message: "读到了"
|
|
473
550
|
};
|
|
474
551
|
const pageCode = codeOf(page) || "read-failed";
|
|
475
|
-
const message = messageOf$
|
|
552
|
+
const message = messageOf$2(page) || `读不到(${pageCode})`;
|
|
476
553
|
const blocker = blockerFor(pageCode);
|
|
477
554
|
if (blocker === "auth-required") return {
|
|
478
555
|
ok: false,
|
|
@@ -514,7 +591,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
514
591
|
function asRecord$8(value) {
|
|
515
592
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
516
593
|
}
|
|
517
|
-
function textOf$
|
|
594
|
+
function textOf$4(value) {
|
|
518
595
|
return typeof value === "string" ? value.trim() : "";
|
|
519
596
|
}
|
|
520
597
|
function entriesOf(value) {
|
|
@@ -527,8 +604,8 @@ function entriesOf(value) {
|
|
|
527
604
|
if (item === void 0) continue;
|
|
528
605
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
529
606
|
entries.push({
|
|
530
|
-
text: textOf$
|
|
531
|
-
tag: textOf$
|
|
607
|
+
text: textOf$4(item["text"]),
|
|
608
|
+
tag: textOf$4(item["tag"]),
|
|
532
609
|
nth
|
|
533
610
|
});
|
|
534
611
|
}
|
|
@@ -537,7 +614,7 @@ function entriesOf(value) {
|
|
|
537
614
|
function reasonOf(stdout, stderr) {
|
|
538
615
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
539
616
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
540
|
-
const message = error === void 0 ? "" : textOf$
|
|
617
|
+
const message = error === void 0 ? "" : textOf$4(error["message"]);
|
|
541
618
|
if (message !== "") return message;
|
|
542
619
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
543
620
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -545,7 +622,7 @@ function reasonOf(stdout, stderr) {
|
|
|
545
622
|
function hintOf(stdout) {
|
|
546
623
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
547
624
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
548
|
-
const hint = error === void 0 ? "" : textOf$
|
|
625
|
+
const hint = error === void 0 ? "" : textOf$4(error["hint"]);
|
|
549
626
|
return hint === "" ? void 0 : hint;
|
|
550
627
|
}
|
|
551
628
|
function sameOrigin(left, right) {
|
|
@@ -559,7 +636,7 @@ function urlOf(stdout) {
|
|
|
559
636
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
560
637
|
if (bare.startsWith("{")) {
|
|
561
638
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
562
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
639
|
+
const url = parsed === void 0 ? "" : textOf$4(parsed["url"]);
|
|
563
640
|
if (url !== "") return url;
|
|
564
641
|
}
|
|
565
642
|
return bare;
|
|
@@ -784,7 +861,8 @@ var PROVISION_ACTIONS = [
|
|
|
784
861
|
"cancel-install",
|
|
785
862
|
"update-dependency",
|
|
786
863
|
"update-plugin",
|
|
787
|
-
"uninstall-plugin"
|
|
864
|
+
"uninstall-plugin",
|
|
865
|
+
"update-workbench"
|
|
788
866
|
];
|
|
789
867
|
var DEPENDENCY_ACTIONS = ["update-dependency"];
|
|
790
868
|
function isDependencyAction(action) {
|
|
@@ -795,6 +873,7 @@ function provisionTargetOf(action) {
|
|
|
795
873
|
if (action === "install-bundle") return "spec";
|
|
796
874
|
if (action === "remove-bundle") return "name";
|
|
797
875
|
if (action === "update-plugin" || action === "uninstall-plugin") return "name";
|
|
876
|
+
if (action === "update-workbench") return "name";
|
|
798
877
|
if (action === "cancel-install") return "requestId";
|
|
799
878
|
return "unit";
|
|
800
879
|
}
|
|
@@ -808,7 +887,7 @@ var TARGET_CODE = {
|
|
|
808
887
|
function asRecord$7(value) {
|
|
809
888
|
return typeof value === "object" && value !== null ? value : {};
|
|
810
889
|
}
|
|
811
|
-
function stringsOf$
|
|
890
|
+
function stringsOf$3(value) {
|
|
812
891
|
if (!Array.isArray(value)) return void 0;
|
|
813
892
|
const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
814
893
|
return items.length === 0 ? void 0 : items;
|
|
@@ -830,6 +909,10 @@ function requestOf$1(action, value) {
|
|
|
830
909
|
action,
|
|
831
910
|
name: value
|
|
832
911
|
};
|
|
912
|
+
if (action === "update-workbench") return {
|
|
913
|
+
action,
|
|
914
|
+
name: value
|
|
915
|
+
};
|
|
833
916
|
if (action === "cancel-install") return {
|
|
834
917
|
action,
|
|
835
918
|
requestId: value
|
|
@@ -860,7 +943,7 @@ function parseProvisionBody(body) {
|
|
|
860
943
|
request: requestOf$1(wanted, value.trim())
|
|
861
944
|
};
|
|
862
945
|
const registry = typeof raw.registry === "string" && raw.registry.trim() !== "" ? raw.registry.trim() : void 0;
|
|
863
|
-
const approvedBuilds = stringsOf$
|
|
946
|
+
const approvedBuilds = stringsOf$3(raw.approvedBuilds);
|
|
864
947
|
const requestId = typeof raw.requestId === "string" && raw.requestId.trim() !== "" ? raw.requestId.trim() : void 0;
|
|
865
948
|
return {
|
|
866
949
|
ok: true,
|
|
@@ -874,6 +957,7 @@ function parseProvisionBody(body) {
|
|
|
874
957
|
}
|
|
875
958
|
//#endregion
|
|
876
959
|
//#region ../shared/dist/host/opencli.js
|
|
960
|
+
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["FORBIDDEN", "PERMISSION_DENIED"]);
|
|
877
961
|
var SITE_SESSION_ARGS = ["--site-session", "persistent"];
|
|
878
962
|
var WorkbenchDataError = class extends Error {
|
|
879
963
|
code;
|
|
@@ -901,6 +985,102 @@ function opencliCommandLine(command, argv, session = "persistent") {
|
|
|
901
985
|
"json"
|
|
902
986
|
].join(" ");
|
|
903
987
|
}
|
|
988
|
+
function createOpencli(options) {
|
|
989
|
+
const { shell, plugin, logger, command = "opencli", timeoutMs = OPENCLI_TIMEOUT_MS, maxConcurrent = 4 } = options;
|
|
990
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
991
|
+
const MAX_CONCURRENT = maxConcurrent;
|
|
992
|
+
let running = 0;
|
|
993
|
+
const waiting = [];
|
|
994
|
+
async function acquire() {
|
|
995
|
+
if (running < MAX_CONCURRENT) {
|
|
996
|
+
running += 1;
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
await new Promise((resolve) => waiting.push(resolve));
|
|
1000
|
+
}
|
|
1001
|
+
function release() {
|
|
1002
|
+
const next = waiting.shift();
|
|
1003
|
+
if (next === void 0) running -= 1;
|
|
1004
|
+
else next();
|
|
1005
|
+
}
|
|
1006
|
+
async function runLimited(task) {
|
|
1007
|
+
await acquire();
|
|
1008
|
+
try {
|
|
1009
|
+
return await task();
|
|
1010
|
+
} finally {
|
|
1011
|
+
release();
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function call(commandLine, workdir) {
|
|
1015
|
+
let result;
|
|
1016
|
+
try {
|
|
1017
|
+
result = await runShell(shell, commandLine, {
|
|
1018
|
+
...workdir === void 0 ? {} : { workdir },
|
|
1019
|
+
timeoutMs
|
|
1020
|
+
});
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
throw new WorkbenchDataError("cli-exec-failed", `执行 opencli 失败:${String(error)}`, { hint: "确认 opencli 已安装且在 PATH 里(which opencli)。" });
|
|
1023
|
+
}
|
|
1024
|
+
if (result.timedOut) throw new WorkbenchDataError("cli-timeout", `opencli 超过 ${Math.round(timeoutMs / 1e3)} 秒没有返回`, { hint: "无头浏览器可能没起来,重试一次通常就好。" });
|
|
1025
|
+
if (result.exitCode !== 0) {
|
|
1026
|
+
const detail = tailLines(result.stderr || result.stdout, 6);
|
|
1027
|
+
const envelope = parseCliErrorEnvelope(result.stderr) ?? parseCliErrorEnvelope(result.stdout);
|
|
1028
|
+
const url = envelope === void 0 ? void 0 : helpUrlOf(envelope);
|
|
1029
|
+
if (envelope?.code === "AUTH_REQUIRED") throw new WorkbenchDataError("auth-required", envelope.message, {
|
|
1030
|
+
detail,
|
|
1031
|
+
...url === void 0 ? {} : { helpUrl: url }
|
|
1032
|
+
});
|
|
1033
|
+
if (envelope !== void 0 && FORBIDDEN_CODES.has(envelope.code)) throw new WorkbenchDataError("permission-denied", envelope.message, {
|
|
1034
|
+
detail,
|
|
1035
|
+
...url === void 0 ? {} : { helpUrl: url }
|
|
1036
|
+
});
|
|
1037
|
+
if (envelope !== void 0) throw new WorkbenchDataError("cli-command-failed", `${envelope.code}:${envelope.message}`, {
|
|
1038
|
+
detail,
|
|
1039
|
+
...envelope.help === void 0 ? {} : { hint: envelope.help }
|
|
1040
|
+
});
|
|
1041
|
+
throw new WorkbenchDataError("cli-unclassified", `opencli 这条命令没跑通(退出码 ${result.exitCode}),而且没有结构化错误码`, {
|
|
1042
|
+
detail,
|
|
1043
|
+
hint: "多半是插件里没有这条命令(版本旧就会缺),也可能是命令自己失败了:先确认命令存在,再更新插件重试。"
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
const parsed = parseJsonLoose(result.stdout);
|
|
1047
|
+
if (parsed === void 0) throw new WorkbenchDataError("cli-bad-output", "opencli 输出不是 JSON", { detail: tailLines(result.stdout, 4) });
|
|
1048
|
+
return parsed;
|
|
1049
|
+
}
|
|
1050
|
+
async function json(args, callOptions = {}) {
|
|
1051
|
+
const { key, workdir } = callOptions;
|
|
1052
|
+
const session = callOptions.session ?? "persistent";
|
|
1053
|
+
const callKey = key ?? args.map((arg) => String(arg)).join(" ");
|
|
1054
|
+
const pending = inflight.get(callKey);
|
|
1055
|
+
if (pending !== void 0) return pending;
|
|
1056
|
+
const commandLine = opencliCommandLine(command, [plugin, ...args], session);
|
|
1057
|
+
const once = async () => {
|
|
1058
|
+
return {
|
|
1059
|
+
value: await call(commandLine, workdir),
|
|
1060
|
+
at: Date.now()
|
|
1061
|
+
};
|
|
1062
|
+
};
|
|
1063
|
+
const task = runLimited(async () => {
|
|
1064
|
+
try {
|
|
1065
|
+
return await once();
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
if (!(error instanceof WorkbenchDataError) || error.code !== "auth-required") throw error;
|
|
1068
|
+
return await once();
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
inflight.set(callKey, task);
|
|
1072
|
+
try {
|
|
1073
|
+
return await task;
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1076
|
+
logger?.warn?.(`[dsh-workbench] opencli 调用失败:${message}`);
|
|
1077
|
+
throw error;
|
|
1078
|
+
} finally {
|
|
1079
|
+
inflight.delete(callKey);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return { json };
|
|
1083
|
+
}
|
|
904
1084
|
//#endregion
|
|
905
1085
|
//#region ../shared/dist/host/write-result.js
|
|
906
1086
|
function writeResultForClient(value, code) {
|
|
@@ -1435,7 +1615,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1435
1615
|
function ledgerKeyOf(entry) {
|
|
1436
1616
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1437
1617
|
}
|
|
1438
|
-
function textOf$
|
|
1618
|
+
function textOf$3(value) {
|
|
1439
1619
|
if (typeof value !== "string") return void 0;
|
|
1440
1620
|
const trimmed = value.trim();
|
|
1441
1621
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1443,13 +1623,13 @@ function textOf$2(value) {
|
|
|
1443
1623
|
function entryOf(value) {
|
|
1444
1624
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1445
1625
|
const box = value;
|
|
1446
|
-
const opencliHome = textOf$
|
|
1447
|
-
const pkg = textOf$
|
|
1448
|
-
const installedAt = textOf$
|
|
1626
|
+
const opencliHome = textOf$3(box["opencliHome"]);
|
|
1627
|
+
const pkg = textOf$3(box["package"]);
|
|
1628
|
+
const installedAt = textOf$3(box["installedAt"]);
|
|
1449
1629
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1450
|
-
const name = textOf$
|
|
1451
|
-
const registry = textOf$
|
|
1452
|
-
const releasedAt = textOf$
|
|
1630
|
+
const name = textOf$3(box["name"]);
|
|
1631
|
+
const registry = textOf$3(box["registry"]);
|
|
1632
|
+
const releasedAt = textOf$3(box["releasedAt"]);
|
|
1453
1633
|
return {
|
|
1454
1634
|
opencliHome,
|
|
1455
1635
|
package: pkg,
|
|
@@ -1616,7 +1796,7 @@ function resolvePluginSource(source, env, options = {}) {
|
|
|
1616
1796
|
}
|
|
1617
1797
|
//#endregion
|
|
1618
1798
|
//#region ../shared/dist/host/tool-approval.js
|
|
1619
|
-
function messageOf(error) {
|
|
1799
|
+
function messageOf$1(error) {
|
|
1620
1800
|
return error instanceof Error ? error.message : String(error);
|
|
1621
1801
|
}
|
|
1622
1802
|
function compose(detail, verdict) {
|
|
@@ -1634,7 +1814,7 @@ async function approvalFor(call, context = {}) {
|
|
|
1634
1814
|
} catch (error) {
|
|
1635
1815
|
return {
|
|
1636
1816
|
kind: "deny",
|
|
1637
|
-
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf(error)}`
|
|
1817
|
+
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf$1(error)}`
|
|
1638
1818
|
};
|
|
1639
1819
|
}
|
|
1640
1820
|
if (verdict?.kind === "read") return void 0;
|
|
@@ -1648,7 +1828,7 @@ async function approvalFor(call, context = {}) {
|
|
|
1648
1828
|
} catch (error) {
|
|
1649
1829
|
return {
|
|
1650
1830
|
kind: "deny",
|
|
1651
|
-
reason: `拿不到"这次会发生什么",已经停下:${messageOf(error)}`
|
|
1831
|
+
reason: `拿不到"这次会发生什么",已经停下:${messageOf$1(error)}`
|
|
1652
1832
|
};
|
|
1653
1833
|
}
|
|
1654
1834
|
if (facts !== void 0 && "blocked" in facts) return {
|
|
@@ -1667,6 +1847,15 @@ function sessionWorkspace(exec) {
|
|
|
1667
1847
|
const cwd = exec?.agent?.session?.header?.cwd;
|
|
1668
1848
|
return typeof cwd === "string" && cwd.trim() !== "" ? cwd.trim() : null;
|
|
1669
1849
|
}
|
|
1850
|
+
function textOutput(text) {
|
|
1851
|
+
return [{
|
|
1852
|
+
type: "text",
|
|
1853
|
+
text
|
|
1854
|
+
}];
|
|
1855
|
+
}
|
|
1856
|
+
function jsonText(value) {
|
|
1857
|
+
return JSON.stringify(value);
|
|
1858
|
+
}
|
|
1670
1859
|
//#endregion
|
|
1671
1860
|
//#region ../shared/dist/host/value-handoff.js
|
|
1672
1861
|
function handoffId(entry) {
|
|
@@ -1988,7 +2177,7 @@ function profileDirOf(ctx) {
|
|
|
1988
2177
|
const dir = typeof profile?.dir === "string" ? profile.dir.trim() : "";
|
|
1989
2178
|
return dir === "" ? null : dir;
|
|
1990
2179
|
}
|
|
1991
|
-
function stringsOf$
|
|
2180
|
+
function stringsOf$2(value) {
|
|
1992
2181
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1993
2182
|
}
|
|
1994
2183
|
function profileManifestOf(profileDir) {
|
|
@@ -2002,7 +2191,7 @@ function profileManifestOf(profileDir) {
|
|
|
2002
2191
|
function profileModules(profileDir) {
|
|
2003
2192
|
const record = profileManifestOf(profileDir);
|
|
2004
2193
|
if (record === void 0) return [];
|
|
2005
|
-
const bundles = stringsOf$
|
|
2194
|
+
const bundles = stringsOf$2(asRecord$5(asRecord$5(record.dsh)?.profile)?.bundles);
|
|
2006
2195
|
const dependencies = asRecord$5(record.dependencies) ?? {};
|
|
2007
2196
|
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
2008
2197
|
const found = [];
|
|
@@ -2013,7 +2202,7 @@ function profileModules(profileDir) {
|
|
|
2013
2202
|
unit: read.unit,
|
|
2014
2203
|
version: read.version,
|
|
2015
2204
|
...read.registry === void 0 ? {} : { registry: read.registry },
|
|
2016
|
-
local: asText$3(dependencies[read.pkg])
|
|
2205
|
+
local: isLocalDependencySpec(asText$3(dependencies[read.pkg]))
|
|
2017
2206
|
});
|
|
2018
2207
|
}
|
|
2019
2208
|
return found;
|
|
@@ -2058,6 +2247,20 @@ function declarationGaps(profileDir) {
|
|
|
2058
2247
|
}
|
|
2059
2248
|
return gaps;
|
|
2060
2249
|
}
|
|
2250
|
+
function declaredPluginCommands(profileDir) {
|
|
2251
|
+
const declared = /* @__PURE__ */ new Map();
|
|
2252
|
+
for (const module of profileModules(profileDir)) {
|
|
2253
|
+
const read = readDeclaration(module.pkg, moduleDeclarationOf(profileDir, module.pkg));
|
|
2254
|
+
for (const need of read.manifest?.env?.plugins ?? []) {
|
|
2255
|
+
const name = need.name.trim();
|
|
2256
|
+
if (name === "") continue;
|
|
2257
|
+
const commands = declared.get(name) ?? /* @__PURE__ */ new Set();
|
|
2258
|
+
for (const command of need.commands ?? []) commands.add(command);
|
|
2259
|
+
declared.set(name, commands);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return declared;
|
|
2263
|
+
}
|
|
2061
2264
|
function readInstalledModule(profileDir, pkg) {
|
|
2062
2265
|
if (profileDir === null || !isPackageName(pkg)) return {
|
|
2063
2266
|
kind: "unknown",
|
|
@@ -2093,6 +2296,32 @@ function readInstalledModule(profileDir, pkg) {
|
|
|
2093
2296
|
...registry === "" ? {} : { registry }
|
|
2094
2297
|
};
|
|
2095
2298
|
}
|
|
2299
|
+
function isLocalDependencySpec(spec) {
|
|
2300
|
+
const trimmed = spec.trim();
|
|
2301
|
+
return trimmed.startsWith("link:") || trimmed.startsWith("file:") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/");
|
|
2302
|
+
}
|
|
2303
|
+
function readProfilePackage(profileDir, pkg) {
|
|
2304
|
+
if (profileDir === null || !isPackageName(pkg)) return void 0;
|
|
2305
|
+
let manifest;
|
|
2306
|
+
try {
|
|
2307
|
+
manifest = JSON.parse(fs.readFileSync(path.join(profileDir, "node_modules", pkg, "package.json"), "utf8"));
|
|
2308
|
+
} catch {
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
const record = asRecord$5(manifest);
|
|
2312
|
+
if (record === void 0) return void 0;
|
|
2313
|
+
const version = asText$3(record.version);
|
|
2314
|
+
if (version === "") return void 0;
|
|
2315
|
+
const declaredRegistry = asText$3(asRecord$5(record.publishConfig)?.registry);
|
|
2316
|
+
const registry = declaredRegistry === "" ? registryFromLockfile(profileDir, pkg) : declaredRegistry;
|
|
2317
|
+
const local = isLocalDependencySpec(asText$3((asRecord$5(profileManifestOf(profileDir)?.dependencies) ?? {})[pkg]));
|
|
2318
|
+
return {
|
|
2319
|
+
pkg,
|
|
2320
|
+
version,
|
|
2321
|
+
...registry === "" ? {} : { registry },
|
|
2322
|
+
...local ? { local: true } : {}
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2096
2325
|
//#endregion
|
|
2097
2326
|
//#region src/shared/api/host/profile-registries.ts
|
|
2098
2327
|
var REGISTRY_MARKER = "# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)";
|
|
@@ -2338,6 +2567,13 @@ var panelsApproval = (call) => {
|
|
|
2338
2567
|
zh: `agent 想${verb} opencli 插件 ${what}(会改这台机器上装的东西)。允许吗?`
|
|
2339
2568
|
});
|
|
2340
2569
|
}
|
|
2570
|
+
if (action === "update-workbench") return askOf("workbench_modules update-workbench replaces the workbench itself with the new version from the registry", {
|
|
2571
|
+
en: "it updates the workbench itself on this machine (the panel picks the new version up by itself). Allow?",
|
|
2572
|
+
zh: "会把工作台自己更新到源上的新版(面板会自己换新)。允许吗?"
|
|
2573
|
+
}, {
|
|
2574
|
+
en: "The agent wants to update the workbench itself to the new version from the registry (it changes what is installed on this machine; the panel picks the new version up by itself). Allow?",
|
|
2575
|
+
zh: "agent 想把工作台自己更新到源上的新版(会改这台机器上装的东西,面板会自己换新)。允许吗?"
|
|
2576
|
+
});
|
|
2341
2577
|
if (action === "update-dependency") return askOf(`workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`, {
|
|
2342
2578
|
en: "it upgrades that shared dependency on this machine. Allow?",
|
|
2343
2579
|
zh: `会升级共用依赖 ${what}。允许吗?`
|
|
@@ -2411,13 +2647,20 @@ var EMPTY_BOARD = {
|
|
|
2411
2647
|
};
|
|
2412
2648
|
var BOARD_TARGETS = ["workspace", "machine"];
|
|
2413
2649
|
var DEFAULT_BOARD_TARGET = "workspace";
|
|
2414
|
-
function
|
|
2415
|
-
|
|
2416
|
-
|
|
2650
|
+
function overrideOf(board, id) {
|
|
2651
|
+
if (board === null) return void 0;
|
|
2652
|
+
if (board.hidden.includes(id)) return false;
|
|
2653
|
+
if (board.shown.includes(id)) return true;
|
|
2654
|
+
}
|
|
2655
|
+
function defaultOf(board, id) {
|
|
2417
2656
|
if (board === null) return false;
|
|
2418
2657
|
if (board.hidden.includes(id)) return false;
|
|
2419
2658
|
return board.shown.includes(id);
|
|
2420
2659
|
}
|
|
2660
|
+
function resolveScope(query) {
|
|
2661
|
+
const { id, boards } = query;
|
|
2662
|
+
return overrideOf(boards.workspace, id) ?? defaultOf(boards.machine, id);
|
|
2663
|
+
}
|
|
2421
2664
|
function strings(raw) {
|
|
2422
2665
|
return Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : [];
|
|
2423
2666
|
}
|
|
@@ -2434,7 +2677,7 @@ function parseReleased(raw) {
|
|
|
2434
2677
|
return strings(raw.ids);
|
|
2435
2678
|
}
|
|
2436
2679
|
function resolvePanels(manifests, boards) {
|
|
2437
|
-
const ordered = boards.workspace?.shown ?? boards.machine?.shown ?? [];
|
|
2680
|
+
const ordered = [.../* @__PURE__ */ new Set([...boards.workspace?.shown ?? [], ...boards.machine?.shown ?? []])];
|
|
2438
2681
|
const rank = new Map(ordered.map((id, index) => [id, index]));
|
|
2439
2682
|
return manifests.filter((manifest) => manifest.card !== null).map((manifest) => {
|
|
2440
2683
|
const on = resolveScope({
|
|
@@ -2447,11 +2690,39 @@ function resolvePanels(manifests, boards) {
|
|
|
2447
2690
|
titleKey: manifest.card?.titleKey ?? manifest.id,
|
|
2448
2691
|
order,
|
|
2449
2692
|
shown: on,
|
|
2450
|
-
source:
|
|
2693
|
+
source: overrideOf(boards.workspace, manifest.id) === void 0 ? "default" : "user",
|
|
2451
2694
|
hasCard: true
|
|
2452
2695
|
};
|
|
2453
2696
|
}).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
2454
2697
|
}
|
|
2698
|
+
var PLUGIN_PANEL_PREFIX = "plugin:";
|
|
2699
|
+
function pluginPanelId(pkg) {
|
|
2700
|
+
return `${PLUGIN_PANEL_PREFIX}${pkg}`;
|
|
2701
|
+
}
|
|
2702
|
+
function resolvePluginPanels(plugins, boards) {
|
|
2703
|
+
const ordered = [.../* @__PURE__ */ new Set([...boards.workspace?.shown ?? [], ...boards.machine?.shown ?? []])];
|
|
2704
|
+
const rank = new Map(ordered.map((id, index) => [id, index]));
|
|
2705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2706
|
+
return plugins.map((plugin) => {
|
|
2707
|
+
const id = pluginPanelId(plugin.pkg);
|
|
2708
|
+
return {
|
|
2709
|
+
id,
|
|
2710
|
+
pkg: plugin.pkg,
|
|
2711
|
+
title: plugin.title,
|
|
2712
|
+
...plugin.name === void 0 ? {} : { name: plugin.name },
|
|
2713
|
+
order: rank.get(id) ?? ordered.length,
|
|
2714
|
+
shown: resolveScope({
|
|
2715
|
+
id,
|
|
2716
|
+
boards
|
|
2717
|
+
}),
|
|
2718
|
+
source: overrideOf(boards.workspace, id) === void 0 ? "default" : "user"
|
|
2719
|
+
};
|
|
2720
|
+
}).filter((entry) => {
|
|
2721
|
+
if (seen.has(entry.id)) return false;
|
|
2722
|
+
seen.add(entry.id);
|
|
2723
|
+
return true;
|
|
2724
|
+
}).sort((a, b) => a.order - b.order || a.pkg.localeCompare(b.pkg));
|
|
2725
|
+
}
|
|
2455
2726
|
function withoutReleased(released, id) {
|
|
2456
2727
|
return released.filter((item) => item !== id);
|
|
2457
2728
|
}
|
|
@@ -2589,7 +2860,7 @@ var PLUGIN_PREFIX = "opencli-plugin-";
|
|
|
2589
2860
|
function text(value) {
|
|
2590
2861
|
return typeof value === "string" ? value.trim() : "";
|
|
2591
2862
|
}
|
|
2592
|
-
function stringsOf(value) {
|
|
2863
|
+
function stringsOf$1(value) {
|
|
2593
2864
|
return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
2594
2865
|
}
|
|
2595
2866
|
function manifestCommandsOf(raw) {
|
|
@@ -2620,7 +2891,7 @@ function manifestCommandsOf(raw) {
|
|
|
2620
2891
|
strategy: text(row["strategy"]).toLowerCase(),
|
|
2621
2892
|
browser: row["browser"] === true,
|
|
2622
2893
|
example: text(row["example"]),
|
|
2623
|
-
columns: stringsOf(row["columns"])
|
|
2894
|
+
columns: stringsOf$1(row["columns"])
|
|
2624
2895
|
});
|
|
2625
2896
|
}
|
|
2626
2897
|
return commands;
|
|
@@ -2712,7 +2983,7 @@ function reconOf(commands, plugins, keywords) {
|
|
|
2712
2983
|
};
|
|
2713
2984
|
return {
|
|
2714
2985
|
...withNext,
|
|
2715
|
-
text: textOf$
|
|
2986
|
+
text: textOf$2(withNext)
|
|
2716
2987
|
};
|
|
2717
2988
|
}
|
|
2718
2989
|
function nextOf(answer) {
|
|
@@ -2725,7 +2996,7 @@ function nextOf(answer) {
|
|
|
2725
2996
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
2726
2997
|
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy action=probe(script 读接口 / css 读 DOM)";
|
|
2727
2998
|
}
|
|
2728
|
-
function textOf$
|
|
2999
|
+
function textOf$2(answer) {
|
|
2729
3000
|
const lines = [];
|
|
2730
3001
|
if (answer.sites.length > 0) {
|
|
2731
3002
|
lines.push(`opencli 自带这 ${String(answer.sites.length)} 组能力(不用装、不用点头):`);
|
|
@@ -2770,7 +3041,7 @@ function installedPluginsOf(dir = vendorPluginsDir()) {
|
|
|
2770
3041
|
}
|
|
2771
3042
|
//#endregion
|
|
2772
3043
|
//#region src/modules/panels/model/diy-tools.ts
|
|
2773
|
-
var ACTIONS$
|
|
3044
|
+
var ACTIONS$2 = [
|
|
2774
3045
|
"list",
|
|
2775
3046
|
"status",
|
|
2776
3047
|
"probe",
|
|
@@ -2907,7 +3178,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2907
3178
|
action: {
|
|
2908
3179
|
type: "string",
|
|
2909
3180
|
description: "list=工坊里有哪些模块(默认);status=某个模块现在走到哪一步、下一步做什么;probe=在用户浏览器里探一条读;recon=先查 opencli 现成的能力(自带 adapter / 本机插件);open=只把它在这个项目上板(不装);install=装上并上板(第一次、会让用户点头)。",
|
|
2910
|
-
enum: [...ACTIONS$
|
|
3181
|
+
enum: [...ACTIONS$2]
|
|
2911
3182
|
},
|
|
2912
3183
|
unit: {
|
|
2913
3184
|
type: "string",
|
|
@@ -2966,7 +3237,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2966
3237
|
async execute(raw, exec) {
|
|
2967
3238
|
const args = asRecord$3(raw) ?? {};
|
|
2968
3239
|
const action = asText$1(args.action) || "list";
|
|
2969
|
-
if (!ACTIONS$
|
|
3240
|
+
if (!ACTIONS$2.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS$2.join(" / ")})`);
|
|
2970
3241
|
const packagesDir = workshopPackagesDir(ctx);
|
|
2971
3242
|
if (packagesDir === null) throw new Error("算不出 DIY 工坊在哪(这台机器没给出 dshHomePath)—— 让用户跑一次 dsh-workbench setup");
|
|
2972
3243
|
const session = sessionWorkspace(exec);
|
|
@@ -3895,9 +4166,45 @@ function createEnvDropper(options) {
|
|
|
3895
4166
|
};
|
|
3896
4167
|
}
|
|
3897
4168
|
//#endregion
|
|
4169
|
+
//#region src/modules/panels/model/market-registry.ts
|
|
4170
|
+
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
4171
|
+
const { registries, logger } = deps;
|
|
4172
|
+
if (registries === void 0 || registry === void 0) return;
|
|
4173
|
+
const scope = scopeOf(pkg);
|
|
4174
|
+
if (scope === null) return;
|
|
4175
|
+
const known = await manager.registries().catch(() => null);
|
|
4176
|
+
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
4177
|
+
try {
|
|
4178
|
+
const added = registries.remember([{
|
|
4179
|
+
scope,
|
|
4180
|
+
registry
|
|
4181
|
+
}]);
|
|
4182
|
+
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
4183
|
+
} catch (error) {
|
|
4184
|
+
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
4188
|
+
const { registries, logger } = deps;
|
|
4189
|
+
if (registries === void 0 || !registryFailure(change)) return change;
|
|
4190
|
+
const known = await manager.registries().catch(() => null);
|
|
4191
|
+
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
4192
|
+
if (missing.length === 0) return change;
|
|
4193
|
+
let added;
|
|
4194
|
+
try {
|
|
4195
|
+
added = registries.remember(missing);
|
|
4196
|
+
} catch (error) {
|
|
4197
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
4198
|
+
return change;
|
|
4199
|
+
}
|
|
4200
|
+
if (added.length === 0) return change;
|
|
4201
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
4202
|
+
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
4203
|
+
}
|
|
4204
|
+
//#endregion
|
|
3898
4205
|
//#region src/modules/panels/model/market.ts
|
|
3899
4206
|
function createMarketService(options) {
|
|
3900
|
-
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, registries, ledger, references } = options;
|
|
4207
|
+
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references } = options;
|
|
3901
4208
|
const command = options.command ?? "opencli";
|
|
3902
4209
|
const catalog = () => (manifestsNow?.() ?? manifests).filter((manifest) => (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0);
|
|
3903
4210
|
function requireEntry(unit) {
|
|
@@ -4042,44 +4349,16 @@ function createMarketService(options) {
|
|
|
4042
4349
|
};
|
|
4043
4350
|
}
|
|
4044
4351
|
}
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
const known = await manager.registries().catch(() => null);
|
|
4050
|
-
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
4051
|
-
try {
|
|
4052
|
-
const added = registries.remember([{
|
|
4053
|
-
scope,
|
|
4054
|
-
registry
|
|
4055
|
-
}]);
|
|
4056
|
-
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
4057
|
-
} catch (error) {
|
|
4058
|
-
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
async function retryAfterRegistryFix(manager, pkg, change) {
|
|
4062
|
-
if (registries === void 0 || !registryFailure(change)) return change;
|
|
4063
|
-
const known = await manager.registries().catch(() => null);
|
|
4064
|
-
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
4065
|
-
if (missing.length === 0) return change;
|
|
4066
|
-
let added;
|
|
4067
|
-
try {
|
|
4068
|
-
added = registries.remember(missing);
|
|
4069
|
-
} catch (error) {
|
|
4070
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
4071
|
-
return change;
|
|
4072
|
-
}
|
|
4073
|
-
if (added.length === 0) return change;
|
|
4074
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
4075
|
-
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
4076
|
-
}
|
|
4352
|
+
const registryFix = {
|
|
4353
|
+
...registries === void 0 ? {} : { registries },
|
|
4354
|
+
...logger === void 0 ? {} : { logger }
|
|
4355
|
+
};
|
|
4077
4356
|
function routeDeps(manager) {
|
|
4078
4357
|
return {
|
|
4079
4358
|
manager,
|
|
4080
4359
|
command,
|
|
4081
4360
|
runAll,
|
|
4082
|
-
rememberSource: rememberInstalledSource,
|
|
4361
|
+
rememberSource: (manager, pkg, registry) => rememberInstalledSource(registryFix, manager, pkg, registry),
|
|
4083
4362
|
supplyAfter,
|
|
4084
4363
|
...shell === void 0 ? {} : { shell },
|
|
4085
4364
|
...installed === void 0 ? {} : { installed },
|
|
@@ -4225,7 +4504,7 @@ function createMarketService(options) {
|
|
|
4225
4504
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
4226
4505
|
const first = await manager.remove(pkg);
|
|
4227
4506
|
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
4228
|
-
const change = await retryAfterRegistryFix(manager, pkg, first);
|
|
4507
|
+
const change = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
4229
4508
|
if (change.application !== "applied" || unit === void 0) return {
|
|
4230
4509
|
change,
|
|
4231
4510
|
keptFor: []
|
|
@@ -4261,6 +4540,24 @@ function createMarketService(options) {
|
|
|
4261
4540
|
await service.update(unit).catch(() => void 0);
|
|
4262
4541
|
return outcome;
|
|
4263
4542
|
},
|
|
4543
|
+
async updateWorkbench(pkg) {
|
|
4544
|
+
const target = pkg.trim() === "" ? PLUGIN_ID : pkg.trim();
|
|
4545
|
+
if (target !== "@dsh-workbench/core") throw new Error(`这条路只更新工作台自己那一份(${PLUGIN_ID}):${target}`);
|
|
4546
|
+
const read = workbench?.(target);
|
|
4547
|
+
if (read === void 0) throw new Error(`读不到 profile 里装的 ${target} —— 没法判断装的是哪一份,更新做不了。`);
|
|
4548
|
+
if (read.local === true) throw new Error("这份工作台是从本仓装进来的(开发副本),不从源上更新。");
|
|
4549
|
+
const registry = read.registry;
|
|
4550
|
+
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
4551
|
+
shell,
|
|
4552
|
+
name: read.pkg,
|
|
4553
|
+
registry,
|
|
4554
|
+
force: true
|
|
4555
|
+
});
|
|
4556
|
+
const spec = latest === void 0 ? `${read.pkg}@latest` : `${read.pkg}@${latest}`;
|
|
4557
|
+
const outcome = await installDshBundle(routeDeps(requireBundles()), spec, { ...registry === void 0 ? {} : { registry } });
|
|
4558
|
+
logger?.info?.(`[dsh-workbench] 已更新工作台 ${read.version} → ${latest ?? "最新版"}`);
|
|
4559
|
+
return outcome;
|
|
4560
|
+
},
|
|
4264
4561
|
async updatePlugin(pkg) {
|
|
4265
4562
|
const outcome = await plugins.update(pkg);
|
|
4266
4563
|
await reprobePlugin(pkg, outcome.name);
|
|
@@ -4325,6 +4622,11 @@ async function dispatchMarket(market, request) {
|
|
|
4325
4622
|
action: "cancel-install",
|
|
4326
4623
|
cancellation: await market.cancelInstall(request.requestId ?? "")
|
|
4327
4624
|
};
|
|
4625
|
+
if (request.action === "update-workbench") return {
|
|
4626
|
+
ok: true,
|
|
4627
|
+
action: "update-workbench",
|
|
4628
|
+
change: (await market.updateWorkbench(request.name ?? "")).change
|
|
4629
|
+
};
|
|
4328
4630
|
if (request.action === "update-plugin") {
|
|
4329
4631
|
await market.updatePlugin(request.name ?? "");
|
|
4330
4632
|
return {
|
|
@@ -4419,12 +4721,16 @@ function describePluginUninstall(pkg, outcome) {
|
|
|
4419
4721
|
if (outcome.removed) return `已卸载 opencli 插件 ${pkg}:没人再用它,文件也删了。`;
|
|
4420
4722
|
return `已从「你装的插件」里去掉 ${pkg}。${outcome.kept === void 0 ? "" : pluginKeptText(outcome.kept)}`;
|
|
4421
4723
|
}
|
|
4724
|
+
function describeWorkbenchUpdate(change) {
|
|
4725
|
+
if (change.application === "failed" || change.application === "cancelled") return `没成功:${change.errorCode ?? change.application}`;
|
|
4726
|
+
return change.application === "restart-required" ? "工作台更新到新版了 —— 这一版要重启一次 dsh 才生效(重启由用户来,别自己重启)。" : "工作台更新到新版了 —— 客户端与宿主自己换上,不用重启。";
|
|
4727
|
+
}
|
|
4422
4728
|
function usersOf(shared, names) {
|
|
4423
4729
|
const hit = shared.find((item) => names.some((name) => name !== void 0 && name !== "" && item.id === `plugin:${name}`));
|
|
4424
4730
|
if (hit === void 0 || hit.usedBy.length === 0) return "";
|
|
4425
4731
|
return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
|
|
4426
4732
|
}
|
|
4427
|
-
var ACTIONS = [
|
|
4733
|
+
var ACTIONS$1 = [
|
|
4428
4734
|
"list",
|
|
4429
4735
|
"install",
|
|
4430
4736
|
"update",
|
|
@@ -4432,7 +4738,8 @@ var ACTIONS = [
|
|
|
4432
4738
|
"install-bundle",
|
|
4433
4739
|
"update-dependency",
|
|
4434
4740
|
"update-plugin",
|
|
4435
|
-
"uninstall-plugin"
|
|
4741
|
+
"uninstall-plugin",
|
|
4742
|
+
"update-workbench"
|
|
4436
4743
|
];
|
|
4437
4744
|
function registerMarketTools(ctx, market) {
|
|
4438
4745
|
const tools = ctx.get("tools");
|
|
@@ -4442,14 +4749,14 @@ function registerMarketTools(ctx, market) {
|
|
|
4442
4749
|
}
|
|
4443
4750
|
const tool = {
|
|
4444
4751
|
name: "workbench_modules",
|
|
4445
|
-
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec(包名,可带版本;git / tarball / 本地路径也行)把模块包装进 profile —— 装完会连带装它声明的 opencli 插件;其余 action 按模块 id 动它要的外部供给;update-plugin / uninstall-plugin
|
|
4752
|
+
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec(包名,可带版本;git / tarball / 本地路径也行)把模块包装进 profile —— 装完会连带装它声明的 opencli 插件;其余 action 按模块 id 动它要的外部供给;update-plugin / uninstall-plugin 按包名或插件名动用户装的那份插件;update-workbench 把工作台自己更新到源上的新版(客户端与宿主一般会自己换上;真要重启时结果里会说,重启由用户来,别自己重启)。卸载只动工作台运行时,不影响终端那套;共用依赖只有升级。",
|
|
4446
4753
|
parameters: {
|
|
4447
4754
|
type: "object",
|
|
4448
4755
|
properties: {
|
|
4449
4756
|
action: {
|
|
4450
4757
|
type: "string",
|
|
4451
|
-
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency=升级共用依赖(要 id),update-plugin / uninstall-plugin=动用户自己装的 opencli 插件(要 plugin
|
|
4452
|
-
enum: [...ACTIONS]
|
|
4758
|
+
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency=升级共用依赖(要 id),update-plugin / uninstall-plugin=动用户自己装的 opencli 插件(要 plugin),update-workbench=把工作台自己更新到新版(不用别的参数),其余=对模块(要 id)。",
|
|
4759
|
+
enum: [...ACTIONS$1]
|
|
4453
4760
|
},
|
|
4454
4761
|
id: {
|
|
4455
4762
|
type: "string",
|
|
@@ -4483,7 +4790,7 @@ function registerMarketTools(ctx, market) {
|
|
|
4483
4790
|
},
|
|
4484
4791
|
async execute(args) {
|
|
4485
4792
|
const action = args?.action ?? "list";
|
|
4486
|
-
if (!ACTIONS.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS.join(" / ")})`);
|
|
4793
|
+
if (!ACTIONS$1.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS$1.join(" / ")})`);
|
|
4487
4794
|
if (action === "list") {
|
|
4488
4795
|
const { modules, shared } = await market.list();
|
|
4489
4796
|
return {
|
|
@@ -4504,6 +4811,13 @@ function registerMarketTools(ctx, market) {
|
|
|
4504
4811
|
text: describeInstall(spec, outcome.module, outcome.supply, outcome.change)
|
|
4505
4812
|
};
|
|
4506
4813
|
}
|
|
4814
|
+
if (action === "update-workbench") {
|
|
4815
|
+
const outcome = await market.updateWorkbench("");
|
|
4816
|
+
return {
|
|
4817
|
+
change: outcome.change,
|
|
4818
|
+
text: describeWorkbenchUpdate(outcome.change)
|
|
4819
|
+
};
|
|
4820
|
+
}
|
|
4507
4821
|
if (action === "update-plugin" || action === "uninstall-plugin") {
|
|
4508
4822
|
const plugin = args?.plugin?.trim() ?? "";
|
|
4509
4823
|
if (plugin === "") throw new Error(`${action} 需要一个 plugin:opencli 插件的包名或插件名 —— 就是市场「已安装 opencli 插件」那一段里的名字。`);
|
|
@@ -4554,53 +4868,70 @@ function registerMarketTools(ctx, market) {
|
|
|
4554
4868
|
//#endregion
|
|
4555
4869
|
//#region src/modules/panels/model/prefs.ts
|
|
4556
4870
|
var PANELS_PREF_RESOURCE = "panels";
|
|
4871
|
+
var ACTIONS = [
|
|
4872
|
+
"show",
|
|
4873
|
+
"hide",
|
|
4874
|
+
"move",
|
|
4875
|
+
"reset",
|
|
4876
|
+
"follow"
|
|
4877
|
+
];
|
|
4557
4878
|
function parsePanelsPrefAction(payload) {
|
|
4558
|
-
if (typeof payload !== "object" || payload === null) throw new Error("偏好写需要一个对象:{ action, id?, index?, path? }");
|
|
4879
|
+
if (typeof payload !== "object" || payload === null) throw new Error("偏好写需要一个对象:{ action, id?, index?, path?, scope? }");
|
|
4559
4880
|
const value = payload;
|
|
4560
4881
|
const action = value.action;
|
|
4561
|
-
if (
|
|
4882
|
+
if (typeof action !== "string" || !ACTIONS.includes(action)) throw new Error(`不认识的 action:${String(action)}(可用:${ACTIONS.join(" / ")})`);
|
|
4883
|
+
if (value.scope !== void 0 && value.scope !== "machine" && value.scope !== "workspace") throw new Error(`不认识的 scope:${String(value.scope)}(可用:workspace / machine)`);
|
|
4562
4884
|
return {
|
|
4563
4885
|
action,
|
|
4564
4886
|
...typeof value.id === "string" ? { id: value.id } : {},
|
|
4565
4887
|
...typeof value.index === "number" ? { index: value.index } : {},
|
|
4566
|
-
...typeof value.path === "string" ? { path: value.path } : {}
|
|
4888
|
+
...typeof value.path === "string" ? { path: value.path } : {},
|
|
4889
|
+
...value.scope === void 0 ? {} : { scope: value.scope }
|
|
4567
4890
|
};
|
|
4568
4891
|
}
|
|
4569
4892
|
function applyPanelsPref(panels, payload) {
|
|
4570
4893
|
const action = parsePanelsPrefAction(payload);
|
|
4571
4894
|
const path = action.path ?? null;
|
|
4895
|
+
const scope = action.scope ?? "workspace";
|
|
4896
|
+
if (action.action === "follow") {
|
|
4897
|
+
if (scope === "machine") throw new Error("机器级默认板没有\"回到跟随\"一说:它就是那份默认本身(要回到的是\"没配过\")。");
|
|
4898
|
+
return {
|
|
4899
|
+
ok: true,
|
|
4900
|
+
units: action.id === void 0 ? panels.follow(path) : panels.follow(path, action.id)
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4572
4903
|
if (action.action === "reset") return {
|
|
4573
4904
|
ok: true,
|
|
4574
|
-
units: panels.reset(path)
|
|
4905
|
+
units: panels.reset(path, scope)
|
|
4575
4906
|
};
|
|
4576
4907
|
if (action.id === void 0) throw new Error(`${action.action} 需要一个 id(当前套件:${panels.list(path).map((entry) => entry.id).join(", ")})`);
|
|
4577
4908
|
if (action.action === "show") return {
|
|
4578
4909
|
ok: true,
|
|
4579
|
-
units: panels.show(action.id, path)
|
|
4910
|
+
units: panels.show(action.id, path, scope)
|
|
4580
4911
|
};
|
|
4581
4912
|
if (action.action === "hide") return {
|
|
4582
4913
|
ok: true,
|
|
4583
|
-
units: panels.hide(action.id, path)
|
|
4914
|
+
units: panels.hide(action.id, path, scope)
|
|
4584
4915
|
};
|
|
4585
4916
|
return {
|
|
4586
4917
|
ok: true,
|
|
4587
|
-
units: panels.move(action.id, action.index ?? 0, path)
|
|
4918
|
+
units: panels.move(action.id, action.index ?? 0, path, scope)
|
|
4588
4919
|
};
|
|
4589
4920
|
}
|
|
4590
4921
|
//#endregion
|
|
4591
4922
|
//#region src/modules/panels/model/handoff.ts
|
|
4592
4923
|
var PANELS_FACT = "panels";
|
|
4593
4924
|
var CAPABILITY_FACT = "capability";
|
|
4594
|
-
function textOf(value) {
|
|
4925
|
+
function textOf$1(value) {
|
|
4595
4926
|
return typeof value === "string" ? value.trim() : "";
|
|
4596
4927
|
}
|
|
4597
4928
|
function actionOf(call) {
|
|
4598
|
-
return textOf(call.args["action"]) || "list";
|
|
4929
|
+
return textOf$1(call.args["action"]) || "list";
|
|
4599
4930
|
}
|
|
4600
4931
|
function requestOf(call) {
|
|
4601
4932
|
const action = actionOf(call);
|
|
4602
4933
|
if (!PROVISION_ACTIONS.includes(action)) return void 0;
|
|
4603
|
-
const target = textOf(call.args["unit"]) || textOf(call.args["id"]);
|
|
4934
|
+
const target = textOf$1(call.args["unit"]) || textOf$1(call.args["id"]);
|
|
4604
4935
|
if (target === "") return void 0;
|
|
4605
4936
|
const wanted = action;
|
|
4606
4937
|
return isDependencyAction(wanted) ? {
|
|
@@ -4612,7 +4943,7 @@ function requestOf(call) {
|
|
|
4612
4943
|
};
|
|
4613
4944
|
}
|
|
4614
4945
|
function panelsHandoffEntry(target = {}) {
|
|
4615
|
-
const path = textOf(target.path);
|
|
4946
|
+
const path = textOf$1(target.path);
|
|
4616
4947
|
return path === "" ? void 0 : {
|
|
4617
4948
|
fact: PANELS_FACT,
|
|
4618
4949
|
key: `path:${path}`
|
|
@@ -4623,6 +4954,7 @@ function panelsFactsOf(call, manifests) {
|
|
|
4623
4954
|
if (call.source === "workbench_diy" && actionOf(call) === "list") return [PANELS_FACT];
|
|
4624
4955
|
if (call.source !== "workbench_modules" && call.source !== "workbench_diy") return void 0;
|
|
4625
4956
|
if (actionOf(call) === "list") return [PANELS_FACT, CAPABILITY_FACT];
|
|
4957
|
+
if (actionOf(call) === "update-workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
4626
4958
|
const request = requestOf(call);
|
|
4627
4959
|
if (request === void 0) return void 0;
|
|
4628
4960
|
return [
|
|
@@ -4710,7 +5042,14 @@ function createPanelsService(options) {
|
|
|
4710
5042
|
const all = manifests();
|
|
4711
5043
|
if (!all.some((manifest) => manifest.id === id)) throw new Error(`没有这个套件:${id}(可用:${all.map((manifest) => manifest.id).join(", ")})`);
|
|
4712
5044
|
}
|
|
5045
|
+
function pluginRefs() {
|
|
5046
|
+
return options.plugins?.() ?? [];
|
|
5047
|
+
}
|
|
5048
|
+
function pluginIds() {
|
|
5049
|
+
return new Set(pluginRefs().map((plugin) => pluginPanelId(plugin.pkg)));
|
|
5050
|
+
}
|
|
4713
5051
|
function requireSwitchable(id) {
|
|
5052
|
+
if (pluginIds().has(id)) return;
|
|
4714
5053
|
requireKnown(id);
|
|
4715
5054
|
if (!manifests().some((manifest) => manifest.id === id && manifest.card !== null)) throw new Error(`${id} 没有卡片,不能收起(它是把别的套件放回来的入口)`);
|
|
4716
5055
|
}
|
|
@@ -4723,6 +5062,9 @@ function createPanelsService(options) {
|
|
|
4723
5062
|
list(workspace, target) {
|
|
4724
5063
|
return resolvePanels(manifests(), target === "machine" ? boardsOf(null) : boardsOf(workspace));
|
|
4725
5064
|
},
|
|
5065
|
+
listPlugins(workspace, target) {
|
|
5066
|
+
return resolvePluginPanels(pluginRefs(), target === "machine" ? boardsOf(null) : boardsOf(workspace));
|
|
5067
|
+
},
|
|
4726
5068
|
boards(workspace) {
|
|
4727
5069
|
return boardsOf(workspace);
|
|
4728
5070
|
},
|
|
@@ -4780,6 +5122,24 @@ function createPanelsService(options) {
|
|
|
4780
5122
|
reset(workspace, target) {
|
|
4781
5123
|
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4782
5124
|
return withPersisted(folder, EMPTY_BOARD, key);
|
|
5125
|
+
},
|
|
5126
|
+
changedBy() {
|
|
5127
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5128
|
+
for (const folder of store.scan(PANELS_MODULE_ID)) {
|
|
5129
|
+
const board = readBoard(folder);
|
|
5130
|
+
if (board === null) continue;
|
|
5131
|
+
for (const id of /* @__PURE__ */ new Set([...board.shown, ...board.hidden])) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
5132
|
+
}
|
|
5133
|
+
return counts;
|
|
5134
|
+
},
|
|
5135
|
+
follow(workspace, id) {
|
|
5136
|
+
const { folder, key } = targetOf("workspace", workspace);
|
|
5137
|
+
if (id === void 0) {
|
|
5138
|
+
folder.remove(PANELS_FILE);
|
|
5139
|
+
notify();
|
|
5140
|
+
return resolvePanels(manifests(), boardsOf(key));
|
|
5141
|
+
}
|
|
5142
|
+
return withPersisted(folder, withoutPanel(boardIn(folder), id), key);
|
|
4783
5143
|
}
|
|
4784
5144
|
};
|
|
4785
5145
|
}
|
|
@@ -4790,13 +5150,15 @@ function panelRoutes(panels, values) {
|
|
|
4790
5150
|
const path = param(search, "path") ?? null;
|
|
4791
5151
|
const entry = panelsHandoffEntry(path === null ? {} : { path });
|
|
4792
5152
|
const handed = entry === void 0 ? void 0 : values?.take(entry);
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
units: handed.value
|
|
4796
|
-
};
|
|
5153
|
+
const defaults = [...panels.list(null, "machine").filter((unit) => unit.shown).map((unit) => unit.id), ...panels.listPlugins(null, "machine").filter((plugin) => plugin.shown).map((plugin) => plugin.id)];
|
|
5154
|
+
const own = panels.boards(path).workspace !== null;
|
|
4797
5155
|
return {
|
|
4798
5156
|
ok: true,
|
|
4799
|
-
units: panels.list(path)
|
|
5157
|
+
units: handed === void 0 ? panels.list(path) : handed.value,
|
|
5158
|
+
plugins: panels.listPlugins(path),
|
|
5159
|
+
defaults,
|
|
5160
|
+
own,
|
|
5161
|
+
changed: Object.fromEntries(panels.changedBy())
|
|
4800
5162
|
};
|
|
4801
5163
|
} };
|
|
4802
5164
|
}
|
|
@@ -4855,6 +5217,18 @@ function createPluginLedger(options) {
|
|
|
4855
5217
|
};
|
|
4856
5218
|
}
|
|
4857
5219
|
//#endregion
|
|
5220
|
+
//#region src/modules/panels/model/plugin-panel.ts
|
|
5221
|
+
function pluginPanelRefs(ledger) {
|
|
5222
|
+
return ledger.list().map((entry) => {
|
|
5223
|
+
const name = pluginNameOf(entry);
|
|
5224
|
+
return {
|
|
5225
|
+
pkg: entry.package,
|
|
5226
|
+
title: name ?? entry.package,
|
|
5227
|
+
...name === void 0 ? {} : { name }
|
|
5228
|
+
};
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
//#endregion
|
|
4858
5232
|
//#region src/shared/api/installed-plugins.ts
|
|
4859
5233
|
var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
|
|
4860
5234
|
//#endregion
|
|
@@ -4944,24 +5318,25 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
4944
5318
|
}
|
|
4945
5319
|
const tool = {
|
|
4946
5320
|
name: "workbench_panels",
|
|
4947
|
-
description: "看或调右侧面板:启用哪些套件、什么顺序。启用/收起 = 卡片与 agent 工具一起上下线(别的项目不受影响,不删数据)。收起是\"这个工作区明确不要它\",连那些在机器级默认板上开着的套件也照样在这个工作区收起来,之后 show 能放回来。默认只动当前工作区(scope=workspace);scope=machine 动的是\"机器级默认板\" —— 机器级默认板只是还没配过的工作区的默认值,已经自己配过的工作区保持自己的选择,不会被它改。在那份板上 hide = 明确写下\"不要它\"
|
|
5321
|
+
description: "看或调右侧面板:启用哪些套件、什么顺序。启用/收起 = 卡片与 agent 工具一起上下线(别的项目不受影响,不删数据)。收起是\"这个工作区明确不要它\",连那些在机器级默认板上开着的套件也照样在这个工作区收起来,之后 show 能放回来。默认只动当前工作区(scope=workspace);scope=machine 动的是\"机器级默认板\" —— 机器级默认板只是还没配过的工作区的默认值,已经自己配过的工作区保持自己的选择,不会被它改。在那份板上 hide = 明确写下\"不要它\",这一笔比名单更管用:名单里还列着它也算不要,没提过它的工作区因此看不到它;show 会把这笔划掉、把它放回去。本工作区那份板只记**改过的那几行**(逐行覆盖):没提到的行跟着机器级默认板走,所以那份板以后改了,没改过的工作区会跟着变。workbench_panels 默认只动当前工作区,动机器级默认板要显式给 scope=machine。",
|
|
4948
5322
|
parameters: {
|
|
4949
5323
|
type: "object",
|
|
4950
5324
|
properties: {
|
|
4951
5325
|
action: {
|
|
4952
5326
|
type: "string",
|
|
4953
|
-
description: "show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它(卡片与工具一起下线,默认在机器上也开着的套件同样收起,别的项目不受影响);move=调顺序;reset
|
|
5327
|
+
description: "show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它(卡片与工具一起下线,默认在机器上也开着的套件同样收起,别的项目不受影响);move=调顺序;reset=清掉这个工作区改过的那些行(改完就全部回到跟着机器级默认板走);follow=把某一行放回跟随(带 id = 只放回那一行;不带 id = 全部放回)。",
|
|
4954
5328
|
enum: [
|
|
4955
5329
|
"list",
|
|
4956
5330
|
"show",
|
|
4957
5331
|
"hide",
|
|
4958
5332
|
"move",
|
|
4959
|
-
"reset"
|
|
5333
|
+
"reset",
|
|
5334
|
+
"follow"
|
|
4960
5335
|
]
|
|
4961
5336
|
},
|
|
4962
5337
|
id: {
|
|
4963
5338
|
type: "string",
|
|
4964
|
-
description: "show / hide / move
|
|
5339
|
+
description: "show / hide / move 必填;follow 可选(带 id = 只把那一行放回跟随)。"
|
|
4965
5340
|
},
|
|
4966
5341
|
index: {
|
|
4967
5342
|
type: "integer",
|
|
@@ -4997,8 +5372,9 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
4997
5372
|
"show",
|
|
4998
5373
|
"hide",
|
|
4999
5374
|
"move",
|
|
5000
|
-
"reset"
|
|
5001
|
-
|
|
5375
|
+
"reset",
|
|
5376
|
+
"follow"
|
|
5377
|
+
].includes(action)) throw new Error(`不认识的 action:${action}(可用:list / show / hide / move / reset / follow)`);
|
|
5002
5378
|
if (action === "list") {
|
|
5003
5379
|
const units = list();
|
|
5004
5380
|
if (scope === "workspace") {
|
|
@@ -5010,11 +5386,19 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5010
5386
|
text: render(units, workspace, scope)
|
|
5011
5387
|
};
|
|
5012
5388
|
}
|
|
5389
|
+
if (action === "follow") {
|
|
5390
|
+
if (scope === "machine") throw new Error("机器级默认板没有\"放回跟随\"一说:它就是那份默认本身,直接 show / hide 写它。");
|
|
5391
|
+
const units = panels.follow(workspace, id);
|
|
5392
|
+
return {
|
|
5393
|
+
units,
|
|
5394
|
+
text: id === void 0 ? `这个工作区改过的行全部清掉 —— 每一行都回到跟着机器级默认板走(那份板以后改了,这里跟着变)。\n${render(units, workspace, scope)}` : `${id} 放回跟随 —— 它不再由这个工作区定,改跟机器级默认板走。\n${render(units, workspace, scope)}`
|
|
5395
|
+
};
|
|
5396
|
+
}
|
|
5013
5397
|
if (action === "reset") {
|
|
5014
5398
|
const units = panels.reset(workspace, scope);
|
|
5015
5399
|
return {
|
|
5016
5400
|
units,
|
|
5017
|
-
text: scope === "machine" ?
|
|
5401
|
+
text: scope === "machine" ? `机器级默认板已清空 —— 从今往后,没在这一行改过的工作区默认什么都不开。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `这个工作区改过的行已全部清掉 —— 每一行都回到跟着机器级默认板走。\n${render(units, workspace, scope)}`
|
|
5018
5402
|
};
|
|
5019
5403
|
}
|
|
5020
5404
|
if (id === void 0) throw new Error(`${action} 需要一个 id(例如 coding-issues)。当前套件:${list().map((entry) => entry.id).join(", ")}`);
|
|
@@ -5047,19 +5431,20 @@ var OWNED = [{
|
|
|
5047
5431
|
unit: panelsUnit.id,
|
|
5048
5432
|
manifest: panelsUnit,
|
|
5049
5433
|
mount({ ctx, logger, manifests, ownedUnits, opencliCommand, probe, store, values }) {
|
|
5434
|
+
const ledger = createPluginLedger({
|
|
5435
|
+
store,
|
|
5436
|
+
home: isolatedHome()
|
|
5437
|
+
});
|
|
5050
5438
|
const panels = createPanelsService({
|
|
5051
5439
|
manifests,
|
|
5052
5440
|
store,
|
|
5053
5441
|
owned: ownedUnits,
|
|
5442
|
+
plugins: () => pluginPanelRefs(ledger),
|
|
5054
5443
|
logger
|
|
5055
5444
|
});
|
|
5056
5445
|
ctx.provide(PANELS_SERVICE, panels);
|
|
5057
5446
|
const profileDir = profileDirOf(ctx);
|
|
5058
5447
|
const shell = ctx.get("shell");
|
|
5059
|
-
const ledger = createPluginLedger({
|
|
5060
|
-
store,
|
|
5061
|
-
home: isolatedHome()
|
|
5062
|
-
});
|
|
5063
5448
|
const market = createMarketService({
|
|
5064
5449
|
manifests: manifests(),
|
|
5065
5450
|
manifestsNow: manifests,
|
|
@@ -5069,6 +5454,7 @@ var OWNED = [{
|
|
|
5069
5454
|
bundles: readBundleManager(ctx),
|
|
5070
5455
|
installed: (pkg) => readInstalledModule(profileDir, pkg),
|
|
5071
5456
|
modules: () => profileModules(profileDir),
|
|
5457
|
+
workbench: (pkg) => readProfilePackage(profileDir, pkg),
|
|
5072
5458
|
registries: createRegistryNotebook(profileDir),
|
|
5073
5459
|
ledger,
|
|
5074
5460
|
references: () => {
|
|
@@ -5448,6 +5834,24 @@ function bundleRegistriesHandler(ctx) {
|
|
|
5448
5834
|
};
|
|
5449
5835
|
}
|
|
5450
5836
|
var refusalLog = /* @__PURE__ */ new Map();
|
|
5837
|
+
async function workbenchVersion(input) {
|
|
5838
|
+
const read = readProfilePackage(input.dir, PLUGIN_ID);
|
|
5839
|
+
if (read === void 0) return void 0;
|
|
5840
|
+
const registry = read.registry ?? input.fallback;
|
|
5841
|
+
const queried = read.local === true || input.shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
5842
|
+
shell: input.shell,
|
|
5843
|
+
name: read.pkg,
|
|
5844
|
+
registry,
|
|
5845
|
+
force: input.force
|
|
5846
|
+
});
|
|
5847
|
+
const latest = queried !== void 0 && isNewerVersion(queried, read.version) ? queried : void 0;
|
|
5848
|
+
return {
|
|
5849
|
+
pkg: read.pkg,
|
|
5850
|
+
installed: read.version,
|
|
5851
|
+
...latest === void 0 ? {} : { latest },
|
|
5852
|
+
...read.local === true ? { local: true } : {}
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5451
5855
|
function moduleVersionsHandler(ctx, shell) {
|
|
5452
5856
|
return async (search) => {
|
|
5453
5857
|
const manager = readBundleManager(ctx);
|
|
@@ -5460,36 +5864,44 @@ function moduleVersionsHandler(ctx, shell) {
|
|
|
5460
5864
|
const refusals = new Map((ctx.get("workbenchUnits")?.refusals() ?? []).filter((one) => one.unit !== "").map((one) => [one.unit, one.reasons]));
|
|
5461
5865
|
const force = param(search, "force") === "1";
|
|
5462
5866
|
const fallback = (await manager.registries())?.resolved ?? void 0;
|
|
5867
|
+
const modules = await Promise.all(found.map(async (module) => {
|
|
5868
|
+
const registry = module.registry ?? fallback;
|
|
5869
|
+
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
5870
|
+
shell,
|
|
5871
|
+
name: module.pkg,
|
|
5872
|
+
registry,
|
|
5873
|
+
force
|
|
5874
|
+
});
|
|
5875
|
+
const raw = moduleDeclarationOf(dir, module.pkg);
|
|
5876
|
+
const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
|
|
5877
|
+
const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
|
|
5878
|
+
if (refused === void 0) refusalLog.delete(module.pkg);
|
|
5879
|
+
else {
|
|
5880
|
+
const line = refused.join(";");
|
|
5881
|
+
if (refusalLog.get(module.pkg) !== line) {
|
|
5882
|
+
refusalLog.set(module.pkg, line);
|
|
5883
|
+
ctx.logger?.warn?.(`[dsh-workbench] ${module.pkg} 装上了但用不了:${line}`);
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
return {
|
|
5887
|
+
unit: module.unit,
|
|
5888
|
+
pkg: module.pkg,
|
|
5889
|
+
installed: module.version,
|
|
5890
|
+
...latest === void 0 ? {} : { latest },
|
|
5891
|
+
...module.local === true ? { local: true } : {},
|
|
5892
|
+
...refused === void 0 ? {} : { refused }
|
|
5893
|
+
};
|
|
5894
|
+
}));
|
|
5895
|
+
const workbench = await workbenchVersion({
|
|
5896
|
+
dir,
|
|
5897
|
+
shell,
|
|
5898
|
+
fallback,
|
|
5899
|
+
force
|
|
5900
|
+
});
|
|
5463
5901
|
return {
|
|
5464
5902
|
ok: true,
|
|
5465
|
-
modules
|
|
5466
|
-
|
|
5467
|
-
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
5468
|
-
shell,
|
|
5469
|
-
name: module.pkg,
|
|
5470
|
-
registry,
|
|
5471
|
-
force
|
|
5472
|
-
});
|
|
5473
|
-
const raw = moduleDeclarationOf(dir, module.pkg);
|
|
5474
|
-
const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
|
|
5475
|
-
const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
|
|
5476
|
-
if (refused === void 0) refusalLog.delete(module.pkg);
|
|
5477
|
-
else {
|
|
5478
|
-
const line = refused.join(";");
|
|
5479
|
-
if (refusalLog.get(module.pkg) !== line) {
|
|
5480
|
-
refusalLog.set(module.pkg, line);
|
|
5481
|
-
ctx.logger?.warn?.(`[dsh-workbench] ${module.pkg} 装上了但用不了:${line}`);
|
|
5482
|
-
}
|
|
5483
|
-
}
|
|
5484
|
-
return {
|
|
5485
|
-
unit: module.unit,
|
|
5486
|
-
pkg: module.pkg,
|
|
5487
|
-
installed: module.version,
|
|
5488
|
-
...latest === void 0 ? {} : { latest },
|
|
5489
|
-
...module.local === true ? { local: true } : {},
|
|
5490
|
-
...refused === void 0 ? {} : { refused }
|
|
5491
|
-
};
|
|
5492
|
-
}))
|
|
5903
|
+
modules,
|
|
5904
|
+
...workbench === void 0 ? {} : { workbench }
|
|
5493
5905
|
};
|
|
5494
5906
|
};
|
|
5495
5907
|
}
|
|
@@ -5516,15 +5928,26 @@ async function declarationRefusal(spec, inspection, ctx, manager) {
|
|
|
5516
5928
|
});
|
|
5517
5929
|
return refusedInspection(declarationRefusalOf(inspection.name, raw));
|
|
5518
5930
|
}
|
|
5519
|
-
async function
|
|
5520
|
-
if (inspection.status !== "accepted") return void 0;
|
|
5931
|
+
async function packageOverride(spec, inspection, ctx, manager, asked) {
|
|
5521
5932
|
const shell = ctx.get("shell");
|
|
5522
|
-
const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
|
|
5523
|
-
|
|
5933
|
+
const registry = (inspection.status === "accepted" && typeof inspection.registry === "string" ? inspection.registry : void 0) ?? (typeof asked === "string" ? asked : (await manager.registries())?.resolved ?? void 0);
|
|
5934
|
+
const route = await packageRouteOf({
|
|
5524
5935
|
spec,
|
|
5525
5936
|
...shell === void 0 ? {} : { shell },
|
|
5526
5937
|
...registry === void 0 ? {} : { registry }
|
|
5527
|
-
})
|
|
5938
|
+
});
|
|
5939
|
+
if (route?.kind === "opencli-plugin") {
|
|
5940
|
+
if (inspection.status !== "refused") return void 0;
|
|
5941
|
+
const ref = splitNpmSpec(spec);
|
|
5942
|
+
return {
|
|
5943
|
+
status: "plugin",
|
|
5944
|
+
pkg: route.pkg ?? ref?.name ?? spec.trim(),
|
|
5945
|
+
registry: registry ?? null,
|
|
5946
|
+
...route.pluginName === void 0 ? {} : { pluginName: route.pluginName },
|
|
5947
|
+
...ref?.version === void 0 ? {} : { version: ref.version }
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
return route?.kind === "neither" && inspection.status === "accepted" ? {
|
|
5528
5951
|
status: "refused",
|
|
5529
5952
|
problem: "not-a-module-or-plugin",
|
|
5530
5953
|
reason: refusalReason()
|
|
@@ -5548,7 +5971,8 @@ function bundleInspectHandler(ctx) {
|
|
|
5548
5971
|
message: "这个 profile 里没有插件管理器,装不了模块。"
|
|
5549
5972
|
}
|
|
5550
5973
|
};
|
|
5551
|
-
const
|
|
5974
|
+
const asked = registryOf(search);
|
|
5975
|
+
const inspection = await manager.inspect(spec, asked);
|
|
5552
5976
|
if (inspection === null) return {
|
|
5553
5977
|
ok: false,
|
|
5554
5978
|
error: {
|
|
@@ -5558,7 +5982,7 @@ function bundleInspectHandler(ctx) {
|
|
|
5558
5982
|
};
|
|
5559
5983
|
return {
|
|
5560
5984
|
ok: true,
|
|
5561
|
-
inspection: await
|
|
5985
|
+
inspection: await packageOverride(spec, inspection, ctx, manager, asked) ?? await declarationRefusal(spec, inspection, ctx, manager) ?? inspection
|
|
5562
5986
|
};
|
|
5563
5987
|
};
|
|
5564
5988
|
}
|
|
@@ -6484,7 +6908,9 @@ function mountUnits(units, deps) {
|
|
|
6484
6908
|
//#endregion
|
|
6485
6909
|
//#region src/shared/api/host/unit-registry.ts
|
|
6486
6910
|
function sourceLabel(source) {
|
|
6487
|
-
|
|
6911
|
+
if (source.kind === "owned") return "外壳自己";
|
|
6912
|
+
if (source.kind === "plugin") return "按已装 opencli 插件合成";
|
|
6913
|
+
return source.pkg;
|
|
6488
6914
|
}
|
|
6489
6915
|
function createUnitRegistry(options) {
|
|
6490
6916
|
const refusals = [];
|
|
@@ -6544,6 +6970,14 @@ function createUnitRegistry(options) {
|
|
|
6544
6970
|
source: { kind: "owned" }
|
|
6545
6971
|
});
|
|
6546
6972
|
},
|
|
6973
|
+
registerPlugin(face) {
|
|
6974
|
+
return add({
|
|
6975
|
+
unit: face.manifest.id,
|
|
6976
|
+
manifest: face.manifest,
|
|
6977
|
+
face,
|
|
6978
|
+
source: { kind: "plugin" }
|
|
6979
|
+
});
|
|
6980
|
+
},
|
|
6547
6981
|
all: () => hosts,
|
|
6548
6982
|
owned: () => hosts.filter((host) => host.source.kind === "owned").map((host) => host.unit),
|
|
6549
6983
|
refusals: () => refusals,
|
|
@@ -6569,6 +7003,563 @@ function bindRosterFacts(options) {
|
|
|
6569
7003
|
});
|
|
6570
7004
|
}
|
|
6571
7005
|
//#endregion
|
|
7006
|
+
//#region src/shared/api/host/plugin-catalog.ts
|
|
7007
|
+
function recordOf(value) {
|
|
7008
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
7009
|
+
}
|
|
7010
|
+
function textOf(value) {
|
|
7011
|
+
return typeof value === "string" ? value : "";
|
|
7012
|
+
}
|
|
7013
|
+
function stringsOf(value) {
|
|
7014
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
7015
|
+
}
|
|
7016
|
+
function argsOf(raw) {
|
|
7017
|
+
if (!Array.isArray(raw)) return [];
|
|
7018
|
+
return raw.flatMap((item) => {
|
|
7019
|
+
const record = recordOf(item);
|
|
7020
|
+
if (record === void 0) return [];
|
|
7021
|
+
const name = textOf(record["name"]).trim();
|
|
7022
|
+
if (name === "") return [];
|
|
7023
|
+
const fallback = record["default"];
|
|
7024
|
+
return [{
|
|
7025
|
+
name,
|
|
7026
|
+
type: textOf(record["type"]) || "string",
|
|
7027
|
+
required: record["required"] === true,
|
|
7028
|
+
positional: record["positional"] === true,
|
|
7029
|
+
valueRequired: record["valueRequired"] === true,
|
|
7030
|
+
choices: stringsOf(record["choices"]),
|
|
7031
|
+
...fallback === void 0 || fallback === null ? {} : { defaultValue: fallback },
|
|
7032
|
+
help: textOf(record["help"])
|
|
7033
|
+
}];
|
|
7034
|
+
});
|
|
7035
|
+
}
|
|
7036
|
+
function parsePluginRows(stdout) {
|
|
7037
|
+
const parsed = parseJsonLoose(stdout);
|
|
7038
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
7039
|
+
const rows = [];
|
|
7040
|
+
for (const item of parsed) {
|
|
7041
|
+
const record = recordOf(item);
|
|
7042
|
+
if (record === void 0) return void 0;
|
|
7043
|
+
const name = textOf(record["name"]).trim();
|
|
7044
|
+
if (name === "") return void 0;
|
|
7045
|
+
rows.push({
|
|
7046
|
+
name,
|
|
7047
|
+
version: textOf(record["version"]),
|
|
7048
|
+
commands: stringsOf(record["commands"])
|
|
7049
|
+
});
|
|
7050
|
+
}
|
|
7051
|
+
return rows;
|
|
7052
|
+
}
|
|
7053
|
+
function parseCommandRows(stdout) {
|
|
7054
|
+
const parsed = parseJsonLoose(stdout);
|
|
7055
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
7056
|
+
const rows = [];
|
|
7057
|
+
for (const item of parsed) {
|
|
7058
|
+
const record = recordOf(item);
|
|
7059
|
+
if (record === void 0) return void 0;
|
|
7060
|
+
const site = textOf(record["site"]).trim();
|
|
7061
|
+
const name = textOf(record["name"]).trim();
|
|
7062
|
+
if (site === "" || name === "") return void 0;
|
|
7063
|
+
rows.push({
|
|
7064
|
+
site,
|
|
7065
|
+
name,
|
|
7066
|
+
description: textOf(record["description"]),
|
|
7067
|
+
access: textOf(record["access"]),
|
|
7068
|
+
...textOf(record["domain"]) === "" ? {} : { domain: textOf(record["domain"]) },
|
|
7069
|
+
args: argsOf(record["args"])
|
|
7070
|
+
});
|
|
7071
|
+
}
|
|
7072
|
+
return rows;
|
|
7073
|
+
}
|
|
7074
|
+
function commandOf(plugin, row) {
|
|
7075
|
+
return {
|
|
7076
|
+
plugin,
|
|
7077
|
+
name: row.name,
|
|
7078
|
+
description: row.description,
|
|
7079
|
+
access: row.access,
|
|
7080
|
+
args: row.args
|
|
7081
|
+
};
|
|
7082
|
+
}
|
|
7083
|
+
function attributeCommands(plugins, rows) {
|
|
7084
|
+
const bySite = /* @__PURE__ */ new Map();
|
|
7085
|
+
for (const row of rows) {
|
|
7086
|
+
const site = bySite.get(row.site) ?? /* @__PURE__ */ new Map();
|
|
7087
|
+
site.set(row.name, row);
|
|
7088
|
+
bySite.set(row.site, site);
|
|
7089
|
+
}
|
|
7090
|
+
const sets = [];
|
|
7091
|
+
const problems = [];
|
|
7092
|
+
if (plugins.length > 0 && rows.length === 0) problems.push(`opencli 报了 ${String(plugins.length)} 个插件,但命令面(opencli list -f json)里一条命令都没有 —— 两份清单对不上`);
|
|
7093
|
+
for (const plugin of plugins) {
|
|
7094
|
+
const found = bySite.get(plugin.name) ?? /* @__PURE__ */ new Map();
|
|
7095
|
+
if (found.size === 0) {
|
|
7096
|
+
problems.push(`「${plugin.name}」在 opencli 插件清单里,但命令面里没有一条 site=${plugin.name} 的命令 —— 归属不了,它这次不合成工具`);
|
|
7097
|
+
continue;
|
|
7098
|
+
}
|
|
7099
|
+
const declared = plugin.commands.length > 0 ? plugin.commands : [...found.keys()];
|
|
7100
|
+
const missing = declared.filter((name) => !found.has(name));
|
|
7101
|
+
if (missing.length > 0) problems.push(`「${plugin.name}」自己的清单里有 ${String(missing.length)} 条命令在命令面里找不到:${missing.join("、")}(插件版本旧或命令面没更新)`);
|
|
7102
|
+
const commands = declared.flatMap((name) => {
|
|
7103
|
+
const row = found.get(name);
|
|
7104
|
+
return row === void 0 ? [] : [commandOf(plugin.name, row)];
|
|
7105
|
+
});
|
|
7106
|
+
if (commands.length === 0) {
|
|
7107
|
+
problems.push(`「${plugin.name}」没有任何一条能归属的命令 —— 它这次不合成工具`);
|
|
7108
|
+
continue;
|
|
7109
|
+
}
|
|
7110
|
+
sets.push({
|
|
7111
|
+
plugin: plugin.name,
|
|
7112
|
+
version: plugin.version,
|
|
7113
|
+
commands
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
return {
|
|
7117
|
+
sets,
|
|
7118
|
+
problems
|
|
7119
|
+
};
|
|
7120
|
+
}
|
|
7121
|
+
function fingerprintOf(plugins) {
|
|
7122
|
+
return plugins.map((plugin) => `${plugin.name}@${plugin.version}:${[...plugin.commands].sort().join(",")}`).sort().join("|");
|
|
7123
|
+
}
|
|
7124
|
+
function messageOf(error) {
|
|
7125
|
+
return error instanceof Error ? error.message : String(error);
|
|
7126
|
+
}
|
|
7127
|
+
function createPluginCatalog(options) {
|
|
7128
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
7129
|
+
let current = {
|
|
7130
|
+
ok: false,
|
|
7131
|
+
at: 0,
|
|
7132
|
+
sets: [],
|
|
7133
|
+
problems: ["还没探过 opencli 的插件命令面"]
|
|
7134
|
+
};
|
|
7135
|
+
let heavy;
|
|
7136
|
+
async function read(line) {
|
|
7137
|
+
const shell = options.shell;
|
|
7138
|
+
if (shell === void 0) return {
|
|
7139
|
+
ok: false,
|
|
7140
|
+
problem: "没有执行缝(shell 服务不可用),探不了这个命令面"
|
|
7141
|
+
};
|
|
7142
|
+
try {
|
|
7143
|
+
const result = await runShell(shell, line, { timeoutMs });
|
|
7144
|
+
if (result.timedOut) return {
|
|
7145
|
+
ok: false,
|
|
7146
|
+
problem: `${line} 超过 ${String(Math.round(timeoutMs / 1e3))} 秒没返回`
|
|
7147
|
+
};
|
|
7148
|
+
if (result.exitCode !== 0) {
|
|
7149
|
+
const tail = result.stderr.trim() || result.stdout.trim();
|
|
7150
|
+
return {
|
|
7151
|
+
ok: false,
|
|
7152
|
+
problem: `${line} 退出码 ${String(result.exitCode)}${tail === "" ? "" : `:${tail.split("\n").slice(-3).join(" ")}`}`
|
|
7153
|
+
};
|
|
7154
|
+
}
|
|
7155
|
+
return {
|
|
7156
|
+
ok: true,
|
|
7157
|
+
stdout: result.stdout
|
|
7158
|
+
};
|
|
7159
|
+
} catch (error) {
|
|
7160
|
+
return {
|
|
7161
|
+
ok: false,
|
|
7162
|
+
problem: `${line} 没跑起来:${messageOf(error)}`
|
|
7163
|
+
};
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
function fail(problem) {
|
|
7167
|
+
current = {
|
|
7168
|
+
ok: false,
|
|
7169
|
+
at: Date.now(),
|
|
7170
|
+
sets: current.sets,
|
|
7171
|
+
problems: [problem]
|
|
7172
|
+
};
|
|
7173
|
+
return current;
|
|
7174
|
+
}
|
|
7175
|
+
async function refresh(callOptions = {}) {
|
|
7176
|
+
const pluginsRun = await read(`${options.command} plugin list -f json`);
|
|
7177
|
+
if (!pluginsRun.ok) return fail(pluginsRun.problem);
|
|
7178
|
+
const plugins = parsePluginRows(pluginsRun.stdout);
|
|
7179
|
+
if (plugins === void 0) return fail("opencli plugin list -f json 的输出读不懂(不是带 name 的 JSON 数组)");
|
|
7180
|
+
const fingerprint = fingerprintOf(plugins);
|
|
7181
|
+
if (!callOptions.force && heavy !== void 0 && heavy.fingerprint === fingerprint) {
|
|
7182
|
+
current = {
|
|
7183
|
+
ok: true,
|
|
7184
|
+
at: Date.now(),
|
|
7185
|
+
sets: heavy.sets,
|
|
7186
|
+
problems: heavy.problems
|
|
7187
|
+
};
|
|
7188
|
+
return current;
|
|
7189
|
+
}
|
|
7190
|
+
const commandsRun = await read(`${options.command} list -f json`);
|
|
7191
|
+
if (!commandsRun.ok) return fail(commandsRun.problem);
|
|
7192
|
+
const rows = parseCommandRows(commandsRun.stdout);
|
|
7193
|
+
if (rows === void 0) return fail("opencli list -f json 的输出读不懂(不是带 site / name 的 JSON 数组)");
|
|
7194
|
+
const attributed = attributeCommands(plugins, rows);
|
|
7195
|
+
heavy = {
|
|
7196
|
+
fingerprint,
|
|
7197
|
+
sets: attributed.sets,
|
|
7198
|
+
problems: attributed.problems
|
|
7199
|
+
};
|
|
7200
|
+
current = {
|
|
7201
|
+
ok: true,
|
|
7202
|
+
at: Date.now(),
|
|
7203
|
+
sets: attributed.sets,
|
|
7204
|
+
problems: attributed.problems
|
|
7205
|
+
};
|
|
7206
|
+
return current;
|
|
7207
|
+
}
|
|
7208
|
+
return {
|
|
7209
|
+
refresh,
|
|
7210
|
+
snapshot: () => current
|
|
7211
|
+
};
|
|
7212
|
+
}
|
|
7213
|
+
//#endregion
|
|
7214
|
+
//#region src/shared/api/host/plugin-login-hint.ts
|
|
7215
|
+
var NOT_LOGGED_IN = /未登录|请先登录|not logged in/i;
|
|
7216
|
+
function propertyOf$1(error, key) {
|
|
7217
|
+
return typeof error === "object" && error !== null ? error[key] : void 0;
|
|
7218
|
+
}
|
|
7219
|
+
function helpUrlOfError(error) {
|
|
7220
|
+
const raw = propertyOf$1(error, "helpUrl");
|
|
7221
|
+
if (typeof raw !== "string") return void 0;
|
|
7222
|
+
const trimmed = raw.trim();
|
|
7223
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
7224
|
+
}
|
|
7225
|
+
function isAuthFailure(error, message) {
|
|
7226
|
+
if (NOT_LOGGED_IN.test(message)) return true;
|
|
7227
|
+
return propertyOf$1(error, "code") === "auth-required";
|
|
7228
|
+
}
|
|
7229
|
+
function loginUrlOfDomain(domain) {
|
|
7230
|
+
const bare = (domain ?? "").trim().replace(/\/+$/, "");
|
|
7231
|
+
if (bare === "" || bare.includes("*")) return void 0;
|
|
7232
|
+
return /^https?:\/\//.test(bare) ? bare : `https://${bare}/login`;
|
|
7233
|
+
}
|
|
7234
|
+
function pluginLoginHint(error, domain) {
|
|
7235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7236
|
+
if (!isAuthFailure(error, message)) return message;
|
|
7237
|
+
const url = helpUrlOfError(error) ?? loginUrlOfDomain(domain);
|
|
7238
|
+
if (url !== void 0) return `${message}\n在 opencli 那扇浏览器窗里打开 ${url} 登一次,然后重试。`;
|
|
7239
|
+
return `${message}\n在 opencli 那扇浏览器窗里登录这个站点(插件没给出具体地址,工作台拼不出来)—— 或跑一次工作台的体检/预热,然后重试。`;
|
|
7240
|
+
}
|
|
7241
|
+
//#endregion
|
|
7242
|
+
//#region src/shared/api/host/plugin-tools.ts
|
|
7243
|
+
var SCHEMA_TYPE = {
|
|
7244
|
+
int: "integer",
|
|
7245
|
+
integer: "integer",
|
|
7246
|
+
number: "number",
|
|
7247
|
+
float: "number",
|
|
7248
|
+
bool: "boolean",
|
|
7249
|
+
boolean: "boolean",
|
|
7250
|
+
string: "string"
|
|
7251
|
+
};
|
|
7252
|
+
var KEY_ARG_LIMIT = 3;
|
|
7253
|
+
function pluginToolName(plugin, command) {
|
|
7254
|
+
return `${plugin}_${command}`.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/_{2,}/g, "_").replace(/^_+|_+$/g, "");
|
|
7255
|
+
}
|
|
7256
|
+
function schemaTypeOf(arg) {
|
|
7257
|
+
return SCHEMA_TYPE[arg.type.toLowerCase()] ?? "string";
|
|
7258
|
+
}
|
|
7259
|
+
function propertyOf(arg) {
|
|
7260
|
+
const type = schemaTypeOf(arg);
|
|
7261
|
+
const enumValues = arg.choices.length > 0 ? arg.choices.map((choice) => choice) : void 0;
|
|
7262
|
+
return {
|
|
7263
|
+
type,
|
|
7264
|
+
...arg.help.trim() === "" ? {} : { description: arg.help.trim() },
|
|
7265
|
+
...enumValues === void 0 ? {} : { enum: enumValues },
|
|
7266
|
+
...arg.defaultValue === void 0 ? {} : { default: arg.defaultValue }
|
|
7267
|
+
};
|
|
7268
|
+
}
|
|
7269
|
+
function pluginToolParameters(command) {
|
|
7270
|
+
const properties = {};
|
|
7271
|
+
for (const arg of command.args) properties[arg.name] = propertyOf(arg);
|
|
7272
|
+
const required = command.args.filter((arg) => arg.required).map((arg) => arg.name);
|
|
7273
|
+
return {
|
|
7274
|
+
type: "object",
|
|
7275
|
+
properties,
|
|
7276
|
+
...required.length === 0 ? {} : { required },
|
|
7277
|
+
additionalProperties: false
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7280
|
+
function pluginToolDescription(command) {
|
|
7281
|
+
const described = command.description.trim();
|
|
7282
|
+
return described === "" ? `${command.plugin} ${command.name}(opencli 命令)` : described;
|
|
7283
|
+
}
|
|
7284
|
+
function pluginToolWrites(command) {
|
|
7285
|
+
return command.access !== "read";
|
|
7286
|
+
}
|
|
7287
|
+
function valuesOf(raw) {
|
|
7288
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
7289
|
+
}
|
|
7290
|
+
function pluginToolArgv(command, raw) {
|
|
7291
|
+
const values = valuesOf(raw);
|
|
7292
|
+
const argv = [];
|
|
7293
|
+
for (const arg of command.args) {
|
|
7294
|
+
if (!arg.positional) continue;
|
|
7295
|
+
const value = values[arg.name];
|
|
7296
|
+
if (value === void 0 || value === null) continue;
|
|
7297
|
+
argv.push(String(value));
|
|
7298
|
+
}
|
|
7299
|
+
for (const arg of command.args) {
|
|
7300
|
+
if (arg.positional) continue;
|
|
7301
|
+
const value = values[arg.name];
|
|
7302
|
+
if (value === void 0 || value === null) continue;
|
|
7303
|
+
if (typeof value === "boolean") argv.push(`--${arg.name}`, value ? "true" : "false");
|
|
7304
|
+
else if (Array.isArray(value)) argv.push(`--${arg.name}`, value.map((item) => String(item)).join(","));
|
|
7305
|
+
else argv.push(`--${arg.name}`, String(value));
|
|
7306
|
+
}
|
|
7307
|
+
return argv;
|
|
7308
|
+
}
|
|
7309
|
+
function pluginKeyArgs(command, raw) {
|
|
7310
|
+
const values = valuesOf(raw);
|
|
7311
|
+
const ordered = [...command.args.filter((arg) => arg.required), ...command.args.filter((arg) => !arg.required)];
|
|
7312
|
+
const parts = [];
|
|
7313
|
+
for (const arg of ordered) {
|
|
7314
|
+
const value = values[arg.name];
|
|
7315
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
7316
|
+
parts.push(`${arg.name}=${String(value)}`);
|
|
7317
|
+
if (parts.length === KEY_ARG_LIMIT) break;
|
|
7318
|
+
}
|
|
7319
|
+
return parts.length === 0 ? "没有带参数" : parts.join("、");
|
|
7320
|
+
}
|
|
7321
|
+
function pluginApprovalQuestion(command, raw) {
|
|
7322
|
+
const key = pluginKeyArgs(command, raw);
|
|
7323
|
+
return {
|
|
7324
|
+
zh: `允许执行 ${command.plugin} 的 ${command.name}(${key})吗?`,
|
|
7325
|
+
en: `Run ${command.plugin} ${command.name} (${key})?`
|
|
7326
|
+
};
|
|
7327
|
+
}
|
|
7328
|
+
function pluginApprovalFor(command) {
|
|
7329
|
+
const denied = command.access === "read";
|
|
7330
|
+
return (call) => {
|
|
7331
|
+
if (denied) return { kind: "read" };
|
|
7332
|
+
const question = pluginApprovalQuestion(command, call.arguments);
|
|
7333
|
+
return {
|
|
7334
|
+
kind: "ask",
|
|
7335
|
+
reason: command.access === "write" ? `${command.plugin} ${command.name} 在 opencli 里标着 write` : `${command.plugin} ${command.name} 没有拿到 access(fail-safe:按写处理)`,
|
|
7336
|
+
question,
|
|
7337
|
+
fallback: question
|
|
7338
|
+
};
|
|
7339
|
+
};
|
|
7340
|
+
}
|
|
7341
|
+
//#endregion
|
|
7342
|
+
//#region src/shared/api/host/plugin-units.ts
|
|
7343
|
+
var TOOL_OUTPUT_SCHEMA = {
|
|
7344
|
+
type: "object",
|
|
7345
|
+
properties: {},
|
|
7346
|
+
additionalProperties: true
|
|
7347
|
+
};
|
|
7348
|
+
function signatureOf(version, commands) {
|
|
7349
|
+
return [version, ...commands.map((command) => `${command.name}:${command.access}:${command.args.map((arg) => `${arg.name}${arg.required ? "!" : ""}`).join("+")}`)].join("|");
|
|
7350
|
+
}
|
|
7351
|
+
function toolsOf(plugin, commands, taken) {
|
|
7352
|
+
const tools = [];
|
|
7353
|
+
const problems = [];
|
|
7354
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
7355
|
+
for (const command of commands) {
|
|
7356
|
+
const name = pluginToolName(plugin, command.name);
|
|
7357
|
+
if (taken.has(name) || claimed.has(name)) {
|
|
7358
|
+
problems.push(`工具名 ${name}(${plugin} ${command.name})已经被占着 —— 这条命令这次不合成(先来的那份说了算)`);
|
|
7359
|
+
continue;
|
|
7360
|
+
}
|
|
7361
|
+
claimed.add(name);
|
|
7362
|
+
tools.push({
|
|
7363
|
+
name,
|
|
7364
|
+
command
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
return {
|
|
7368
|
+
tools,
|
|
7369
|
+
problems
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
function planOf(options, registered) {
|
|
7373
|
+
const sets = new Map(options.catalog.snapshot().sets.map((set) => [set.plugin, set]));
|
|
7374
|
+
const mine = new Set([...registered.values()].flatMap((item) => item.tools));
|
|
7375
|
+
const taken = new Set([...options.toolNames?.() ?? []].filter((name) => !mine.has(name)));
|
|
7376
|
+
const units = [];
|
|
7377
|
+
const rows = [];
|
|
7378
|
+
const problems = [];
|
|
7379
|
+
for (const row of options.rows()) {
|
|
7380
|
+
const plugin = row.name?.trim() ?? "";
|
|
7381
|
+
if (plugin === "") {
|
|
7382
|
+
problems.push(`面板上的插件行 ${row.id}(${row.pkg})没有 opencli 插件名 —— 认不出它,这次不给它合成工具`);
|
|
7383
|
+
continue;
|
|
7384
|
+
}
|
|
7385
|
+
const set = sets.get(plugin);
|
|
7386
|
+
if (set === void 0) {
|
|
7387
|
+
problems.push(`已装插件「${plugin}」在 opencli 的插件清单 / 命令面里找不到 —— 归属不了,这次不给它合成工具`);
|
|
7388
|
+
continue;
|
|
7389
|
+
}
|
|
7390
|
+
sets.delete(plugin);
|
|
7391
|
+
const declared = options.declared(plugin);
|
|
7392
|
+
const overlapped = set.commands.filter((command) => declared.has(command.name));
|
|
7393
|
+
if (overlapped.length > 0) problems.push(`「${plugin}」有 ${overlapped.length} 条命令由已装模块自己提供(${overlapped.map((command) => command.name).join("、")})—— 通用桥不重复合成`);
|
|
7394
|
+
const commands = set.commands.filter((command) => !declared.has(command.name));
|
|
7395
|
+
const built = toolsOf(plugin, commands, taken);
|
|
7396
|
+
problems.push(...built.problems);
|
|
7397
|
+
for (const tool of built.tools) taken.add(tool.name);
|
|
7398
|
+
if (built.tools.length === 0 && commands.length === set.commands.length) problems.push(`「${plugin}」的命令一条都没能合成工具`);
|
|
7399
|
+
rows.push({
|
|
7400
|
+
plugin,
|
|
7401
|
+
pkg: row.pkg,
|
|
7402
|
+
version: set.version,
|
|
7403
|
+
commands: set.commands.length,
|
|
7404
|
+
synthesized: built.tools.length,
|
|
7405
|
+
tools: built.tools.map((tool) => tool.name)
|
|
7406
|
+
});
|
|
7407
|
+
if (built.tools.length === 0) continue;
|
|
7408
|
+
units.push({
|
|
7409
|
+
id: row.id,
|
|
7410
|
+
plugin,
|
|
7411
|
+
pkg: row.pkg,
|
|
7412
|
+
version: set.version,
|
|
7413
|
+
signature: signatureOf(set.version, commands),
|
|
7414
|
+
commands,
|
|
7415
|
+
tools: built.tools
|
|
7416
|
+
});
|
|
7417
|
+
}
|
|
7418
|
+
for (const set of sets.values()) problems.push(`opencli 里装着「${set.plugin}」(${String(set.commands.length)} 条命令),但它不在插件台账里(不是从市场装的)—— 面板上没有它的开关,它的命令这次不进工具面`);
|
|
7419
|
+
return {
|
|
7420
|
+
units,
|
|
7421
|
+
rows,
|
|
7422
|
+
problems
|
|
7423
|
+
};
|
|
7424
|
+
}
|
|
7425
|
+
function faceOf(unit, opencliFor) {
|
|
7426
|
+
const byTool = new Map(unit.tools.map((tool) => [tool.name, tool.command]));
|
|
7427
|
+
const manifest = {
|
|
7428
|
+
id: unit.id,
|
|
7429
|
+
card: null,
|
|
7430
|
+
tools: unit.tools.map((tool) => ({
|
|
7431
|
+
name: tool.name,
|
|
7432
|
+
writes: pluginToolWrites(tool.command)
|
|
7433
|
+
})),
|
|
7434
|
+
host: { inject: [] }
|
|
7435
|
+
};
|
|
7436
|
+
return {
|
|
7437
|
+
unit: unit.id,
|
|
7438
|
+
manifest,
|
|
7439
|
+
mount(deps) {
|
|
7440
|
+
const tools = deps.ctx.get("tools");
|
|
7441
|
+
if (tools === void 0) {
|
|
7442
|
+
deps.logger?.info?.(`[dsh-workbench] 没有 tools 服务,跳过 ${unit.plugin} 的命令工具注册`);
|
|
7443
|
+
return {
|
|
7444
|
+
routes: {},
|
|
7445
|
+
registerTools: () => []
|
|
7446
|
+
};
|
|
7447
|
+
}
|
|
7448
|
+
const opencli = opencliFor(unit.plugin);
|
|
7449
|
+
const definitions = unit.tools.map((tool) => ({
|
|
7450
|
+
name: tool.name,
|
|
7451
|
+
description: pluginToolDescription(tool.command),
|
|
7452
|
+
parameters: pluginToolParameters(tool.command),
|
|
7453
|
+
output: {
|
|
7454
|
+
schema: TOOL_OUTPUT_SCHEMA,
|
|
7455
|
+
render: (_args, value) => textOutput(jsonText(value))
|
|
7456
|
+
},
|
|
7457
|
+
async execute(raw, exec) {
|
|
7458
|
+
const workspace = sessionWorkspace(exec);
|
|
7459
|
+
try {
|
|
7460
|
+
return (await opencli.json([tool.command.name, ...pluginToolArgv(tool.command, raw)], workspace === null ? {} : { workdir: workspace })).value;
|
|
7461
|
+
} catch (error) {
|
|
7462
|
+
throw new Error(pluginLoginHint(error, tool.command.domain), { cause: error });
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
}));
|
|
7466
|
+
return {
|
|
7467
|
+
routes: {},
|
|
7468
|
+
factsOf: () => [],
|
|
7469
|
+
registerTools: () => definitions.map((definition) => tools.register(definition)),
|
|
7470
|
+
needsApproval: (call) => {
|
|
7471
|
+
const command = call.name === void 0 ? void 0 : byTool.get(call.name);
|
|
7472
|
+
return command === void 0 ? void 0 : pluginApprovalFor(command)(call);
|
|
7473
|
+
}
|
|
7474
|
+
};
|
|
7475
|
+
}
|
|
7476
|
+
};
|
|
7477
|
+
}
|
|
7478
|
+
function reconcile(registered, units, options) {
|
|
7479
|
+
const wanted = new Map(units.map((unit) => [unit.id, unit]));
|
|
7480
|
+
for (const [id, item] of [...registered]) {
|
|
7481
|
+
const next = wanted.get(id);
|
|
7482
|
+
if (next !== void 0 && next.signature === item.signature) continue;
|
|
7483
|
+
item.drop();
|
|
7484
|
+
registered.delete(id);
|
|
7485
|
+
}
|
|
7486
|
+
for (const unit of units) {
|
|
7487
|
+
if (registered.has(unit.id)) continue;
|
|
7488
|
+
registered.set(unit.id, {
|
|
7489
|
+
signature: unit.signature,
|
|
7490
|
+
tools: unit.tools.map((tool) => tool.name),
|
|
7491
|
+
drop: options.registry.registerPlugin(faceOf(unit, options.opencliFor))
|
|
7492
|
+
});
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
function createPluginUnits(options) {
|
|
7496
|
+
const registered = /* @__PURE__ */ new Map();
|
|
7497
|
+
let report = {
|
|
7498
|
+
ok: false,
|
|
7499
|
+
at: 0,
|
|
7500
|
+
plugins: [],
|
|
7501
|
+
problems: ["还没探过 opencli 的插件命令面"]
|
|
7502
|
+
};
|
|
7503
|
+
let inflight;
|
|
7504
|
+
let lastTouch = 0;
|
|
7505
|
+
let warned = "";
|
|
7506
|
+
function warn(problems) {
|
|
7507
|
+
const text = [...problems].sort().join("\n");
|
|
7508
|
+
if (text === "" || text === warned) return;
|
|
7509
|
+
warned = text;
|
|
7510
|
+
options.logger?.warn?.(`[dsh-workbench] opencli 插件命令面:\n${text}`);
|
|
7511
|
+
}
|
|
7512
|
+
async function run(force) {
|
|
7513
|
+
const probed = await options.catalog.refresh(force ? { force: true } : {});
|
|
7514
|
+
if (!probed.ok) {
|
|
7515
|
+
const problems = [...probed.problems, "这次探测没成功:上面这些插件行的工具面维持在上一份(不是\"这些插件没有命令\")"];
|
|
7516
|
+
report = {
|
|
7517
|
+
ok: false,
|
|
7518
|
+
at: Date.now(),
|
|
7519
|
+
plugins: report.plugins,
|
|
7520
|
+
problems
|
|
7521
|
+
};
|
|
7522
|
+
warn(problems);
|
|
7523
|
+
return report;
|
|
7524
|
+
}
|
|
7525
|
+
const plan = planOf(options, registered);
|
|
7526
|
+
reconcile(registered, plan.units, options);
|
|
7527
|
+
report = {
|
|
7528
|
+
ok: true,
|
|
7529
|
+
at: Date.now(),
|
|
7530
|
+
plugins: plan.rows,
|
|
7531
|
+
problems: plan.problems
|
|
7532
|
+
};
|
|
7533
|
+
warn(plan.problems);
|
|
7534
|
+
return report;
|
|
7535
|
+
}
|
|
7536
|
+
function refresh(callOptions = {}) {
|
|
7537
|
+
if (inflight !== void 0) return inflight;
|
|
7538
|
+
const task = run(callOptions.force === true).finally(() => {
|
|
7539
|
+
inflight = void 0;
|
|
7540
|
+
});
|
|
7541
|
+
inflight = task;
|
|
7542
|
+
return task;
|
|
7543
|
+
}
|
|
7544
|
+
return {
|
|
7545
|
+
refresh,
|
|
7546
|
+
touch() {
|
|
7547
|
+
const now = Date.now();
|
|
7548
|
+
if (inflight !== void 0) return;
|
|
7549
|
+
if (now - lastTouch < 3e4) return;
|
|
7550
|
+
lastTouch = now;
|
|
7551
|
+
refresh().catch((error) => {
|
|
7552
|
+
options.logger?.warn?.(`[dsh-workbench] 探 opencli 插件命令面时抛了错:${String(error)}`);
|
|
7553
|
+
});
|
|
7554
|
+
},
|
|
7555
|
+
report: () => report,
|
|
7556
|
+
dispose() {
|
|
7557
|
+
for (const item of registered.values()) item.drop();
|
|
7558
|
+
registered.clear();
|
|
7559
|
+
}
|
|
7560
|
+
};
|
|
7561
|
+
}
|
|
7562
|
+
//#endregion
|
|
6572
7563
|
//#region src/shared/api/host/tool-scope.ts
|
|
6573
7564
|
function agentOf(value) {
|
|
6574
7565
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -6755,6 +7746,23 @@ function apply(ctx) {
|
|
|
6755
7746
|
deniedTools: (workspace) => deniedNow(workspace),
|
|
6756
7747
|
logger: ctx.logger
|
|
6757
7748
|
});
|
|
7749
|
+
const pluginTools = shell === void 0 ? void 0 : createPluginUnits({
|
|
7750
|
+
registry: units,
|
|
7751
|
+
catalog: createPluginCatalog({
|
|
7752
|
+
shell,
|
|
7753
|
+
command: opencli.command
|
|
7754
|
+
}),
|
|
7755
|
+
rows: () => ctx.get("workbenchPanels")?.listPlugins(null) ?? [],
|
|
7756
|
+
declared: (plugin) => declaredPluginCommands(profileDir).get(plugin) ?? /* @__PURE__ */ new Set(),
|
|
7757
|
+
opencliFor: (plugin) => createOpencli({
|
|
7758
|
+
shell,
|
|
7759
|
+
plugin,
|
|
7760
|
+
command: opencli.command,
|
|
7761
|
+
logger: ctx.logger
|
|
7762
|
+
}),
|
|
7763
|
+
toolNames: () => new Set(unitsOfTool().keys()),
|
|
7764
|
+
logger: ctx.logger
|
|
7765
|
+
});
|
|
6758
7766
|
const mounted = /* @__PURE__ */ new Map();
|
|
6759
7767
|
const mountFailures = /* @__PURE__ */ new Map();
|
|
6760
7768
|
const factsOfMap = /* @__PURE__ */ new Map();
|
|
@@ -6908,6 +7916,23 @@ function apply(ctx) {
|
|
|
6908
7916
|
});
|
|
6909
7917
|
}
|
|
6910
7918
|
sync();
|
|
7919
|
+
if (pluginTools !== void 0) {
|
|
7920
|
+
const health = routes.health;
|
|
7921
|
+
routes.health = async (search) => ({
|
|
7922
|
+
...await health?.(search),
|
|
7923
|
+
pluginTools: pluginTools.report()
|
|
7924
|
+
});
|
|
7925
|
+
ctx.effect(() => {
|
|
7926
|
+
pluginTools.refresh().catch((error) => {
|
|
7927
|
+
warn(ctx, `探 opencli 插件的命令面时抛了错:${String(error)}`);
|
|
7928
|
+
});
|
|
7929
|
+
return () => pluginTools.dispose();
|
|
7930
|
+
}, `${PLUGIN_ID}: 已装且上板的 opencli 插件各合成一个单元(工具面照旧由面板那道闸收窄)`);
|
|
7931
|
+
ctx.effect(() => {
|
|
7932
|
+
const off = ctx.get(PANELS_SERVICE)?.subscribe?.(() => pluginTools.touch());
|
|
7933
|
+
return () => off?.();
|
|
7934
|
+
}, `${PLUGIN_ID}: 面板配置一动就重探插件命令面(装上 / 收起都在那一条链上)`);
|
|
7935
|
+
}
|
|
6911
7936
|
ctx.effect(() => () => {
|
|
6912
7937
|
for (const id of [...mounted.keys()]) unmountUnit(id);
|
|
6913
7938
|
for (const dispose of undoPost) dispose?.();
|
|
@@ -6937,11 +7962,15 @@ function apply(ctx) {
|
|
|
6937
7962
|
return () => off?.();
|
|
6938
7963
|
}, `${PLUGIN_ID}: 事实归属(读工具与写工具同一处声明)`);
|
|
6939
7964
|
ctx.effect(() => {
|
|
6940
|
-
const off = ctx.on?.("agent/created", (payload) =>
|
|
7965
|
+
const off = ctx.on?.("agent/created", (payload) => {
|
|
7966
|
+
toolScope.apply(payload?.agent);
|
|
7967
|
+
pluginTools?.touch();
|
|
7968
|
+
});
|
|
6941
7969
|
return () => off?.();
|
|
6942
7970
|
}, `${PLUGIN_ID}: agent 的工具面按工作区收窄(创建时)`);
|
|
6943
7971
|
ctx.effect(() => {
|
|
6944
7972
|
const off = ctx.on?.("system-prompt/assemble", async (_assembly, context, next) => {
|
|
7973
|
+
pluginTools?.touch();
|
|
6945
7974
|
const resolved = await next();
|
|
6946
7975
|
return toolScope.narrow(resolved, context?.scope);
|
|
6947
7976
|
});
|