@dsh-workbench/core 0.2.10 → 0.2.11
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 +942 -211
- package/dist/board.css +0 -12
- package/dist/client/client.body.js +942 -211
- package/dist/client/index.js +943 -212
- package/dist/host/index.js +2902 -859
- package/dist/types/app/client.d.ts +42 -6
- package/dist/types/app/layouts/rightbar/board-plan.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/kit-preview-controls.d.ts +10 -0
- package/dist/types/app/layouts/rightbar/kit-preview-frame.d.ts +14 -0
- package/dist/types/app/layouts/rightbar/kit-preview-rows.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/kit-preview.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-row-icon.d.ts +1 -1
- package/dist/types/app/layouts/rightbar/market-section-icon.d.ts +1 -1
- package/dist/types/app/layouts/sidebar/workspace-groups.d.ts +0 -1
- package/dist/types/app/layouts/sidebar/workspace-items.d.ts +2 -3
- package/dist/types/modules/panels/__tests__/diy-harness.d.ts +32 -0
- package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +1 -1
- package/dist/types/modules/panels/__tests__/scope-support.d.ts +1 -1
- package/dist/types/modules/panels/model/diy-act.d.ts +4 -0
- package/dist/types/modules/panels/model/diy-act.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-browser-act.d.ts +47 -0
- package/dist/types/modules/panels/model/diy-browser.d.ts +47 -0
- package/dist/types/modules/panels/model/diy-browser.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-context.d.ts +102 -0
- package/dist/types/modules/panels/model/diy-context.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-logs.d.ts +18 -0
- package/dist/types/modules/panels/model/diy-logs.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-look.d.ts +14 -0
- package/dist/types/modules/panels/model/diy-look.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-net.d.ts +3 -0
- package/dist/types/modules/panels/model/diy-net.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-page.d.ts +15 -0
- package/dist/types/modules/panels/model/diy-page.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-schema.d.ts +9 -0
- package/dist/types/modules/panels/model/diy-schema.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-scripts.d.ts +64 -0
- package/dist/types/modules/panels/unit.d.ts +43 -3
- package/dist/types/shared/api/diy.d.ts +4 -0
- package/dist/types/shared/api/host/__tests__/png-fixtures.d.ts +17 -0
- package/dist/types/shared/api/host/mount-units.d.ts +6 -0
- package/dist/types/shared/api/host/png-crop.d.ts +21 -0
- package/dist/types/shared/api/host/png-crop.test.d.ts +1 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/BUILD.md +4 -3
- package/skills/workbench-module-diy/DATASOURCE.md +4 -4
- package/skills/workbench-module-diy/DESIGN.md +1 -1
- package/skills/workbench-module-diy/KIT.md +121 -0
- package/skills/workbench-module-diy/PANEL.md +12 -2
- package/skills/workbench-module-diy/REVIEW.md +12 -6
- package/skills/workbench-module-diy/SKILL.md +7 -6
- package/skills/workbench-module-diy/TOOLING.md +5 -4
- package/skills/workbench-module-diy/TOOLS.md +3 -3
- package/skills/workbench-module-diy/WIRING.md +6 -6
- package/dist/types/app/layouts/sidebar/WorkspaceSearchField.d.ts +0 -9
package/dist/host/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { deflateSync, inflateSync } from "node:zlib";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -247,7 +248,7 @@ var PHASES = [
|
|
|
247
248
|
function asRecord$10(value) {
|
|
248
249
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
249
250
|
}
|
|
250
|
-
function textOf$
|
|
251
|
+
function textOf$9(value, max) {
|
|
251
252
|
if (typeof value !== "string") return "";
|
|
252
253
|
const trimmed = value.trim();
|
|
253
254
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -260,8 +261,8 @@ function atOf(value) {
|
|
|
260
261
|
function errorOf(raw) {
|
|
261
262
|
const record = asRecord$10(raw);
|
|
262
263
|
if (record === void 0) return void 0;
|
|
263
|
-
const code = textOf$
|
|
264
|
-
const message = textOf$
|
|
264
|
+
const code = textOf$9(record["code"], 120);
|
|
265
|
+
const message = textOf$9(record["message"], PANEL_STATE_MAX_TEXT);
|
|
265
266
|
if (code === "" && message === "") return void 0;
|
|
266
267
|
return {
|
|
267
268
|
code: code === "" ? "unknown" : code,
|
|
@@ -274,7 +275,7 @@ function parseCardsReport(payload) {
|
|
|
274
275
|
ok: false,
|
|
275
276
|
message: "请求体要是一个对象"
|
|
276
277
|
};
|
|
277
|
-
const workspace = textOf$
|
|
278
|
+
const workspace = textOf$9(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
278
279
|
if (workspace === "") return {
|
|
279
280
|
ok: false,
|
|
280
281
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -288,9 +289,9 @@ function parseCardsReport(payload) {
|
|
|
288
289
|
for (const item of raw.slice(0, 32)) {
|
|
289
290
|
const card = asRecord$10(item);
|
|
290
291
|
if (card === void 0) continue;
|
|
291
|
-
const unit = textOf$
|
|
292
|
-
const route = textOf$
|
|
293
|
-
const phase = textOf$
|
|
292
|
+
const unit = textOf$9(card["unit"], 200);
|
|
293
|
+
const route = textOf$9(card["route"], 400);
|
|
294
|
+
const phase = textOf$9(card["phase"], 20);
|
|
294
295
|
const at = atOf(card["at"]);
|
|
295
296
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
296
297
|
const error = errorOf(card["error"]);
|
|
@@ -320,9 +321,9 @@ function parseCrashReport(payload) {
|
|
|
320
321
|
ok: false,
|
|
321
322
|
message: "请求体要是一个对象"
|
|
322
323
|
};
|
|
323
|
-
const workspace = textOf$
|
|
324
|
-
const unit = textOf$
|
|
325
|
-
const reason = textOf$
|
|
324
|
+
const workspace = textOf$9(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
325
|
+
const unit = textOf$9(record["unit"], 200);
|
|
326
|
+
const reason = textOf$9(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
326
327
|
if (workspace === "") return {
|
|
327
328
|
ok: false,
|
|
328
329
|
message: "workspace 不能是空的"
|
|
@@ -568,20 +569,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
568
569
|
function asRecord$9(value) {
|
|
569
570
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
570
571
|
}
|
|
571
|
-
function textOf$
|
|
572
|
+
function textOf$8(value) {
|
|
572
573
|
return typeof value === "string" ? value.trim() : "";
|
|
573
574
|
}
|
|
574
575
|
function codeOf(page) {
|
|
575
576
|
const record = asRecord$9(page);
|
|
576
|
-
return record === void 0 ? "" : textOf$
|
|
577
|
+
return record === void 0 ? "" : textOf$8(record["code"]);
|
|
577
578
|
}
|
|
578
579
|
function messageOf$2(page) {
|
|
579
580
|
const record = asRecord$9(page);
|
|
580
|
-
return record === void 0 ? "" : textOf$
|
|
581
|
+
return record === void 0 ? "" : textOf$8(record["message"]);
|
|
581
582
|
}
|
|
582
583
|
function hintOf$1(page) {
|
|
583
584
|
const record = asRecord$9(page);
|
|
584
|
-
const hint = record === void 0 ? "" : textOf$
|
|
585
|
+
const hint = record === void 0 ? "" : textOf$8(record["hint"]);
|
|
585
586
|
return hint === "" ? void 0 : hint;
|
|
586
587
|
}
|
|
587
588
|
function pageFailed(page) {
|
|
@@ -812,7 +813,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
812
813
|
function asRecord$8(value) {
|
|
813
814
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
814
815
|
}
|
|
815
|
-
function textOf$
|
|
816
|
+
function textOf$7(value) {
|
|
816
817
|
return typeof value === "string" ? value.trim() : "";
|
|
817
818
|
}
|
|
818
819
|
function entriesOf(value) {
|
|
@@ -825,8 +826,8 @@ function entriesOf(value) {
|
|
|
825
826
|
if (item === void 0) continue;
|
|
826
827
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
827
828
|
entries.push({
|
|
828
|
-
text: textOf$
|
|
829
|
-
tag: textOf$
|
|
829
|
+
text: textOf$7(item["text"]),
|
|
830
|
+
tag: textOf$7(item["tag"]),
|
|
830
831
|
nth
|
|
831
832
|
});
|
|
832
833
|
}
|
|
@@ -835,7 +836,7 @@ function entriesOf(value) {
|
|
|
835
836
|
function reasonOf(stdout, stderr) {
|
|
836
837
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
837
838
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
838
|
-
const message = error === void 0 ? "" : textOf$
|
|
839
|
+
const message = error === void 0 ? "" : textOf$7(error["message"]);
|
|
839
840
|
if (message !== "") return message;
|
|
840
841
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
841
842
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -843,7 +844,7 @@ function reasonOf(stdout, stderr) {
|
|
|
843
844
|
function hintOf(stdout) {
|
|
844
845
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
845
846
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
846
|
-
const hint = error === void 0 ? "" : textOf$
|
|
847
|
+
const hint = error === void 0 ? "" : textOf$7(error["hint"]);
|
|
847
848
|
return hint === "" ? void 0 : hint;
|
|
848
849
|
}
|
|
849
850
|
function sameOrigin(left, right) {
|
|
@@ -857,7 +858,7 @@ function urlOf(stdout) {
|
|
|
857
858
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
858
859
|
if (bare.startsWith("{")) {
|
|
859
860
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
860
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
861
|
+
const url = parsed === void 0 ? "" : textOf$7(parsed["url"]);
|
|
861
862
|
if (url !== "") return url;
|
|
862
863
|
}
|
|
863
864
|
return bare;
|
|
@@ -1108,7 +1109,7 @@ var TARGET_CODE = {
|
|
|
1108
1109
|
function asRecord$7(value) {
|
|
1109
1110
|
return typeof value === "object" && value !== null ? value : {};
|
|
1110
1111
|
}
|
|
1111
|
-
function stringsOf$
|
|
1112
|
+
function stringsOf$4(value) {
|
|
1112
1113
|
if (!Array.isArray(value)) return void 0;
|
|
1113
1114
|
const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
1114
1115
|
return items.length === 0 ? void 0 : items;
|
|
@@ -1164,7 +1165,7 @@ function parseProvisionBody(body) {
|
|
|
1164
1165
|
request: requestOf$1(wanted, value.trim())
|
|
1165
1166
|
};
|
|
1166
1167
|
const registry = typeof raw.registry === "string" && raw.registry.trim() !== "" ? raw.registry.trim() : void 0;
|
|
1167
|
-
const approvedBuilds = stringsOf$
|
|
1168
|
+
const approvedBuilds = stringsOf$4(raw.approvedBuilds);
|
|
1168
1169
|
const requestId = typeof raw.requestId === "string" && raw.requestId.trim() !== "" ? raw.requestId.trim() : void 0;
|
|
1169
1170
|
return {
|
|
1170
1171
|
ok: true,
|
|
@@ -1872,7 +1873,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1872
1873
|
function ledgerKeyOf(entry) {
|
|
1873
1874
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1874
1875
|
}
|
|
1875
|
-
function textOf$
|
|
1876
|
+
function textOf$6(value) {
|
|
1876
1877
|
if (typeof value !== "string") return void 0;
|
|
1877
1878
|
const trimmed = value.trim();
|
|
1878
1879
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1880,13 +1881,13 @@ function textOf$4(value) {
|
|
|
1880
1881
|
function entryOf(value) {
|
|
1881
1882
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1882
1883
|
const box = value;
|
|
1883
|
-
const opencliHome = textOf$
|
|
1884
|
-
const pkg = textOf$
|
|
1885
|
-
const installedAt = textOf$
|
|
1884
|
+
const opencliHome = textOf$6(box["opencliHome"]);
|
|
1885
|
+
const pkg = textOf$6(box["package"]);
|
|
1886
|
+
const installedAt = textOf$6(box["installedAt"]);
|
|
1886
1887
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1887
|
-
const name = textOf$
|
|
1888
|
-
const registry = textOf$
|
|
1889
|
-
const releasedAt = textOf$
|
|
1888
|
+
const name = textOf$6(box["name"]);
|
|
1889
|
+
const registry = textOf$6(box["registry"]);
|
|
1890
|
+
const releasedAt = textOf$6(box["releasedAt"]);
|
|
1890
1891
|
return {
|
|
1891
1892
|
opencliHome,
|
|
1892
1893
|
package: pkg,
|
|
@@ -2107,7 +2108,7 @@ async function approvalFor(call, context = {}) {
|
|
|
2107
2108
|
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf$1(error)}`
|
|
2108
2109
|
};
|
|
2109
2110
|
}
|
|
2110
|
-
if (verdict?.kind === "read") return void 0;
|
|
2111
|
+
if (verdict?.kind === "read" || verdict?.kind === "allow") return void 0;
|
|
2111
2112
|
if (verdict?.kind !== "ask") return {
|
|
2112
2113
|
kind: "deny",
|
|
2113
2114
|
reason: `${call.name ?? "这把工具"} 在清单里声明会写东西,但它的套件没认领这次调用 —— 停下:在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去`
|
|
@@ -2497,7 +2498,7 @@ function profileDirOf(ctx) {
|
|
|
2497
2498
|
const dir = typeof profile?.dir === "string" ? profile.dir.trim() : "";
|
|
2498
2499
|
return dir === "" ? null : dir;
|
|
2499
2500
|
}
|
|
2500
|
-
function stringsOf$
|
|
2501
|
+
function stringsOf$3(value) {
|
|
2501
2502
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2502
2503
|
}
|
|
2503
2504
|
function profileManifestOf(profileDir) {
|
|
@@ -2511,7 +2512,7 @@ function profileManifestOf(profileDir) {
|
|
|
2511
2512
|
function profileModules(profileDir) {
|
|
2512
2513
|
const record = profileManifestOf(profileDir);
|
|
2513
2514
|
if (record === void 0) return [];
|
|
2514
|
-
const bundles = stringsOf$
|
|
2515
|
+
const bundles = stringsOf$3(asRecord$5(asRecord$5(record.dsh)?.profile)?.bundles);
|
|
2515
2516
|
const dependencies = asRecord$5(record.dependencies) ?? {};
|
|
2516
2517
|
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
2517
2518
|
const found = [];
|
|
@@ -2661,7 +2662,7 @@ function readProfilePackage(profileDir, pkg) {
|
|
|
2661
2662
|
//#endregion
|
|
2662
2663
|
//#region src/shared/api/host/profile-registries.ts
|
|
2663
2664
|
var REGISTRY_MARKER = "# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)";
|
|
2664
|
-
function scopeOf(pkg) {
|
|
2665
|
+
function scopeOf$1(pkg) {
|
|
2665
2666
|
const slash = pkg.indexOf("/");
|
|
2666
2667
|
return pkg.startsWith("@") && slash > 1 ? pkg.slice(0, slash) : null;
|
|
2667
2668
|
}
|
|
@@ -2701,7 +2702,7 @@ function sourcesFromLockfile(text) {
|
|
|
2701
2702
|
if (name === null) continue;
|
|
2702
2703
|
const tarball = TARBALL.exec(line);
|
|
2703
2704
|
if (tarball === null) continue;
|
|
2704
|
-
const scope = scopeOf(name);
|
|
2705
|
+
const scope = scopeOf$1(name);
|
|
2705
2706
|
const root = registryRootOf(name, tarball[1] ?? "");
|
|
2706
2707
|
name = null;
|
|
2707
2708
|
if (scope === null || root === null) continue;
|
|
@@ -2832,26 +2833,121 @@ function inWorkshopSession(ctx, workspace) {
|
|
|
2832
2833
|
}
|
|
2833
2834
|
//#endregion
|
|
2834
2835
|
//#region src/modules/panels/unit.ts
|
|
2835
|
-
var
|
|
2836
|
-
var
|
|
2837
|
-
var
|
|
2836
|
+
var PANELS_LIST_TOOL = "workbench_panels_list";
|
|
2837
|
+
var PANELS_SHOW_TOOL = "workbench_panels_show";
|
|
2838
|
+
var PANELS_HIDE_TOOL = "workbench_panels_hide";
|
|
2839
|
+
var PANELS_MOVE_TOOL = "workbench_panels_move";
|
|
2840
|
+
var PANELS_RESET_TOOL = "workbench_panels_reset";
|
|
2841
|
+
var PANELS_FOLLOW_TOOL = "workbench_panels_follow";
|
|
2842
|
+
var MODULES_LIST_TOOL = "workbench_modules_list";
|
|
2843
|
+
var MODULES_INSTALL_TOOL = "workbench_modules_install";
|
|
2844
|
+
var MODULES_UPDATE_TOOL = "workbench_modules_update";
|
|
2845
|
+
var MODULES_UNINSTALL_TOOL = "workbench_modules_uninstall";
|
|
2846
|
+
var MODULES_INSTALL_BUNDLE_TOOL = "workbench_modules_install_bundle";
|
|
2847
|
+
var MODULES_UPDATE_DEPENDENCY_TOOL = "workbench_modules_update_dependency";
|
|
2848
|
+
var MODULES_UPDATE_PLUGIN_TOOL = "workbench_modules_update_plugin";
|
|
2849
|
+
var MODULES_UNINSTALL_PLUGIN_TOOL = "workbench_modules_uninstall_plugin";
|
|
2850
|
+
var MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
|
|
2851
|
+
var DIY_LIST_TOOL = "workbench_diy_list";
|
|
2852
|
+
var DIY_STATUS_TOOL = "workbench_diy_status";
|
|
2853
|
+
var DIY_PANEL_TOOL = "workbench_diy_panel";
|
|
2854
|
+
var DIY_RECON_TOOL = "workbench_diy_recon";
|
|
2855
|
+
var DIY_PROBE_TOOL = "workbench_diy_probe";
|
|
2856
|
+
var DIY_LOOK_TOOL = "workbench_diy_look";
|
|
2857
|
+
var DIY_INSTALL_TOOL = "workbench_diy_install";
|
|
2858
|
+
var DIY_SHOW_TOOL = "workbench_diy_show";
|
|
2859
|
+
var DIY_CLICK_TOOL = "workbench_diy_click";
|
|
2860
|
+
var DIY_TYPE_TOOL = "workbench_diy_type";
|
|
2861
|
+
var DIY_FILL_TOOL = "workbench_diy_fill";
|
|
2862
|
+
var DIY_KEYS_TOOL = "workbench_diy_keys";
|
|
2863
|
+
var DIY_HOVER_TOOL = "workbench_diy_hover";
|
|
2864
|
+
var DIY_SELECT_TOOL = "workbench_diy_select";
|
|
2865
|
+
var DIY_SCROLL_TOOL = "workbench_diy_scroll";
|
|
2866
|
+
var DIY_RELOAD_TOOL = "workbench_diy_reload";
|
|
2867
|
+
var DIY_VIEWPORT_TOOL = "workbench_diy_viewport";
|
|
2868
|
+
var DIY_NET_WATCH_TOOL = "workbench_diy_net_watch";
|
|
2869
|
+
var DIY_NET_READ_TOOL = "workbench_diy_net_read";
|
|
2870
|
+
var DIY_NET_STOP_TOOL = "workbench_diy_net_stop";
|
|
2871
|
+
var DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
|
|
2872
|
+
var DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
|
|
2873
|
+
var DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
|
|
2874
|
+
var READ_TOOLS = [
|
|
2875
|
+
PANELS_LIST_TOOL,
|
|
2876
|
+
MODULES_LIST_TOOL,
|
|
2877
|
+
DIY_LIST_TOOL,
|
|
2878
|
+
DIY_STATUS_TOOL,
|
|
2879
|
+
DIY_PANEL_TOOL,
|
|
2880
|
+
DIY_RECON_TOOL,
|
|
2881
|
+
DIY_PROBE_TOOL,
|
|
2882
|
+
DIY_LOOK_TOOL,
|
|
2883
|
+
DIY_CLICK_TOOL,
|
|
2884
|
+
DIY_TYPE_TOOL,
|
|
2885
|
+
DIY_FILL_TOOL,
|
|
2886
|
+
DIY_KEYS_TOOL,
|
|
2887
|
+
DIY_HOVER_TOOL,
|
|
2888
|
+
DIY_SELECT_TOOL,
|
|
2889
|
+
DIY_SCROLL_TOOL,
|
|
2890
|
+
DIY_RELOAD_TOOL,
|
|
2891
|
+
DIY_VIEWPORT_TOOL,
|
|
2892
|
+
DIY_NET_WATCH_TOOL,
|
|
2893
|
+
DIY_NET_READ_TOOL,
|
|
2894
|
+
DIY_NET_STOP_TOOL,
|
|
2895
|
+
DIY_LOGS_WATCH_TOOL,
|
|
2896
|
+
DIY_LOGS_READ_TOOL,
|
|
2897
|
+
DIY_LOGS_STOP_TOOL
|
|
2898
|
+
];
|
|
2899
|
+
var WRITE_TOOLS = [
|
|
2900
|
+
PANELS_SHOW_TOOL,
|
|
2901
|
+
PANELS_HIDE_TOOL,
|
|
2902
|
+
PANELS_MOVE_TOOL,
|
|
2903
|
+
PANELS_RESET_TOOL,
|
|
2904
|
+
PANELS_FOLLOW_TOOL,
|
|
2905
|
+
MODULES_INSTALL_TOOL,
|
|
2906
|
+
MODULES_UPDATE_TOOL,
|
|
2907
|
+
MODULES_UNINSTALL_TOOL,
|
|
2908
|
+
MODULES_INSTALL_BUNDLE_TOOL,
|
|
2909
|
+
MODULES_UPDATE_DEPENDENCY_TOOL,
|
|
2910
|
+
MODULES_UPDATE_PLUGIN_TOOL,
|
|
2911
|
+
MODULES_UNINSTALL_PLUGIN_TOOL,
|
|
2912
|
+
MODULES_UPDATE_WORKBENCH_TOOL,
|
|
2913
|
+
DIY_INSTALL_TOOL,
|
|
2914
|
+
DIY_SHOW_TOOL
|
|
2915
|
+
];
|
|
2916
|
+
var DIY_TOOLS = [
|
|
2917
|
+
DIY_LIST_TOOL,
|
|
2918
|
+
DIY_STATUS_TOOL,
|
|
2919
|
+
DIY_PANEL_TOOL,
|
|
2920
|
+
DIY_RECON_TOOL,
|
|
2921
|
+
DIY_PROBE_TOOL,
|
|
2922
|
+
DIY_LOOK_TOOL,
|
|
2923
|
+
DIY_INSTALL_TOOL,
|
|
2924
|
+
DIY_SHOW_TOOL,
|
|
2925
|
+
DIY_CLICK_TOOL,
|
|
2926
|
+
DIY_TYPE_TOOL,
|
|
2927
|
+
DIY_FILL_TOOL,
|
|
2928
|
+
DIY_KEYS_TOOL,
|
|
2929
|
+
DIY_HOVER_TOOL,
|
|
2930
|
+
DIY_SELECT_TOOL,
|
|
2931
|
+
DIY_SCROLL_TOOL,
|
|
2932
|
+
DIY_RELOAD_TOOL,
|
|
2933
|
+
DIY_VIEWPORT_TOOL,
|
|
2934
|
+
DIY_NET_WATCH_TOOL,
|
|
2935
|
+
DIY_NET_READ_TOOL,
|
|
2936
|
+
DIY_NET_STOP_TOOL,
|
|
2937
|
+
DIY_LOGS_WATCH_TOOL,
|
|
2938
|
+
DIY_LOGS_READ_TOOL,
|
|
2939
|
+
DIY_LOGS_STOP_TOOL
|
|
2940
|
+
];
|
|
2838
2941
|
var panelsUnit = {
|
|
2839
2942
|
id: "panels",
|
|
2840
2943
|
card: null,
|
|
2841
|
-
tools: [
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
writes: true
|
|
2849
|
-
},
|
|
2850
|
-
{
|
|
2851
|
-
name: DIY_TOOL,
|
|
2852
|
-
writes: true
|
|
2853
|
-
}
|
|
2854
|
-
],
|
|
2944
|
+
tools: [...READ_TOOLS.map((name) => ({
|
|
2945
|
+
name,
|
|
2946
|
+
writes: false
|
|
2947
|
+
})), ...WRITE_TOOLS.map((name) => ({
|
|
2948
|
+
name,
|
|
2949
|
+
writes: true
|
|
2950
|
+
}))],
|
|
2855
2951
|
host: {
|
|
2856
2952
|
inject: [],
|
|
2857
2953
|
optional: ["tools", "shell"]
|
|
@@ -2859,14 +2955,6 @@ var panelsUnit = {
|
|
|
2859
2955
|
};
|
|
2860
2956
|
//#endregion
|
|
2861
2957
|
//#region src/modules/panels/model/approval.ts
|
|
2862
|
-
var READ = { kind: "read" };
|
|
2863
|
-
var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
2864
|
-
"list",
|
|
2865
|
-
"status",
|
|
2866
|
-
"probe",
|
|
2867
|
-
"recon",
|
|
2868
|
-
""
|
|
2869
|
-
]);
|
|
2870
2958
|
var SPEC_LIMIT = 80;
|
|
2871
2959
|
function missing(id) {
|
|
2872
2960
|
return id === void 0 || id === "";
|
|
@@ -2874,6 +2962,12 @@ function missing(id) {
|
|
|
2874
2962
|
function pad(token) {
|
|
2875
2963
|
return token.startsWith("[") ? ` ${token} ` : token;
|
|
2876
2964
|
}
|
|
2965
|
+
function pair(zh, en) {
|
|
2966
|
+
return {
|
|
2967
|
+
zh,
|
|
2968
|
+
en
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2877
2971
|
function workbenchModule(id) {
|
|
2878
2972
|
return missing(id) ? {
|
|
2879
2973
|
zh: "工作台模块",
|
|
@@ -2883,12 +2977,6 @@ function workbenchModule(id) {
|
|
|
2883
2977
|
en: `the workbench [${id}] module`
|
|
2884
2978
|
};
|
|
2885
2979
|
}
|
|
2886
|
-
function anyWorkbenchModule(id) {
|
|
2887
|
-
return missing(id) ? {
|
|
2888
|
-
zh: "工作台的一个模块",
|
|
2889
|
-
en: "a workbench module"
|
|
2890
|
-
} : workbenchModule(id);
|
|
2891
|
-
}
|
|
2892
2980
|
function workbenchPlugin(id) {
|
|
2893
2981
|
return missing(id) ? {
|
|
2894
2982
|
zh: "工作台的 opencli 插件",
|
|
@@ -2916,18 +3004,24 @@ function workbenchPanelRow(id) {
|
|
|
2916
3004
|
en: `the workbench [${id}] module row`
|
|
2917
3005
|
};
|
|
2918
3006
|
}
|
|
2919
|
-
function
|
|
2920
|
-
const
|
|
2921
|
-
if (
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
return {
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
3007
|
+
function specOf(raw) {
|
|
3008
|
+
const spec = typeof raw["spec"] === "string" ? raw["spec"].replace(/\s+/g, " ").trim() : "";
|
|
3009
|
+
if (spec === "") return "";
|
|
3010
|
+
return spec.length > SPEC_LIMIT ? `${spec.slice(0, SPEC_LIMIT)}…` : spec;
|
|
3011
|
+
}
|
|
3012
|
+
function boardName(scope) {
|
|
3013
|
+
return scope === "machine" ? {
|
|
3014
|
+
zh: "机器级默认板",
|
|
3015
|
+
en: "the machine-level board"
|
|
3016
|
+
} : {
|
|
3017
|
+
zh: "当前项目的面板",
|
|
3018
|
+
en: "this project's panel"
|
|
2929
3019
|
};
|
|
2930
3020
|
}
|
|
3021
|
+
function argsOf$1(call) {
|
|
3022
|
+
const raw = call.arguments;
|
|
3023
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
3024
|
+
}
|
|
2931
3025
|
function askOf(reason, question, fallback) {
|
|
2932
3026
|
return {
|
|
2933
3027
|
kind: "ask",
|
|
@@ -2937,152 +3031,62 @@ function askOf(reason, question, fallback) {
|
|
|
2937
3031
|
};
|
|
2938
3032
|
}
|
|
2939
3033
|
var panelsApproval = (call) => {
|
|
2940
|
-
|
|
2941
|
-
const
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
en: `The agent wants to install the workbench module from [${spec}]. Allow?`,
|
|
2978
|
-
zh: `agent 想安装工作台模块 [${spec}],允许吗?`
|
|
2979
|
-
});
|
|
2980
|
-
if (action === "install") {
|
|
3034
|
+
if (call.name !== void 0 && DIY_TOOLS.includes(call.name)) return { kind: "allow" };
|
|
3035
|
+
const raw = argsOf$1(call);
|
|
3036
|
+
const text = (key) => {
|
|
3037
|
+
const value = raw[key];
|
|
3038
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3039
|
+
};
|
|
3040
|
+
const id = text("id") ?? text("unit");
|
|
3041
|
+
const plugin = text("plugin");
|
|
3042
|
+
const spec = specOf(raw);
|
|
3043
|
+
const scope = text("scope");
|
|
3044
|
+
const board = boardName(scope);
|
|
3045
|
+
switch (call.name) {
|
|
3046
|
+
case PANELS_SHOW_TOOL: {
|
|
3047
|
+
const row = workbenchPanelRow(id);
|
|
3048
|
+
return askOf(`${PANELS_SHOW_TOOL} ${id ?? ""} puts a workbench module row on this project's board`, pair(`会把${row.zh}加回${board.zh},允许吗?`, `it puts ${row.en} back on ${board.en}. Allow?`), pair(`agent 想把${row.zh}加到${board.zh},允许吗?`, `The agent wants to put ${row.en} on ${board.en}. Allow?`));
|
|
3049
|
+
}
|
|
3050
|
+
case PANELS_HIDE_TOOL: {
|
|
3051
|
+
const row = workbenchPanelRow(id);
|
|
3052
|
+
return askOf(`${PANELS_HIDE_TOOL} ${id ?? ""} takes a workbench module row off this project's board`, pair(`会把${row.zh}从${board.zh}上收起,允许吗?`, `it takes ${row.en} off ${board.en} — its card and its tools go too. Allow?`), pair(`agent 想把${row.zh}收起,允许吗?`, `The agent wants to take ${row.en} off the board. Allow?`));
|
|
3053
|
+
}
|
|
3054
|
+
case PANELS_MOVE_TOOL: {
|
|
3055
|
+
const row = workbenchPanelRow(id);
|
|
3056
|
+
return askOf(`${PANELS_MOVE_TOOL} ${id ?? ""} reorders the workbench panel`, pair(`会把${row.zh}移到${board.zh}最前面,允许吗?`, `it moves ${row.en} to the front of ${board.en}. Allow?`), pair(`agent 想把${row.zh}移到最前面,允许吗?`, `The agent wants to move ${row.en} to the front. Allow?`));
|
|
3057
|
+
}
|
|
3058
|
+
case PANELS_RESET_TOOL: return askOf(`${PANELS_RESET_TOOL} clears the rows this project changed on the workbench panel`, pair(scope === "machine" ? "会清空工作台那份机器级默认板,允许吗?" : "会清空工作台在当前项目的面板,允许吗?", `it clears the rows this project changed on the workbench ${board.en}. Allow?`), pair(scope === "machine" ? "agent 想清空工作台那份机器级默认板,允许吗?" : "agent 想清空工作台在当前项目的面板,允许吗?", `The agent wants to clear this project's rows on the workbench ${board.en}. Allow?`));
|
|
3059
|
+
case PANELS_FOLLOW_TOOL: {
|
|
3060
|
+
const row = workbenchPanelRow(id);
|
|
3061
|
+
return askOf(`${PANELS_FOLLOW_TOOL} ${id ?? ""} puts a workbench panel row back to following the machine-level board`, pair(`会把${row.zh}改回跟随机器级默认板,允许吗?`, `it puts ${row.en} back to following the machine-level board. Allow?`), pair(`agent 想把${row.zh}改回跟随机器级默认板,允许吗?`, `The agent wants to put ${row.en} back to following the machine-level board. Allow?`));
|
|
3062
|
+
}
|
|
3063
|
+
case MODULES_INSTALL_TOOL:
|
|
3064
|
+
case MODULES_UPDATE_TOOL: {
|
|
3065
|
+
const install = call.name === MODULES_INSTALL_TOOL;
|
|
3066
|
+
const verb = install ? "装" : "升";
|
|
3067
|
+
const verbEn = install ? "installs" : "upgrades";
|
|
3068
|
+
return askOf(`${call.name} ${id ?? ""} changes the opencli plugins and libraries a workbench module declares`, pair(`会${verb}${workbenchModule(id).zh}要的插件与库,不换模块包本身,允许吗?`, `it ${verbEn} the plugins the ${workbenchModule(id).en} declares, without replacing its package. Allow?`), pair(`agent 想${verb}${workbenchModule(id).zh}要的插件与库,允许吗?`, `The agent wants to ${install ? "install" : "upgrade"} the plugins ${workbenchModule(id).en} declares. Allow?`));
|
|
3069
|
+
}
|
|
3070
|
+
case MODULES_UNINSTALL_TOOL: {
|
|
2981
3071
|
const target = workbenchModule(id);
|
|
2982
|
-
return askOf(
|
|
2983
|
-
en: `it installs ${target.en}. Allow?`,
|
|
2984
|
-
zh: `会安装${target.zh},允许吗?`
|
|
2985
|
-
}, {
|
|
2986
|
-
en: `The agent wants to install ${target.en}. Allow?`,
|
|
2987
|
-
zh: `agent 想安装${target.zh},允许吗?`
|
|
2988
|
-
});
|
|
3072
|
+
return askOf(`${MODULES_UNINSTALL_TOOL} ${id ?? ""} removes a workbench module from this machine`, pair(`会把${target.zh}连它的包一起卸掉,允许吗?`, `it uninstalls ${target.en}, package included. Allow?`), pair(`agent 想卸载${target.zh},允许吗?`, `The agent wants to uninstall ${target.en}. Allow?`));
|
|
2989
3073
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
zh: `会更新${target.zh},允许吗?`
|
|
2995
|
-
}, {
|
|
2996
|
-
en: `The agent wants to update ${target.en}. Allow?`,
|
|
2997
|
-
zh: `agent 想更新${target.zh},允许吗?`
|
|
2998
|
-
});
|
|
3074
|
+
case MODULES_INSTALL_BUNDLE_TOOL: return askOf(`${MODULES_INSTALL_BUNDLE_TOOL} ${spec} installs a module package into this profile`, pair(`会把 [${spec}] 装成工作台的模块包,允许吗?`, `it installs [${spec}] as a workbench module package. Allow?`), pair(`agent 想装工作台的模块包 [${spec}],允许吗?`, `The agent wants to install the workbench module package [${spec}]. Allow?`));
|
|
3075
|
+
case MODULES_UPDATE_DEPENDENCY_TOOL: {
|
|
3076
|
+
const target = workbenchDependency(id);
|
|
3077
|
+
return askOf(`${MODULES_UPDATE_DEPENDENCY_TOOL} ${id ?? ""} upgrades a shared dependency on this machine`, pair(`会升级${target.zh},允许吗?`, `it upgrades ${target.en}. Allow?`), pair(`agent 想升级${target.zh},允许吗?`, `The agent wants to upgrade ${target.en}. Allow?`));
|
|
2999
3078
|
}
|
|
3000
|
-
|
|
3001
|
-
const target =
|
|
3002
|
-
return askOf(
|
|
3003
|
-
en: `it uninstalls ${target.en}. Allow?`,
|
|
3004
|
-
zh: `会卸载${target.zh},允许吗?`
|
|
3005
|
-
}, {
|
|
3006
|
-
en: `The agent wants to uninstall ${target.en}. Allow?`,
|
|
3007
|
-
zh: `agent 想卸载${target.zh},允许吗?`
|
|
3008
|
-
});
|
|
3079
|
+
case MODULES_UPDATE_PLUGIN_TOOL: {
|
|
3080
|
+
const target = workbenchPlugin(plugin);
|
|
3081
|
+
return askOf(`${MODULES_UPDATE_PLUGIN_TOOL} ${plugin ?? ""} updates an opencli plugin on this machine`, pair(`会更新${target.zh},允许吗?`, `it updates ${target.en}. Allow?`), pair(`agent 想更新${target.zh},允许吗?`, `The agent wants to update ${target.en}. Allow?`));
|
|
3009
3082
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
}, {
|
|
3014
|
-
en: "The agent wants to change the plugins installed in the workbench. Allow?",
|
|
3015
|
-
zh: "agent 想改动工作台装的插件,允许吗?"
|
|
3016
|
-
});
|
|
3017
|
-
}
|
|
3018
|
-
if (call.name === "workbench_diy") {
|
|
3019
|
-
if (readAction) return READ;
|
|
3020
|
-
if (action === "open") {
|
|
3021
|
-
const target = anyWorkbenchModule(id);
|
|
3022
|
-
return askOf("workbench_diy open puts an already-installed module on this project panel", {
|
|
3023
|
-
en: `it shows ${target.en} on this project's panel. Allow?`,
|
|
3024
|
-
zh: `会把${target.zh}显示在当前项目的面板上,允许吗?`
|
|
3025
|
-
}, {
|
|
3026
|
-
en: `The agent wants to show ${target.en} on this project's panel. Allow?`,
|
|
3027
|
-
zh: `agent 想把${target.zh}显示在当前项目的面板上,允许吗?`
|
|
3028
|
-
});
|
|
3083
|
+
case MODULES_UNINSTALL_PLUGIN_TOOL: {
|
|
3084
|
+
const target = workbenchPlugin(plugin);
|
|
3085
|
+
return askOf(`${MODULES_UNINSTALL_PLUGIN_TOOL} ${plugin ?? ""} uninstalls an opencli plugin on this machine`, pair(`会卸载${target.zh},允许吗?`, `it uninstalls ${target.en}. Allow?`), pair(`agent 想卸载${target.zh},允许吗?`, `The agent wants to uninstall ${target.en}. Allow?`));
|
|
3029
3086
|
}
|
|
3030
|
-
|
|
3031
|
-
return
|
|
3032
|
-
en: `it installs ${target.en} and opens it on this project's panel. Allow?`,
|
|
3033
|
-
zh: `会安装${target.zh},并上到当前项目的面板,允许吗?`
|
|
3034
|
-
}, {
|
|
3035
|
-
en: `The agent wants to install ${target.en} and open it on this project's panel. Allow?`,
|
|
3036
|
-
zh: `agent 想安装${target.zh},并上到当前项目的面板,允许吗?`
|
|
3037
|
-
});
|
|
3038
|
-
}
|
|
3039
|
-
if (call.name !== "workbench_panels") return void 0;
|
|
3040
|
-
if (readAction) return READ;
|
|
3041
|
-
if (action === "hide") {
|
|
3042
|
-
const target = anyWorkbenchModule(id);
|
|
3043
|
-
return askOf("workbench_panels hide removes a module from the panel", {
|
|
3044
|
-
en: `it takes ${target.en} off this project's panel. Allow?`,
|
|
3045
|
-
zh: `会把${target.zh}从当前项目的面板上收起来,允许吗?`
|
|
3046
|
-
}, {
|
|
3047
|
-
en: `The agent wants to take ${target.en} off this project's panel. Allow?`,
|
|
3048
|
-
zh: `agent 想把${target.zh}从当前项目的面板上收起来,允许吗?`
|
|
3049
|
-
});
|
|
3050
|
-
}
|
|
3051
|
-
if (action === "move") {
|
|
3052
|
-
const target = anyWorkbenchModule(id);
|
|
3053
|
-
return askOf("workbench_panels move reorders the panel", {
|
|
3054
|
-
en: `it moves ${target.en} to the front of this project's panel. Allow?`,
|
|
3055
|
-
zh: `会把${target.zh}移到当前项目面板的最前面,允许吗?`
|
|
3056
|
-
}, {
|
|
3057
|
-
en: `The agent wants to move ${target.en} to the front of this project's panel. Allow?`,
|
|
3058
|
-
zh: `agent 想把${target.zh}移到当前项目面板的最前面,允许吗?`
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
if (action === "reset") return askOf("workbench_panels reset clears the panel", {
|
|
3062
|
-
en: `it clears the workbench panel for this project. Allow?`,
|
|
3063
|
-
zh: "会清空工作台在当前项目的面板,允许吗?"
|
|
3064
|
-
}, {
|
|
3065
|
-
en: "The agent wants to clear the workbench panel for this project. Allow?",
|
|
3066
|
-
zh: "agent 想清空工作台在当前项目的面板,允许吗?"
|
|
3067
|
-
});
|
|
3068
|
-
if (action === "follow") {
|
|
3069
|
-
const target = workbenchPanelRow(id);
|
|
3070
|
-
return askOf("workbench_panels follow puts a row back to following the machine-level board", {
|
|
3071
|
-
en: `it puts ${target.en} back to following the machine-level board. Allow?`,
|
|
3072
|
-
zh: `会把${target.zh}改回跟随机器级默认板,允许吗?`
|
|
3073
|
-
}, {
|
|
3074
|
-
en: `The agent wants to put ${target.en} back to following the machine-level board. Allow?`,
|
|
3075
|
-
zh: `agent 想把${target.zh}改回跟随机器级默认板,允许吗?`
|
|
3076
|
-
});
|
|
3087
|
+
case MODULES_UPDATE_WORKBENCH_TOOL: return askOf(`${MODULES_UPDATE_WORKBENCH_TOOL} replaces the workbench itself with the new version from the registry`, pair("会把工作台本身更新到源上的新版,允许吗?", "it updates the workbench itself to the new version from the registry. Allow?"), pair("agent 想把工作台本身更新到源上的新版,允许吗?", "The agent wants to update the workbench itself from the registry. Allow?"));
|
|
3088
|
+
default: return;
|
|
3077
3089
|
}
|
|
3078
|
-
const target = anyWorkbenchModule(id);
|
|
3079
|
-
return askOf(`workbench_panels ${action ?? "(unknown action)"} changes the user's panel`, {
|
|
3080
|
-
en: `it adds ${target.en} to this project's panel. Allow?`,
|
|
3081
|
-
zh: `会把${target.zh}加到当前项目的面板上,允许吗?`
|
|
3082
|
-
}, {
|
|
3083
|
-
en: `The agent wants to add ${target.en} to this project's panel. Allow?`,
|
|
3084
|
-
zh: `agent 想把${target.zh}加到当前项目的面板上,允许吗?`
|
|
3085
|
-
});
|
|
3086
3090
|
};
|
|
3087
3091
|
//#endregion
|
|
3088
3092
|
//#region src/modules/panels/model/config.ts
|
|
@@ -3264,116 +3268,1983 @@ function opencliCommandOf(ctx) {
|
|
|
3264
3268
|
isolated
|
|
3265
3269
|
};
|
|
3266
3270
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3271
|
+
var OUTPUT$2 = {
|
|
3272
|
+
schema: {
|
|
3273
|
+
type: "object",
|
|
3274
|
+
properties: {
|
|
3275
|
+
text: { type: "string" },
|
|
3276
|
+
workshop: { type: "string" },
|
|
3277
|
+
modules: {
|
|
3278
|
+
type: "array",
|
|
3279
|
+
items: {
|
|
3280
|
+
type: "object",
|
|
3281
|
+
additionalProperties: true
|
|
3282
|
+
}
|
|
3283
|
+
},
|
|
3284
|
+
unit: { type: "string" },
|
|
3285
|
+
path: { type: "string" },
|
|
3286
|
+
install: {
|
|
3287
|
+
type: "object",
|
|
3288
|
+
additionalProperties: true
|
|
3289
|
+
},
|
|
3290
|
+
alreadyInstalled: { type: "boolean" },
|
|
3291
|
+
panel: {
|
|
3292
|
+
type: "object",
|
|
3293
|
+
additionalProperties: true
|
|
3294
|
+
}
|
|
3295
|
+
},
|
|
3296
|
+
additionalProperties: true
|
|
3297
|
+
},
|
|
3298
|
+
render: (_args, value) => {
|
|
3299
|
+
const payload = value;
|
|
3300
|
+
return [{
|
|
3301
|
+
type: "text",
|
|
3302
|
+
text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
|
|
3303
|
+
}];
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3306
|
+
function asRecord$3(value) {
|
|
3307
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
3308
|
+
}
|
|
3309
|
+
function asNumber(value) {
|
|
3310
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3311
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3312
|
+
const parsed = Number(value);
|
|
3313
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
function asText$1(value) {
|
|
3274
3317
|
return typeof value === "string" ? value.trim() : "";
|
|
3275
3318
|
}
|
|
3276
|
-
function
|
|
3277
|
-
return
|
|
3319
|
+
function plainJson(value) {
|
|
3320
|
+
return JSON.parse(JSON.stringify(value));
|
|
3278
3321
|
}
|
|
3279
|
-
function
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3322
|
+
function verdictOf$1(change) {
|
|
3323
|
+
if (change.application === "applied") return "装好了,已生效";
|
|
3324
|
+
if (change.application === "restart-required") return "装好了,要重启一次才生效(装新模块、同名重装、替换已有包都走这一支)";
|
|
3325
|
+
if (change.application === "overridden") return "被更高层的声明盖住了,这次没生效";
|
|
3326
|
+
if (change.application === "cancelled") return "安装被取消,没有生效";
|
|
3327
|
+
const tail = (change.packageResult?.output ?? "").trim().split("\n").slice(-3).join(" | ");
|
|
3328
|
+
return `没装上(${change.errorCode ?? "failed"})${tail === "" ? "" : `:${tail}`}`;
|
|
3329
|
+
}
|
|
3330
|
+
function renderProbe(out) {
|
|
3331
|
+
if (out.ok) return `读到了 —— 这就是那条读的返回值,把它固化成模块宿主半侧的那一条:\n${(JSON.stringify(out.value, null, 2) ?? "undefined").slice(0, 2e3)}`;
|
|
3332
|
+
const lines = [`没读到(${out.code}):${out.message}`];
|
|
3333
|
+
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
3334
|
+
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
3335
|
+
if (out.pageCode !== void 0) lines.push(`页面自己回的码:${out.pageCode}`);
|
|
3336
|
+
return lines.join("\n");
|
|
3337
|
+
}
|
|
3338
|
+
function renderFind(out) {
|
|
3339
|
+
if (!out.ok) {
|
|
3340
|
+
const lines = [`没找到(${out.code}):${out.message}`];
|
|
3341
|
+
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
3342
|
+
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
3343
|
+
return lines.join("\n");
|
|
3285
3344
|
}
|
|
3286
|
-
if (
|
|
3287
|
-
const
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
name: name === "" ? command.slice(command.indexOf("/") + 1) : name,
|
|
3301
|
-
domain: text(row["domain"]).toLowerCase(),
|
|
3302
|
-
description: text(row["description"]),
|
|
3303
|
-
access: access === "" ? "read" : access,
|
|
3304
|
-
strategy: text(row["strategy"]).toLowerCase(),
|
|
3305
|
-
browser: row["browser"] === true,
|
|
3306
|
-
example: text(row["example"]),
|
|
3307
|
-
columns: stringsOf$1(row["columns"])
|
|
3308
|
-
});
|
|
3345
|
+
if (out.count === 0) return "找到了 0 个元素 —— 选择器没匹配到东西。先确认停在哪一页(probe 会自己问页面),再把选择器放宽一点(例如 table tr 而不是某个 id)。";
|
|
3346
|
+
const head = out.entries.slice(0, 20).map((one, index) => `${String(index + 1)}. ${one.text.replace(/\n+/g, " | ").slice(0, 200)}`);
|
|
3347
|
+
const more = out.entries.length > 20 ? `\n… 还有 ${String(out.entries.length - 20)} 个` : "";
|
|
3348
|
+
return `找到了 ${String(out.count)} 个元素 —— 每行的单元格按换行拼在 text 里;把它固化成模块宿主半侧的那一条(host 的 browserFind + 切格 + 按列序取值):\n${head.join("\n")}${more}`;
|
|
3349
|
+
}
|
|
3350
|
+
function showOrReason(panels, unit, workspace) {
|
|
3351
|
+
try {
|
|
3352
|
+
panels.show(unit, workspace);
|
|
3353
|
+
return { ok: true };
|
|
3354
|
+
} catch (error) {
|
|
3355
|
+
return {
|
|
3356
|
+
ok: false,
|
|
3357
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
3358
|
+
};
|
|
3309
3359
|
}
|
|
3310
|
-
return commands;
|
|
3311
3360
|
}
|
|
3312
|
-
function
|
|
3313
|
-
|
|
3361
|
+
function workshopOf(ctx, exec) {
|
|
3362
|
+
const packagesDir = workshopPackagesDir(ctx);
|
|
3363
|
+
if (packagesDir === null) throw new Error("算不出 DIY 工坊在哪(这台机器没给出 dshHomePath)—— 让用户跑一次 dsh-workbench setup");
|
|
3364
|
+
const session = sessionWorkspace(exec);
|
|
3365
|
+
if (!inWorkshopSession(ctx, session)) throw new Error(`这把工具只在 DIY 工坊里用:当前会话不在工坊里(这个会话的工作区 ${session ?? "(不知道)"})—— 普通项目里装模块用 workbench_modules_install,工坊的入口是左栏那一行「工作台DIY」`);
|
|
3366
|
+
return {
|
|
3367
|
+
packagesDir,
|
|
3368
|
+
workspace: session
|
|
3369
|
+
};
|
|
3314
3370
|
}
|
|
3315
|
-
function
|
|
3316
|
-
|
|
3371
|
+
function modulesOf(ctx, packagesDir) {
|
|
3372
|
+
const installed = profileModules(profileDirOf(ctx)).map((row) => row.unit);
|
|
3373
|
+
return {
|
|
3374
|
+
installed,
|
|
3375
|
+
modules: workshopModules(packagesDir, installed)
|
|
3376
|
+
};
|
|
3317
3377
|
}
|
|
3318
|
-
function
|
|
3319
|
-
const trimmed = url.trim();
|
|
3320
|
-
if (trimmed === "") return "";
|
|
3378
|
+
function panelEntriesOf(panels, workspace) {
|
|
3321
3379
|
try {
|
|
3322
|
-
return
|
|
3380
|
+
return panels.list(workspace);
|
|
3323
3381
|
} catch {
|
|
3324
|
-
return
|
|
3382
|
+
return [];
|
|
3325
3383
|
}
|
|
3326
3384
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3385
|
+
//#endregion
|
|
3386
|
+
//#region src/shared/api/host/png-crop.ts
|
|
3387
|
+
var SIGNATURE = Buffer.from([
|
|
3388
|
+
137,
|
|
3389
|
+
80,
|
|
3390
|
+
78,
|
|
3391
|
+
71,
|
|
3392
|
+
13,
|
|
3393
|
+
10,
|
|
3394
|
+
26,
|
|
3395
|
+
10
|
|
3396
|
+
]);
|
|
3397
|
+
var CHANNELS = {
|
|
3398
|
+
0: 1,
|
|
3399
|
+
2: 3,
|
|
3400
|
+
4: 2,
|
|
3401
|
+
6: 4
|
|
3402
|
+
};
|
|
3403
|
+
var CRC_TABLE = (() => {
|
|
3404
|
+
const table = /* @__PURE__ */ new Int32Array(256);
|
|
3405
|
+
for (let index = 0; index < 256; index += 1) {
|
|
3406
|
+
let value = index;
|
|
3407
|
+
for (let bit = 0; bit < 8; bit += 1) value = (value & 1) === 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
|
|
3408
|
+
table[index] = value;
|
|
3409
|
+
}
|
|
3410
|
+
return table;
|
|
3411
|
+
})();
|
|
3412
|
+
function crc32(bytes) {
|
|
3413
|
+
let value = -1;
|
|
3414
|
+
for (const byte of bytes) value = CRC_TABLE[(value ^ byte) & 255] ^ value >>> 8;
|
|
3415
|
+
return (value ^ -1) >>> 0;
|
|
3416
|
+
}
|
|
3417
|
+
function chunkOf(type, data) {
|
|
3418
|
+
const head = Buffer.alloc(4);
|
|
3419
|
+
head.writeUInt32BE(data.length, 0);
|
|
3420
|
+
const body = Buffer.concat([Buffer.from(type, "latin1"), data]);
|
|
3421
|
+
const tail = Buffer.alloc(4);
|
|
3422
|
+
tail.writeUInt32BE(crc32(body), 0);
|
|
3423
|
+
return Buffer.concat([
|
|
3424
|
+
head,
|
|
3425
|
+
body,
|
|
3426
|
+
tail
|
|
3427
|
+
]);
|
|
3334
3428
|
}
|
|
3335
|
-
function
|
|
3336
|
-
const
|
|
3337
|
-
const
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
if (
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
if (
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3429
|
+
function paethOf(left, above, corner) {
|
|
3430
|
+
const estimate = left + above - corner;
|
|
3431
|
+
const toLeft = Math.abs(estimate - left);
|
|
3432
|
+
const toAbove = Math.abs(estimate - above);
|
|
3433
|
+
const toCorner = Math.abs(estimate - corner);
|
|
3434
|
+
if (toLeft <= toAbove && toLeft <= toCorner) return left;
|
|
3435
|
+
return toAbove <= toCorner ? above : corner;
|
|
3436
|
+
}
|
|
3437
|
+
function headerOf(bytes) {
|
|
3438
|
+
if (bytes.length < 8 || !bytes.subarray(0, 8).equals(SIGNATURE)) return { reason: "不是一张 PNG" };
|
|
3439
|
+
let at = 8;
|
|
3440
|
+
let raw;
|
|
3441
|
+
const parts = [];
|
|
3442
|
+
while (at + 8 <= bytes.length) {
|
|
3443
|
+
const length = bytes.readUInt32BE(at);
|
|
3444
|
+
const type = bytes.toString("latin1", at + 4, at + 8);
|
|
3445
|
+
const data = bytes.subarray(at + 8, at + 8 + length);
|
|
3446
|
+
if (type === "IHDR") raw = {
|
|
3447
|
+
width: data.readUInt32BE(0),
|
|
3448
|
+
height: data.readUInt32BE(4),
|
|
3449
|
+
depth: data[8] ?? 0,
|
|
3450
|
+
color: data[9] ?? 0,
|
|
3451
|
+
interlace: data[12] ?? 0
|
|
3452
|
+
};
|
|
3453
|
+
else if (type === "IDAT") parts.push(Buffer.from(data));
|
|
3454
|
+
else if (type === "IEND") break;
|
|
3455
|
+
at += 12 + length;
|
|
3456
|
+
}
|
|
3457
|
+
if (raw === void 0) return { reason: "这张 PNG 没有 IHDR" };
|
|
3458
|
+
const channels = CHANNELS[raw.color] ?? 0;
|
|
3459
|
+
if (raw.depth !== 8 || channels === 0) return { reason: `这张 PNG 的形状不认(位深 ${String(raw.depth)} / 颜色类型 ${String(raw.color)})` };
|
|
3460
|
+
if (raw.interlace !== 0) return { reason: "这张 PNG 是隔行的,不认" };
|
|
3461
|
+
return { header: {
|
|
3462
|
+
...raw,
|
|
3463
|
+
channels
|
|
3464
|
+
} };
|
|
3348
3465
|
}
|
|
3349
|
-
function
|
|
3350
|
-
const
|
|
3351
|
-
const
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3466
|
+
function rowsOf$1(header, parts, keep) {
|
|
3467
|
+
const stride = header.width * header.channels;
|
|
3468
|
+
const inflated = inflateSync(Buffer.concat(parts));
|
|
3469
|
+
const above = Buffer.alloc(stride);
|
|
3470
|
+
const current = Buffer.alloc(stride);
|
|
3471
|
+
const out = Buffer.alloc(stride * keep);
|
|
3472
|
+
let at = 0;
|
|
3473
|
+
for (let y = 0; y < header.height; y += 1) {
|
|
3474
|
+
const filter = inflated[at] ?? 0;
|
|
3475
|
+
const line = inflated.subarray(at + 1, at + 1 + stride);
|
|
3476
|
+
at += 1 + stride;
|
|
3477
|
+
for (let i = 0; i < stride; i += 1) {
|
|
3478
|
+
const source = line[i] ?? 0;
|
|
3479
|
+
const left = i >= header.channels ? current[i - header.channels] ?? 0 : 0;
|
|
3480
|
+
const up = above[i] ?? 0;
|
|
3481
|
+
const corner = i >= header.channels ? above[i - header.channels] ?? 0 : 0;
|
|
3482
|
+
const value = filter === 0 ? source : filter === 1 ? source + left : filter === 2 ? source + up : filter === 3 ? source + (left + up >> 1) : source + paethOf(left, up, corner);
|
|
3483
|
+
current[i] = value & 255;
|
|
3484
|
+
}
|
|
3485
|
+
if (y < keep) current.copy(out, y * stride);
|
|
3486
|
+
above.set(current);
|
|
3487
|
+
if (y + 1 >= keep) break;
|
|
3361
3488
|
}
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3489
|
+
return out;
|
|
3490
|
+
}
|
|
3491
|
+
function encodeOf(width, height, color, pixels) {
|
|
3492
|
+
const stride = width * (CHANNELS[color] ?? 3);
|
|
3493
|
+
const raw = Buffer.alloc((stride + 1) * height);
|
|
3494
|
+
for (let y = 0; y < height; y += 1) {
|
|
3495
|
+
raw[y * (stride + 1)] = 0;
|
|
3496
|
+
pixels.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
|
|
3497
|
+
}
|
|
3498
|
+
const ihdr = Buffer.alloc(13);
|
|
3499
|
+
ihdr.writeUInt32BE(width, 0);
|
|
3500
|
+
ihdr.writeUInt32BE(height, 4);
|
|
3501
|
+
ihdr[8] = 8;
|
|
3502
|
+
ihdr[9] = color;
|
|
3503
|
+
return Buffer.concat([
|
|
3504
|
+
SIGNATURE,
|
|
3505
|
+
chunkOf("IHDR", ihdr),
|
|
3506
|
+
chunkOf("IDAT", deflateSync(raw, { level: 9 })),
|
|
3507
|
+
chunkOf("IEND", Buffer.alloc(0))
|
|
3508
|
+
]);
|
|
3509
|
+
}
|
|
3510
|
+
function cropPng(bytes, rect) {
|
|
3511
|
+
const found = headerOf(bytes);
|
|
3512
|
+
if ("reason" in found) return {
|
|
3513
|
+
ok: false,
|
|
3514
|
+
bytes: Buffer.alloc(0),
|
|
3515
|
+
width: 0,
|
|
3516
|
+
height: 0,
|
|
3517
|
+
clamped: false,
|
|
3518
|
+
reason: found.reason
|
|
3519
|
+
};
|
|
3520
|
+
const { header } = found;
|
|
3521
|
+
const x = Math.max(0, Math.min(header.width - 1, Math.trunc(rect.x)));
|
|
3522
|
+
const y = Math.max(0, Math.min(header.height - 1, Math.trunc(rect.y)));
|
|
3523
|
+
const width = Math.max(1, Math.min(header.width - x, Math.trunc(rect.width)));
|
|
3524
|
+
const height = Math.max(1, Math.min(header.height - y, Math.trunc(rect.height)));
|
|
3525
|
+
const clamped = x !== Math.trunc(rect.x) || y !== Math.trunc(rect.y) || width !== Math.trunc(rect.width) || height !== Math.trunc(rect.height);
|
|
3526
|
+
const parts = [];
|
|
3527
|
+
let at = 8;
|
|
3528
|
+
while (at + 8 <= bytes.length) {
|
|
3529
|
+
const length = bytes.readUInt32BE(at);
|
|
3530
|
+
const type = bytes.toString("latin1", at + 4, at + 8);
|
|
3531
|
+
if (type === "IDAT") parts.push(Buffer.from(bytes.subarray(at + 8, at + 8 + length)));
|
|
3532
|
+
if (type === "IEND") break;
|
|
3533
|
+
at += 12 + length;
|
|
3534
|
+
}
|
|
3535
|
+
const stride = header.width * header.channels;
|
|
3536
|
+
const rows = rowsOf$1(header, parts, y + height);
|
|
3537
|
+
const outStride = width * header.channels;
|
|
3538
|
+
const pixels = Buffer.alloc(outStride * height);
|
|
3539
|
+
for (let row = 0; row < height; row += 1) {
|
|
3540
|
+
const from = (y + row) * stride + x * header.channels;
|
|
3541
|
+
rows.copy(pixels, row * outStride, from, from + outStride);
|
|
3542
|
+
}
|
|
3543
|
+
return {
|
|
3544
|
+
ok: true,
|
|
3545
|
+
bytes: encodeOf(width, height, header.color, pixels),
|
|
3546
|
+
width,
|
|
3547
|
+
height,
|
|
3548
|
+
clamped,
|
|
3549
|
+
reason: clamped ? "矩形超出图外,已经按边界收进来" : ""
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
function cropPngFile(input, output, rect) {
|
|
3553
|
+
let source;
|
|
3554
|
+
try {
|
|
3555
|
+
source = readFileSync(input);
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
return {
|
|
3558
|
+
ok: false,
|
|
3559
|
+
clamped: false,
|
|
3560
|
+
reason: `读不到那张截图:${error instanceof Error ? error.message : String(error)}`
|
|
3367
3561
|
};
|
|
3368
|
-
group.best = Math.max(group.best, entry.score);
|
|
3369
|
-
group.rows.push(entry);
|
|
3370
|
-
grouped.set(entry.command.site, group);
|
|
3371
3562
|
}
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3563
|
+
const cropped = cropPng(source, rect);
|
|
3564
|
+
if (!cropped.ok) return {
|
|
3565
|
+
ok: false,
|
|
3566
|
+
clamped: false,
|
|
3567
|
+
reason: cropped.reason
|
|
3568
|
+
};
|
|
3569
|
+
try {
|
|
3570
|
+
writeFileSync(output, cropped.bytes);
|
|
3571
|
+
} catch (error) {
|
|
3572
|
+
return {
|
|
3573
|
+
ok: false,
|
|
3574
|
+
clamped: false,
|
|
3575
|
+
reason: `裁好的图写不回去:${error instanceof Error ? error.message : String(error)}`
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3578
|
+
return {
|
|
3579
|
+
ok: true,
|
|
3580
|
+
clamped: cropped.clamped,
|
|
3581
|
+
reason: cropped.reason
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
//#endregion
|
|
3585
|
+
//#region src/modules/panels/model/diy-scripts.ts
|
|
3586
|
+
function textOf$5(value) {
|
|
3587
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3588
|
+
}
|
|
3589
|
+
function numberIn$1(value, fallback) {
|
|
3590
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3591
|
+
}
|
|
3592
|
+
var PAGE_PROBE = `(async () => {
|
|
3593
|
+
const answer = await fetch('/workbench/api/health').then((one) => one.json()).catch(() => null);
|
|
3594
|
+
return JSON.stringify({
|
|
3595
|
+
plugin: answer === null ? null : answer.plugin,
|
|
3596
|
+
pid: answer === null ? null : answer.pid,
|
|
3597
|
+
url: location.href,
|
|
3598
|
+
title: document.title,
|
|
3599
|
+
});
|
|
3600
|
+
})()`;
|
|
3601
|
+
function cardSelectorOf(unit) {
|
|
3602
|
+
return `#wb-card-${unit}`;
|
|
3603
|
+
}
|
|
3604
|
+
var DOM_MODES = [
|
|
3605
|
+
"none",
|
|
3606
|
+
"outline",
|
|
3607
|
+
"html"
|
|
3608
|
+
];
|
|
3609
|
+
var DEFAULT_PROPS = [
|
|
3610
|
+
"display",
|
|
3611
|
+
"flexDirection",
|
|
3612
|
+
"gap",
|
|
3613
|
+
"padding",
|
|
3614
|
+
"width",
|
|
3615
|
+
"height",
|
|
3616
|
+
"overflow",
|
|
3617
|
+
"backgroundColor",
|
|
3618
|
+
"color",
|
|
3619
|
+
"borderRadius",
|
|
3620
|
+
"fontSize",
|
|
3621
|
+
"lineHeight",
|
|
3622
|
+
"fontFamily",
|
|
3623
|
+
"boxSizing",
|
|
3624
|
+
"--dsw-radius-xl",
|
|
3625
|
+
"--dsw-alias-bg-layer-1",
|
|
3626
|
+
"--dsw-alias-label-primary"
|
|
3627
|
+
];
|
|
3628
|
+
var DEFAULT_PARTS = [".wb-cardHead", ".wb-cardBody"];
|
|
3629
|
+
var MAX_PROPS = 40;
|
|
3630
|
+
var MAX_PARTS = 6;
|
|
3631
|
+
function cleanNames(value, cap) {
|
|
3632
|
+
if (!Array.isArray(value)) return [];
|
|
3633
|
+
const out = [];
|
|
3634
|
+
for (const one of value) {
|
|
3635
|
+
if (typeof one !== "string") continue;
|
|
3636
|
+
const trimmed = one.trim();
|
|
3637
|
+
if (trimmed === "" || trimmed.length > 80 || out.includes(trimmed)) continue;
|
|
3638
|
+
out.push(trimmed);
|
|
3639
|
+
if (out.length >= cap) break;
|
|
3640
|
+
}
|
|
3641
|
+
return out;
|
|
3642
|
+
}
|
|
3643
|
+
function pickOf(raw) {
|
|
3644
|
+
const props = cleanNames(raw["props"], MAX_PROPS);
|
|
3645
|
+
const parts = cleanNames(raw["parts"], MAX_PARTS);
|
|
3646
|
+
const asked = textOf$5(raw["dom"]);
|
|
3647
|
+
return {
|
|
3648
|
+
props: props.length === 0 ? [...DEFAULT_PROPS] : props,
|
|
3649
|
+
parts: parts.length === 0 ? [...DEFAULT_PARTS] : parts,
|
|
3650
|
+
dom: DOM_MODES.includes(asked) ? asked : "outline"
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
var NO_PICK = {
|
|
3654
|
+
card: {},
|
|
3655
|
+
parts: {}
|
|
3656
|
+
};
|
|
3657
|
+
var EMPTY_CARD = {
|
|
3658
|
+
ok: false,
|
|
3659
|
+
anchored: false,
|
|
3660
|
+
width: 0,
|
|
3661
|
+
height: 0,
|
|
3662
|
+
head: "",
|
|
3663
|
+
text: "",
|
|
3664
|
+
clipped: false,
|
|
3665
|
+
cards: 0,
|
|
3666
|
+
rect: {
|
|
3667
|
+
x: 0,
|
|
3668
|
+
y: 0,
|
|
3669
|
+
width: 0,
|
|
3670
|
+
height: 0
|
|
3671
|
+
},
|
|
3672
|
+
dpr: 1,
|
|
3673
|
+
viewport: {
|
|
3674
|
+
width: 0,
|
|
3675
|
+
height: 0
|
|
3676
|
+
},
|
|
3677
|
+
pick: NO_PICK,
|
|
3678
|
+
slot: {
|
|
3679
|
+
parentWidth: 0,
|
|
3680
|
+
parentDisplay: "",
|
|
3681
|
+
columns: "",
|
|
3682
|
+
index: 0,
|
|
3683
|
+
cards: 0,
|
|
3684
|
+
viewportHeight: 0,
|
|
3685
|
+
scrollHeight: 0
|
|
3686
|
+
},
|
|
3687
|
+
outline: [],
|
|
3688
|
+
html: ""
|
|
3689
|
+
};
|
|
3690
|
+
function measureScriptOf(unit, pick) {
|
|
3691
|
+
return `(() => {
|
|
3692
|
+
const props = ${JSON.stringify([...pick.props])};
|
|
3693
|
+
const parts = ${JSON.stringify([...pick.parts])};
|
|
3694
|
+
const wantDom = ${JSON.stringify(pick.dom)};
|
|
3695
|
+
const kebab = (one) => (one.startsWith('--') ? one : one.replace(/[A-Z]/g, (hit) => '-' + hit.toLowerCase()));
|
|
3696
|
+
const scrub = (one) => (one ?? '').replace(/\\s+/g, ' ').trim();
|
|
3697
|
+
const read = (el) => {
|
|
3698
|
+
if (el === null || el === undefined) return {};
|
|
3699
|
+
const computed = getComputedStyle(el);
|
|
3700
|
+
const out = {};
|
|
3701
|
+
for (const one of props) out[one] = computed.getPropertyValue(kebab(one)).trim().slice(0, 200);
|
|
3702
|
+
return out;
|
|
3703
|
+
};
|
|
3704
|
+
const anchor = document.querySelector(${JSON.stringify(cardSelectorOf(unit))});
|
|
3705
|
+
const all = [...document.querySelectorAll('.wb-card')];
|
|
3706
|
+
if (anchor === null) return JSON.stringify({ ok: false, cards: all.length, anchored: false });
|
|
3707
|
+
const card = anchor.classList.contains('wb-card') ? anchor : anchor.querySelector('.wb-card');
|
|
3708
|
+
if (card === null) return JSON.stringify({ ok: false, cards: all.length, anchored: true });
|
|
3709
|
+
const box = card.getBoundingClientRect();
|
|
3710
|
+
const head = card.querySelector('.wb-cardTitle');
|
|
3711
|
+
const text = scrub(card.textContent);
|
|
3712
|
+
const shown = scrub(card.innerText);
|
|
3713
|
+
const clipped = [...card.querySelectorAll('*')].some((one) => one.scrollWidth > one.clientWidth + 1);
|
|
3714
|
+
const picked = {};
|
|
3715
|
+
for (const one of parts) {
|
|
3716
|
+
let found = null;
|
|
3717
|
+
try {
|
|
3718
|
+
found = card.querySelector(one);
|
|
3719
|
+
} catch {
|
|
3720
|
+
found = null;
|
|
3721
|
+
}
|
|
3722
|
+
picked[one] = found === null ? {} : read(found);
|
|
3723
|
+
}
|
|
3724
|
+
const parent = card.parentElement === anchor ? anchor.parentElement : card.parentElement;
|
|
3725
|
+
let scrollable = parent;
|
|
3726
|
+
while (scrollable !== null && !/(auto|scroll)/.test(getComputedStyle(scrollable).overflowY)) {
|
|
3727
|
+
scrollable = scrollable.parentElement;
|
|
3728
|
+
}
|
|
3729
|
+
const outline = [];
|
|
3730
|
+
if (wantDom === 'outline') {
|
|
3731
|
+
const walk = (el, depth) => {
|
|
3732
|
+
if (depth > 3 || outline.length >= 40) return;
|
|
3733
|
+
const classed =
|
|
3734
|
+
typeof el.className === 'string'
|
|
3735
|
+
? el.className.split(/\\s+/).filter((one) => one.startsWith('wb-')).slice(0, 2).join('.')
|
|
3736
|
+
: '';
|
|
3737
|
+
outline.push({
|
|
3738
|
+
depth,
|
|
3739
|
+
tag: el.tagName.toLowerCase(),
|
|
3740
|
+
cls: classed,
|
|
3741
|
+
role: el.getAttribute('role') ?? '',
|
|
3742
|
+
name: el.getAttribute('aria-label') ?? '',
|
|
3743
|
+
text: el.children.length === 0 ? scrub(el.textContent).slice(0, 30) : '',
|
|
3744
|
+
});
|
|
3745
|
+
for (const child of [...el.children]) walk(child, depth + 1);
|
|
3746
|
+
};
|
|
3747
|
+
walk(card, 0);
|
|
3748
|
+
}
|
|
3749
|
+
return JSON.stringify({
|
|
3750
|
+
ok: true,
|
|
3751
|
+
cards: all.length,
|
|
3752
|
+
width: Math.ceil(box.width),
|
|
3753
|
+
height: Math.ceil(box.height),
|
|
3754
|
+
x: Math.round(box.x),
|
|
3755
|
+
y: Math.round(box.y),
|
|
3756
|
+
head: head === null ? '' : scrub(head.textContent),
|
|
3757
|
+
text: (shown === '' ? text : shown).slice(0, 600),
|
|
3758
|
+
clipped,
|
|
3759
|
+
dpr: window.devicePixelRatio || 1,
|
|
3760
|
+
vw: Math.round(window.innerWidth),
|
|
3761
|
+
vh: Math.round(window.innerHeight),
|
|
3762
|
+
slot: {
|
|
3763
|
+
parentWidth: parent === null ? 0 : Math.round(parent.getBoundingClientRect().width),
|
|
3764
|
+
parentDisplay: parent === null ? '' : getComputedStyle(parent).display,
|
|
3765
|
+
columns: parent === null ? '' : getComputedStyle(parent).gridTemplateColumns,
|
|
3766
|
+
index: all.indexOf(card),
|
|
3767
|
+
cards: all.length,
|
|
3768
|
+
viewportHeight: scrollable === null ? 0 : scrollable.clientHeight,
|
|
3769
|
+
scrollHeight: scrollable === null ? 0 : scrollable.scrollHeight,
|
|
3770
|
+
},
|
|
3771
|
+
outline,
|
|
3772
|
+
html: wantDom === 'html' ? card.outerHTML.slice(0, 4000) : '',
|
|
3773
|
+
pick: { card: read(card), parts: picked },
|
|
3774
|
+
});
|
|
3775
|
+
})()`;
|
|
3776
|
+
}
|
|
3777
|
+
function nodesOf(value) {
|
|
3778
|
+
if (!Array.isArray(value)) return [];
|
|
3779
|
+
const out = [];
|
|
3780
|
+
for (const one of value) {
|
|
3781
|
+
if (typeof one !== "object" || one === null) continue;
|
|
3782
|
+
const record = one;
|
|
3783
|
+
const depth = numberIn$1(record["depth"], -1);
|
|
3784
|
+
const tag = textOf$5(record["tag"]);
|
|
3785
|
+
if (depth < 0 || tag === "") continue;
|
|
3786
|
+
out.push({
|
|
3787
|
+
depth,
|
|
3788
|
+
tag,
|
|
3789
|
+
cls: textOf$5(record["cls"]),
|
|
3790
|
+
role: textOf$5(record["role"]),
|
|
3791
|
+
name: textOf$5(record["name"]),
|
|
3792
|
+
text: textOf$5(record["text"])
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
3795
|
+
return out;
|
|
3796
|
+
}
|
|
3797
|
+
function slotOf(value) {
|
|
3798
|
+
const record = typeof value === "object" && value !== null ? value : {};
|
|
3799
|
+
return {
|
|
3800
|
+
parentWidth: numberIn$1(record["parentWidth"], 0),
|
|
3801
|
+
parentDisplay: textOf$5(record["parentDisplay"]),
|
|
3802
|
+
columns: textOf$5(record["columns"]),
|
|
3803
|
+
index: numberIn$1(record["index"], 0),
|
|
3804
|
+
cards: numberIn$1(record["cards"], 0),
|
|
3805
|
+
viewportHeight: numberIn$1(record["viewportHeight"], 0),
|
|
3806
|
+
scrollHeight: numberIn$1(record["scrollHeight"], 0)
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
function stringsOf$2(value) {
|
|
3810
|
+
if (typeof value !== "object" || value === null) return {};
|
|
3811
|
+
const out = {};
|
|
3812
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
3813
|
+
const text = textOf$5(raw);
|
|
3814
|
+
if (text !== "") out[key] = text;
|
|
3815
|
+
}
|
|
3816
|
+
return out;
|
|
3817
|
+
}
|
|
3818
|
+
function pickFactsOf(value) {
|
|
3819
|
+
if (typeof value !== "object" || value === null) return NO_PICK;
|
|
3820
|
+
const record = value;
|
|
3821
|
+
const raw = record["parts"];
|
|
3822
|
+
const parts = {};
|
|
3823
|
+
if (typeof raw === "object" && raw !== null) for (const [key, one] of Object.entries(raw)) parts[key] = stringsOf$2(one);
|
|
3824
|
+
return {
|
|
3825
|
+
card: stringsOf$2(record["card"]),
|
|
3826
|
+
parts
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
//#endregion
|
|
3830
|
+
//#region src/modules/panels/model/diy-browser.ts
|
|
3831
|
+
var LOOK_TIMEOUT_MS = 3e4;
|
|
3832
|
+
async function runBrowserStep(shell, command, argv, timeoutMs = LOOK_TIMEOUT_MS) {
|
|
3833
|
+
const result = await runShell(shell, backgroundLine(command, argv), { timeoutMs });
|
|
3834
|
+
return {
|
|
3835
|
+
ok: result.exitCode === 0 && !result.timedOut,
|
|
3836
|
+
stdout: result.stdout.trim(),
|
|
3837
|
+
stderr: result.stderr.trim(),
|
|
3838
|
+
exitCode: result.exitCode,
|
|
3839
|
+
timedOut: result.timedOut
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
function jsonOf(raw) {
|
|
3843
|
+
const start = raw.indexOf("{");
|
|
3844
|
+
const end = raw.lastIndexOf("}");
|
|
3845
|
+
if (start === -1 || end <= start) return void 0;
|
|
3846
|
+
try {
|
|
3847
|
+
const parsed = JSON.parse(raw.slice(start, end + 1));
|
|
3848
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
3849
|
+
} catch {
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
function textOf$4(value) {
|
|
3854
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3855
|
+
}
|
|
3856
|
+
function numberIn(value, fallback) {
|
|
3857
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3858
|
+
}
|
|
3859
|
+
async function bindCurrentTab(shell, command, session) {
|
|
3860
|
+
const step = await runBrowserStep(shell, command, [
|
|
3861
|
+
"browser",
|
|
3862
|
+
session,
|
|
3863
|
+
"bind",
|
|
3864
|
+
"--window",
|
|
3865
|
+
"background"
|
|
3866
|
+
]);
|
|
3867
|
+
const parsed = jsonOf(step.stdout);
|
|
3868
|
+
if (parsed === void 0) return {
|
|
3869
|
+
ok: false,
|
|
3870
|
+
url: "",
|
|
3871
|
+
title: "",
|
|
3872
|
+
reason: step.timedOut ? "没在 30 秒内返回" : step.stderr || step.stdout
|
|
3873
|
+
};
|
|
3874
|
+
const error = parsed["error"];
|
|
3875
|
+
if (typeof error === "object" && error !== null) {
|
|
3876
|
+
const message = textOf$4(error["message"]);
|
|
3877
|
+
return {
|
|
3878
|
+
ok: false,
|
|
3879
|
+
url: "",
|
|
3880
|
+
title: "",
|
|
3881
|
+
reason: message === "" ? "opencli 报了个错" : message
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
return {
|
|
3885
|
+
ok: true,
|
|
3886
|
+
url: textOf$4(parsed["url"]),
|
|
3887
|
+
title: textOf$4(parsed["title"]),
|
|
3888
|
+
reason: ""
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
async function workbenchPageOf(shell, command, session) {
|
|
3892
|
+
const parsed = jsonOf((await runBrowserStep(shell, command, [
|
|
3893
|
+
"browser",
|
|
3894
|
+
session,
|
|
3895
|
+
"eval",
|
|
3896
|
+
PAGE_PROBE,
|
|
3897
|
+
"--window",
|
|
3898
|
+
"background"
|
|
3899
|
+
])).stdout);
|
|
3900
|
+
if (parsed === void 0) return {
|
|
3901
|
+
ok: false,
|
|
3902
|
+
url: "",
|
|
3903
|
+
title: ""
|
|
3904
|
+
};
|
|
3905
|
+
return {
|
|
3906
|
+
ok: textOf$4(parsed["plugin"]) === "@dsh-workbench/core",
|
|
3907
|
+
url: textOf$4(parsed["url"]),
|
|
3908
|
+
title: textOf$4(parsed["title"])
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
async function writeShotsDir(shell, dir) {
|
|
3912
|
+
const result = await runShell(shell, [
|
|
3913
|
+
"mkdir",
|
|
3914
|
+
"-p",
|
|
3915
|
+
dir
|
|
3916
|
+
].map((one) => `'${one}'`).join(" "), { timeoutMs: LOOK_TIMEOUT_MS });
|
|
3917
|
+
return result.exitCode === 0 && !result.timedOut;
|
|
3918
|
+
}
|
|
3919
|
+
async function readyPageOf(shell, command, session) {
|
|
3920
|
+
const already = await workbenchPageOf(shell, command, session);
|
|
3921
|
+
if (already.ok) return {
|
|
3922
|
+
ok: true,
|
|
3923
|
+
bound: false,
|
|
3924
|
+
page: already,
|
|
3925
|
+
reason: ""
|
|
3926
|
+
};
|
|
3927
|
+
const bound = await bindCurrentTab(shell, command, session);
|
|
3928
|
+
if (!bound.ok) return {
|
|
3929
|
+
ok: false,
|
|
3930
|
+
bound: false,
|
|
3931
|
+
page: already,
|
|
3932
|
+
reason: bound.reason
|
|
3933
|
+
};
|
|
3934
|
+
const after = await workbenchPageOf(shell, command, session);
|
|
3935
|
+
return {
|
|
3936
|
+
ok: after.ok,
|
|
3937
|
+
bound: true,
|
|
3938
|
+
page: after,
|
|
3939
|
+
reason: ""
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
async function measureCard(shell, command, session, unit, pick) {
|
|
3943
|
+
const parsed = jsonOf((await runBrowserStep(shell, command, [
|
|
3944
|
+
"browser",
|
|
3945
|
+
session,
|
|
3946
|
+
"eval",
|
|
3947
|
+
measureScriptOf(unit, pick),
|
|
3948
|
+
"--window",
|
|
3949
|
+
"background"
|
|
3950
|
+
])).stdout);
|
|
3951
|
+
if (parsed === void 0 || parsed["ok"] !== true) return {
|
|
3952
|
+
...EMPTY_CARD,
|
|
3953
|
+
cards: numberIn(parsed?.["cards"], 0),
|
|
3954
|
+
anchored: parsed?.["anchored"] === true
|
|
3955
|
+
};
|
|
3956
|
+
const width = numberIn(parsed["width"], 0);
|
|
3957
|
+
const height = numberIn(parsed["height"], 0);
|
|
3958
|
+
return {
|
|
3959
|
+
ok: true,
|
|
3960
|
+
anchored: true,
|
|
3961
|
+
width,
|
|
3962
|
+
height,
|
|
3963
|
+
head: textOf$4(parsed["head"]),
|
|
3964
|
+
text: textOf$4(parsed["text"]),
|
|
3965
|
+
clipped: parsed["clipped"] === true,
|
|
3966
|
+
cards: numberIn(parsed["cards"], 0),
|
|
3967
|
+
rect: {
|
|
3968
|
+
x: numberIn(parsed["x"], 0),
|
|
3969
|
+
y: numberIn(parsed["y"], 0),
|
|
3970
|
+
width,
|
|
3971
|
+
height
|
|
3972
|
+
},
|
|
3973
|
+
dpr: numberIn(parsed["dpr"], 1) || 1,
|
|
3974
|
+
viewport: {
|
|
3975
|
+
width: numberIn(parsed["vw"], 0),
|
|
3976
|
+
height: numberIn(parsed["vh"], 0)
|
|
3977
|
+
},
|
|
3978
|
+
pick: pickFactsOf(parsed["pick"]),
|
|
3979
|
+
slot: slotOf(parsed["slot"]),
|
|
3980
|
+
outline: nodesOf(parsed["outline"]),
|
|
3981
|
+
html: textOf$4(parsed["html"])
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
function insideViewportOf(view) {
|
|
3985
|
+
return view.rect.x >= 0 && view.rect.y >= 0 && view.rect.x + view.rect.width <= view.viewport.width && view.rect.y + view.rect.height <= view.viewport.height;
|
|
3986
|
+
}
|
|
3987
|
+
function deviceRectOf(view) {
|
|
3988
|
+
return {
|
|
3989
|
+
x: Math.max(0, Math.round(view.rect.x * view.dpr)),
|
|
3990
|
+
y: Math.max(0, Math.round(view.rect.y * view.dpr)),
|
|
3991
|
+
width: Math.max(1, Math.round(view.rect.width * view.dpr)),
|
|
3992
|
+
height: Math.max(1, Math.round(view.rect.height * view.dpr))
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
function cropFile(file, view) {
|
|
3996
|
+
const crop = cropPngFile(file, file, deviceRectOf(view));
|
|
3997
|
+
return {
|
|
3998
|
+
cropped: crop.ok,
|
|
3999
|
+
reason: crop.reason
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
async function shootCard(shell, command, session, unit, file, view) {
|
|
4003
|
+
const step = await runBrowserStep(shell, command, [
|
|
4004
|
+
"browser",
|
|
4005
|
+
session,
|
|
4006
|
+
"screenshot",
|
|
4007
|
+
file,
|
|
4008
|
+
"--window",
|
|
4009
|
+
"background"
|
|
4010
|
+
]);
|
|
4011
|
+
if (!step.ok) return {
|
|
4012
|
+
ok: false,
|
|
4013
|
+
file: "",
|
|
4014
|
+
width: 0,
|
|
4015
|
+
height: 0,
|
|
4016
|
+
cropped: false,
|
|
4017
|
+
reason: step.timedOut ? "截图没在 30 秒内回来" : step.stderr || step.stdout
|
|
4018
|
+
};
|
|
4019
|
+
if (!insideViewportOf(view)) return {
|
|
4020
|
+
ok: true,
|
|
4021
|
+
file,
|
|
4022
|
+
width: Math.round(view.rect.width),
|
|
4023
|
+
height: Math.round(view.rect.height),
|
|
4024
|
+
cropped: false,
|
|
4025
|
+
reason: `${unit} 这张卡有一部分在视口外面(卡片 ${String(Math.round(view.rect.width))}×${String(Math.round(view.rect.height))},视口 ${String(view.viewport.width)}×${String(view.viewport.height)})—— 滚过一次还是放不下,这一张是整屏图,卡片在 ${String(Math.round(view.rect.x))},${String(Math.round(view.rect.y))}。`
|
|
4026
|
+
};
|
|
4027
|
+
const crop = cropFile(file, view);
|
|
4028
|
+
return {
|
|
4029
|
+
ok: true,
|
|
4030
|
+
file,
|
|
4031
|
+
width: Math.round(view.rect.width),
|
|
4032
|
+
height: Math.round(view.rect.height),
|
|
4033
|
+
cropped: crop.cropped,
|
|
4034
|
+
reason: crop.cropped ? crop.reason : `${crop.reason} —— 给的是整屏图:卡片在 ${String(Math.round(view.rect.x))},${String(Math.round(view.rect.y))}`
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
//#endregion
|
|
4038
|
+
//#region src/modules/panels/model/diy-page.ts
|
|
4039
|
+
async function pageAt(ctx, exec, raw, options = {}) {
|
|
4040
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
4041
|
+
const args = asRecord$3(raw);
|
|
4042
|
+
const asked = asText$1(args["unit"]);
|
|
4043
|
+
const modules = modulesOf(ctx, packagesDir).modules;
|
|
4044
|
+
const wanted = asked === "" && modules.length === 1 ? modules[0]?.unit ?? "" : asked;
|
|
4045
|
+
if (wanted === "" && options.requireUnit !== false) throw new Error(`看一眼要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
4046
|
+
const shell = ctx.get("shell");
|
|
4047
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,动不了浏览器");
|
|
4048
|
+
const command = opencliCommandOf(ctx).command;
|
|
4049
|
+
const session = asText$1(args["session"]) || "workbench";
|
|
4050
|
+
const handshake = await readyPageOf(shell, command, session);
|
|
4051
|
+
if (!handshake.ok && !handshake.bound) throw new Error(`连不上浏览器:${handshake.reason}。先让用户把工作台那一页切到前台(只有第一次要这样);还不行就跑 opencli doctor 看扩展连没连上。`);
|
|
4052
|
+
if (!handshake.ok && options.requireWorkbench !== false) {
|
|
4053
|
+
const { page } = handshake;
|
|
4054
|
+
throw new Error(`你现在这一页不是工作台(${page.title || "(没有标题)"} ${page.url})—— 把工作台那一页切到前台,再喊我一次。我只看用户正在看的那一页。`);
|
|
4055
|
+
}
|
|
4056
|
+
return {
|
|
4057
|
+
packagesDir,
|
|
4058
|
+
unit: wanted,
|
|
4059
|
+
args,
|
|
4060
|
+
shell,
|
|
4061
|
+
command,
|
|
4062
|
+
session,
|
|
4063
|
+
bound: handshake.bound
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
//#endregion
|
|
4067
|
+
//#region src/modules/panels/model/diy-browser-act.ts
|
|
4068
|
+
var ACT_TIMEOUT_MS = 15e3;
|
|
4069
|
+
function actTargetOf(raw) {
|
|
4070
|
+
const nth = raw["nth"];
|
|
4071
|
+
return {
|
|
4072
|
+
selector: textOf$4(raw["selector"]),
|
|
4073
|
+
role: textOf$4(raw["role"]),
|
|
4074
|
+
name: textOf$4(raw["name"]),
|
|
4075
|
+
testid: textOf$4(raw["testid"]),
|
|
4076
|
+
nth: typeof nth === "number" && Number.isFinite(nth) ? Math.max(0, Math.trunc(nth)) : null,
|
|
4077
|
+
page: textOf$4(raw["scope"]) === "page"
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
function scopedSelectorOf(unit, target) {
|
|
4081
|
+
if (target.selector === "") return "";
|
|
4082
|
+
return target.page ? target.selector : `${cardSelectorOf(unit)} ${target.selector}`;
|
|
4083
|
+
}
|
|
4084
|
+
var SCOPE_ATTR = "data-diy-scope";
|
|
4085
|
+
function semanticOptionsOf(target) {
|
|
4086
|
+
const out = [];
|
|
4087
|
+
if (target.role !== "") out.push("--role", target.role);
|
|
4088
|
+
if (target.name !== "") out.push("--name", target.name);
|
|
4089
|
+
if (target.testid !== "") out.push("--testid", target.testid);
|
|
4090
|
+
return out;
|
|
4091
|
+
}
|
|
4092
|
+
function targetOptionsOf(target) {
|
|
4093
|
+
const out = [...semanticOptionsOf(target)];
|
|
4094
|
+
if (target.nth !== null) out.push("--nth", String(target.nth));
|
|
4095
|
+
return out;
|
|
4096
|
+
}
|
|
4097
|
+
function refsOf(payload) {
|
|
4098
|
+
const entries = payload?.["entries"];
|
|
4099
|
+
if (!Array.isArray(entries)) return [];
|
|
4100
|
+
const out = [];
|
|
4101
|
+
for (const one of entries) {
|
|
4102
|
+
if (typeof one !== "object" || one === null) continue;
|
|
4103
|
+
const ref = one["ref"];
|
|
4104
|
+
if (typeof ref === "number") out.push(ref);
|
|
4105
|
+
}
|
|
4106
|
+
return out;
|
|
4107
|
+
}
|
|
4108
|
+
function totalMatchesOf(payload) {
|
|
4109
|
+
const total = payload?.["matches_n"];
|
|
4110
|
+
return typeof total === "number" ? total : 0;
|
|
4111
|
+
}
|
|
4112
|
+
function scopeMarkScriptOf(unit, refs, nth, token) {
|
|
4113
|
+
return `(() => {
|
|
4114
|
+
const card = document.querySelector(${JSON.stringify(cardSelectorOf(unit))});
|
|
4115
|
+
const refs = ${JSON.stringify(refs)};
|
|
4116
|
+
if (card === null) return { ok: false, code: 'no-card', inside: 0, total: refs.length };
|
|
4117
|
+
const found = [];
|
|
4118
|
+
for (const ref of refs) {
|
|
4119
|
+
const el = document.querySelector('[data-opencli-ref="' + ref + '"]');
|
|
4120
|
+
if (el !== null && card.contains(el)) found.push(el);
|
|
4121
|
+
}
|
|
4122
|
+
for (const stale of card.querySelectorAll('[${SCOPE_ATTR}]')) stale.removeAttribute('${SCOPE_ATTR}');
|
|
4123
|
+
const pick = found[${String(nth)}];
|
|
4124
|
+
if (pick === undefined) return { ok: false, code: 'not-in-card', inside: found.length, total: refs.length };
|
|
4125
|
+
pick.setAttribute('${SCOPE_ATTR}', ${JSON.stringify(token)});
|
|
4126
|
+
return { ok: true, code: '', inside: found.length, total: refs.length };
|
|
4127
|
+
})()`;
|
|
4128
|
+
}
|
|
4129
|
+
function scopeClearScriptOf(token) {
|
|
4130
|
+
return `(() => {
|
|
4131
|
+
const marked = document.querySelectorAll('[${SCOPE_ATTR}="${token}"]');
|
|
4132
|
+
const removed = marked.length;
|
|
4133
|
+
for (const el of marked) el.removeAttribute('${SCOPE_ATTR}');
|
|
4134
|
+
return { ok: true, removed };
|
|
4135
|
+
})()`;
|
|
4136
|
+
}
|
|
4137
|
+
function scopedMarkerSelectorOf(unit, token) {
|
|
4138
|
+
return `${cardSelectorOf(unit)} [${SCOPE_ATTR}="${token}"]`;
|
|
4139
|
+
}
|
|
4140
|
+
async function actBrowser(shell, command, session, argv, timeoutMs = ACT_TIMEOUT_MS) {
|
|
4141
|
+
const step = await runBrowserStep(shell, command, [
|
|
4142
|
+
"browser",
|
|
4143
|
+
session,
|
|
4144
|
+
...argv,
|
|
4145
|
+
"--window",
|
|
4146
|
+
"background"
|
|
4147
|
+
], timeoutMs);
|
|
4148
|
+
return {
|
|
4149
|
+
...step,
|
|
4150
|
+
payload: jsonOf(step.stdout)
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
function actErrorOf(step) {
|
|
4154
|
+
if (step.timedOut) return `opencli 没在 ${String(Math.round(ACT_TIMEOUT_MS / 1e3))} 秒内回来`;
|
|
4155
|
+
const error = step.payload?.["error"];
|
|
4156
|
+
if (typeof error === "object" && error !== null) {
|
|
4157
|
+
const message = textOf$4(error["message"]);
|
|
4158
|
+
if (message !== "") return message;
|
|
4159
|
+
}
|
|
4160
|
+
if (step.ok) return "";
|
|
4161
|
+
return step.stderr || step.stdout || "opencli 报了个错";
|
|
4162
|
+
}
|
|
4163
|
+
function matchesOf(step) {
|
|
4164
|
+
const raw = step.payload?.["matches_n"];
|
|
4165
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : -1;
|
|
4166
|
+
}
|
|
4167
|
+
async function scrollIntoView(shell, command, session, unit, selector) {
|
|
4168
|
+
const asked = selector === "" ? ".wb-card" : selector;
|
|
4169
|
+
return actBrowser(shell, command, session, ["eval", `(() => {
|
|
4170
|
+
const anchor = document.querySelector(${JSON.stringify(cardSelectorOf(unit))});
|
|
4171
|
+
const sel = ${JSON.stringify(asked)};
|
|
4172
|
+
if (anchor === null) return JSON.stringify({ ok: false, found: false });
|
|
4173
|
+
const el = anchor.matches(sel) ? anchor : anchor.querySelector(sel);
|
|
4174
|
+
if (el === null) return JSON.stringify({ ok: false, found: false });
|
|
4175
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
4176
|
+
const box = el.getBoundingClientRect();
|
|
4177
|
+
return JSON.stringify({ ok: true, found: true, x: Math.round(box.x), y: Math.round(box.y), width: Math.ceil(box.width), height: Math.ceil(box.height) });
|
|
4178
|
+
})()`]);
|
|
4179
|
+
}
|
|
4180
|
+
async function reloadPage(shell, command, session) {
|
|
4181
|
+
return actBrowser(shell, command, session, ["eval", `(() => { setTimeout(() => location.reload(), 30); return JSON.stringify({ ok: true }); })()`]);
|
|
4182
|
+
}
|
|
4183
|
+
async function shootAtWidth(shell, command, session, file, width, height) {
|
|
4184
|
+
const step = await actBrowser(shell, command, session, [
|
|
4185
|
+
"screenshot",
|
|
4186
|
+
file,
|
|
4187
|
+
"--width",
|
|
4188
|
+
String(width),
|
|
4189
|
+
...height > 0 ? ["--height", String(height)] : []
|
|
4190
|
+
], LOOK_TIMEOUT_MS);
|
|
4191
|
+
return {
|
|
4192
|
+
ok: step.ok,
|
|
4193
|
+
file: step.ok ? file : "",
|
|
4194
|
+
width,
|
|
4195
|
+
height,
|
|
4196
|
+
reason: step.ok ? "" : step.timedOut ? "截图没在 30 秒内回来" : step.stderr || step.stdout
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
var NET_CAP_LINES = 2e3;
|
|
4200
|
+
function followerPatternOf(session) {
|
|
4201
|
+
return `[b]rowser ${session} network --follow`;
|
|
4202
|
+
}
|
|
4203
|
+
function killFollowerLine(session, file) {
|
|
4204
|
+
const pidFile = quoteShellArg(`${file}.pid`);
|
|
4205
|
+
const reaperFile = quoteShellArg(`${file}.reaper`);
|
|
4206
|
+
return [
|
|
4207
|
+
`kill "$(cat ${pidFile} 2>/dev/null)" 2>/dev/null`,
|
|
4208
|
+
`pkill -f ${quoteShellArg(followerPatternOf(session))} 2>/dev/null`,
|
|
4209
|
+
`pkill -P "$(cat ${reaperFile} 2>/dev/null)" 2>/dev/null`,
|
|
4210
|
+
`kill "$(cat ${reaperFile} 2>/dev/null)" 2>/dev/null`
|
|
4211
|
+
].join("; ");
|
|
4212
|
+
}
|
|
4213
|
+
async function armNetwork(shell, command, session, file) {
|
|
4214
|
+
const capture = `${backgroundLine(command, [
|
|
4215
|
+
"browser",
|
|
4216
|
+
session,
|
|
4217
|
+
"network",
|
|
4218
|
+
"--follow",
|
|
4219
|
+
"--window",
|
|
4220
|
+
"background"
|
|
4221
|
+
])} 2>&1 | head -n ${String(NET_CAP_LINES)} > ${quoteShellArg(file)}`;
|
|
4222
|
+
const reaper = `sleep ${String(900)}; ${killFollowerLine(session, file)}`;
|
|
4223
|
+
const step = await runShell(shell, [
|
|
4224
|
+
"rm",
|
|
4225
|
+
"-f",
|
|
4226
|
+
quoteShellArg(file),
|
|
4227
|
+
quoteShellArg(`${file}.pid`),
|
|
4228
|
+
quoteShellArg(`${file}.reaper`),
|
|
4229
|
+
";",
|
|
4230
|
+
"nohup",
|
|
4231
|
+
"sh",
|
|
4232
|
+
"-c",
|
|
4233
|
+
quoteShellArg(capture),
|
|
4234
|
+
">",
|
|
4235
|
+
"/dev/null",
|
|
4236
|
+
"2>&1",
|
|
4237
|
+
"&",
|
|
4238
|
+
"echo",
|
|
4239
|
+
"$!",
|
|
4240
|
+
">",
|
|
4241
|
+
quoteShellArg(`${file}.pid`),
|
|
4242
|
+
";",
|
|
4243
|
+
"nohup",
|
|
4244
|
+
"sh",
|
|
4245
|
+
"-c",
|
|
4246
|
+
quoteShellArg(reaper),
|
|
4247
|
+
">",
|
|
4248
|
+
"/dev/null",
|
|
4249
|
+
"2>&1",
|
|
4250
|
+
"&",
|
|
4251
|
+
"echo",
|
|
4252
|
+
"$!",
|
|
4253
|
+
">",
|
|
4254
|
+
quoteShellArg(`${file}.reaper`),
|
|
4255
|
+
";",
|
|
4256
|
+
"sleep",
|
|
4257
|
+
"0.6",
|
|
4258
|
+
";",
|
|
4259
|
+
"cat",
|
|
4260
|
+
quoteShellArg(`${file}.pid`)
|
|
4261
|
+
].join(" "), { timeoutMs: ACT_TIMEOUT_MS });
|
|
4262
|
+
const pid = step.stdout.trim().split("\n").slice(-1)[0] ?? "";
|
|
4263
|
+
return {
|
|
4264
|
+
ok: step.exitCode === 0 && pid !== "",
|
|
4265
|
+
pid,
|
|
4266
|
+
reason: step.exitCode === 0 ? "" : step.stderr.trim() || step.stdout.trim()
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
4269
|
+
async function stopNetwork(shell, session, file) {
|
|
4270
|
+
return (await runShell(shell, [
|
|
4271
|
+
killFollowerLine(session, file),
|
|
4272
|
+
";",
|
|
4273
|
+
"rm",
|
|
4274
|
+
"-f",
|
|
4275
|
+
quoteShellArg(`${file}.pid`),
|
|
4276
|
+
quoteShellArg(`${file}.reaper`)
|
|
4277
|
+
].join(" "), { timeoutMs: ACT_TIMEOUT_MS })).exitCode === 0;
|
|
4278
|
+
}
|
|
4279
|
+
async function readNetwork(shell, file, lines) {
|
|
4280
|
+
const step = await runShell(shell, `tail -n ${String(lines)} ${quoteShellArg(file)}`, { timeoutMs: ACT_TIMEOUT_MS });
|
|
4281
|
+
const out = [];
|
|
4282
|
+
for (const raw of step.stdout.split("\n")) {
|
|
4283
|
+
const parsed = jsonOf(raw);
|
|
4284
|
+
if (parsed !== void 0) out.push(parsed);
|
|
4285
|
+
}
|
|
4286
|
+
return out;
|
|
4287
|
+
}
|
|
4288
|
+
//#endregion
|
|
4289
|
+
//#region src/modules/panels/model/diy-look.ts
|
|
4290
|
+
function registerLookTools(ctx, tools) {
|
|
4291
|
+
const lookTool = {
|
|
4292
|
+
name: DIY_LOOK_TOOL,
|
|
4293
|
+
description: "看一眼这张卡:只截它这一张 + 按你点的那几项读它的样式与字。要用户把工作台那一页切到前台。",
|
|
4294
|
+
parameters: {
|
|
4295
|
+
type: "object",
|
|
4296
|
+
properties: {
|
|
4297
|
+
unit: {
|
|
4298
|
+
type: "string",
|
|
4299
|
+
description: "工坊里的 unit(不填 = 工坊里只有一个就用它)。"
|
|
4300
|
+
},
|
|
4301
|
+
props: {
|
|
4302
|
+
type: "array",
|
|
4303
|
+
items: { type: "string" },
|
|
4304
|
+
description: "要读的 CSS 属性(含 --令牌;不填给默认档)。最多 40 个。"
|
|
4305
|
+
},
|
|
4306
|
+
parts: {
|
|
4307
|
+
type: "array",
|
|
4308
|
+
items: { type: "string" },
|
|
4309
|
+
description: "卡片里再看哪几块(选择器,限卡片内;不填 = 卡头与卡片体)。最多 6 个。"
|
|
4310
|
+
},
|
|
4311
|
+
dom: {
|
|
4312
|
+
type: "string",
|
|
4313
|
+
description: "结构快照:outline(默认,tag+类名+role+文本的浅层大纲)| html(卡片子树原始 HTML)| none。"
|
|
4314
|
+
},
|
|
4315
|
+
session: {
|
|
4316
|
+
type: "string",
|
|
4317
|
+
description: "会话名(默认 workbench,一般不用填)。"
|
|
4318
|
+
}
|
|
4319
|
+
},
|
|
4320
|
+
additionalProperties: false
|
|
4321
|
+
},
|
|
4322
|
+
output: OUTPUT$2,
|
|
4323
|
+
async execute(raw, exec) {
|
|
4324
|
+
const { packagesDir, unit, shell, command, session } = await pageAt(ctx, exec, raw);
|
|
4325
|
+
const pick = pickOf(asRecord$3(raw));
|
|
4326
|
+
let view = await measureCard(shell, command, session, unit, pick);
|
|
4327
|
+
if (!view.ok) return plainJson({
|
|
4328
|
+
workshop: packagesDir,
|
|
4329
|
+
unit,
|
|
4330
|
+
ok: false,
|
|
4331
|
+
cards: view.cards,
|
|
4332
|
+
text: renderMissingCard(unit, view.cards, view.anchored)
|
|
4333
|
+
});
|
|
4334
|
+
if (!insideViewportOf(view)) {
|
|
4335
|
+
await scrollIntoView(shell, command, session, unit, "");
|
|
4336
|
+
view = await measureCard(shell, command, session, unit, pick);
|
|
4337
|
+
}
|
|
4338
|
+
const file = join(dirname(packagesDir), "shots", `${unit}-${String(Date.now())}.png`);
|
|
4339
|
+
await writeShotsDir(shell, dirname(file));
|
|
4340
|
+
const shot = await shootCard(shell, command, session, unit, file, view);
|
|
4341
|
+
return plainJson({
|
|
4342
|
+
workshop: packagesDir,
|
|
4343
|
+
unit,
|
|
4344
|
+
ok: shot.ok,
|
|
4345
|
+
file: shot.file,
|
|
4346
|
+
rect: view.rect,
|
|
4347
|
+
cropped: shot.cropped,
|
|
4348
|
+
clipped: view.clipped,
|
|
4349
|
+
head: view.head,
|
|
4350
|
+
pick: view.pick,
|
|
4351
|
+
slot: view.slot,
|
|
4352
|
+
outline: view.outline,
|
|
4353
|
+
html: view.html,
|
|
4354
|
+
text: renderLook(unit, view, shot, pick)
|
|
4355
|
+
});
|
|
4356
|
+
}
|
|
4357
|
+
};
|
|
4358
|
+
return [tools.register(lookTool)];
|
|
4359
|
+
}
|
|
4360
|
+
function rgbOf(value) {
|
|
4361
|
+
const hit = /rgba?\((\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(value);
|
|
4362
|
+
if (hit === null) return void 0;
|
|
4363
|
+
return [
|
|
4364
|
+
Number(hit[1]),
|
|
4365
|
+
Number(hit[2]),
|
|
4366
|
+
Number(hit[3])
|
|
4367
|
+
];
|
|
4368
|
+
}
|
|
4369
|
+
function luminanceOf(rgb) {
|
|
4370
|
+
const channel = (one) => {
|
|
4371
|
+
const scaled = one / 255;
|
|
4372
|
+
return scaled <= .03928 ? scaled / 12.92 : ((scaled + .055) / 1.055) ** 2.4;
|
|
4373
|
+
};
|
|
4374
|
+
return .2126 * channel(rgb[0]) + .7152 * channel(rgb[1]) + .0722 * channel(rgb[2]);
|
|
4375
|
+
}
|
|
4376
|
+
function contrastOf(foreground, background) {
|
|
4377
|
+
const front = rgbOf(foreground);
|
|
4378
|
+
const back = rgbOf(background);
|
|
4379
|
+
if (front === void 0 || back === void 0) return void 0;
|
|
4380
|
+
const one = luminanceOf(front);
|
|
4381
|
+
const other = luminanceOf(back);
|
|
4382
|
+
const ratio = (Math.max(one, other) + .05) / (Math.min(one, other) + .05);
|
|
4383
|
+
return Math.round(ratio * 100) / 100;
|
|
4384
|
+
}
|
|
4385
|
+
function styleLineOf(view, pick) {
|
|
4386
|
+
return pick.props.filter((one) => (view.pick.card[one] ?? "") !== "").map((one) => `${one}=${view.pick.card[one] ?? ""}`).join(" · ");
|
|
4387
|
+
}
|
|
4388
|
+
function partsLineOf(view, pick) {
|
|
4389
|
+
return pick.parts.map((one) => {
|
|
4390
|
+
const found = view.pick.parts[one] ?? {};
|
|
4391
|
+
const inside = Object.entries(found).map(([key, value]) => `${key}=${value}`).join(" ");
|
|
4392
|
+
return `${one}${inside === "" ? "(页面里没有这个)" : ` ${inside}`}`;
|
|
4393
|
+
}).join("\n");
|
|
4394
|
+
}
|
|
4395
|
+
function slotText(slot, width) {
|
|
4396
|
+
const columns = slot.columns === "" || slot.columns === "none" ? "一列" : `多列(${slot.columns})`;
|
|
4397
|
+
const fit = slot.parentWidth === 0 ? "" : `、这张卡占 ${String(width)}/${String(slot.parentWidth)}px`;
|
|
4398
|
+
return `它在第 ${String(slot.index + 1)}/${String(slot.cards)} 张;卡片格子 ${String(slot.parentWidth)}px 宽、${columns}${fit};滚动容器可视 ${String(slot.viewportHeight)}px / 内容 ${String(slot.scrollHeight)}px`;
|
|
4399
|
+
}
|
|
4400
|
+
function slotLineOf(view) {
|
|
4401
|
+
return slotText(view.slot, view.width);
|
|
4402
|
+
}
|
|
4403
|
+
function outlineText(view) {
|
|
4404
|
+
return view.outline.map((one) => {
|
|
4405
|
+
const bits = [`${" ".repeat(one.depth)}${one.tag}${one.cls === "" ? "" : `.${one.cls}`}`];
|
|
4406
|
+
if (one.role !== "") bits.push(`role=${one.role}`);
|
|
4407
|
+
if (one.name !== "") bits.push(`name="${one.name}"`);
|
|
4408
|
+
if (one.text !== "") bits.push(`"${one.text}"`);
|
|
4409
|
+
return bits.join(" ");
|
|
4410
|
+
}).join("\n");
|
|
4411
|
+
}
|
|
4412
|
+
function contrastLineOf(view) {
|
|
4413
|
+
const ratio = contrastOf((view.pick.parts[".wb-cardBody"] ?? view.pick.card)["color"] ?? "", view.pick.card["backgroundColor"] ?? "");
|
|
4414
|
+
if (ratio === void 0) return "";
|
|
4415
|
+
return ratio >= 4.5 ? `正文对比度 ${String(ratio)}(够)` : `正文对比度 ${String(ratio)} —— 低于 4.5,正文会看不清(要改)`;
|
|
4416
|
+
}
|
|
4417
|
+
function renderLook(unit, view, shot, pick) {
|
|
4418
|
+
const where = `面板里 ${String(view.rect.x)},${String(view.rect.y)}(${String(view.width)}×${String(view.height)})`;
|
|
4419
|
+
return [
|
|
4420
|
+
shot.ok ? `看到了 ${unit} 这张卡:${where}` : `没拍成:${shot.reason}`,
|
|
4421
|
+
...shot.ok ? [shot.file] : [],
|
|
4422
|
+
...shot.ok && shot.reason !== "" ? [shot.reason] : [],
|
|
4423
|
+
...view.head === "" ? [] : [`卡头:${view.head}`],
|
|
4424
|
+
...view.text === "" ? [] : [`卡片上写着:${view.text}`],
|
|
4425
|
+
`格子里:${slotLineOf(view)}`,
|
|
4426
|
+
...styleLineOf(view, pick) === "" ? [] : [`你点的那几项:${styleLineOf(view, pick)}`],
|
|
4427
|
+
...[partsLineOf(view, pick)].filter((one) => one !== ""),
|
|
4428
|
+
...contrastLineOf(view) === "" ? [] : [contrastLineOf(view)],
|
|
4429
|
+
...pick.dom === "outline" && view.outline.length > 0 ? ["结构:", outlineText(view)] : [],
|
|
4430
|
+
...pick.dom === "html" ? [`原始 HTML:${String(view.html.length)} 字符(在返回的 html 字段里)`] : [],
|
|
4431
|
+
...view.clipped ? ["有内容被裁掉了(某一格比它自己的宽度还宽)—— 这是要改的信号"] : []
|
|
4432
|
+
].join("\n");
|
|
4433
|
+
}
|
|
4434
|
+
function renderMissingCard(unit, cards, anchored) {
|
|
4435
|
+
if (anchored) return `面板里给 ${unit} 留了位置(锚点在),但里面没有卡片 —— 它的 View 没画出来:要么渲染时抛了(看 workbench_diy_panel 的运行态上报与 workbench_diy_status),要么它返回了空 / 没套卡壳。你现在这一页面板里一共 ${String(cards)} 张卡。`;
|
|
4436
|
+
return `你现在这一页的面板里没有 ${unit} 这张卡(面板里现在有 ${String(cards)} 张卡)。三件事,按顺序试:① workbench_diy_show(unit=${unit})把它上板;② 右栏可能没停在「工作台面板」那一列 —— 让用户切回来;③ 页面上跑的外壳可能还是旧的一份(锚点由外壳盖)—— 让用户刷新一次页面。`;
|
|
4437
|
+
}
|
|
4438
|
+
//#endregion
|
|
4439
|
+
//#region src/modules/panels/model/diy-logs.ts
|
|
4440
|
+
var LOGS_KEY = "__dshDiyLogs";
|
|
4441
|
+
var LOGS_CAP = 200;
|
|
4442
|
+
var LOGS_TEXT_MAX = 400;
|
|
4443
|
+
var LEVELS = [
|
|
4444
|
+
"log",
|
|
4445
|
+
"info",
|
|
4446
|
+
"warn",
|
|
4447
|
+
"error",
|
|
4448
|
+
"debug"
|
|
4449
|
+
];
|
|
4450
|
+
var LOGS_ARM_SCRIPT = `(() => {
|
|
4451
|
+
const KEY = ${JSON.stringify(LOGS_KEY)};
|
|
4452
|
+
const CAP = ${String(LOGS_CAP)};
|
|
4453
|
+
const TEXT_MAX = ${String(LOGS_TEXT_MAX)};
|
|
4454
|
+
const LEVELS = ${JSON.stringify(LEVELS)};
|
|
4455
|
+
let buf = window[KEY];
|
|
4456
|
+
if (!Array.isArray(buf)) {
|
|
4457
|
+
buf = [];
|
|
4458
|
+
window[KEY] = buf;
|
|
4459
|
+
}
|
|
4460
|
+
const textOf = (value) => {
|
|
4461
|
+
try {
|
|
4462
|
+
if (typeof value === 'string') return value;
|
|
4463
|
+
const json = JSON.stringify(value);
|
|
4464
|
+
return typeof json === 'string' ? json : String(value);
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
return '[unprintable]';
|
|
4467
|
+
}
|
|
4468
|
+
};
|
|
4469
|
+
let hooked = 0;
|
|
4470
|
+
for (const level of LEVELS) {
|
|
4471
|
+
const current = console[level];
|
|
4472
|
+
if (typeof current === 'function' && current.__dshDiy === true) continue;
|
|
4473
|
+
const real = typeof current === 'function' ? current.bind(console) : () => {};
|
|
4474
|
+
const patched = (...args) => {
|
|
4475
|
+
try {
|
|
4476
|
+
buf.push({ level, text: args.map(textOf).join(' ').slice(0, TEXT_MAX), at: Date.now() });
|
|
4477
|
+
if (buf.length > CAP) buf.splice(0, buf.length - CAP);
|
|
4478
|
+
} catch (error) {}
|
|
4479
|
+
return real(...args);
|
|
4480
|
+
};
|
|
4481
|
+
patched.__dshDiy = true;
|
|
4482
|
+
patched.__real = typeof current === 'function' ? current : null;
|
|
4483
|
+
console[level] = patched;
|
|
4484
|
+
hooked += 1;
|
|
4485
|
+
}
|
|
4486
|
+
return { ok: true, cap: CAP, levels: LEVELS.length, hooked, size: buf.length };
|
|
4487
|
+
})()`;
|
|
4488
|
+
var LOGS_READ_SCRIPT = `(() => {
|
|
4489
|
+
const buf = window[${JSON.stringify(LOGS_KEY)}];
|
|
4490
|
+
if (!Array.isArray(buf)) return { ok: false, code: 'not-armed', seen: 0, lines: [] };
|
|
4491
|
+
return { ok: true, seen: buf.length, lines: buf.slice(-${String(LOGS_CAP)}) };
|
|
4492
|
+
})()`;
|
|
4493
|
+
var LOGS_STOP_SCRIPT = `(() => {
|
|
4494
|
+
const KEY = ${JSON.stringify(LOGS_KEY)};
|
|
4495
|
+
const LEVELS = ${JSON.stringify(LEVELS)};
|
|
4496
|
+
const buf = window[KEY];
|
|
4497
|
+
const dropped = Array.isArray(buf) ? buf.length : 0;
|
|
4498
|
+
let unhooked = 0;
|
|
4499
|
+
for (const level of LEVELS) {
|
|
4500
|
+
const current = console[level];
|
|
4501
|
+
if (typeof current !== 'function' || current.__dshDiy !== true) continue;
|
|
4502
|
+
if (typeof current.__real === 'function') console[level] = current.__real;
|
|
4503
|
+
else delete console[level];
|
|
4504
|
+
unhooked += 1;
|
|
4505
|
+
}
|
|
4506
|
+
try {
|
|
4507
|
+
delete window[KEY];
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
window[KEY] = undefined;
|
|
4510
|
+
}
|
|
4511
|
+
return { ok: true, unhooked, dropped };
|
|
4512
|
+
})()`;
|
|
4513
|
+
function logsReadOf(payload) {
|
|
4514
|
+
const raw = payload ?? {};
|
|
4515
|
+
const lines = Array.isArray(raw["lines"]) ? raw["lines"] : [];
|
|
4516
|
+
const kept = [];
|
|
4517
|
+
for (const one of lines) {
|
|
4518
|
+
if (typeof one !== "object" || one === null) continue;
|
|
4519
|
+
const record = one;
|
|
4520
|
+
const text = typeof record["text"] === "string" ? record["text"] : "";
|
|
4521
|
+
if (text === "") continue;
|
|
4522
|
+
kept.push({
|
|
4523
|
+
level: typeof record["level"] === "string" && record["level"] !== "" ? record["level"] : "log",
|
|
4524
|
+
text,
|
|
4525
|
+
at: typeof record["at"] === "number" ? record["at"] : 0
|
|
4526
|
+
});
|
|
4527
|
+
}
|
|
4528
|
+
const seen = typeof raw["seen"] === "number" ? raw["seen"] : kept.length;
|
|
4529
|
+
return {
|
|
4530
|
+
ok: raw["ok"] === true,
|
|
4531
|
+
code: typeof raw["code"] === "string" ? raw["code"] : "",
|
|
4532
|
+
seen: Math.max(seen, kept.length),
|
|
4533
|
+
lines: kept
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
function renderLogs(read) {
|
|
4537
|
+
if (!read.ok) return "这一页上还没有钩子(看不到控制台)—— 先 workbench_diy_logs_watch 装上,做完你要看的那件事,再 workbench_diy_logs_read。";
|
|
4538
|
+
if (read.lines.length === 0) return "钩子装上了,但这一页到现在一条 console.* 都没打过。";
|
|
4539
|
+
const shown = read.lines.slice(-50);
|
|
4540
|
+
const more = read.seen > shown.length ? `(攒了 ${String(read.seen)} 条,这里只列最近 ${String(shown.length)} 条)` : "";
|
|
4541
|
+
return [`这一页最近 ${String(shown.length)} 条控制台${more}:`, ...shown.map((one) => `[${one.level}] ${one.text}`)].join("\n");
|
|
4542
|
+
}
|
|
4543
|
+
function registerLogTools(ctx, tools) {
|
|
4544
|
+
const schema = {
|
|
4545
|
+
type: "object",
|
|
4546
|
+
properties: { session: {
|
|
4547
|
+
type: "string",
|
|
4548
|
+
description: "会话名(默认 workbench,一般不用填)。"
|
|
4549
|
+
} },
|
|
4550
|
+
additionalProperties: false
|
|
4551
|
+
};
|
|
4552
|
+
async function run(exec, raw, script) {
|
|
4553
|
+
const page = await pageAt(ctx, exec, raw, {
|
|
4554
|
+
requireUnit: false,
|
|
4555
|
+
requireWorkbench: false
|
|
4556
|
+
});
|
|
4557
|
+
return {
|
|
4558
|
+
page,
|
|
4559
|
+
step: await actBrowser(page.shell, page.command, page.session, ["eval", script])
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
const watchTool = {
|
|
4563
|
+
name: DIY_LOGS_WATCH_TOOL,
|
|
4564
|
+
description: "把这一页的控制台听上(每页最多 200 条、单条截 400 字,随页面刷新消失)。",
|
|
4565
|
+
parameters: schema,
|
|
4566
|
+
output: OUTPUT$2,
|
|
4567
|
+
async execute(raw, exec) {
|
|
4568
|
+
const { page, step } = await run(exec, raw, LOGS_ARM_SCRIPT);
|
|
4569
|
+
const error = actErrorOf(step);
|
|
4570
|
+
const hooked = typeof step.payload?.["hooked"] === "number" ? step.payload["hooked"] : 0;
|
|
4571
|
+
const size = typeof step.payload?.["size"] === "number" ? step.payload["size"] : 0;
|
|
4572
|
+
const text = error !== "" ? `没听上:${error}` : hooked === 0 ? `已经在听了(钩子还在,攒着 ${String(size)} 条)。做完那件事用 ${DIY_LOGS_READ_TOOL} 读。` : `听上了(装了 ${String(hooked)} 个钩子,最多 ${String(LOGS_CAP)} 条):去做你要看的那件事,回头用 ${DIY_LOGS_READ_TOOL} 读;读完用 ${DIY_LOGS_STOP_TOOL} 卸掉。`;
|
|
4573
|
+
return plainJson({
|
|
4574
|
+
workshop: page.packagesDir,
|
|
4575
|
+
ok: step.ok && error === "",
|
|
4576
|
+
hooked,
|
|
4577
|
+
cap: LOGS_CAP,
|
|
4578
|
+
text
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
};
|
|
4582
|
+
const readTool = {
|
|
4583
|
+
name: DIY_LOGS_READ_TOOL,
|
|
4584
|
+
description: "读刚才听下来的控制台(最近 50 条;没装钩子会说清,不假装是\"没有报错\")。",
|
|
4585
|
+
parameters: schema,
|
|
4586
|
+
output: OUTPUT$2,
|
|
4587
|
+
async execute(raw, exec) {
|
|
4588
|
+
const { page, step } = await run(exec, raw, LOGS_READ_SCRIPT);
|
|
4589
|
+
const error = actErrorOf(step);
|
|
4590
|
+
const read = logsReadOf(step.payload);
|
|
4591
|
+
const text = error !== "" ? `读不到:${error}` : renderLogs(read);
|
|
4592
|
+
return plainJson({
|
|
4593
|
+
workshop: page.packagesDir,
|
|
4594
|
+
ok: step.ok && error === "",
|
|
4595
|
+
seen: read.seen,
|
|
4596
|
+
lines: read.lines.slice(-50),
|
|
4597
|
+
text
|
|
4598
|
+
});
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4601
|
+
const stopTool = {
|
|
4602
|
+
name: DIY_LOGS_STOP_TOOL,
|
|
4603
|
+
description: "控制台收听收工:卸掉钩子、扔掉攒下的记录。",
|
|
4604
|
+
parameters: schema,
|
|
4605
|
+
output: OUTPUT$2,
|
|
4606
|
+
async execute(raw, exec) {
|
|
4607
|
+
const { page, step } = await run(exec, raw, LOGS_STOP_SCRIPT);
|
|
4608
|
+
const error = actErrorOf(step);
|
|
4609
|
+
const unhooked = typeof step.payload?.["unhooked"] === "number" ? step.payload["unhooked"] : 0;
|
|
4610
|
+
const dropped = typeof step.payload?.["dropped"] === "number" ? step.payload["dropped"] : 0;
|
|
4611
|
+
const text = error !== "" ? `没卸干净:${error}` : `收工了:卸掉 ${String(unhooked)} 个钩子、扔掉 ${String(dropped)} 条记录。`;
|
|
4612
|
+
return plainJson({
|
|
4613
|
+
workshop: page.packagesDir,
|
|
4614
|
+
ok: step.ok && error === "",
|
|
4615
|
+
unhooked,
|
|
4616
|
+
dropped,
|
|
4617
|
+
text
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
};
|
|
4621
|
+
return [
|
|
4622
|
+
tools.register(watchTool),
|
|
4623
|
+
tools.register(readTool),
|
|
4624
|
+
tools.register(stopTool)
|
|
4625
|
+
];
|
|
4626
|
+
}
|
|
4627
|
+
//#endregion
|
|
4628
|
+
//#region src/modules/panels/model/diy-schema.ts
|
|
4629
|
+
var UNIT_PROP = {
|
|
4630
|
+
type: "string",
|
|
4631
|
+
description: "工坊里的 unit(不填 = 工坊里只有一个就用它)。"
|
|
4632
|
+
};
|
|
4633
|
+
var SESSION_PROP = {
|
|
4634
|
+
type: "string",
|
|
4635
|
+
description: "会话名(默认 workbench,一般不用填)。"
|
|
4636
|
+
};
|
|
4637
|
+
function pageSchema(extra = {}, required = []) {
|
|
4638
|
+
return {
|
|
4639
|
+
type: "object",
|
|
4640
|
+
properties: {
|
|
4641
|
+
unit: UNIT_PROP,
|
|
4642
|
+
...extra,
|
|
4643
|
+
session: SESSION_PROP
|
|
4644
|
+
},
|
|
4645
|
+
...required.length === 0 ? {} : { required },
|
|
4646
|
+
additionalProperties: false
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
//#endregion
|
|
4650
|
+
//#region src/modules/panels/model/diy-net.ts
|
|
4651
|
+
var SIBLING_TRIES = 6;
|
|
4652
|
+
var NET_BODY_MAX = 2e4;
|
|
4653
|
+
function nearestKeyOf(payload, key) {
|
|
4654
|
+
const entries = payload?.["entries"];
|
|
4655
|
+
if (!Array.isArray(entries)) return "";
|
|
4656
|
+
let recent = "";
|
|
4657
|
+
for (const one of entries) {
|
|
4658
|
+
if (typeof one !== "object" || one === null) continue;
|
|
4659
|
+
const candidate = one["key"];
|
|
4660
|
+
if (typeof candidate !== "string" || candidate === "") continue;
|
|
4661
|
+
if (candidate === key) return key;
|
|
4662
|
+
if (candidate.startsWith(`${key}#`)) recent = candidate;
|
|
4663
|
+
}
|
|
4664
|
+
return recent;
|
|
4665
|
+
}
|
|
4666
|
+
function registerNetTools(ctx, tools) {
|
|
4667
|
+
async function netDetail(page, key) {
|
|
4668
|
+
return await actBrowser(page.shell, page.command, page.session, [
|
|
4669
|
+
"network",
|
|
4670
|
+
"--detail",
|
|
4671
|
+
key,
|
|
4672
|
+
"--max-body",
|
|
4673
|
+
String(NET_BODY_MAX)
|
|
4674
|
+
]);
|
|
4675
|
+
}
|
|
4676
|
+
async function netFileOf(exec, raw) {
|
|
4677
|
+
const page = await pageAt(ctx, exec, raw, {
|
|
4678
|
+
requireWorkbench: false,
|
|
4679
|
+
requireUnit: false
|
|
4680
|
+
});
|
|
4681
|
+
return {
|
|
4682
|
+
page,
|
|
4683
|
+
file: join(dirname(page.packagesDir), "shots", `net-${page.session}.jsonl`)
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
const netWatchTool = {
|
|
4687
|
+
name: DIY_NET_WATCH_TOOL,
|
|
4688
|
+
description: `把网络捕获臂上(opencli 的 network 不臂上就收不到):随后你在这页上做的事都会被记下(最多 ${String(NET_CAP_LINES)} 行 / ${String(Math.round(15))} 分钟后自己收工)。`,
|
|
4689
|
+
parameters: pageSchema(),
|
|
4690
|
+
output: OUTPUT$2,
|
|
4691
|
+
async execute(raw, exec) {
|
|
4692
|
+
const { page, file } = await netFileOf(exec, raw);
|
|
4693
|
+
await writeShotsDir(page.shell, dirname(file));
|
|
4694
|
+
await stopNetwork(page.shell, page.session, file);
|
|
4695
|
+
const armed = await armNetwork(page.shell, page.command, page.session, file);
|
|
4696
|
+
return plainJson({
|
|
4697
|
+
workshop: page.packagesDir,
|
|
4698
|
+
unit: page.unit,
|
|
4699
|
+
ok: armed.ok,
|
|
4700
|
+
pid: armed.pid,
|
|
4701
|
+
capLines: NET_CAP_LINES,
|
|
4702
|
+
capSeconds: 900,
|
|
4703
|
+
text: armed.ok ? `捕获臂上了(pid ${armed.pid};最多 ${String(NET_CAP_LINES)} 行、${String(900)} 秒后自己收工)。现在去做你要做的事,回头用 ${DIY_NET_READ_TOOL} 读;读完 ${DIY_NET_STOP_TOOL} 可以提前收工。` : `臂不上:${armed.reason}`
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
const netReadTool = {
|
|
4708
|
+
name: DIY_NET_READ_TOOL,
|
|
4709
|
+
description: "读刚才臂上之后记下的网络(给 key 就取那一条的完整 body)。",
|
|
4710
|
+
parameters: pageSchema({
|
|
4711
|
+
lines: {
|
|
4712
|
+
type: "number",
|
|
4713
|
+
description: "读最近几条(默认 60)。"
|
|
4714
|
+
},
|
|
4715
|
+
failed: {
|
|
4716
|
+
type: "boolean",
|
|
4717
|
+
description: "只看失败请求(0 或 >= 400)。"
|
|
4718
|
+
},
|
|
4719
|
+
grep: {
|
|
4720
|
+
type: "string",
|
|
4721
|
+
description: "再按 URL 里的一段词收。"
|
|
4722
|
+
},
|
|
4723
|
+
key: {
|
|
4724
|
+
type: "string",
|
|
4725
|
+
description: "给 key = 取这一条请求的完整 body(read 的返回里带 key)。"
|
|
4726
|
+
}
|
|
4727
|
+
}),
|
|
4728
|
+
output: OUTPUT$2,
|
|
4729
|
+
async execute(raw, exec) {
|
|
4730
|
+
const { page, file } = await netFileOf(exec, raw);
|
|
4731
|
+
const key = asText$1(page.args["key"]);
|
|
4732
|
+
if (key !== "") {
|
|
4733
|
+
const tried = [key];
|
|
4734
|
+
let used = key;
|
|
4735
|
+
let detail = await netDetail(page, key);
|
|
4736
|
+
for (let n = 2; n <= SIBLING_TRIES && !detail.ok; n += 1) {
|
|
4737
|
+
const sibling = `${key}#${String(n)}`;
|
|
4738
|
+
const attempt = await netDetail(page, sibling);
|
|
4739
|
+
tried.push(sibling);
|
|
4740
|
+
if (attempt.ok) {
|
|
4741
|
+
detail = attempt;
|
|
4742
|
+
used = sibling;
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
let refreshed = false;
|
|
4746
|
+
if (!detail.ok) {
|
|
4747
|
+
const capture = await actBrowser(page.shell, page.command, page.session, ["network"]);
|
|
4748
|
+
const grabbed = Array.isArray(capture.payload?.["entries"]) ? capture.payload["entries"].length : 0;
|
|
4749
|
+
refreshed = capture.ok && grabbed > 0;
|
|
4750
|
+
if (refreshed) {
|
|
4751
|
+
const retry = await netDetail(page, key);
|
|
4752
|
+
tried.push(key);
|
|
4753
|
+
if (retry.ok) {
|
|
4754
|
+
detail = retry;
|
|
4755
|
+
used = key;
|
|
4756
|
+
} else {
|
|
4757
|
+
const alt = nearestKeyOf(capture.payload, key);
|
|
4758
|
+
if (alt !== "" && alt !== key) {
|
|
4759
|
+
const attempt = await netDetail(page, alt);
|
|
4760
|
+
tried.push(alt);
|
|
4761
|
+
if (attempt.ok) {
|
|
4762
|
+
detail = attempt;
|
|
4763
|
+
used = alt;
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
return plainJson({
|
|
4770
|
+
workshop: page.packagesDir,
|
|
4771
|
+
unit: page.unit,
|
|
4772
|
+
ok: detail.ok,
|
|
4773
|
+
key: used,
|
|
4774
|
+
tried,
|
|
4775
|
+
refreshed,
|
|
4776
|
+
detail: detail.payload,
|
|
4777
|
+
text: detail.ok ? `${used} 的完整 body 在 detail.body 里${used === key ? "" : `(给的 key 不在缓存里,取了同名的 ${used})`}${refreshed ? "(先跑了一次性抓取把缓存落下来)" : ""}。` : `没取到:${actErrorOf(detail)} —— 试过 ${String(tried.length)} 个 key(同名请求在缓存里会带 #2/#3…),缓存里都没有${refreshed ? "" : ",而刚才那次补抓一个请求也没抓到"}。重抓一次(${DIY_NET_WATCH_TOOL} → 做那件事 → ${DIY_NET_READ_TOOL})之后趁热再取;要的只是这一次接口的返回值的话,用 workbench_diy_probe 在页面里直接读更快。`
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
const lines = asNumber(page.args["lines"]) ?? 60;
|
|
4781
|
+
const onlyFailed = page.args["failed"] === true;
|
|
4782
|
+
const grep = asText$1(page.args["grep"]);
|
|
4783
|
+
const all = await readNetwork(page.shell, file, Math.max(1, Math.min(500, lines)));
|
|
4784
|
+
const entries = all.filter((one) => {
|
|
4785
|
+
const status = typeof one["status"] === "number" ? one["status"] : 0;
|
|
4786
|
+
if (onlyFailed && !(status === 0 || status >= 400)) return false;
|
|
4787
|
+
if (grep !== "" && !String(one["url"] ?? "").includes(grep)) return false;
|
|
4788
|
+
return true;
|
|
4789
|
+
});
|
|
4790
|
+
return plainJson({
|
|
4791
|
+
workshop: page.packagesDir,
|
|
4792
|
+
unit: page.unit,
|
|
4793
|
+
ok: true,
|
|
4794
|
+
count: entries.length,
|
|
4795
|
+
seen: all.length,
|
|
4796
|
+
entries: entries.slice(-40),
|
|
4797
|
+
text: all.length === 0 ? "这份捕获是空的 —— 先用 workbench_diy_net_watch 臂上,再去做那件事,然后回来读。" : `${String(entries.length)}/${String(all.length)} 条(最近的在后面)。每条的 key 可以拿去取完整 body:${DIY_NET_READ_TOOL} key="<某条的 key>"。`
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
};
|
|
4801
|
+
const netStopTool = {
|
|
4802
|
+
name: DIY_NET_STOP_TOOL,
|
|
4803
|
+
description: "网络捕获收工(把臂上的那条 follower 停掉)。",
|
|
4804
|
+
parameters: pageSchema(),
|
|
4805
|
+
output: OUTPUT$2,
|
|
4806
|
+
async execute(raw, exec) {
|
|
4807
|
+
const { page, file } = await netFileOf(exec, raw);
|
|
4808
|
+
const stopped = await stopNetwork(page.shell, page.session, file);
|
|
4809
|
+
return plainJson({
|
|
4810
|
+
workshop: page.packagesDir,
|
|
4811
|
+
unit: page.unit,
|
|
4812
|
+
ok: stopped,
|
|
4813
|
+
text: stopped ? `捕获收工了(follower 与看门狗都停掉)。` : `收工那一下没跑成(命令退出码非 0)—— 再调一次 ${DIY_NET_STOP_TOOL};还不行看一眼 shots/ 那份 .pid / .reaper。`
|
|
4814
|
+
});
|
|
4815
|
+
}
|
|
4816
|
+
};
|
|
4817
|
+
return [
|
|
4818
|
+
tools.register(netWatchTool),
|
|
4819
|
+
tools.register(netReadTool),
|
|
4820
|
+
tools.register(netStopTool)
|
|
4821
|
+
];
|
|
4822
|
+
}
|
|
4823
|
+
//#endregion
|
|
4824
|
+
//#region src/modules/panels/model/diy-act.ts
|
|
4825
|
+
var TARGET_PROPS = {
|
|
4826
|
+
selector: {
|
|
4827
|
+
type: "string",
|
|
4828
|
+
description: "卡内的 CSS 选择器(默认只在这张卡里找;scope=page 才是整页)。"
|
|
4829
|
+
},
|
|
4830
|
+
role: {
|
|
4831
|
+
type: "string",
|
|
4832
|
+
description: "语义定位:角色(button / link / textbox / option…)。"
|
|
4833
|
+
},
|
|
4834
|
+
name: {
|
|
4835
|
+
type: "string",
|
|
4836
|
+
description: "语义定位:无障碍名(aria-label / 可见文字)包含这段。"
|
|
4837
|
+
},
|
|
4838
|
+
testid: {
|
|
4839
|
+
type: "string",
|
|
4840
|
+
description: "语义定位:data-testid 包含这段。"
|
|
4841
|
+
},
|
|
4842
|
+
nth: {
|
|
4843
|
+
type: "number",
|
|
4844
|
+
description: "匹配到多个时取第几个(0 起)。"
|
|
4845
|
+
},
|
|
4846
|
+
scope: {
|
|
4847
|
+
type: "string",
|
|
4848
|
+
description: "card(默认,只在这张卡里找)| page(整页找)。"
|
|
4849
|
+
}
|
|
4850
|
+
};
|
|
4851
|
+
function targetSchema(extra = {}, required = []) {
|
|
4852
|
+
return {
|
|
4853
|
+
type: "object",
|
|
4854
|
+
properties: {
|
|
4855
|
+
unit: UNIT_PROP,
|
|
4856
|
+
...TARGET_PROPS,
|
|
4857
|
+
...extra,
|
|
4858
|
+
session: SESSION_PROP
|
|
4859
|
+
},
|
|
4860
|
+
...required.length === 0 ? {} : { required },
|
|
4861
|
+
additionalProperties: false
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
function hasTarget(target) {
|
|
4865
|
+
return target.selector !== "" || target.role !== "" || target.name !== "" || target.testid !== "";
|
|
4866
|
+
}
|
|
4867
|
+
function describeTarget(target, selector) {
|
|
4868
|
+
if (selector !== "") return `\`${selector}\``;
|
|
4869
|
+
const bits = [];
|
|
4870
|
+
if (target.role !== "") bits.push(`role=${target.role}`);
|
|
4871
|
+
if (target.name !== "") bits.push(`name=${target.name}`);
|
|
4872
|
+
if (target.testid !== "") bits.push(`testid=${target.testid}`);
|
|
4873
|
+
return bits.join(" · ");
|
|
4874
|
+
}
|
|
4875
|
+
function renderAct(verb, target, selector, step) {
|
|
4876
|
+
const matches = matchesOf(step);
|
|
4877
|
+
if (matches === 0) return `没找到要${verb}的东西(${describeTarget(target, selector)})—— 用 workbench_diy_look 的 dom=outline 看一眼这张卡里到底有什么。`;
|
|
4878
|
+
const error = actErrorOf(step);
|
|
4879
|
+
if (error !== "") return `${verb}没成:${error}`;
|
|
4880
|
+
const many = matches > 1 ? `(匹配 ${String(matches)} 个,用的是第 ${String((target.nth ?? 0) + 1)} 个)` : "";
|
|
4881
|
+
return `${verb}了 ${describeTarget(target, selector)}${many}。看一眼 workbench_diy_look 就知道这一下有没有生效。`;
|
|
4882
|
+
}
|
|
4883
|
+
function registerActTools(ctx, tools) {
|
|
4884
|
+
async function resolveTarget(page, target, verb) {
|
|
4885
|
+
if (target.selector !== "") return {
|
|
4886
|
+
selector: scopedSelectorOf(page.unit, target),
|
|
4887
|
+
options: [],
|
|
4888
|
+
token: "",
|
|
4889
|
+
via: "css"
|
|
4890
|
+
};
|
|
4891
|
+
const semantic = semanticOptionsOf(target);
|
|
4892
|
+
if (target.page || semantic.length === 0) return {
|
|
4893
|
+
selector: "",
|
|
4894
|
+
options: targetOptionsOf(target),
|
|
4895
|
+
token: "",
|
|
4896
|
+
via: "page-semantic"
|
|
4897
|
+
};
|
|
4898
|
+
const found = await actBrowser(page.shell, page.command, page.session, [
|
|
4899
|
+
"find",
|
|
4900
|
+
...semantic,
|
|
4901
|
+
"--limit",
|
|
4902
|
+
"200"
|
|
4903
|
+
]);
|
|
4904
|
+
const refs = refsOf(found.payload);
|
|
4905
|
+
const total = totalMatchesOf(found.payload) || refs.length;
|
|
4906
|
+
if (refs.length === 0) throw new Error(`${verb}谁?这一页上没有匹配 ${describeTarget(target, "")} 的东西 —— 用 workbench_diy_look 的 dom=outline 看一眼这张卡里有什么,或者换成卡内 CSS(selector)。`);
|
|
4907
|
+
const token = `diy-${Date.now().toString(36)}`;
|
|
4908
|
+
const nth = target.nth ?? 0;
|
|
4909
|
+
const marked = await actBrowser(page.shell, page.command, page.session, ["eval", scopeMarkScriptOf(page.unit, refs, nth, token)]);
|
|
4910
|
+
if (marked.payload?.["ok"] !== true) {
|
|
4911
|
+
const inside = marked.payload?.["inside"];
|
|
4912
|
+
const why = marked.payload?.["code"] === "no-card" ? `这一页上没有 ${page.unit} 这张卡` : `卡内只有 ${String(typeof inside === "number" ? inside : 0)} 个匹配,拿不出第 ${String(nth + 1)} 个`;
|
|
4913
|
+
throw new Error(`${describeTarget(target, "")} 这一页上匹配了 ${String(total)} 个,但${why} —— 是不是 unit 给错了?也可以在 selector 里点名卡内 CSS,或 scope=page 明说要整页那一个。`);
|
|
4914
|
+
}
|
|
4915
|
+
return {
|
|
4916
|
+
selector: scopedMarkerSelectorOf(page.unit, token),
|
|
4917
|
+
options: [],
|
|
4918
|
+
token,
|
|
4919
|
+
via: "card-semantic"
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4922
|
+
async function runTargeted(exec, raw, verb, build, needSelector = false) {
|
|
4923
|
+
const page = await pageAt(ctx, exec, raw);
|
|
4924
|
+
const target = actTargetOf(page.args);
|
|
4925
|
+
if (!hasTarget(target)) throw new Error(`${verb}谁?给一个 selector(卡内 CSS),或 role / name / testid 之一。`);
|
|
4926
|
+
const resolved = await resolveTarget(page, target, verb);
|
|
4927
|
+
if (needSelector && resolved.selector === "") throw new Error(`${verb}要一个 selector:输入框得点名才敢动手(语义定位请去掉 scope=page)。`);
|
|
4928
|
+
const step = await actBrowser(page.shell, page.command, page.session, [...build(resolved.selector, target), ...resolved.options]);
|
|
4929
|
+
if (resolved.token !== "") await actBrowser(page.shell, page.command, page.session, ["eval", scopeClearScriptOf(resolved.token)]);
|
|
4930
|
+
return plainJson({
|
|
4931
|
+
workshop: page.packagesDir,
|
|
4932
|
+
unit: page.unit,
|
|
4933
|
+
ok: step.ok,
|
|
4934
|
+
verb,
|
|
4935
|
+
via: resolved.via,
|
|
4936
|
+
matches: matchesOf(step),
|
|
4937
|
+
text: renderAct(verb, target, target.selector === "" ? "" : resolved.selector, step) + (resolved.via === "card-semantic" ? `(语义定位收进了 ${page.unit} 这张卡)` : "")
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
const clickTool = {
|
|
4941
|
+
name: DIY_CLICK_TOOL,
|
|
4942
|
+
description: "在这张卡里点一下(按钮 / 链接 / 行)。默认只在这张卡里找目标,找不到就直说。",
|
|
4943
|
+
parameters: targetSchema(),
|
|
4944
|
+
output: OUTPUT$2,
|
|
4945
|
+
execute: (raw, exec) => runTargeted(exec, raw, "点", (selector) => selector === "" ? ["click"] : ["click", selector])
|
|
4946
|
+
};
|
|
4947
|
+
const typeTool = {
|
|
4948
|
+
name: DIY_TYPE_TOOL,
|
|
4949
|
+
description: "往这张卡里的某个输入框敲字(先点它再敲)—— 要一个 selector 与 value。",
|
|
4950
|
+
parameters: targetSchema({ value: {
|
|
4951
|
+
type: "string",
|
|
4952
|
+
description: "要敲进去的字。"
|
|
4953
|
+
} }, ["selector", "value"]),
|
|
4954
|
+
output: OUTPUT$2,
|
|
4955
|
+
execute: (raw, exec) => runTargeted(exec, raw, "敲", (selector) => [
|
|
4956
|
+
"type",
|
|
4957
|
+
selector,
|
|
4958
|
+
asText$1(asRecord$3(raw)["value"])
|
|
4959
|
+
], true)
|
|
4960
|
+
};
|
|
4961
|
+
const fillTool = {
|
|
4962
|
+
name: DIY_FILL_TOOL,
|
|
4963
|
+
description: "把这张卡里某个输入框的值直接设成这段字并核对(不改别处)—— 要一个 selector 与 value。",
|
|
4964
|
+
parameters: targetSchema({ value: {
|
|
4965
|
+
type: "string",
|
|
4966
|
+
description: "要设进去的值。"
|
|
4967
|
+
} }, ["selector", "value"]),
|
|
4968
|
+
output: OUTPUT$2,
|
|
4969
|
+
execute: (raw, exec) => runTargeted(exec, raw, "填", (selector) => [
|
|
4970
|
+
"fill",
|
|
4971
|
+
selector,
|
|
4972
|
+
asText$1(asRecord$3(raw)["value"])
|
|
4973
|
+
], true)
|
|
4974
|
+
};
|
|
4975
|
+
const keysTool = {
|
|
4976
|
+
name: DIY_KEYS_TOOL,
|
|
4977
|
+
description: "在这一页上按一个键(Enter / Escape / Tab / Control+a)—— 收下拉、提交、挪焦点用它。",
|
|
4978
|
+
parameters: pageSchema({ key: {
|
|
4979
|
+
type: "string",
|
|
4980
|
+
description: "要按的键。"
|
|
4981
|
+
} }, ["key"]),
|
|
4982
|
+
output: OUTPUT$2,
|
|
4983
|
+
async execute(raw, exec) {
|
|
4984
|
+
const page = await pageAt(ctx, exec, raw, { requireUnit: false });
|
|
4985
|
+
const key = asText$1(page.args["key"]);
|
|
4986
|
+
if (key === "") throw new Error("按哪个键?给 key(Enter / Escape / Tab / Control+a)。");
|
|
4987
|
+
const step = await actBrowser(page.shell, page.command, page.session, ["keys", key]);
|
|
4988
|
+
const error = actErrorOf(step);
|
|
4989
|
+
return plainJson({
|
|
4990
|
+
workshop: page.packagesDir,
|
|
4991
|
+
unit: page.unit,
|
|
4992
|
+
ok: step.ok,
|
|
4993
|
+
verb: "keys",
|
|
4994
|
+
text: error === "" ? `按了 ${key}。` : `按键没成:${error}`
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
};
|
|
4998
|
+
const hoverTool = {
|
|
4999
|
+
name: DIY_HOVER_TOOL,
|
|
5000
|
+
description: "把鼠标移到这张卡里的某个东西上(悬停才出来的东西靠它)。",
|
|
5001
|
+
parameters: targetSchema(),
|
|
5002
|
+
output: OUTPUT$2,
|
|
5003
|
+
execute: (raw, exec) => runTargeted(exec, raw, "悬停", (selector) => selector === "" ? ["hover"] : ["hover", selector])
|
|
5004
|
+
};
|
|
5005
|
+
const selectTool = {
|
|
5006
|
+
name: DIY_SELECT_TOOL,
|
|
5007
|
+
description: "在这张卡的下拉里选一项 —— 要一个 selector 与选项文字。",
|
|
5008
|
+
parameters: targetSchema({ value: {
|
|
5009
|
+
type: "string",
|
|
5010
|
+
description: "选项文字(或 value)。"
|
|
5011
|
+
} }, ["selector", "value"]),
|
|
5012
|
+
output: OUTPUT$2,
|
|
5013
|
+
execute: (raw, exec) => runTargeted(exec, raw, "选", (selector) => [
|
|
5014
|
+
"select",
|
|
5015
|
+
selector,
|
|
5016
|
+
asText$1(asRecord$3(raw)["value"])
|
|
5017
|
+
], true)
|
|
5018
|
+
};
|
|
5019
|
+
const scrollTool = {
|
|
5020
|
+
name: DIY_SCROLL_TOOL,
|
|
5021
|
+
description: "滚这一页(direction + amount),或把这张卡 / 卡里某块滚到看得见(into)。",
|
|
5022
|
+
parameters: pageSchema({
|
|
5023
|
+
direction: {
|
|
5024
|
+
type: "string",
|
|
5025
|
+
description: "up | down(不给 into 时用它)。"
|
|
5026
|
+
},
|
|
5027
|
+
amount: {
|
|
5028
|
+
type: "number",
|
|
5029
|
+
description: "滚多少像素(默认 500)。"
|
|
5030
|
+
},
|
|
5031
|
+
into: {
|
|
5032
|
+
type: "string",
|
|
5033
|
+
description: "滚到看得见:留空 = 这张卡;填卡内 CSS = 卡里那一块。"
|
|
5034
|
+
}
|
|
5035
|
+
}),
|
|
5036
|
+
output: OUTPUT$2,
|
|
5037
|
+
async execute(raw, exec) {
|
|
5038
|
+
const page = await pageAt(ctx, exec, raw);
|
|
5039
|
+
const into = page.args["into"];
|
|
5040
|
+
if (into !== void 0) {
|
|
5041
|
+
const step = await scrollIntoView(page.shell, page.command, page.session, page.unit, asText$1(into));
|
|
5042
|
+
const found = step.payload?.["found"] === true;
|
|
5043
|
+
return plainJson({
|
|
5044
|
+
workshop: page.packagesDir,
|
|
5045
|
+
unit: page.unit,
|
|
5046
|
+
ok: step.ok && found,
|
|
5047
|
+
verb: "scroll",
|
|
5048
|
+
text: actErrorOf(step) !== "" ? `滚不动:${actErrorOf(step)}` : found ? "滚到看得见了。" : `卡里没有要滚的那一块(${asText$1(into) || "卡片本身"})。`
|
|
5049
|
+
});
|
|
5050
|
+
}
|
|
5051
|
+
const direction = asText$1(page.args["direction"]) || "down";
|
|
5052
|
+
const amount = asNumber(page.args["amount"]) ?? 500;
|
|
5053
|
+
const step = await actBrowser(page.shell, page.command, page.session, [
|
|
5054
|
+
"scroll",
|
|
5055
|
+
direction,
|
|
5056
|
+
"--amount",
|
|
5057
|
+
String(amount)
|
|
5058
|
+
]);
|
|
5059
|
+
const error = actErrorOf(step);
|
|
5060
|
+
return plainJson({
|
|
5061
|
+
workshop: page.packagesDir,
|
|
5062
|
+
unit: page.unit,
|
|
5063
|
+
ok: step.ok,
|
|
5064
|
+
verb: "scroll",
|
|
5065
|
+
text: error === "" ? `这一页往${direction === "up" ? "上" : "下"}滚了 ${String(amount)}px。` : `滚不动:${error}`
|
|
5066
|
+
});
|
|
5067
|
+
}
|
|
5068
|
+
};
|
|
5069
|
+
const reloadTool = {
|
|
5070
|
+
name: DIY_RELOAD_TOOL,
|
|
5071
|
+
description: "刷新用户正开着的这一页(页面上跑的外壳/共享包是旧的一份时用它;卡片自己会热更新,一般不用)。",
|
|
5072
|
+
parameters: pageSchema(),
|
|
5073
|
+
output: OUTPUT$2,
|
|
5074
|
+
async execute(raw, exec) {
|
|
5075
|
+
const page = await pageAt(ctx, exec, raw, {
|
|
5076
|
+
requireWorkbench: false,
|
|
5077
|
+
requireUnit: false
|
|
5078
|
+
});
|
|
5079
|
+
const step = await reloadPage(page.shell, page.command, page.session);
|
|
5080
|
+
const error = actErrorOf(step);
|
|
5081
|
+
return plainJson({
|
|
5082
|
+
workshop: page.packagesDir,
|
|
5083
|
+
unit: page.unit,
|
|
5084
|
+
ok: step.ok,
|
|
5085
|
+
verb: "reload",
|
|
5086
|
+
text: error === "" ? "让这一页自己刷新了(等两秒再看)。" : `刷新没成:${error}`
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
};
|
|
5090
|
+
const viewportTool = {
|
|
5091
|
+
name: DIY_VIEWPORT_TOOL,
|
|
5092
|
+
description: "按某个宽度重拍一张整屏图(看窄屏/宽屏下这张卡挤不挤)—— 只在这一拍里换视口。",
|
|
5093
|
+
parameters: pageSchema({
|
|
5094
|
+
width: {
|
|
5095
|
+
type: "number",
|
|
5096
|
+
description: "视口宽度(CSS px,例如 390)。"
|
|
5097
|
+
},
|
|
5098
|
+
height: {
|
|
5099
|
+
type: "number",
|
|
5100
|
+
description: "视口高度(可不要)。"
|
|
5101
|
+
}
|
|
5102
|
+
}, ["width"]),
|
|
5103
|
+
output: OUTPUT$2,
|
|
5104
|
+
async execute(raw, exec) {
|
|
5105
|
+
const page = await pageAt(ctx, exec, raw, { requireUnit: false });
|
|
5106
|
+
const width = asNumber(page.args["width"]);
|
|
5107
|
+
if (width === void 0 || width <= 0) throw new Error("多宽?给 width(CSS px)。");
|
|
5108
|
+
const height = asNumber(page.args["height"]) ?? 0;
|
|
5109
|
+
const file = join(dirname(page.packagesDir), "shots", `${page.unit || "page"}-${String(width)}-${String(Date.now())}.png`);
|
|
5110
|
+
await writeShotsDir(page.shell, dirname(file));
|
|
5111
|
+
const shot = await shootAtWidth(page.shell, page.command, page.session, file, width, height);
|
|
5112
|
+
return plainJson({
|
|
5113
|
+
workshop: page.packagesDir,
|
|
5114
|
+
unit: page.unit,
|
|
5115
|
+
ok: shot.ok,
|
|
5116
|
+
file: shot.file,
|
|
5117
|
+
width,
|
|
5118
|
+
viewport: {
|
|
5119
|
+
width,
|
|
5120
|
+
height
|
|
5121
|
+
},
|
|
5122
|
+
text: shot.ok ? `按 ${String(width)}px 宽重拍了一张:${shot.file}(页面按新视口回流,只在这一拍里生效)。` : `没拍成:${shot.reason}`
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
5125
|
+
};
|
|
5126
|
+
return [
|
|
5127
|
+
tools.register(clickTool),
|
|
5128
|
+
tools.register(typeTool),
|
|
5129
|
+
tools.register(fillTool),
|
|
5130
|
+
tools.register(keysTool),
|
|
5131
|
+
tools.register(hoverTool),
|
|
5132
|
+
tools.register(selectTool),
|
|
5133
|
+
tools.register(scrollTool),
|
|
5134
|
+
tools.register(reloadTool),
|
|
5135
|
+
tools.register(viewportTool)
|
|
5136
|
+
];
|
|
5137
|
+
}
|
|
5138
|
+
//#endregion
|
|
5139
|
+
//#region src/shared/api/host/diy-recon.ts
|
|
5140
|
+
var SITE_LIMIT = 6;
|
|
5141
|
+
var COMMAND_LIMIT = 12;
|
|
5142
|
+
var SHOWN_LIMIT = 6;
|
|
5143
|
+
var PLUGIN_PREFIX = "opencli-plugin-";
|
|
5144
|
+
function text(value) {
|
|
5145
|
+
return typeof value === "string" ? value.trim() : "";
|
|
5146
|
+
}
|
|
5147
|
+
function stringsOf$1(value) {
|
|
5148
|
+
return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
5149
|
+
}
|
|
5150
|
+
function manifestCommandsOf(raw) {
|
|
5151
|
+
let parsed;
|
|
5152
|
+
try {
|
|
5153
|
+
parsed = JSON.parse(raw);
|
|
5154
|
+
} catch {
|
|
5155
|
+
return [];
|
|
5156
|
+
}
|
|
5157
|
+
if (!Array.isArray(parsed)) return [];
|
|
5158
|
+
const commands = [];
|
|
5159
|
+
for (const one of parsed) {
|
|
5160
|
+
if (typeof one !== "object" || one === null) continue;
|
|
5161
|
+
const row = one;
|
|
5162
|
+
const declared = text(row["command"]);
|
|
5163
|
+
const site = text(row["site"]) || declared.split("/")[0] || "";
|
|
5164
|
+
const name = text(row["name"]) || declared.split("/")[1] || "";
|
|
5165
|
+
const command = declared === "" ? site === "" || name === "" ? "" : `${site}/${name}` : declared;
|
|
5166
|
+
if (command === "" || site === "") continue;
|
|
5167
|
+
const access = text(row["access"]);
|
|
5168
|
+
commands.push({
|
|
5169
|
+
command,
|
|
5170
|
+
site,
|
|
5171
|
+
name: name === "" ? command.slice(command.indexOf("/") + 1) : name,
|
|
5172
|
+
domain: text(row["domain"]).toLowerCase(),
|
|
5173
|
+
description: text(row["description"]),
|
|
5174
|
+
access: access === "" ? "read" : access,
|
|
5175
|
+
strategy: text(row["strategy"]).toLowerCase(),
|
|
5176
|
+
browser: row["browser"] === true,
|
|
5177
|
+
example: text(row["example"]),
|
|
5178
|
+
columns: stringsOf$1(row["columns"])
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
return commands;
|
|
5182
|
+
}
|
|
5183
|
+
function pluginNamesOf(entries) {
|
|
5184
|
+
return entries.filter((one) => one.startsWith(PLUGIN_PREFIX)).map((one) => one.slice(15)).filter((one) => one !== "").sort();
|
|
5185
|
+
}
|
|
5186
|
+
function pluginCommandsOf(files) {
|
|
5187
|
+
return files.filter((one) => one.endsWith(".js")).map((one) => one.slice(0, -3)).filter((one) => one !== "index" && !one.startsWith("_")).sort();
|
|
5188
|
+
}
|
|
5189
|
+
function hostOf(url) {
|
|
5190
|
+
const trimmed = url.trim();
|
|
5191
|
+
if (trimmed === "") return "";
|
|
5192
|
+
try {
|
|
5193
|
+
return new URL(trimmed).hostname.toLowerCase().replace(/^www\./, "");
|
|
5194
|
+
} catch {
|
|
5195
|
+
return "";
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
function keywordsOf(keyword, url) {
|
|
5199
|
+
const words = /* @__PURE__ */ new Set();
|
|
5200
|
+
const trimmed = keyword.trim().toLowerCase();
|
|
5201
|
+
if (trimmed !== "") words.add(trimmed);
|
|
5202
|
+
const host = hostOf(url);
|
|
5203
|
+
if (host !== "") words.add(host);
|
|
5204
|
+
return [...words];
|
|
5205
|
+
}
|
|
5206
|
+
function scoreOf(command, word) {
|
|
5207
|
+
const site = command.site.toLowerCase();
|
|
5208
|
+
const name = command.name.toLowerCase();
|
|
5209
|
+
if (word === "") return 0;
|
|
5210
|
+
if (site === word) return 100;
|
|
5211
|
+
if (site.includes(word)) return 80;
|
|
5212
|
+
if (command.domain !== "" && command.domain.includes(word)) return 70;
|
|
5213
|
+
if (site.length >= 3 && word.includes(site)) return 45;
|
|
5214
|
+
if (name === word) return 65;
|
|
5215
|
+
if (name.includes(word)) return 55;
|
|
5216
|
+
if (command.command.toLowerCase().includes(word)) return 50;
|
|
5217
|
+
if (command.description.toLowerCase().includes(word)) return 30;
|
|
5218
|
+
return 0;
|
|
5219
|
+
}
|
|
5220
|
+
function reconOf(commands, plugins, keywords) {
|
|
5221
|
+
const keyword = keywords[0] ?? "";
|
|
5222
|
+
const best = /* @__PURE__ */ new Map();
|
|
5223
|
+
for (const command of commands) {
|
|
5224
|
+
let score = 0;
|
|
5225
|
+
for (const word of keywords) score = Math.max(score, scoreOf(command, word));
|
|
5226
|
+
if (score === 0) continue;
|
|
5227
|
+
const current = best.get(command.command);
|
|
5228
|
+
if (current === void 0 || current.score < score) best.set(command.command, {
|
|
5229
|
+
command,
|
|
5230
|
+
score
|
|
5231
|
+
});
|
|
5232
|
+
}
|
|
5233
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5234
|
+
for (const entry of best.values()) {
|
|
5235
|
+
const group = grouped.get(entry.command.site) ?? {
|
|
5236
|
+
best: 0,
|
|
5237
|
+
rows: []
|
|
5238
|
+
};
|
|
5239
|
+
group.best = Math.max(group.best, entry.score);
|
|
5240
|
+
group.rows.push(entry);
|
|
5241
|
+
grouped.set(entry.command.site, group);
|
|
5242
|
+
}
|
|
5243
|
+
const base = {
|
|
5244
|
+
keyword,
|
|
5245
|
+
sites: [...grouped.entries()].sort((left, right) => right[1].best - left[1].best || left[0].localeCompare(right[0])).slice(0, SITE_LIMIT).map(([site, group]) => {
|
|
5246
|
+
const picked = [...group.rows].sort((left, right) => (left.command.access === "write" ? 1 : 0) - (right.command.access === "write" ? 1 : 0) || right.score - left.score || left.command.command.localeCompare(right.command.command)).slice(0, COMMAND_LIMIT).map((one) => one.command);
|
|
5247
|
+
const first = picked[0];
|
|
3377
5248
|
return {
|
|
3378
5249
|
site,
|
|
3379
5250
|
domain: first?.domain ?? "",
|
|
@@ -3407,7 +5278,7 @@ function nextOf(answer) {
|
|
|
3407
5278
|
}
|
|
3408
5279
|
const plugin = answer.plugins[0];
|
|
3409
5280
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
3410
|
-
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:
|
|
5281
|
+
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy_probe(script 读接口 / css 读 DOM)";
|
|
3411
5282
|
}
|
|
3412
5283
|
function textOf$3(answer) {
|
|
3413
5284
|
const lines = [];
|
|
@@ -3575,7 +5446,7 @@ function renderPanel(options) {
|
|
|
3575
5446
|
function statusOf(options) {
|
|
3576
5447
|
const { dir, unit, name, installed, known, open, health } = options;
|
|
3577
5448
|
const facts = moduleBuildFacts(dir);
|
|
3578
|
-
const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:
|
|
5449
|
+
const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:workbench_diy_install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 workbench_diy_show" : !open ? "上板:workbench_diy_show(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
|
|
3579
5450
|
return {
|
|
3580
5451
|
unit,
|
|
3581
5452
|
name,
|
|
@@ -3607,342 +5478,319 @@ function renderStatus(packagesDir, lines) {
|
|
|
3607
5478
|
function renderList(packagesDir, modules) {
|
|
3608
5479
|
if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
|
|
3609
5480
|
const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
|
|
3610
|
-
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:
|
|
5481
|
+
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy_install(unit=<上面那个 unit>)";
|
|
3611
5482
|
return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
|
|
3612
5483
|
}
|
|
3613
5484
|
//#endregion
|
|
3614
5485
|
//#region src/modules/panels/model/diy-tools.ts
|
|
3615
|
-
var ACTIONS$2 = [
|
|
3616
|
-
"list",
|
|
3617
|
-
"status",
|
|
3618
|
-
"panel",
|
|
3619
|
-
"probe",
|
|
3620
|
-
"recon",
|
|
3621
|
-
"install",
|
|
3622
|
-
"open"
|
|
3623
|
-
];
|
|
3624
|
-
var OUTPUT_SCHEMA$2 = {
|
|
3625
|
-
type: "object",
|
|
3626
|
-
properties: {
|
|
3627
|
-
text: { type: "string" },
|
|
3628
|
-
workshop: { type: "string" },
|
|
3629
|
-
modules: {
|
|
3630
|
-
type: "array",
|
|
3631
|
-
items: {
|
|
3632
|
-
type: "object",
|
|
3633
|
-
additionalProperties: true
|
|
3634
|
-
}
|
|
3635
|
-
},
|
|
3636
|
-
unit: { type: "string" },
|
|
3637
|
-
path: { type: "string" },
|
|
3638
|
-
install: {
|
|
3639
|
-
type: "object",
|
|
3640
|
-
additionalProperties: true
|
|
3641
|
-
},
|
|
3642
|
-
alreadyInstalled: { type: "boolean" },
|
|
3643
|
-
panel: {
|
|
3644
|
-
type: "object",
|
|
3645
|
-
additionalProperties: true
|
|
3646
|
-
}
|
|
3647
|
-
},
|
|
3648
|
-
additionalProperties: true
|
|
3649
|
-
};
|
|
3650
|
-
function asRecord$3(value) {
|
|
3651
|
-
return typeof value === "object" && value !== null ? value : void 0;
|
|
3652
|
-
}
|
|
3653
|
-
function asNumber(value) {
|
|
3654
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3655
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
3656
|
-
const parsed = Number(value);
|
|
3657
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
function asText$1(value) {
|
|
3661
|
-
return typeof value === "string" ? value.trim() : "";
|
|
3662
|
-
}
|
|
3663
|
-
function plainJson(value) {
|
|
3664
|
-
return JSON.parse(JSON.stringify(value));
|
|
3665
|
-
}
|
|
3666
|
-
function verdictOf$1(change) {
|
|
3667
|
-
if (change.application === "applied") return "装好了,已生效";
|
|
3668
|
-
if (change.application === "restart-required") return "装好了,要重启一次才生效(装新模块、同名重装、替换已有包都走这一支)";
|
|
3669
|
-
if (change.application === "overridden") return "被更高层的声明盖住了,这次没生效";
|
|
3670
|
-
if (change.application === "cancelled") return "安装被取消,没有生效";
|
|
3671
|
-
const tail = (change.packageResult?.output ?? "").trim().split("\n").slice(-3).join(" | ");
|
|
3672
|
-
return `没装上(${change.errorCode ?? "failed"})${tail === "" ? "" : `:${tail}`}`;
|
|
3673
|
-
}
|
|
3674
|
-
function renderProbe(out) {
|
|
3675
|
-
if (out.ok) return `读到了 —— 这就是那条读的返回值,把它固化成模块宿主半侧的那一条:\n${(JSON.stringify(out.value, null, 2) ?? "undefined").slice(0, 2e3)}`;
|
|
3676
|
-
const lines = [`没读到(${out.code}):${out.message}`];
|
|
3677
|
-
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
3678
|
-
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
3679
|
-
if (out.pageCode !== void 0) lines.push(`页面自己回的码:${out.pageCode}`);
|
|
3680
|
-
return lines.join("\n");
|
|
3681
|
-
}
|
|
3682
|
-
function renderFind(out) {
|
|
3683
|
-
if (!out.ok) {
|
|
3684
|
-
const lines = [`没找到(${out.code}):${out.message}`];
|
|
3685
|
-
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
3686
|
-
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
3687
|
-
return lines.join("\n");
|
|
3688
|
-
}
|
|
3689
|
-
if (out.count === 0) return "找到了 0 个元素 —— 选择器没匹配到东西。先确认停在哪一页(probe 会自己问页面),再把选择器放宽一点(例如 table tr 而不是某个 id)。";
|
|
3690
|
-
const head = out.entries.slice(0, 20).map((one, index) => `${String(index + 1)}. ${one.text.replace(/\n+/g, " | ").slice(0, 200)}`);
|
|
3691
|
-
const more = out.entries.length > 20 ? `\n… 还有 ${String(out.entries.length - 20)} 个` : "";
|
|
3692
|
-
return `找到了 ${String(out.count)} 个元素 —— 每行的单元格按换行拼在 text 里;把它固化成模块宿主半侧的那一条(host 的 browserFind + 切格 + 按列序取值):\n${head.join("\n")}${more}`;
|
|
3693
|
-
}
|
|
3694
|
-
function showOrReason(panels, unit, workspace) {
|
|
3695
|
-
try {
|
|
3696
|
-
panels.show(unit, workspace);
|
|
3697
|
-
return { ok: true };
|
|
3698
|
-
} catch (error) {
|
|
3699
|
-
return {
|
|
3700
|
-
ok: false,
|
|
3701
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
3702
|
-
};
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
5486
|
function registerDiyTools(ctx, market, panels) {
|
|
3706
5487
|
const tools = ctx.get("tools");
|
|
3707
5488
|
if (tools === void 0) {
|
|
3708
5489
|
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
|
|
3709
5490
|
return [];
|
|
3710
5491
|
}
|
|
3711
|
-
const
|
|
3712
|
-
name:
|
|
3713
|
-
description: "DIY
|
|
3714
|
-
parameters:
|
|
5492
|
+
const listTool = {
|
|
5493
|
+
name: DIY_LIST_TOOL,
|
|
5494
|
+
description: "DIY 工坊里有哪些模块、装没装 —— 先跑它拿到 unit。普通项目里装模块用 workbench_modules_install。",
|
|
5495
|
+
parameters: {
|
|
5496
|
+
type: "object",
|
|
5497
|
+
properties: {},
|
|
5498
|
+
additionalProperties: false
|
|
5499
|
+
},
|
|
5500
|
+
output: OUTPUT$2,
|
|
5501
|
+
async execute(_raw, exec) {
|
|
5502
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
5503
|
+
const { modules } = modulesOf(ctx, packagesDir);
|
|
5504
|
+
return plainJson({
|
|
5505
|
+
workshop: packagesDir,
|
|
5506
|
+
modules,
|
|
5507
|
+
text: renderList(packagesDir, modules)
|
|
5508
|
+
});
|
|
5509
|
+
}
|
|
5510
|
+
};
|
|
5511
|
+
const statusTool = {
|
|
5512
|
+
name: DIY_STATUS_TOOL,
|
|
5513
|
+
description: "某个模块走到哪一步:装没装、面板认不认得、起得来没,以及下一步做什么。",
|
|
5514
|
+
parameters: {
|
|
5515
|
+
type: "object",
|
|
5516
|
+
properties: { unit: {
|
|
5517
|
+
type: "string",
|
|
5518
|
+
description: "单元 id(workbench_diy_list 会给)。"
|
|
5519
|
+
} },
|
|
5520
|
+
required: ["unit"],
|
|
5521
|
+
additionalProperties: false
|
|
5522
|
+
},
|
|
5523
|
+
output: OUTPUT$2,
|
|
5524
|
+
async execute(raw, exec) {
|
|
5525
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5526
|
+
const { modules } = modulesOf(ctx, packagesDir);
|
|
5527
|
+
const registry = ctx.get(UNITS_SERVICE);
|
|
5528
|
+
const panelEntries = panelEntriesOf(panels, workspace);
|
|
5529
|
+
const wanted = asText$1(asRecord$3(raw)["unit"]);
|
|
5530
|
+
const picked = wanted === "" ? modules : modules.filter((one) => one.unit === wanted);
|
|
5531
|
+
if (picked.length === 0) throw new Error(`工坊里没有单元 id 为「${wanted}」的模块(可用的:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
5532
|
+
const rows = picked.map((one) => statusOf({
|
|
5533
|
+
dir: one.dir,
|
|
5534
|
+
unit: one.unit,
|
|
5535
|
+
name: one.name,
|
|
5536
|
+
installed: one.installed,
|
|
5537
|
+
known: panelEntries.some((entry) => entry.id === one.unit),
|
|
5538
|
+
open: panels.isEnabled(one.unit, workspace),
|
|
5539
|
+
health: healthOf({
|
|
5540
|
+
registry,
|
|
5541
|
+
unit: one.unit,
|
|
5542
|
+
installed: one.installed
|
|
5543
|
+
})
|
|
5544
|
+
}));
|
|
5545
|
+
return plainJson({
|
|
5546
|
+
workshop: packagesDir,
|
|
5547
|
+
status: rows,
|
|
5548
|
+
text: renderStatus(packagesDir, rows)
|
|
5549
|
+
});
|
|
5550
|
+
}
|
|
5551
|
+
};
|
|
5552
|
+
const panelTool = {
|
|
5553
|
+
name: DIY_PANEL_TOOL,
|
|
5554
|
+
description: "面板此刻的真实样子(每张卡读到没 / 崩没崩)—— 用户说\"卡片不对 / 一片空白\"就先调它。",
|
|
5555
|
+
parameters: {
|
|
5556
|
+
type: "object",
|
|
5557
|
+
properties: { unit: {
|
|
5558
|
+
type: "string",
|
|
5559
|
+
description: "只看某一个单元(不填 = 全看)。"
|
|
5560
|
+
} },
|
|
5561
|
+
additionalProperties: false
|
|
5562
|
+
},
|
|
5563
|
+
output: OUTPUT$2,
|
|
5564
|
+
async execute(raw, exec) {
|
|
5565
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5566
|
+
const { installed, modules } = modulesOf(ctx, packagesDir);
|
|
5567
|
+
const panelState = ctx.get(PANEL_STATE_SERVICE);
|
|
5568
|
+
const registry = ctx.get(UNITS_SERVICE);
|
|
5569
|
+
const panelEntries = panelEntriesOf(panels, workspace);
|
|
5570
|
+
const snapshot = panelState?.snapshot(workspace) ?? {
|
|
5571
|
+
workspace,
|
|
5572
|
+
at: 0,
|
|
5573
|
+
units: []
|
|
5574
|
+
};
|
|
5575
|
+
const wanted = asText$1(asRecord$3(raw)["unit"]);
|
|
5576
|
+
const board = panelEntries.filter((entry) => entry.shown && entry.hasCard).map((entry) => entry.id);
|
|
5577
|
+
const nameOf = new Map(modules.map((one) => [one.unit, one.name]));
|
|
5578
|
+
const reported = new Map(snapshot.units.map((one) => [one.unit, one]));
|
|
5579
|
+
const rows = [.../* @__PURE__ */ new Set([
|
|
5580
|
+
...board,
|
|
5581
|
+
...reported.keys(),
|
|
5582
|
+
...nameOf.keys()
|
|
5583
|
+
])].filter((unit) => wanted === "" || unit === wanted).map((unit) => {
|
|
5584
|
+
const report = reported.get(unit);
|
|
5585
|
+
return {
|
|
5586
|
+
unit,
|
|
5587
|
+
name: nameOf.get(unit) ?? "",
|
|
5588
|
+
health: healthOf({
|
|
5589
|
+
registry,
|
|
5590
|
+
unit,
|
|
5591
|
+
installed: installed.includes(unit)
|
|
5592
|
+
}),
|
|
5593
|
+
...report === void 0 ? {} : { report }
|
|
5594
|
+
};
|
|
5595
|
+
});
|
|
5596
|
+
return plainJson({
|
|
5597
|
+
workshop: packagesDir,
|
|
5598
|
+
workspace,
|
|
5599
|
+
board,
|
|
5600
|
+
report: snapshot,
|
|
5601
|
+
panels: rows,
|
|
5602
|
+
text: renderPanel({
|
|
5603
|
+
packagesDir,
|
|
5604
|
+
workspace,
|
|
5605
|
+
rows,
|
|
5606
|
+
board,
|
|
5607
|
+
at: snapshot.at,
|
|
5608
|
+
now: Date.now()
|
|
5609
|
+
})
|
|
5610
|
+
});
|
|
5611
|
+
}
|
|
5612
|
+
};
|
|
5613
|
+
const reconTool = {
|
|
5614
|
+
name: DIY_RECON_TOOL,
|
|
5615
|
+
description: "先查 opencli 有没有现成的(自带 adapter / 本机插件)—— opencli 是什么自己查,别问用户。",
|
|
5616
|
+
parameters: {
|
|
3715
5617
|
type: "object",
|
|
3716
5618
|
properties: {
|
|
3717
|
-
|
|
5619
|
+
keyword: {
|
|
3718
5620
|
type: "string",
|
|
3719
|
-
description: "
|
|
3720
|
-
enum: [...ACTIONS$2]
|
|
5621
|
+
description: "站点名 / 系统名 / 域名(英文名或域名最准,例:bilibili、arxiv.org、cmdb)。"
|
|
3721
5622
|
},
|
|
3722
|
-
|
|
5623
|
+
url: {
|
|
3723
5624
|
type: "string",
|
|
3724
|
-
description: "
|
|
3725
|
-
}
|
|
5625
|
+
description: "那套系统的地址(从它取域名)。"
|
|
5626
|
+
}
|
|
5627
|
+
},
|
|
5628
|
+
required: ["keyword"],
|
|
5629
|
+
additionalProperties: false
|
|
5630
|
+
},
|
|
5631
|
+
output: OUTPUT$2,
|
|
5632
|
+
async execute(raw, exec) {
|
|
5633
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
5634
|
+
const args = asRecord$3(raw);
|
|
5635
|
+
const keywords = keywordsOf(asText$1(args["keyword"]), asText$1(args["url"]));
|
|
5636
|
+
if (keywords.length === 0) throw new Error("recon 要一个关键词(站点名 / 系统名 / 域名都行):先拿它去 opencli 自带的那份目录里找有没有现成的能力,别一上来就自己写 adapter;用户不知道 opencli 是什么,这一步别问他。");
|
|
5637
|
+
const catalog = reconCatalogOf();
|
|
5638
|
+
if (catalog === void 0) throw new Error(`读不到 opencli 那份目录(${cliCatalogPath()})—— 隔离运行时还没装好;照做:${isolatedInstallCommand()}`);
|
|
5639
|
+
return plainJson({
|
|
5640
|
+
workshop: packagesDir,
|
|
5641
|
+
ok: true,
|
|
5642
|
+
...reconOf(catalog, installedPluginsOf(), keywords)
|
|
5643
|
+
});
|
|
5644
|
+
}
|
|
5645
|
+
};
|
|
5646
|
+
const probeTool = {
|
|
5647
|
+
name: DIY_PROBE_TOOL,
|
|
5648
|
+
description: "用用户浏览器里现有的登录探一条读:script 读接口 / css 读 DOM。不在站点上会自己打开 url。",
|
|
5649
|
+
parameters: {
|
|
5650
|
+
type: "object",
|
|
5651
|
+
properties: {
|
|
3726
5652
|
script: {
|
|
3727
5653
|
type: "string",
|
|
3728
|
-
description: "
|
|
5654
|
+
description: "与 css 二选一:页面里跑一段回 JSON 的脚本。不在站点回 { ok: false, code: \"not-on-site\" },登录过期回 \"not-logged-in\",成功回 { ok: true, ...数据 }。"
|
|
3729
5655
|
},
|
|
3730
5656
|
css: {
|
|
3731
5657
|
type: "string",
|
|
3732
|
-
description: "
|
|
5658
|
+
description: "与 script 二选一:给一个 CSS 选择器,宿主回匹配到的元素文本(每行单元格按换行拼在 text 里)。"
|
|
3733
5659
|
},
|
|
3734
5660
|
limit: {
|
|
3735
5661
|
type: "number",
|
|
3736
|
-
description: "
|
|
5662
|
+
description: "配 css 用:最多回几个元素(默认 50)。"
|
|
3737
5663
|
},
|
|
3738
5664
|
textMax: {
|
|
3739
5665
|
type: "number",
|
|
3740
|
-
description: "
|
|
5666
|
+
description: "配 css 用:每个元素文本最多留几个字(默认 200)。"
|
|
3741
5667
|
},
|
|
3742
5668
|
url: {
|
|
3743
5669
|
type: "string",
|
|
3744
|
-
description: "
|
|
3745
|
-
},
|
|
3746
|
-
keyword: {
|
|
3747
|
-
type: "string",
|
|
3748
|
-
description: "recon 要:站点名 / 系统名 / 域名(英文名或域名最准,例:bilibili、arxiv.org、cmdb)。"
|
|
5670
|
+
description: "那套系统的地址(不在站点上时自己打开它再读一遍)。"
|
|
3749
5671
|
},
|
|
3750
5672
|
loginUrl: {
|
|
3751
5673
|
type: "string",
|
|
3752
|
-
description: "
|
|
5674
|
+
description: "要登录时那颗\"去登录\"该去哪(默认用 url)。"
|
|
3753
5675
|
},
|
|
3754
5676
|
session: {
|
|
3755
5677
|
type: "string",
|
|
3756
|
-
description: "
|
|
5678
|
+
description: "会话名(默认 workbench,一般不用填)。"
|
|
3757
5679
|
},
|
|
3758
5680
|
open: {
|
|
3759
5681
|
type: "boolean",
|
|
3760
|
-
description: "
|
|
5682
|
+
description: "不在站点上时要不要顺手打开(默认要)。"
|
|
3761
5683
|
}
|
|
3762
5684
|
},
|
|
3763
|
-
required: ["action"],
|
|
3764
5685
|
additionalProperties: false
|
|
3765
|
-
}, { required: true }),
|
|
3766
|
-
output: {
|
|
3767
|
-
schema: OUTPUT_SCHEMA$2,
|
|
3768
|
-
render: (_args, value) => {
|
|
3769
|
-
const payload = value;
|
|
3770
|
-
return [{
|
|
3771
|
-
type: "text",
|
|
3772
|
-
text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
|
|
3773
|
-
}];
|
|
3774
|
-
}
|
|
3775
5686
|
},
|
|
5687
|
+
output: OUTPUT$2,
|
|
3776
5688
|
async execute(raw, exec) {
|
|
3777
|
-
const
|
|
3778
|
-
const
|
|
3779
|
-
|
|
3780
|
-
const
|
|
3781
|
-
if (
|
|
3782
|
-
const
|
|
3783
|
-
if (
|
|
3784
|
-
const
|
|
3785
|
-
const
|
|
3786
|
-
const
|
|
3787
|
-
const
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
text: renderList(packagesDir, modules)
|
|
3793
|
-
});
|
|
3794
|
-
const panelEntries = (() => {
|
|
3795
|
-
try {
|
|
3796
|
-
return panels.list(workspace);
|
|
3797
|
-
} catch {
|
|
3798
|
-
return [];
|
|
3799
|
-
}
|
|
3800
|
-
})();
|
|
3801
|
-
const statusOfUnit = (one) => statusOf({
|
|
3802
|
-
dir: one.dir,
|
|
3803
|
-
unit: one.unit,
|
|
3804
|
-
name: one.name,
|
|
3805
|
-
installed: one.installed,
|
|
3806
|
-
known: panelEntries.some((entry) => entry.id === one.unit),
|
|
3807
|
-
open: panels.isEnabled(one.unit, workspace),
|
|
3808
|
-
health: healthOf({
|
|
3809
|
-
registry,
|
|
3810
|
-
unit: one.unit,
|
|
3811
|
-
installed: one.installed
|
|
3812
|
-
})
|
|
3813
|
-
});
|
|
3814
|
-
if (action === "status") {
|
|
3815
|
-
const wanted = asText$1(args.unit);
|
|
3816
|
-
const picked = wanted === "" ? modules : modules.filter((one) => one.unit === wanted);
|
|
3817
|
-
if (wanted !== "" && picked.length === 0) throw new Error(`工坊里没有单元 id 为「${wanted}」的模块(可用的:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
3818
|
-
const rows = picked.map(statusOfUnit);
|
|
3819
|
-
return plainJson({
|
|
3820
|
-
workshop: packagesDir,
|
|
3821
|
-
status: rows,
|
|
3822
|
-
text: renderStatus(packagesDir, rows)
|
|
3823
|
-
});
|
|
3824
|
-
}
|
|
3825
|
-
if (action === "panel") {
|
|
3826
|
-
const snapshot = panelState?.snapshot(workspace) ?? {
|
|
3827
|
-
workspace,
|
|
3828
|
-
at: 0,
|
|
3829
|
-
units: []
|
|
3830
|
-
};
|
|
3831
|
-
const wanted = asText$1(args.unit);
|
|
3832
|
-
const board = panelEntries.filter((entry) => entry.shown && entry.hasCard).map((entry) => entry.id);
|
|
3833
|
-
const nameOf = new Map(modules.map((one) => [one.unit, one.name]));
|
|
3834
|
-
const reported = new Map(snapshot.units.map((one) => [one.unit, one]));
|
|
3835
|
-
const rows = [.../* @__PURE__ */ new Set([
|
|
3836
|
-
...board,
|
|
3837
|
-
...reported.keys(),
|
|
3838
|
-
...nameOf.keys()
|
|
3839
|
-
])].filter((unit) => wanted === "" || unit === wanted).map((unit) => {
|
|
3840
|
-
const report = reported.get(unit);
|
|
3841
|
-
return {
|
|
3842
|
-
unit,
|
|
3843
|
-
name: nameOf.get(unit) ?? "",
|
|
3844
|
-
health: healthOf({
|
|
3845
|
-
registry,
|
|
3846
|
-
unit,
|
|
3847
|
-
installed: installed.includes(unit)
|
|
3848
|
-
}),
|
|
3849
|
-
...report === void 0 ? {} : { report }
|
|
3850
|
-
};
|
|
3851
|
-
});
|
|
3852
|
-
return plainJson({
|
|
3853
|
-
workshop: packagesDir,
|
|
3854
|
-
workspace,
|
|
3855
|
-
board,
|
|
3856
|
-
report: snapshot,
|
|
3857
|
-
panels: rows,
|
|
3858
|
-
text: renderPanel({
|
|
3859
|
-
packagesDir,
|
|
3860
|
-
workspace,
|
|
3861
|
-
rows,
|
|
3862
|
-
board,
|
|
3863
|
-
at: snapshot.at,
|
|
3864
|
-
now: Date.now()
|
|
3865
|
-
})
|
|
3866
|
-
});
|
|
3867
|
-
}
|
|
3868
|
-
if (action === "recon") {
|
|
3869
|
-
const keywords = keywordsOf(asText$1(args.keyword), asText$1(args.url));
|
|
3870
|
-
if (keywords.length === 0) throw new Error("recon 要一个关键词(站点名 / 系统名 / 域名都行):先拿它去 opencli 自带的那份目录里找有没有现成的能力,别一上来就自己写 adapter;用户不知道 opencli 是什么,这一步别问他。");
|
|
3871
|
-
const catalog = reconCatalogOf();
|
|
3872
|
-
if (catalog === void 0) throw new Error(`读不到 opencli 那份目录(${cliCatalogPath()})—— 隔离运行时还没装好;照做:${isolatedInstallCommand()}`);
|
|
3873
|
-
return plainJson({
|
|
3874
|
-
workshop: packagesDir,
|
|
3875
|
-
ok: true,
|
|
3876
|
-
...reconOf(catalog, installedPluginsOf(), keywords)
|
|
3877
|
-
});
|
|
3878
|
-
}
|
|
3879
|
-
if (action === "probe") {
|
|
3880
|
-
const script = asText$1(args.script);
|
|
3881
|
-
const css = asText$1(args.css);
|
|
3882
|
-
if (script === "" && css === "") throw new Error("probe 要 script(页面脚本,回 JSON)或 css(读 DOM 那一行的选择器)之一。script 的约定:不在那个站点上就回 { ok: false, code: \"not-on-site\" }(我会自己把它打开再读一遍);登录过期回 { ok: false, code: \"not-logged-in\" };成功回 { ok: true, ...数据 }。页面里压根没有接口时给 css,别写 document.querySelectorAll(宿主产物不许出现 DOM 字样)。");
|
|
3883
|
-
const shell = ctx.get("shell");
|
|
3884
|
-
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,probe 跑不了");
|
|
3885
|
-
const url = asText$1(args.url);
|
|
3886
|
-
const loginUrl = asText$1(args.loginUrl);
|
|
3887
|
-
const session = asText$1(args.session);
|
|
3888
|
-
const open = typeof args.open === "boolean" ? args.open : void 0;
|
|
3889
|
-
if (css !== "") {
|
|
3890
|
-
const limit = asNumber(args.limit);
|
|
3891
|
-
const textMax = asNumber(args.textMax);
|
|
3892
|
-
const found = await browserFind({
|
|
3893
|
-
shell,
|
|
3894
|
-
css,
|
|
3895
|
-
command: opencliCommandOf(ctx).command,
|
|
3896
|
-
...url === "" ? {} : { url },
|
|
3897
|
-
...session === "" ? {} : { session },
|
|
3898
|
-
...open === void 0 ? {} : { open },
|
|
3899
|
-
...limit === void 0 ? {} : { limit },
|
|
3900
|
-
...textMax === void 0 ? {} : { textMax }
|
|
3901
|
-
});
|
|
3902
|
-
return plainJson({
|
|
3903
|
-
workshop: packagesDir,
|
|
3904
|
-
...found,
|
|
3905
|
-
text: renderFind(found)
|
|
3906
|
-
});
|
|
3907
|
-
}
|
|
3908
|
-
const out = await browserRead({
|
|
5689
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
5690
|
+
const args = asRecord$3(raw);
|
|
5691
|
+
const script = asText$1(args["script"]);
|
|
5692
|
+
const css = asText$1(args["css"]);
|
|
5693
|
+
if (script === "" && css === "") throw new Error("probe 要 script(页面脚本,回 JSON)或 css(读 DOM 那一行的选择器)之一。script 的约定:不在那个站点上就回 { ok: false, code: \"not-on-site\" }(我会自己把它打开再读一遍);登录过期回 { ok: false, code: \"not-logged-in\" };成功回 { ok: true, ...数据 }。页面里压根没有接口时给 css,别写 document.querySelectorAll(宿主产物不许出现 DOM 字样)。");
|
|
5694
|
+
const shell = ctx.get("shell");
|
|
5695
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,probe 跑不了");
|
|
5696
|
+
const url = asText$1(args["url"]);
|
|
5697
|
+
const loginUrl = asText$1(args["loginUrl"]);
|
|
5698
|
+
const session = asText$1(args["session"]);
|
|
5699
|
+
const open = typeof args["open"] === "boolean" ? args["open"] : void 0;
|
|
5700
|
+
if (css !== "") {
|
|
5701
|
+
const limit = asNumber(args["limit"]);
|
|
5702
|
+
const textMax = asNumber(args["textMax"]);
|
|
5703
|
+
const found = await browserFind({
|
|
3909
5704
|
shell,
|
|
3910
|
-
|
|
5705
|
+
css,
|
|
3911
5706
|
command: opencliCommandOf(ctx).command,
|
|
3912
5707
|
...url === "" ? {} : { url },
|
|
3913
|
-
...loginUrl === "" ? {} : { loginUrl },
|
|
3914
5708
|
...session === "" ? {} : { session },
|
|
3915
|
-
...open === void 0 ? {} : { open }
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
workshop: packagesDir,
|
|
3919
|
-
...out,
|
|
3920
|
-
text: renderProbe(out)
|
|
5709
|
+
...open === void 0 ? {} : { open },
|
|
5710
|
+
...limit === void 0 ? {} : { limit },
|
|
5711
|
+
...textMax === void 0 ? {} : { textMax }
|
|
3921
5712
|
});
|
|
3922
|
-
}
|
|
3923
|
-
const unit = asText$1(args.unit);
|
|
3924
|
-
if (unit === "") throw new Error(`install 要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
3925
|
-
const found = modules.find((one) => one.unit === unit);
|
|
3926
|
-
if (found === void 0) throw new Error(`工坊里没有单元 id 为「${unit}」的模块(可用的:${modules.map((one) => one.unit).join(", ") || "(空)"})—— 先装上的前提是它在工坊里造好了`);
|
|
3927
|
-
if (!insideWorkshop(packagesDir, found.dir)) throw new Error(`这个模块不在工坊里(${found.dir})—— 只装 ${packagesDir} 之下的模块`);
|
|
3928
|
-
if (action === "open") {
|
|
3929
|
-
if (!found.installed) throw new Error(`${unit} 还没装 —— open 只负责上板。先用 action=install 装上(第一次会让用户点头一次)`);
|
|
3930
|
-
const panel = showOrReason(panels, unit, workspace);
|
|
3931
|
-
const text = panel.ok ? `面板上已经打开它了(${workspace})—— 让用户看一眼` : `面板上还没打开:${panel.reason}。这就是它的下一步(status 会给出同样的判断)`;
|
|
3932
5713
|
return plainJson({
|
|
3933
5714
|
workshop: packagesDir,
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
panel,
|
|
3937
|
-
text
|
|
5715
|
+
...found,
|
|
5716
|
+
text: renderFind(found)
|
|
3938
5717
|
});
|
|
3939
5718
|
}
|
|
5719
|
+
const out = await browserRead({
|
|
5720
|
+
shell,
|
|
5721
|
+
script,
|
|
5722
|
+
command: opencliCommandOf(ctx).command,
|
|
5723
|
+
...url === "" ? {} : { url },
|
|
5724
|
+
...loginUrl === "" ? {} : { loginUrl },
|
|
5725
|
+
...session === "" ? {} : { session },
|
|
5726
|
+
...open === void 0 ? {} : { open }
|
|
5727
|
+
});
|
|
5728
|
+
return plainJson({
|
|
5729
|
+
workshop: packagesDir,
|
|
5730
|
+
...out,
|
|
5731
|
+
text: renderProbe(out)
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
};
|
|
5735
|
+
function unitOf(packagesDir, raw, label) {
|
|
5736
|
+
const { modules } = modulesOf(ctx, packagesDir);
|
|
5737
|
+
const unit = asText$1(asRecord$3(raw)["unit"]);
|
|
5738
|
+
if (unit === "") throw new Error(`${label} 要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
5739
|
+
const found = modules.find((one) => one.unit === unit);
|
|
5740
|
+
if (found === void 0) throw new Error(`工坊里没有单元 id 为「${unit}」的模块(可用的:${modules.map((one) => one.unit).join(", ") || "(空)"})—— 先装上的前提是它在工坊里造好了`);
|
|
5741
|
+
if (!insideWorkshop(packagesDir, found.dir)) throw new Error(`这个模块不在工坊里(${found.dir})—— 只装 ${packagesDir} 之下的模块`);
|
|
5742
|
+
return found;
|
|
5743
|
+
}
|
|
5744
|
+
const showTool = {
|
|
5745
|
+
name: DIY_SHOW_TOOL,
|
|
5746
|
+
description: "只上板(不装):把一个已经装好的工坊模块显示在当前项目的面板上。",
|
|
5747
|
+
parameters: withApprovalNote({
|
|
5748
|
+
type: "object",
|
|
5749
|
+
properties: { unit: {
|
|
5750
|
+
type: "string",
|
|
5751
|
+
description: "单元 id(workbench_diy_list 会给)。"
|
|
5752
|
+
} },
|
|
5753
|
+
required: ["unit"],
|
|
5754
|
+
additionalProperties: false
|
|
5755
|
+
}, { required: true }),
|
|
5756
|
+
output: OUTPUT$2,
|
|
5757
|
+
async execute(raw, exec) {
|
|
5758
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5759
|
+
const found = unitOf(packagesDir, raw, "workbench_diy_show");
|
|
5760
|
+
if (!found.installed) throw new Error(`${found.unit} 还没装 —— 只上板的前提是它装过了。先用 workbench_diy_install(第一次会让用户点头一次)`);
|
|
5761
|
+
const panel = showOrReason(panels, found.unit, workspace);
|
|
5762
|
+
const text = panel.ok ? `面板上已经打开它了(${workspace})—— 让用户看一眼` : `面板上还没打开:${panel.reason}。这就是它的下一步(workbench_diy_status 会给出同样的判断)`;
|
|
5763
|
+
return plainJson({
|
|
5764
|
+
workshop: packagesDir,
|
|
5765
|
+
unit: found.unit,
|
|
5766
|
+
path: found.dir,
|
|
5767
|
+
panel,
|
|
5768
|
+
text
|
|
5769
|
+
});
|
|
5770
|
+
}
|
|
5771
|
+
};
|
|
5772
|
+
const installTool = {
|
|
5773
|
+
name: DIY_INSTALL_TOOL,
|
|
5774
|
+
description: "把工坊里造好的模块装上并上到当前项目的面板。这是持久变更,一个模块只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换)。",
|
|
5775
|
+
parameters: withApprovalNote({
|
|
5776
|
+
type: "object",
|
|
5777
|
+
properties: { unit: {
|
|
5778
|
+
type: "string",
|
|
5779
|
+
description: "单元 id(workbench_diy_list 会给)。"
|
|
5780
|
+
} },
|
|
5781
|
+
required: ["unit"],
|
|
5782
|
+
additionalProperties: false
|
|
5783
|
+
}, { required: true }),
|
|
5784
|
+
output: OUTPUT$2,
|
|
5785
|
+
async execute(raw, exec) {
|
|
5786
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5787
|
+
const found = unitOf(packagesDir, raw, "workbench_diy_install");
|
|
3940
5788
|
if (found.installed) {
|
|
3941
|
-
const panel = showOrReason(panels, unit, workspace);
|
|
3942
|
-
const text = [
|
|
5789
|
+
const panel = showOrReason(panels, found.unit, workspace);
|
|
5790
|
+
const text = ["它已经装过了(就是工坊里这一份)—— **不要再装**:改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面)。", panel.ok ? `面板上已经打开它了(${workspace})—— 让用户看一眼` : `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})`].join("\n");
|
|
3943
5791
|
return plainJson({
|
|
3944
5792
|
workshop: packagesDir,
|
|
3945
|
-
unit,
|
|
5793
|
+
unit: found.unit,
|
|
3946
5794
|
path: found.dir,
|
|
3947
5795
|
alreadyInstalled: true,
|
|
3948
5796
|
panel,
|
|
@@ -3952,19 +5800,19 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3952
5800
|
const outcome = await market.installBundle(found.dir);
|
|
3953
5801
|
const verdict = verdictOf$1(outcome.change);
|
|
3954
5802
|
const restartRequired = outcome.change.application === "restart-required";
|
|
3955
|
-
const panel = outcome.change.application === "applied" ? showOrReason(panels, unit, workspace) : {
|
|
5803
|
+
const panel = outcome.change.application === "applied" ? showOrReason(panels, found.unit, workspace) : {
|
|
3956
5804
|
ok: false,
|
|
3957
5805
|
reason: `安装结果是 ${outcome.change.application},还没生效`
|
|
3958
5806
|
};
|
|
3959
|
-
const panelText = panel.ok ? `面板上已经打开它了(${workspace})—— 让用户看一眼` : outcome.change.application === "applied" ? `面板上还没打开:${panel.reason}。调
|
|
5807
|
+
const panelText = panel.ok ? `面板上已经打开它了(${workspace})—— 让用户看一眼` : outcome.change.application === "applied" ? `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})试试。` : restartRequired ? `面板上还没打开(${panel.reason}):模块要**重启一次工作台**才注册进面板 —— 把这句话转给用户,重启后再调 workbench_diy_show(unit=${found.unit})。` : `面板上还没打开:${panel.reason}。先把上面那句"没装成"处理掉,再谈上板。`;
|
|
3960
5808
|
const text = [
|
|
3961
|
-
`${verdict}:${unit}(${found.name})`,
|
|
5809
|
+
`${verdict}:${found.unit}(${found.name})`,
|
|
3962
5810
|
panelText,
|
|
3963
|
-
"之后只改源码 + dsh-wb-module build
|
|
5811
|
+
"之后只改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面):不要再装一遍。"
|
|
3964
5812
|
].join("\n");
|
|
3965
5813
|
return plainJson({
|
|
3966
5814
|
workshop: packagesDir,
|
|
3967
|
-
unit,
|
|
5815
|
+
unit: found.unit,
|
|
3968
5816
|
path: found.dir,
|
|
3969
5817
|
install: outcome.change,
|
|
3970
5818
|
module: outcome.module,
|
|
@@ -3973,7 +5821,19 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3973
5821
|
});
|
|
3974
5822
|
}
|
|
3975
5823
|
};
|
|
3976
|
-
return [
|
|
5824
|
+
return [
|
|
5825
|
+
tools.register(listTool),
|
|
5826
|
+
tools.register(statusTool),
|
|
5827
|
+
tools.register(panelTool),
|
|
5828
|
+
tools.register(reconTool),
|
|
5829
|
+
tools.register(probeTool),
|
|
5830
|
+
...registerLookTools(ctx, tools),
|
|
5831
|
+
...registerLogTools(ctx, tools),
|
|
5832
|
+
...registerNetTools(ctx, tools),
|
|
5833
|
+
...registerActTools(ctx, tools),
|
|
5834
|
+
tools.register(showTool),
|
|
5835
|
+
tools.register(installTool)
|
|
5836
|
+
];
|
|
3977
5837
|
}
|
|
3978
5838
|
//#endregion
|
|
3979
5839
|
//#region src/shared/api/host/release.ts
|
|
@@ -5136,7 +6996,7 @@ function createEnvDropper(options) {
|
|
|
5136
6996
|
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
5137
6997
|
const { registries, logger } = deps;
|
|
5138
6998
|
if (registries === void 0 || registry === void 0) return;
|
|
5139
|
-
const scope = scopeOf(pkg);
|
|
6999
|
+
const scope = scopeOf$1(pkg);
|
|
5140
7000
|
if (scope === null) return;
|
|
5141
7001
|
const known = await manager.registries().catch(() => null);
|
|
5142
7002
|
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
@@ -5694,27 +7554,41 @@ function failureText(change) {
|
|
|
5694
7554
|
function describeInstall(spec, module, supply, change) {
|
|
5695
7555
|
return `${change.application === "applied" ? "装好了,已生效" : change.application === "restart-required" ? "装好了,要重启一次才生效" : `没成功:${failureText(change)}`}:${module.kind === "module" ? `${module.unit}(${module.pkg})` : module.pkg}${supply.state === "installed" ? ";它要的 opencli 插件也装上了" : supply.state === "pending-restart" ? ";重启后再装它要的 opencli 插件" : supply.state === "not-a-module" ? ";但它不是工作台模块(没有 dsh.workbench 声明,不会出现在面板上)" : supply.state === "failed" ? `;但它要的插件没装上:${supply.reason}` : ""}(spec:${spec})`;
|
|
5696
7556
|
}
|
|
5697
|
-
var
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
7557
|
+
var OUTPUT$1 = {
|
|
7558
|
+
schema: {
|
|
7559
|
+
type: "object",
|
|
7560
|
+
properties: {
|
|
7561
|
+
text: { type: "string" },
|
|
7562
|
+
modules: {
|
|
7563
|
+
type: "array",
|
|
7564
|
+
items: {
|
|
7565
|
+
type: "object",
|
|
7566
|
+
additionalProperties: true
|
|
7567
|
+
}
|
|
7568
|
+
},
|
|
7569
|
+
shared: {
|
|
7570
|
+
type: "array",
|
|
7571
|
+
items: {
|
|
7572
|
+
type: "object",
|
|
7573
|
+
additionalProperties: true
|
|
7574
|
+
}
|
|
5706
7575
|
}
|
|
5707
7576
|
},
|
|
5708
|
-
|
|
5709
|
-
type: "array",
|
|
5710
|
-
items: {
|
|
5711
|
-
type: "object",
|
|
5712
|
-
additionalProperties: true
|
|
5713
|
-
}
|
|
5714
|
-
}
|
|
7577
|
+
additionalProperties: true
|
|
5715
7578
|
},
|
|
5716
|
-
|
|
7579
|
+
render: (_args, value) => {
|
|
7580
|
+
const payload = value;
|
|
7581
|
+
return [{
|
|
7582
|
+
type: "text",
|
|
7583
|
+
text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
|
|
7584
|
+
}];
|
|
7585
|
+
}
|
|
5717
7586
|
};
|
|
7587
|
+
function textArg(raw, key) {
|
|
7588
|
+
if (typeof raw !== "object" || raw === null) return "";
|
|
7589
|
+
const value = raw[key];
|
|
7590
|
+
return typeof value === "string" ? value.trim() : "";
|
|
7591
|
+
}
|
|
5718
7592
|
function describePluginUninstall(pkg, outcome) {
|
|
5719
7593
|
if (outcome.removed) return `已卸载 opencli 插件 ${pkg}:没人再用它,文件也删了。`;
|
|
5720
7594
|
return `已从「你装的插件」里去掉 ${pkg}。${outcome.kept === void 0 ? "" : pluginKeptText(outcome.kept)}`;
|
|
@@ -5728,98 +7602,167 @@ function usersOf(shared, names) {
|
|
|
5728
7602
|
if (hit === void 0 || hit.usedBy.length === 0) return "";
|
|
5729
7603
|
return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
|
|
5730
7604
|
}
|
|
5731
|
-
var ACTIONS$1 = [
|
|
5732
|
-
"list",
|
|
5733
|
-
"install",
|
|
5734
|
-
"update",
|
|
5735
|
-
"uninstall",
|
|
5736
|
-
"install-bundle",
|
|
5737
|
-
"update-dependency",
|
|
5738
|
-
"update-plugin",
|
|
5739
|
-
"uninstall-plugin",
|
|
5740
|
-
"update-workbench"
|
|
5741
|
-
];
|
|
5742
7605
|
function registerMarketTools(ctx, market) {
|
|
5743
7606
|
const tools = ctx.get("tools");
|
|
5744
7607
|
if (tools === void 0) {
|
|
5745
7608
|
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过模块市场工具注册");
|
|
5746
7609
|
return [];
|
|
5747
7610
|
}
|
|
5748
|
-
const
|
|
5749
|
-
|
|
5750
|
-
|
|
7611
|
+
const noParams = {
|
|
7612
|
+
type: "object",
|
|
7613
|
+
properties: {},
|
|
7614
|
+
additionalProperties: false
|
|
7615
|
+
};
|
|
7616
|
+
async function idOrList(raw, label) {
|
|
7617
|
+
const id = textArg(raw, "id");
|
|
7618
|
+
if (id !== "") return id;
|
|
7619
|
+
const { modules, shared } = await market.list();
|
|
7620
|
+
throw new Error(`${label} 需要一个 id(例如 coding-issues)。可安装的模块:${modules.map((entry) => entry.unit).join(", ") || "(空)"};共用依赖:${shared.map((dep) => dep.id).join(", ") || "(空)"}`);
|
|
7621
|
+
}
|
|
7622
|
+
function moduleSupplyTool(spec) {
|
|
7623
|
+
return {
|
|
7624
|
+
name: spec.name,
|
|
7625
|
+
description: spec.description,
|
|
7626
|
+
parameters: withApprovalNote({
|
|
7627
|
+
type: "object",
|
|
7628
|
+
properties: { id: {
|
|
7629
|
+
type: "string",
|
|
7630
|
+
description: "模块 id(如 coding-issues)。"
|
|
7631
|
+
} },
|
|
7632
|
+
required: ["id"],
|
|
7633
|
+
additionalProperties: false
|
|
7634
|
+
}, { required: true }),
|
|
7635
|
+
output: OUTPUT$1,
|
|
7636
|
+
async execute(raw) {
|
|
7637
|
+
const id = await idOrList(raw, spec.name);
|
|
7638
|
+
const entry = spec.install ? await market.install(id) : await market.update(id);
|
|
7639
|
+
return {
|
|
7640
|
+
module: entry,
|
|
7641
|
+
text: `${spec.install ? "已安装" : "已更新"} ${id}:${stateOf(entry)}`
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
};
|
|
7645
|
+
}
|
|
7646
|
+
const listTool = {
|
|
7647
|
+
name: MODULES_LIST_TOOL,
|
|
7648
|
+
description: "看模块市场与已装:每个模块装没装、可用不可用、要哪些 opencli 插件;还有共用依赖(只有升级)。",
|
|
7649
|
+
parameters: noParams,
|
|
7650
|
+
output: OUTPUT$1,
|
|
7651
|
+
async execute() {
|
|
7652
|
+
const { modules, shared } = await market.list();
|
|
7653
|
+
return {
|
|
7654
|
+
modules,
|
|
7655
|
+
shared,
|
|
7656
|
+
text: render$1(modules, shared)
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
};
|
|
7660
|
+
const installTool = moduleSupplyTool({
|
|
7661
|
+
name: MODULES_INSTALL_TOOL,
|
|
7662
|
+
description: "按模块 id 装它声明的那份外部供给(opencli 插件与库),不换模块包本身。",
|
|
7663
|
+
install: true
|
|
7664
|
+
});
|
|
7665
|
+
const updateTool = moduleSupplyTool({
|
|
7666
|
+
name: MODULES_UPDATE_TOOL,
|
|
7667
|
+
description: "按模块 id 升它声明的那份外部供给(opencli 插件与库),不换模块包本身。",
|
|
7668
|
+
install: false
|
|
7669
|
+
});
|
|
7670
|
+
const uninstallTool = {
|
|
7671
|
+
name: MODULES_UNINSTALL_TOOL,
|
|
7672
|
+
description: "卸模块:连模块包一起卸(只动工作台运行时);要换一份实现先卸再装。",
|
|
7673
|
+
parameters: withApprovalNote({
|
|
7674
|
+
type: "object",
|
|
7675
|
+
properties: { id: {
|
|
7676
|
+
type: "string",
|
|
7677
|
+
description: "模块 id(如 coding-issues)。"
|
|
7678
|
+
} },
|
|
7679
|
+
required: ["id"],
|
|
7680
|
+
additionalProperties: false
|
|
7681
|
+
}, { required: true }),
|
|
7682
|
+
output: OUTPUT$1,
|
|
7683
|
+
async execute(raw) {
|
|
7684
|
+
const id = await idOrList(raw, MODULES_UNINSTALL_TOOL);
|
|
7685
|
+
const outcome = await market.uninstall(id);
|
|
7686
|
+
const kept = outcome.keptFor.length === 0 ? "" : `(还有模块在面板上要用其中一份安装,所以那份留着:${outcome.keptFor.join("、")})`;
|
|
7687
|
+
return {
|
|
7688
|
+
module: outcome.entry,
|
|
7689
|
+
text: `已卸载 ${id}:${stateOf(outcome.entry)}${kept}`
|
|
7690
|
+
};
|
|
7691
|
+
}
|
|
7692
|
+
};
|
|
7693
|
+
const installBundleTool = {
|
|
7694
|
+
name: MODULES_INSTALL_BUNDLE_TOOL,
|
|
7695
|
+
description: "按 spec 把模块包装进这份 profile(包名 / git 地址 / tarball / 本地路径);装完它要的插件也一起装。",
|
|
5751
7696
|
parameters: withApprovalNote({
|
|
5752
7697
|
type: "object",
|
|
5753
7698
|
properties: {
|
|
5754
|
-
action: {
|
|
5755
|
-
type: "string",
|
|
5756
|
-
description: "list=看状态(默认);install / update / uninstall 要 id;install-bundle 要 spec(换源给 registry);update-dependency 要 id;update-plugin / uninstall-plugin 要 plugin。",
|
|
5757
|
-
enum: [...ACTIONS$1]
|
|
5758
|
-
},
|
|
5759
|
-
id: {
|
|
5760
|
-
type: "string",
|
|
5761
|
-
description: "模块 id(如 coding-issues);update-dependency 用共用依赖 id(如 plugin:coding)。"
|
|
5762
|
-
},
|
|
5763
7699
|
spec: {
|
|
5764
7700
|
type: "string",
|
|
5765
|
-
description: "包名(可带
|
|
7701
|
+
description: "包名(可带 @版本)/ git 地址 / tarball / 本地路径。"
|
|
5766
7702
|
},
|
|
5767
7703
|
registry: {
|
|
5768
7704
|
type: "string",
|
|
5769
|
-
description: "
|
|
5770
|
-
},
|
|
5771
|
-
plugin: {
|
|
5772
|
-
type: "string",
|
|
5773
|
-
description: "opencli 插件的包名或插件名(如 cmdb 或 opencli-plugin-cmdb)。"
|
|
7705
|
+
description: "去哪个源装(不填用 profile 自己配的源)。"
|
|
5774
7706
|
}
|
|
5775
7707
|
},
|
|
5776
|
-
required: ["
|
|
7708
|
+
required: ["spec"],
|
|
5777
7709
|
additionalProperties: false
|
|
5778
7710
|
}, { required: true }),
|
|
5779
|
-
output:
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
7711
|
+
output: OUTPUT$1,
|
|
7712
|
+
async execute(raw) {
|
|
7713
|
+
const spec = textArg(raw, "spec");
|
|
7714
|
+
if (spec === "") throw new Error("install-bundle 需要一个 spec:包名(可带版本,如 @scope/module@1.2.3),也可以是 git 地址 / tarball / 本地绝对路径。");
|
|
7715
|
+
const registry = textArg(raw, "registry");
|
|
7716
|
+
const outcome = await market.installBundle(spec, registry === "" ? {} : { registry });
|
|
7717
|
+
return {
|
|
7718
|
+
change: outcome.change,
|
|
7719
|
+
module: outcome.module,
|
|
7720
|
+
supply: outcome.supply,
|
|
7721
|
+
text: describeInstall(spec, outcome.module, outcome.supply, outcome.change)
|
|
7722
|
+
};
|
|
7723
|
+
}
|
|
7724
|
+
};
|
|
7725
|
+
const updateDependencyTool = {
|
|
7726
|
+
name: MODULES_UPDATE_DEPENDENCY_TOOL,
|
|
7727
|
+
description: "升共用依赖(工作台自己那份营生,如 plugin:coding):只有升级,用依赖 id。",
|
|
7728
|
+
parameters: withApprovalNote({
|
|
7729
|
+
type: "object",
|
|
7730
|
+
properties: { id: {
|
|
7731
|
+
type: "string",
|
|
7732
|
+
description: "共用依赖 id(如 plugin:coding)。"
|
|
7733
|
+
} },
|
|
7734
|
+
required: ["id"],
|
|
7735
|
+
additionalProperties: false
|
|
7736
|
+
}, { required: true }),
|
|
7737
|
+
output: OUTPUT$1,
|
|
7738
|
+
async execute(raw) {
|
|
7739
|
+
const id = await idOrList(raw, MODULES_UPDATE_DEPENDENCY_TOOL);
|
|
7740
|
+
const shared = await market.updateDependency(id);
|
|
7741
|
+
const dep = shared.find((item) => item.id === id);
|
|
7742
|
+
return {
|
|
7743
|
+
shared,
|
|
7744
|
+
text: `已更新共用依赖 ${id}${dep === void 0 ? "" : `:${stateOfDependency(dep)}`}`
|
|
7745
|
+
};
|
|
7746
|
+
}
|
|
7747
|
+
};
|
|
7748
|
+
function pluginTool(spec) {
|
|
7749
|
+
return {
|
|
7750
|
+
name: spec.name,
|
|
7751
|
+
description: spec.description,
|
|
7752
|
+
parameters: withApprovalNote({
|
|
7753
|
+
type: "object",
|
|
7754
|
+
properties: { plugin: {
|
|
7755
|
+
type: "string",
|
|
7756
|
+
description: "opencli 插件的包名或插件名(如 cmdb)。"
|
|
7757
|
+
} },
|
|
7758
|
+
required: ["plugin"],
|
|
7759
|
+
additionalProperties: false
|
|
7760
|
+
}, { required: true }),
|
|
7761
|
+
output: OUTPUT$1,
|
|
7762
|
+
async execute(raw) {
|
|
7763
|
+
const plugin = textArg(raw, "plugin");
|
|
7764
|
+
if (plugin === "") throw new Error(`${spec.name} 需要一个 plugin:opencli 插件的包名或插件名 —— 就是市场「已安装 opencli 插件」那一段里的名字。`);
|
|
7765
|
+
if (spec.update) {
|
|
5823
7766
|
const updated = await market.updatePlugin(plugin);
|
|
5824
7767
|
const users = usersOf((await market.list()).shared, [updated.name, plugin]);
|
|
5825
7768
|
return { text: `已更新 opencli 插件 ${plugin}${users === "" ? "。" : `;${users}。`}` };
|
|
@@ -5833,35 +7776,42 @@ function registerMarketTools(ctx, market) {
|
|
|
5833
7776
|
text: `${describePluginUninstall(plugin, outcome)}${users === "" ? "" : `${users}。`}`
|
|
5834
7777
|
};
|
|
5835
7778
|
}
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
} : await market.uninstall(id);
|
|
5856
|
-
const verb = action === "uninstall" ? "已卸载" : action === "update" ? "已更新" : "已安装";
|
|
5857
|
-
const kept = outcome.keptFor.length === 0 ? "" : `(还有模块在面板上要用其中一份安装,所以那份留着:${outcome.keptFor.join("、")})`;
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
const updatePluginTool = pluginTool({
|
|
7782
|
+
name: MODULES_UPDATE_PLUGIN_TOOL,
|
|
7783
|
+
description: "更新用户装的 opencli 插件(按包名或插件名);老插件跑不动了先试它。",
|
|
7784
|
+
update: true
|
|
7785
|
+
});
|
|
7786
|
+
const uninstallPluginTool = pluginTool({
|
|
7787
|
+
name: MODULES_UNINSTALL_PLUGIN_TOOL,
|
|
7788
|
+
description: "卸用户装的 opencli 插件:没人再用它时文件也删;还有模块用着就只从名单去掉。",
|
|
7789
|
+
update: false
|
|
7790
|
+
});
|
|
7791
|
+
const updateWorkbenchTool = {
|
|
7792
|
+
name: MODULES_UPDATE_WORKBENCH_TOOL,
|
|
7793
|
+
description: "把工作台自己更新到源上的新版(要重启时结果里会说;重启由用户来)。",
|
|
7794
|
+
parameters: withApprovalNote(noParams, { required: true }),
|
|
7795
|
+
output: OUTPUT$1,
|
|
7796
|
+
async execute() {
|
|
7797
|
+
const outcome = await market.updateWorkbench("");
|
|
5858
7798
|
return {
|
|
5859
|
-
|
|
5860
|
-
text:
|
|
7799
|
+
change: outcome.change,
|
|
7800
|
+
text: describeWorkbenchUpdate(outcome.change)
|
|
5861
7801
|
};
|
|
5862
7802
|
}
|
|
5863
7803
|
};
|
|
5864
|
-
return [
|
|
7804
|
+
return [
|
|
7805
|
+
tools.register(listTool),
|
|
7806
|
+
tools.register(installTool),
|
|
7807
|
+
tools.register(updateTool),
|
|
7808
|
+
tools.register(uninstallTool),
|
|
7809
|
+
tools.register(installBundleTool),
|
|
7810
|
+
tools.register(updateDependencyTool),
|
|
7811
|
+
tools.register(updatePluginTool),
|
|
7812
|
+
tools.register(uninstallPluginTool),
|
|
7813
|
+
tools.register(updateWorkbenchTool)
|
|
7814
|
+
];
|
|
5865
7815
|
}
|
|
5866
7816
|
//#endregion
|
|
5867
7817
|
//#region src/modules/panels/model/prefs.ts
|
|
@@ -5920,23 +7870,26 @@ function applyPanelsPref(panels, payload) {
|
|
|
5920
7870
|
//#region src/modules/panels/model/handoff.ts
|
|
5921
7871
|
var PANELS_FACT = "panels";
|
|
5922
7872
|
var CAPABILITY_FACT = "capability";
|
|
7873
|
+
var PROVISION_TOOLS = {
|
|
7874
|
+
[MODULES_INSTALL_TOOL]: "install",
|
|
7875
|
+
[MODULES_UPDATE_TOOL]: "update",
|
|
7876
|
+
[MODULES_UNINSTALL_TOOL]: "uninstall",
|
|
7877
|
+
[MODULES_UPDATE_DEPENDENCY_TOOL]: "update-dependency",
|
|
7878
|
+
[DIY_INSTALL_TOOL]: "install"
|
|
7879
|
+
};
|
|
5923
7880
|
function textOf$2(value) {
|
|
5924
7881
|
return typeof value === "string" ? value.trim() : "";
|
|
5925
7882
|
}
|
|
5926
|
-
function actionOf(call) {
|
|
5927
|
-
return textOf$2(call.args["action"]) || "list";
|
|
5928
|
-
}
|
|
5929
7883
|
function requestOf(call) {
|
|
5930
|
-
const action =
|
|
5931
|
-
if (
|
|
7884
|
+
const action = PROVISION_TOOLS[call.source];
|
|
7885
|
+
if (action === void 0) return void 0;
|
|
5932
7886
|
const target = textOf$2(call.args["unit"]) || textOf$2(call.args["id"]);
|
|
5933
7887
|
if (target === "") return void 0;
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
action: wanted,
|
|
7888
|
+
return isDependencyAction(action) ? {
|
|
7889
|
+
action,
|
|
5937
7890
|
id: target
|
|
5938
7891
|
} : {
|
|
5939
|
-
action
|
|
7892
|
+
action,
|
|
5940
7893
|
unit: target
|
|
5941
7894
|
};
|
|
5942
7895
|
}
|
|
@@ -5948,11 +7901,10 @@ function panelsHandoffEntry(target = {}) {
|
|
|
5948
7901
|
};
|
|
5949
7902
|
}
|
|
5950
7903
|
function panelsFactsOf(call, manifests) {
|
|
5951
|
-
if (call.source === "
|
|
5952
|
-
if (call.source === "
|
|
5953
|
-
if (call.source
|
|
5954
|
-
if (
|
|
5955
|
-
if (actionOf(call) === "update-workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
7904
|
+
if (call.source === "workbench_panels_list") return [PANELS_FACT];
|
|
7905
|
+
if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
|
|
7906
|
+
if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
7907
|
+
if (call.source === "workbench_diy_list") return [PANELS_FACT];
|
|
5956
7908
|
const request = requestOf(call);
|
|
5957
7909
|
if (request === void 0) return void 0;
|
|
5958
7910
|
return [
|
|
@@ -6280,16 +8232,28 @@ function pluginRoutes(options) {
|
|
|
6280
8232
|
//#region src/modules/panels/model/tools.ts
|
|
6281
8233
|
var MACHINE_LABEL = "机器级默认板 —— 还没配过的工作区默认开这些:";
|
|
6282
8234
|
var MACHINE_CAVEAT = "机器级默认板只是还没配过的工作区的默认值:已经自己配过的工作区保持自己的选择,不会被它改。";
|
|
8235
|
+
var SCOPE_PARAM = {
|
|
8236
|
+
type: "string",
|
|
8237
|
+
description: "workspace(默认)=只动当前工作区;machine=动机器级默认板(只影响还没配过的工作区)。",
|
|
8238
|
+
enum: ["workspace", "machine"]
|
|
8239
|
+
};
|
|
8240
|
+
var ID_PARAM = {
|
|
8241
|
+
type: "string",
|
|
8242
|
+
description: "套件 id(如 cmdb)。"
|
|
8243
|
+
};
|
|
8244
|
+
function noted(parameters) {
|
|
8245
|
+
return withApprovalNote(parameters, { required: true });
|
|
8246
|
+
}
|
|
6283
8247
|
function listed(entries) {
|
|
6284
8248
|
return entries.map((entry, index) => `${index + 1}. ${entry.id} · ${entry.shown ? "已启用" : "未启用"}${entry.hasCard ? "" : "(无卡片)"}${entry.source === "user" ? " · 用户改过" : ""}`).join("\n");
|
|
6285
8249
|
}
|
|
6286
8250
|
function render(entries, workspace, scope) {
|
|
6287
8251
|
if (scope === "machine") {
|
|
6288
|
-
if (entries.length === 0) return "机器级默认板是空的(还没配过的工作区默认什么卡片都不开)——用
|
|
8252
|
+
if (entries.length === 0) return "机器级默认板是空的(还没配过的工作区默认什么卡片都不开)——用 workbench_panels_show 放一个上去。";
|
|
6289
8253
|
return `${MACHINE_LABEL}\n${listed(entries)}`;
|
|
6290
8254
|
}
|
|
6291
8255
|
const where = workspace === null ? "(不知道当前会话在哪个工作区)" : workspace;
|
|
6292
|
-
if (entries.length === 0) return `这个工作区的面板是空的(${where})——用
|
|
8256
|
+
if (entries.length === 0) return `这个工作区的面板是空的(${where})——用 workbench_panels_show 放一个上去。`;
|
|
6293
8257
|
return `当前工作区:${where}\n${listed(entries)}`;
|
|
6294
8258
|
}
|
|
6295
8259
|
function parseScope(raw) {
|
|
@@ -6297,141 +8261,205 @@ function parseScope(raw) {
|
|
|
6297
8261
|
if (typeof raw === "string" && BOARD_TARGETS.includes(raw)) return raw;
|
|
6298
8262
|
throw new Error(`不认识的 scope:${String(raw)}(可用:workspace / machine)`);
|
|
6299
8263
|
}
|
|
6300
|
-
var
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
8264
|
+
var OUTPUT = {
|
|
8265
|
+
schema: {
|
|
8266
|
+
type: "object",
|
|
8267
|
+
properties: {
|
|
8268
|
+
text: { type: "string" },
|
|
8269
|
+
units: {
|
|
8270
|
+
type: "array",
|
|
8271
|
+
items: {
|
|
8272
|
+
type: "object",
|
|
8273
|
+
properties: {
|
|
8274
|
+
id: { type: "string" },
|
|
8275
|
+
shown: { type: "boolean" },
|
|
8276
|
+
order: { type: "integer" },
|
|
8277
|
+
hasCard: { type: "boolean" },
|
|
8278
|
+
source: { type: "string" }
|
|
8279
|
+
},
|
|
8280
|
+
additionalProperties: true
|
|
8281
|
+
}
|
|
6316
8282
|
}
|
|
6317
|
-
}
|
|
8283
|
+
},
|
|
8284
|
+
additionalProperties: true
|
|
6318
8285
|
},
|
|
6319
|
-
|
|
8286
|
+
render: (_args, value) => {
|
|
8287
|
+
const payload = value;
|
|
8288
|
+
return [{
|
|
8289
|
+
type: "text",
|
|
8290
|
+
text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
|
|
8291
|
+
}];
|
|
8292
|
+
}
|
|
6320
8293
|
};
|
|
8294
|
+
function idOf(raw) {
|
|
8295
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
8296
|
+
const value = raw["id"];
|
|
8297
|
+
return typeof value === "string" ? value : void 0;
|
|
8298
|
+
}
|
|
8299
|
+
function scopeOf(raw) {
|
|
8300
|
+
if (typeof raw !== "object" || raw === null) return parseScope(void 0);
|
|
8301
|
+
return parseScope(raw["scope"]);
|
|
8302
|
+
}
|
|
6321
8303
|
function registerPanelTools(ctx, panels, values) {
|
|
6322
8304
|
const tools = ctx.get("tools");
|
|
6323
8305
|
if (tools === void 0) {
|
|
6324
8306
|
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过户表面板工具注册");
|
|
6325
8307
|
return [];
|
|
6326
8308
|
}
|
|
6327
|
-
const
|
|
6328
|
-
name:
|
|
6329
|
-
description: "
|
|
6330
|
-
parameters:
|
|
8309
|
+
const listTool = {
|
|
8310
|
+
name: PANELS_LIST_TOOL,
|
|
8311
|
+
description: "看现在这份板:哪些套件开着、什么顺序。两份板:本工作区那份优先,没提过的行看机器级默认板 ——那只是还没配过的工作区的默认值(自己配过的保持自己的选择)。",
|
|
8312
|
+
parameters: {
|
|
8313
|
+
type: "object",
|
|
8314
|
+
properties: { scope: SCOPE_PARAM },
|
|
8315
|
+
additionalProperties: false
|
|
8316
|
+
},
|
|
8317
|
+
output: OUTPUT,
|
|
8318
|
+
async execute(raw, exec) {
|
|
8319
|
+
const scope = scopeOf(raw);
|
|
8320
|
+
const workspace = sessionWorkspace(exec);
|
|
8321
|
+
const units = panels.list(workspace, scope);
|
|
8322
|
+
if (scope === "workspace") {
|
|
8323
|
+
const entry = panelsHandoffEntry(workspace === null ? {} : { path: workspace });
|
|
8324
|
+
if (entry !== void 0) values?.hand(entry, units);
|
|
8325
|
+
}
|
|
8326
|
+
return {
|
|
8327
|
+
units,
|
|
8328
|
+
text: render(units, workspace, scope)
|
|
8329
|
+
};
|
|
8330
|
+
}
|
|
8331
|
+
};
|
|
8332
|
+
const showTool = {
|
|
8333
|
+
name: PANELS_SHOW_TOOL,
|
|
8334
|
+
description: "这个工作区要它:之前收起的也放回来,卡片与 agent 工具一起上线;那份板上写过\"不要\"的也划掉。",
|
|
8335
|
+
parameters: noted({
|
|
6331
8336
|
type: "object",
|
|
6332
8337
|
properties: {
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
},
|
|
8338
|
+
id: ID_PARAM,
|
|
8339
|
+
scope: SCOPE_PARAM
|
|
8340
|
+
},
|
|
8341
|
+
required: ["id"],
|
|
8342
|
+
additionalProperties: false
|
|
8343
|
+
}),
|
|
8344
|
+
output: OUTPUT,
|
|
8345
|
+
async execute(raw, exec) {
|
|
8346
|
+
const id = idOf(raw);
|
|
8347
|
+
const scope = scopeOf(raw);
|
|
8348
|
+
const workspace = sessionWorkspace(exec);
|
|
8349
|
+
if (id === void 0 || id === "") throw new Error(`workbench_panels_show 需要一个 id(例如 coding-issues)。当前套件:${panels.list(workspace, scope).map((entry) => entry.id).join(", ")}`);
|
|
8350
|
+
const units = panels.show(id, workspace, scope);
|
|
8351
|
+
return {
|
|
8352
|
+
units,
|
|
8353
|
+
text: scope === "machine" ? `已把 ${id} 放进机器级默认板 —— 还没配过的工作区会带上它(卡片与工具一起上线);那份板上要是给它写过"不要",这一下也划掉了。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已启用 ${id} —— 这个工作区要它:之前收起的也放回来了,卡片与工具一起上线。\n${render(units, workspace, scope)}`
|
|
8354
|
+
};
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
const hideTool = {
|
|
8358
|
+
name: PANELS_HIDE_TOOL,
|
|
8359
|
+
description: "这个工作区明确不要它:卡片与 agent 工具一起下线(机器板上开着的也照样收起);别的项目不受影响,要放回来用 workbench_panels_show。在机器板上 hide 是写下\"不要\"这一笔,比名单管用。",
|
|
8360
|
+
parameters: noted({
|
|
8361
|
+
type: "object",
|
|
8362
|
+
properties: {
|
|
8363
|
+
id: ID_PARAM,
|
|
8364
|
+
scope: SCOPE_PARAM
|
|
8365
|
+
},
|
|
8366
|
+
required: ["id"],
|
|
8367
|
+
additionalProperties: false
|
|
8368
|
+
}),
|
|
8369
|
+
output: OUTPUT,
|
|
8370
|
+
async execute(raw, exec) {
|
|
8371
|
+
const id = idOf(raw);
|
|
8372
|
+
const scope = scopeOf(raw);
|
|
8373
|
+
const workspace = sessionWorkspace(exec);
|
|
8374
|
+
if (id === void 0 || id === "") throw new Error(`workbench_panels_hide 需要一个 id(例如 coding-issues)。当前套件:${panels.list(workspace, scope).map((entry) => entry.id).join(", ")}`);
|
|
8375
|
+
const units = panels.hide(id, workspace, scope);
|
|
8376
|
+
return {
|
|
8377
|
+
units,
|
|
8378
|
+
text: scope === "machine" ? `已把 ${id} 从机器级默认板划掉,并在那份板上明确写下"不要它" —— 还没配过的工作区从此看不到它(卡片与工具一起下线);名单里还列着它也一样不算数,要放回来用 workbench_panels_show。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已收起 ${id} —— 这个工作区明确不要它:卡片与工具一起下线(默认在机器上也开着的套件同样收起),别的项目不受影响;要放回来用 workbench_panels_show。\n${render(units, workspace, scope)}`
|
|
8379
|
+
};
|
|
8380
|
+
}
|
|
8381
|
+
};
|
|
8382
|
+
const moveTool = {
|
|
8383
|
+
name: PANELS_MOVE_TOOL,
|
|
8384
|
+
description: "调顺序:放到最前面(新开一行时排最后)。顺序只属于工作区;机器级默认板上没有顺序一说。",
|
|
8385
|
+
parameters: noted({
|
|
8386
|
+
type: "object",
|
|
8387
|
+
properties: {
|
|
8388
|
+
id: ID_PARAM,
|
|
6349
8389
|
index: {
|
|
6350
8390
|
type: "integer",
|
|
6351
|
-
description: "
|
|
8391
|
+
description: "位置,0 基(数的是当前开着的那几行)。"
|
|
6352
8392
|
},
|
|
6353
|
-
scope:
|
|
6354
|
-
type: "string",
|
|
6355
|
-
description: "workspace(默认)=只动当前工作区;machine=动那份机器级默认板(只影响还没配过的工作区;没有顺序一说,move 会报错)。",
|
|
6356
|
-
enum: ["workspace", "machine"]
|
|
6357
|
-
}
|
|
8393
|
+
scope: SCOPE_PARAM
|
|
6358
8394
|
},
|
|
6359
|
-
required: ["
|
|
8395
|
+
required: ["id"],
|
|
6360
8396
|
additionalProperties: false
|
|
6361
|
-
}
|
|
6362
|
-
output:
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
return [{
|
|
6367
|
-
type: "text",
|
|
6368
|
-
text: typeof payload?.text === "string" ? payload.text : JSON.stringify(value)
|
|
6369
|
-
}];
|
|
6370
|
-
}
|
|
6371
|
-
},
|
|
6372
|
-
async execute(args, exec) {
|
|
6373
|
-
const action = args?.action ?? "list";
|
|
6374
|
-
const id = args?.id;
|
|
6375
|
-
const scope = parseScope(args?.scope);
|
|
8397
|
+
}),
|
|
8398
|
+
output: OUTPUT,
|
|
8399
|
+
async execute(raw, exec) {
|
|
8400
|
+
const id = idOf(raw);
|
|
8401
|
+
const scope = scopeOf(raw);
|
|
6376
8402
|
const workspace = sessionWorkspace(exec);
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
"
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
units,
|
|
6424
|
-
text: scope === "machine" ? `已把 ${id} 从机器级默认板划掉,并在那份板上明确写下"不要它" —— 还没配过的工作区从此看不到它(卡片与工具一起下线);名单里还列着它也一样不算数,要放回来用 show。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已收起 ${id} —— 这个工作区明确不要它:卡片与工具一起下线(默认在机器上也开着的套件同样收起),别的项目不受影响;要放回来用 show。\n${render(units, workspace, scope)}`
|
|
6425
|
-
};
|
|
6426
|
-
}
|
|
6427
|
-
const units = panels.move(id, args?.index ?? 0, workspace, scope);
|
|
8403
|
+
if (id === void 0 || id === "") throw new Error(`workbench_panels_move 需要一个 id(例如 coding-issues)。当前套件:${panels.list(workspace, scope).map((entry) => entry.id).join(", ")}`);
|
|
8404
|
+
const index = typeof raw === "object" && raw !== null && typeof raw.index === "number" ? raw.index : 0;
|
|
8405
|
+
const units = panels.move(id, index, workspace, scope);
|
|
8406
|
+
return {
|
|
8407
|
+
units,
|
|
8408
|
+
text: scope === "machine" ? `已把 ${id} 放到机器级默认板第 ${index + 1} 位 —— 只影响还没配过的工作区。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已把 ${id} 放到第 ${index + 1} 位。\n${render(units, workspace, scope)}`
|
|
8409
|
+
};
|
|
8410
|
+
}
|
|
8411
|
+
};
|
|
8412
|
+
const resetTool = {
|
|
8413
|
+
name: PANELS_RESET_TOOL,
|
|
8414
|
+
description: "清掉这个工作区改过的行(改完全部回到跟着机器级默认板走);scope=machine 清的是整份机器级默认板。",
|
|
8415
|
+
parameters: noted({
|
|
8416
|
+
type: "object",
|
|
8417
|
+
properties: { scope: SCOPE_PARAM },
|
|
8418
|
+
additionalProperties: false
|
|
8419
|
+
}),
|
|
8420
|
+
output: OUTPUT,
|
|
8421
|
+
async execute(raw, exec) {
|
|
8422
|
+
const scope = scopeOf(raw);
|
|
8423
|
+
const workspace = sessionWorkspace(exec);
|
|
8424
|
+
const units = panels.reset(workspace, scope);
|
|
8425
|
+
return {
|
|
8426
|
+
units,
|
|
8427
|
+
text: scope === "machine" ? `机器级默认板已清空 —— 从今往后,没在这一行改过的工作区默认什么都不开。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `这个工作区改过的行已全部清掉 —— 每一行都回到跟着机器级默认板走。\n${render(units, workspace, scope)}`
|
|
8428
|
+
};
|
|
8429
|
+
}
|
|
8430
|
+
};
|
|
8431
|
+
const followTool = {
|
|
8432
|
+
name: PANELS_FOLLOW_TOOL,
|
|
8433
|
+
description: "某一行放回跟随机器级默认板(带 id = 只那一行,不带 = 全部)。",
|
|
8434
|
+
parameters: noted({
|
|
8435
|
+
type: "object",
|
|
8436
|
+
properties: {
|
|
8437
|
+
id: ID_PARAM,
|
|
8438
|
+
scope: SCOPE_PARAM
|
|
8439
|
+
},
|
|
8440
|
+
additionalProperties: false
|
|
8441
|
+
}),
|
|
8442
|
+
output: OUTPUT,
|
|
8443
|
+
async execute(raw, exec) {
|
|
8444
|
+
const id = idOf(raw);
|
|
8445
|
+
const scope = scopeOf(raw);
|
|
8446
|
+
const workspace = sessionWorkspace(exec);
|
|
8447
|
+
if (scope === "machine") throw new Error("机器级默认板没有\"放回跟随\"一说:它就是那份默认本身,直接 show / hide 写它。");
|
|
8448
|
+
const units = panels.follow(workspace, id);
|
|
6428
8449
|
return {
|
|
6429
8450
|
units,
|
|
6430
|
-
text:
|
|
8451
|
+
text: id === void 0 ? `这个工作区改过的行全部清掉 —— 每一行都回到跟着机器级默认板走(那份板以后改了,这里跟着变)。\n${render(units, workspace, scope)}` : `${id} 放回跟随 —— 它不再由这个工作区定,改跟机器级默认板走。\n${render(units, workspace, scope)}`
|
|
6431
8452
|
};
|
|
6432
8453
|
}
|
|
6433
8454
|
};
|
|
6434
|
-
return [
|
|
8455
|
+
return [
|
|
8456
|
+
tools.register(listTool),
|
|
8457
|
+
tools.register(showTool),
|
|
8458
|
+
tools.register(hideTool),
|
|
8459
|
+
tools.register(moveTool),
|
|
8460
|
+
tools.register(resetTool),
|
|
8461
|
+
tools.register(followTool)
|
|
8462
|
+
];
|
|
6435
8463
|
}
|
|
6436
8464
|
//#endregion
|
|
6437
8465
|
//#region src/app/units/host.ts
|
|
@@ -6514,7 +8542,7 @@ var OWNED = [{
|
|
|
6514
8542
|
return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
|
|
6515
8543
|
id: manifest.id,
|
|
6516
8544
|
boards
|
|
6517
|
-
}) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [
|
|
8545
|
+
}) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [...DIY_TOOLS]];
|
|
6518
8546
|
},
|
|
6519
8547
|
subscribe: (listener) => panels.subscribe(listener)
|
|
6520
8548
|
},
|
|
@@ -6562,18 +8590,19 @@ function withRunsRoute(result, unit) {
|
|
|
6562
8590
|
}
|
|
6563
8591
|
};
|
|
6564
8592
|
}
|
|
6565
|
-
function notedRegistry(registry, writesOf) {
|
|
8593
|
+
function notedRegistry(registry, writesOf, journal) {
|
|
6566
8594
|
return { register(definition) {
|
|
6567
|
-
|
|
6568
|
-
const
|
|
6569
|
-
return registry.register(noted === definition.parameters ? definition : {
|
|
8595
|
+
const noted = writesOf(definition.name) ? withApprovalNote(definition.parameters, { required: true }) : void 0;
|
|
8596
|
+
const registered = registry.register(noted === void 0 || noted === definition.parameters ? definition : {
|
|
6570
8597
|
...definition,
|
|
6571
8598
|
parameters: noted
|
|
6572
8599
|
});
|
|
8600
|
+
journal.push(registered);
|
|
8601
|
+
return registered;
|
|
6573
8602
|
} };
|
|
6574
8603
|
}
|
|
6575
|
-
function scopedMountCtx(ctx, registry, writesOf) {
|
|
6576
|
-
const tools = notedRegistry(registry, writesOf);
|
|
8604
|
+
function scopedMountCtx(ctx, registry, writesOf, journal) {
|
|
8605
|
+
const tools = notedRegistry(registry, writesOf, journal);
|
|
6577
8606
|
const get = (name) => {
|
|
6578
8607
|
const value = ctx.get(name);
|
|
6579
8608
|
return name === "tools" && value !== void 0 ? tools : value;
|
|
@@ -6588,6 +8617,12 @@ function failureReasonOf(error) {
|
|
|
6588
8617
|
if (error instanceof Error) return error.message;
|
|
6589
8618
|
return typeof error === "string" ? error : String(error);
|
|
6590
8619
|
}
|
|
8620
|
+
function applyPlanFailures(failures, sinks) {
|
|
8621
|
+
for (const failure of failures) {
|
|
8622
|
+
sinks.note(failure.id, failure.reason);
|
|
8623
|
+
sinks.warn(`[dsh-workbench] ${failure.id} 套件起不来,已摘掉它这一份:${failure.reason}`);
|
|
8624
|
+
}
|
|
8625
|
+
}
|
|
6591
8626
|
function fail(ledger, item, reason) {
|
|
6592
8627
|
ledger.failed.add(item);
|
|
6593
8628
|
ledger.failures.push({
|
|
@@ -6729,7 +8764,8 @@ function mountUnits(units, deps) {
|
|
|
6729
8764
|
const unit = host.manifest.id;
|
|
6730
8765
|
const tools = deps.ctx.get("tools");
|
|
6731
8766
|
const writes = toolUnits(deps.manifests());
|
|
6732
|
-
const
|
|
8767
|
+
const toolJournal = [];
|
|
8768
|
+
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools, (name) => writes.get(name)?.writes === true, toolJournal);
|
|
6733
8769
|
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
6734
8770
|
scope: deps.longTasks.scope,
|
|
6735
8771
|
unit,
|
|
@@ -6761,7 +8797,10 @@ function mountUnits(units, deps) {
|
|
|
6761
8797
|
const item = {
|
|
6762
8798
|
host,
|
|
6763
8799
|
readiness,
|
|
6764
|
-
result: withRunsRoute(result, unit)
|
|
8800
|
+
result: withRunsRoute(result, unit),
|
|
8801
|
+
rollbackTools: () => {
|
|
8802
|
+
for (const dispose of toolJournal.splice(0)) dispose();
|
|
8803
|
+
}
|
|
6765
8804
|
};
|
|
6766
8805
|
mounted.push(item);
|
|
6767
8806
|
if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
|
|
@@ -9153,13 +11192,16 @@ function apply(ctx) {
|
|
|
9153
11192
|
return;
|
|
9154
11193
|
}
|
|
9155
11194
|
for (const item of one.skipped) ctx.logger?.warn?.(`[dsh-workbench] ${item.id} 套件缺硬依赖,跳过:${item.missing.join(", ")}`);
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
11195
|
+
applyPlanFailures(one.failed, {
|
|
11196
|
+
note: (unit, reason) => {
|
|
11197
|
+
mountFailures.set(unit, {
|
|
11198
|
+
reason,
|
|
11199
|
+
host
|
|
11200
|
+
});
|
|
11201
|
+
units.fail(unit, reason);
|
|
11202
|
+
},
|
|
11203
|
+
warn: (message) => ctx.logger?.warn?.(message)
|
|
11204
|
+
});
|
|
9163
11205
|
const slot = one.mounted[0];
|
|
9164
11206
|
if (slot === void 0) {
|
|
9165
11207
|
one.disposeLongTasks();
|
|
@@ -9183,6 +11225,7 @@ function apply(ctx) {
|
|
|
9183
11225
|
mountFailures.delete(id);
|
|
9184
11226
|
units.recovered(id);
|
|
9185
11227
|
} catch (error) {
|
|
11228
|
+
slot.rollbackTools();
|
|
9186
11229
|
for (const route of added) delete routes[route];
|
|
9187
11230
|
for (const kind of one.contextRenderers.keys()) renderers.delete(kind);
|
|
9188
11231
|
for (const unit of one.factsOf.keys()) factsOfMap.delete(unit);
|