@dsh-workbench/core 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/bin/dsh-workbench +12 -43
- package/client.js +914 -350
- package/dist/client/client.body.js +914 -350
- package/dist/client/index.js +915 -351
- package/dist/host/index.js +1314 -667
- package/dist/types/app/client.d.ts +216 -6
- package/dist/types/app/layouts/config-sync/ConfigSyncSection.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-order.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-running-display.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-workbench-act.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-act.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +13 -0
- package/dist/types/app/layouts/rightbar/market-settle.d.ts +22 -0
- package/dist/types/app/layouts/rightbar/market-transient-state.d.ts +22 -0
- package/dist/types/app/layouts/rightbar/market-view-text.d.ts +26 -1
- package/dist/types/app/layouts/sidebar/workspace-groups.d.ts +4 -1
- package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +3 -0
- package/dist/types/modules/panels/model/market-workbench-update.d.ts +19 -0
- package/dist/types/modules/panels/model/market.d.ts +4 -0
- package/dist/types/modules/sessions/index.d.ts +1 -1
- package/dist/types/modules/sessions/model/session-node.d.ts +1 -0
- package/dist/types/shared/api/bundle-install.d.ts +1 -0
- package/dist/types/shared/api/config-sync.d.ts +35 -0
- package/dist/types/shared/api/config-sync.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/config-sync.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/post-channels.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/self-update.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-manager.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-routes.d.ts +1 -1
- package/dist/types/shared/api/host/config-sync.d.ts +46 -0
- package/dist/types/shared/api/host/post-channels.d.ts +18 -0
- package/dist/types/shared/api/host/self-update.d.ts +45 -0
- package/docs/HANDOFF.md +19 -14
- package/docs/OPERATIONS.md +40 -8
- package/package.json +4 -4
- package/scripts/seed-profile.sh +21 -85
package/dist/host/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import path, { delimiter, dirname, join, resolve, sep } from "node:path";
|
|
2
|
+
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -247,7 +247,7 @@ var PHASES = [
|
|
|
247
247
|
function asRecord$10(value) {
|
|
248
248
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
249
249
|
}
|
|
250
|
-
function textOf$
|
|
250
|
+
function textOf$7(value, max) {
|
|
251
251
|
if (typeof value !== "string") return "";
|
|
252
252
|
const trimmed = value.trim();
|
|
253
253
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -260,8 +260,8 @@ function atOf(value) {
|
|
|
260
260
|
function errorOf(raw) {
|
|
261
261
|
const record = asRecord$10(raw);
|
|
262
262
|
if (record === void 0) return void 0;
|
|
263
|
-
const code = textOf$
|
|
264
|
-
const message = textOf$
|
|
263
|
+
const code = textOf$7(record["code"], 120);
|
|
264
|
+
const message = textOf$7(record["message"], PANEL_STATE_MAX_TEXT);
|
|
265
265
|
if (code === "" && message === "") return void 0;
|
|
266
266
|
return {
|
|
267
267
|
code: code === "" ? "unknown" : code,
|
|
@@ -274,7 +274,7 @@ function parseCardsReport(payload) {
|
|
|
274
274
|
ok: false,
|
|
275
275
|
message: "请求体要是一个对象"
|
|
276
276
|
};
|
|
277
|
-
const workspace = textOf$
|
|
277
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
278
278
|
if (workspace === "") return {
|
|
279
279
|
ok: false,
|
|
280
280
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -288,9 +288,9 @@ function parseCardsReport(payload) {
|
|
|
288
288
|
for (const item of raw.slice(0, 32)) {
|
|
289
289
|
const card = asRecord$10(item);
|
|
290
290
|
if (card === void 0) continue;
|
|
291
|
-
const unit = textOf$
|
|
292
|
-
const route = textOf$
|
|
293
|
-
const phase = textOf$
|
|
291
|
+
const unit = textOf$7(card["unit"], 200);
|
|
292
|
+
const route = textOf$7(card["route"], 400);
|
|
293
|
+
const phase = textOf$7(card["phase"], 20);
|
|
294
294
|
const at = atOf(card["at"]);
|
|
295
295
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
296
296
|
const error = errorOf(card["error"]);
|
|
@@ -320,9 +320,9 @@ function parseCrashReport(payload) {
|
|
|
320
320
|
ok: false,
|
|
321
321
|
message: "请求体要是一个对象"
|
|
322
322
|
};
|
|
323
|
-
const workspace = textOf$
|
|
324
|
-
const unit = textOf$
|
|
325
|
-
const reason = textOf$
|
|
323
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
324
|
+
const unit = textOf$7(record["unit"], 200);
|
|
325
|
+
const reason = textOf$7(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
326
326
|
if (workspace === "") return {
|
|
327
327
|
ok: false,
|
|
328
328
|
message: "workspace 不能是空的"
|
|
@@ -568,20 +568,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
568
568
|
function asRecord$9(value) {
|
|
569
569
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
570
570
|
}
|
|
571
|
-
function textOf$
|
|
571
|
+
function textOf$6(value) {
|
|
572
572
|
return typeof value === "string" ? value.trim() : "";
|
|
573
573
|
}
|
|
574
574
|
function codeOf(page) {
|
|
575
575
|
const record = asRecord$9(page);
|
|
576
|
-
return record === void 0 ? "" : textOf$
|
|
576
|
+
return record === void 0 ? "" : textOf$6(record["code"]);
|
|
577
577
|
}
|
|
578
578
|
function messageOf$2(page) {
|
|
579
579
|
const record = asRecord$9(page);
|
|
580
|
-
return record === void 0 ? "" : textOf$
|
|
580
|
+
return record === void 0 ? "" : textOf$6(record["message"]);
|
|
581
581
|
}
|
|
582
582
|
function hintOf$1(page) {
|
|
583
583
|
const record = asRecord$9(page);
|
|
584
|
-
const hint = record === void 0 ? "" : textOf$
|
|
584
|
+
const hint = record === void 0 ? "" : textOf$6(record["hint"]);
|
|
585
585
|
return hint === "" ? void 0 : hint;
|
|
586
586
|
}
|
|
587
587
|
function pageFailed(page) {
|
|
@@ -812,7 +812,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
812
812
|
function asRecord$8(value) {
|
|
813
813
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
814
814
|
}
|
|
815
|
-
function textOf$
|
|
815
|
+
function textOf$5(value) {
|
|
816
816
|
return typeof value === "string" ? value.trim() : "";
|
|
817
817
|
}
|
|
818
818
|
function entriesOf(value) {
|
|
@@ -825,8 +825,8 @@ function entriesOf(value) {
|
|
|
825
825
|
if (item === void 0) continue;
|
|
826
826
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
827
827
|
entries.push({
|
|
828
|
-
text: textOf$
|
|
829
|
-
tag: textOf$
|
|
828
|
+
text: textOf$5(item["text"]),
|
|
829
|
+
tag: textOf$5(item["tag"]),
|
|
830
830
|
nth
|
|
831
831
|
});
|
|
832
832
|
}
|
|
@@ -835,7 +835,7 @@ function entriesOf(value) {
|
|
|
835
835
|
function reasonOf(stdout, stderr) {
|
|
836
836
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
837
837
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
838
|
-
const message = error === void 0 ? "" : textOf$
|
|
838
|
+
const message = error === void 0 ? "" : textOf$5(error["message"]);
|
|
839
839
|
if (message !== "") return message;
|
|
840
840
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
841
841
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -843,7 +843,7 @@ function reasonOf(stdout, stderr) {
|
|
|
843
843
|
function hintOf(stdout) {
|
|
844
844
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
845
845
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
846
|
-
const hint = error === void 0 ? "" : textOf$
|
|
846
|
+
const hint = error === void 0 ? "" : textOf$5(error["hint"]);
|
|
847
847
|
return hint === "" ? void 0 : hint;
|
|
848
848
|
}
|
|
849
849
|
function sameOrigin(left, right) {
|
|
@@ -857,7 +857,7 @@ function urlOf(stdout) {
|
|
|
857
857
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
858
858
|
if (bare.startsWith("{")) {
|
|
859
859
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
860
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
860
|
+
const url = parsed === void 0 ? "" : textOf$5(parsed["url"]);
|
|
861
861
|
if (url !== "") return url;
|
|
862
862
|
}
|
|
863
863
|
return bare;
|
|
@@ -1872,7 +1872,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1872
1872
|
function ledgerKeyOf(entry) {
|
|
1873
1873
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1874
1874
|
}
|
|
1875
|
-
function textOf$
|
|
1875
|
+
function textOf$4(value) {
|
|
1876
1876
|
if (typeof value !== "string") return void 0;
|
|
1877
1877
|
const trimmed = value.trim();
|
|
1878
1878
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1880,13 +1880,13 @@ function textOf$3(value) {
|
|
|
1880
1880
|
function entryOf(value) {
|
|
1881
1881
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1882
1882
|
const box = value;
|
|
1883
|
-
const opencliHome = textOf$
|
|
1884
|
-
const pkg = textOf$
|
|
1885
|
-
const installedAt = textOf$
|
|
1883
|
+
const opencliHome = textOf$4(box["opencliHome"]);
|
|
1884
|
+
const pkg = textOf$4(box["package"]);
|
|
1885
|
+
const installedAt = textOf$4(box["installedAt"]);
|
|
1886
1886
|
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$
|
|
1887
|
+
const name = textOf$4(box["name"]);
|
|
1888
|
+
const registry = textOf$4(box["registry"]);
|
|
1889
|
+
const releasedAt = textOf$4(box["releasedAt"]);
|
|
1890
1890
|
return {
|
|
1891
1891
|
opencliHome,
|
|
1892
1892
|
package: pkg,
|
|
@@ -2456,6 +2456,10 @@ function readBundleManager(ctx) {
|
|
|
2456
2456
|
async remove(name) {
|
|
2457
2457
|
return asChange(await face.removeBundle?.(name));
|
|
2458
2458
|
},
|
|
2459
|
+
async setEnabled(name, enabled) {
|
|
2460
|
+
if (typeof face.setBundleEnabled !== "function") return null;
|
|
2461
|
+
return asChange(await face.setBundleEnabled(name, enabled));
|
|
2462
|
+
},
|
|
2459
2463
|
async cancel(requestId) {
|
|
2460
2464
|
if (typeof face.cancelInstall !== "function") return null;
|
|
2461
2465
|
return pick(CANCELLATIONS, (asRecord$6(await face.cancelInstall(requestId)) ?? {}).status) ?? null;
|
|
@@ -2849,20 +2853,67 @@ var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
2849
2853
|
"recon",
|
|
2850
2854
|
""
|
|
2851
2855
|
]);
|
|
2856
|
+
var SPEC_LIMIT = 80;
|
|
2857
|
+
function missing(id) {
|
|
2858
|
+
return id === void 0 || id === "";
|
|
2859
|
+
}
|
|
2860
|
+
function pad(token) {
|
|
2861
|
+
return token.startsWith("[") ? ` ${token} ` : token;
|
|
2862
|
+
}
|
|
2863
|
+
function workbenchModule(id) {
|
|
2864
|
+
return missing(id) ? {
|
|
2865
|
+
zh: "工作台模块",
|
|
2866
|
+
en: "the workbench module"
|
|
2867
|
+
} : {
|
|
2868
|
+
zh: `工作台${pad(`[${id}]`)}模块`,
|
|
2869
|
+
en: `the workbench [${id}] module`
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
function anyWorkbenchModule(id) {
|
|
2873
|
+
return missing(id) ? {
|
|
2874
|
+
zh: "工作台的一个模块",
|
|
2875
|
+
en: "a workbench module"
|
|
2876
|
+
} : workbenchModule(id);
|
|
2877
|
+
}
|
|
2878
|
+
function workbenchPlugin(id) {
|
|
2879
|
+
return missing(id) ? {
|
|
2880
|
+
zh: "工作台的 opencli 插件",
|
|
2881
|
+
en: "a workbench opencli plugin"
|
|
2882
|
+
} : {
|
|
2883
|
+
zh: `工作台${pad(`[${id}]`)}opencli 插件`,
|
|
2884
|
+
en: `the workbench [${id}] opencli plugin`
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
function workbenchDependency(id) {
|
|
2888
|
+
return missing(id) ? {
|
|
2889
|
+
zh: "工作台共用依赖",
|
|
2890
|
+
en: "a workbench shared dependency"
|
|
2891
|
+
} : {
|
|
2892
|
+
zh: `工作台共用依赖 [${id}]`,
|
|
2893
|
+
en: `the workbench shared dependency [${id}]`
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
function workbenchPanelRow(id) {
|
|
2897
|
+
return missing(id) ? {
|
|
2898
|
+
zh: "工作台在当前项目的面板",
|
|
2899
|
+
en: "the workbench panel for this project"
|
|
2900
|
+
} : {
|
|
2901
|
+
zh: `工作台${pad(`[${id}]`)}模块这一行`,
|
|
2902
|
+
en: `the workbench [${id}] module row`
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2852
2905
|
function actionOf$1(call) {
|
|
2853
2906
|
const raw = call.arguments;
|
|
2854
2907
|
if (typeof raw !== "object" || raw === null) return {};
|
|
2855
2908
|
const value = raw;
|
|
2856
2909
|
const target = typeof value.plugin === "string" ? value.plugin : typeof value.name === "string" ? value.name : typeof value.id === "string" ? value.id : typeof value.unit === "string" ? value.unit : void 0;
|
|
2910
|
+
const spec = typeof value.spec === "string" ? value.spec.replace(/\s+/g, " ").trim() : "";
|
|
2857
2911
|
return {
|
|
2858
2912
|
...typeof value.action === "string" ? { action: value.action } : {},
|
|
2859
|
-
...target === void 0 ? {} : { id: target }
|
|
2913
|
+
...target === void 0 ? {} : { id: target },
|
|
2914
|
+
...spec === "" ? {} : { spec: spec.length > SPEC_LIMIT ? `${spec.slice(0, SPEC_LIMIT)}…` : spec }
|
|
2860
2915
|
};
|
|
2861
2916
|
}
|
|
2862
|
-
function whatOf(id) {
|
|
2863
|
-
if (id === void 0 || id === "") return "一个模块";
|
|
2864
|
-
return `「${id}」`;
|
|
2865
|
-
}
|
|
2866
2917
|
function askOf(reason, question, fallback) {
|
|
2867
2918
|
return {
|
|
2868
2919
|
kind: "ask",
|
|
@@ -2872,92 +2923,151 @@ function askOf(reason, question, fallback) {
|
|
|
2872
2923
|
};
|
|
2873
2924
|
}
|
|
2874
2925
|
var panelsApproval = (call) => {
|
|
2875
|
-
const { action, id } = actionOf$1(call);
|
|
2926
|
+
const { action, id, spec } = actionOf$1(call);
|
|
2876
2927
|
const readAction = action !== void 0 && action !== "" && READ_ONLY_ACTIONS.has(action);
|
|
2877
|
-
const what = whatOf(id);
|
|
2878
2928
|
if (call.name === "workbench_modules") {
|
|
2879
2929
|
if (readAction) return READ;
|
|
2880
2930
|
if (action === "update-plugin" || action === "uninstall-plugin") {
|
|
2881
2931
|
const verb = action === "update-plugin" ? "更新" : "卸载";
|
|
2882
|
-
const verbEn = action === "update-plugin" ? "update" : "
|
|
2932
|
+
const verbEn = action === "update-plugin" ? "update" : "uninstall";
|
|
2933
|
+
const target = workbenchPlugin(id);
|
|
2883
2934
|
return askOf(`workbench_modules ${action} ${id ?? ""} changes an opencli plugin on this machine`, {
|
|
2884
|
-
en: `it ${verbEn}s
|
|
2885
|
-
zh: `会${verb}
|
|
2935
|
+
en: `it ${verbEn}s ${target.en}. Allow?`,
|
|
2936
|
+
zh: `会${verb}${target.zh},允许吗?`
|
|
2886
2937
|
}, {
|
|
2887
|
-
en: `The agent wants to ${verbEn}
|
|
2888
|
-
zh: `agent 想${verb}
|
|
2938
|
+
en: `The agent wants to ${verbEn} ${target.en}. Allow?`,
|
|
2939
|
+
zh: `agent 想${verb}${target.zh},允许吗?`
|
|
2889
2940
|
});
|
|
2890
2941
|
}
|
|
2891
2942
|
if (action === "update-workbench") return askOf("workbench_modules update-workbench replaces the workbench itself with the new version from the registry", {
|
|
2892
|
-
en: "it updates the workbench itself
|
|
2893
|
-
zh: "
|
|
2943
|
+
en: "it updates the workbench itself to the new version from the registry. Allow?",
|
|
2944
|
+
zh: "会把工作台本身更新到源上的新版,允许吗?"
|
|
2894
2945
|
}, {
|
|
2895
|
-
en: "The agent wants to update the workbench itself to the new version from the registry
|
|
2896
|
-
zh: "agent
|
|
2946
|
+
en: "The agent wants to update the workbench itself to the new version from the registry. Allow?",
|
|
2947
|
+
zh: "agent 想把工作台本身更新到源上的新版,允许吗?"
|
|
2897
2948
|
});
|
|
2898
|
-
if (action === "update-dependency")
|
|
2899
|
-
|
|
2900
|
-
|
|
2949
|
+
if (action === "update-dependency") {
|
|
2950
|
+
const target = workbenchDependency(id);
|
|
2951
|
+
return askOf(`workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`, {
|
|
2952
|
+
en: `it upgrades ${target.en}. Allow?`,
|
|
2953
|
+
zh: `会升级${target.zh},允许吗?`
|
|
2954
|
+
}, {
|
|
2955
|
+
en: `The agent wants to upgrade ${target.en}. Allow?`,
|
|
2956
|
+
zh: `agent 想升级${target.zh},允许吗?`
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
if (action === "install-bundle" && spec !== void 0) return askOf(`workbench_modules install-bundle ${spec} installs a workbench module from a spec`, {
|
|
2960
|
+
en: `it installs the workbench module from [${spec}]. Allow?`,
|
|
2961
|
+
zh: `会安装工作台模块 [${spec}],允许吗?`
|
|
2901
2962
|
}, {
|
|
2902
|
-
en: `The agent wants to
|
|
2903
|
-
zh: `agent
|
|
2963
|
+
en: `The agent wants to install the workbench module from [${spec}]. Allow?`,
|
|
2964
|
+
zh: `agent 想安装工作台模块 [${spec}],允许吗?`
|
|
2904
2965
|
});
|
|
2905
|
-
|
|
2906
|
-
|
|
2966
|
+
if (action === "install") {
|
|
2967
|
+
const target = workbenchModule(id);
|
|
2968
|
+
return askOf(`workbench_modules install ${id ?? ""} installs a workbench module on this machine`, {
|
|
2969
|
+
en: `it installs ${target.en}. Allow?`,
|
|
2970
|
+
zh: `会安装${target.zh},允许吗?`
|
|
2971
|
+
}, {
|
|
2972
|
+
en: `The agent wants to install ${target.en}. Allow?`,
|
|
2973
|
+
zh: `agent 想安装${target.zh},允许吗?`
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
if (action === "update") {
|
|
2977
|
+
const target = workbenchModule(id);
|
|
2978
|
+
return askOf(`workbench_modules update ${id ?? ""} updates a workbench module on this machine`, {
|
|
2979
|
+
en: `it updates ${target.en}. Allow?`,
|
|
2980
|
+
zh: `会更新${target.zh},允许吗?`
|
|
2981
|
+
}, {
|
|
2982
|
+
en: `The agent wants to update ${target.en}. Allow?`,
|
|
2983
|
+
zh: `agent 想更新${target.zh},允许吗?`
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
if (action === "uninstall") {
|
|
2987
|
+
const target = workbenchModule(id);
|
|
2988
|
+
return askOf(`workbench_modules uninstall ${id ?? ""} removes a workbench module from this machine`, {
|
|
2989
|
+
en: `it uninstalls ${target.en}. Allow?`,
|
|
2990
|
+
zh: `会卸载${target.zh},允许吗?`
|
|
2991
|
+
}, {
|
|
2992
|
+
en: `The agent wants to uninstall ${target.en}. Allow?`,
|
|
2993
|
+
zh: `agent 想卸载${target.zh},允许吗?`
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2907
2996
|
return askOf(`workbench_modules ${action ?? "(unknown action)"} ${id ?? ""} changes plugins on this machine`.trim(), {
|
|
2908
|
-
en: "it changes plugins
|
|
2909
|
-
zh: "
|
|
2997
|
+
en: "it changes the plugins installed in the workbench. Allow?",
|
|
2998
|
+
zh: "会改动工作台装的插件,允许吗?"
|
|
2910
2999
|
}, {
|
|
2911
|
-
en:
|
|
2912
|
-
zh:
|
|
3000
|
+
en: "The agent wants to change the plugins installed in the workbench. Allow?",
|
|
3001
|
+
zh: "agent 想改动工作台装的插件,允许吗?"
|
|
2913
3002
|
});
|
|
2914
3003
|
}
|
|
2915
3004
|
if (call.name === "workbench_diy") {
|
|
2916
3005
|
if (readAction) return READ;
|
|
2917
|
-
if (action === "open")
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
3006
|
+
if (action === "open") {
|
|
3007
|
+
const target = anyWorkbenchModule(id);
|
|
3008
|
+
return askOf("workbench_diy open puts an already-installed module on this project panel", {
|
|
3009
|
+
en: `it shows ${target.en} on this project's panel. Allow?`,
|
|
3010
|
+
zh: `会把${target.zh}显示在当前项目的面板上,允许吗?`
|
|
3011
|
+
}, {
|
|
3012
|
+
en: `The agent wants to show ${target.en} on this project's panel. Allow?`,
|
|
3013
|
+
zh: `agent 想把${target.zh}显示在当前项目的面板上,允许吗?`
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
const target = workbenchModule(id);
|
|
2924
3017
|
return askOf(`workbench_diy ${action ?? "(unknown action)"} installs a module built in the workshop and opens it on this project's panel`, {
|
|
2925
|
-
en: `it installs ${
|
|
2926
|
-
zh:
|
|
3018
|
+
en: `it installs ${target.en} and shows it on this project's panel. Allow?`,
|
|
3019
|
+
zh: `会安装${target.zh},并显示在当前项目的面板上,允许吗?`
|
|
2927
3020
|
}, {
|
|
2928
|
-
en: `The agent wants to install
|
|
2929
|
-
zh: `agent
|
|
3021
|
+
en: `The agent wants to install ${target.en} built in the DIY workshop and show it on this project's panel. Allow?`,
|
|
3022
|
+
zh: `agent 想安装${target.zh},并显示在当前项目的面板上,允许吗?`
|
|
2930
3023
|
});
|
|
2931
3024
|
}
|
|
2932
3025
|
if (call.name !== "workbench_panels") return void 0;
|
|
2933
3026
|
if (readAction) return READ;
|
|
2934
|
-
if (action === "hide")
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3027
|
+
if (action === "hide") {
|
|
3028
|
+
const target = anyWorkbenchModule(id);
|
|
3029
|
+
return askOf("workbench_panels hide removes a module from the panel", {
|
|
3030
|
+
en: `it takes ${target.en} off this project's panel. Allow?`,
|
|
3031
|
+
zh: `会把${target.zh}从当前项目的面板上收起来,允许吗?`
|
|
3032
|
+
}, {
|
|
3033
|
+
en: `The agent wants to take ${target.en} off this project's panel. Allow?`,
|
|
3034
|
+
zh: `agent 想把${target.zh}从当前项目的面板上收起来,允许吗?`
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
if (action === "move") {
|
|
3038
|
+
const target = anyWorkbenchModule(id);
|
|
3039
|
+
return askOf("workbench_panels move reorders the panel", {
|
|
3040
|
+
en: `it moves ${target.en} to the front of this project's panel. Allow?`,
|
|
3041
|
+
zh: `会把${target.zh}移到当前项目面板的最前面,允许吗?`
|
|
3042
|
+
}, {
|
|
3043
|
+
en: `The agent wants to move ${target.en} to the front of this project's panel. Allow?`,
|
|
3044
|
+
zh: `agent 想把${target.zh}移到当前项目面板的最前面,允许吗?`
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
2948
3047
|
if (action === "reset") return askOf("workbench_panels reset clears the panel", {
|
|
2949
|
-
en:
|
|
2950
|
-
zh: "
|
|
3048
|
+
en: `it clears the workbench panel for this project. Allow?`,
|
|
3049
|
+
zh: "会清空工作台在当前项目的面板,允许吗?"
|
|
2951
3050
|
}, {
|
|
2952
|
-
en: "The agent wants to clear
|
|
2953
|
-
zh: "agent
|
|
3051
|
+
en: "The agent wants to clear the workbench panel for this project. Allow?",
|
|
3052
|
+
zh: "agent 想清空工作台在当前项目的面板,允许吗?"
|
|
2954
3053
|
});
|
|
3054
|
+
if (action === "follow") {
|
|
3055
|
+
const target = workbenchPanelRow(id);
|
|
3056
|
+
return askOf("workbench_panels follow puts a row back to following the machine-level board", {
|
|
3057
|
+
en: `it puts ${target.en} back to following the machine-level board. Allow?`,
|
|
3058
|
+
zh: `会把${target.zh}改回跟随机器级默认板,允许吗?`
|
|
3059
|
+
}, {
|
|
3060
|
+
en: `The agent wants to put ${target.en} back to following the machine-level board. Allow?`,
|
|
3061
|
+
zh: `agent 想把${target.zh}改回跟随机器级默认板,允许吗?`
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
const target = anyWorkbenchModule(id);
|
|
2955
3065
|
return askOf(`workbench_panels ${action ?? "(unknown action)"} changes the user's panel`, {
|
|
2956
|
-
en: `it adds ${
|
|
2957
|
-
zh:
|
|
3066
|
+
en: `it adds ${target.en} to this project's panel. Allow?`,
|
|
3067
|
+
zh: `会把${target.zh}加到当前项目的面板上,允许吗?`
|
|
2958
3068
|
}, {
|
|
2959
|
-
en: `The agent wants to add ${
|
|
2960
|
-
zh: `agent
|
|
3069
|
+
en: `The agent wants to add ${target.en} to this project's panel. Allow?`,
|
|
3070
|
+
zh: `agent 想把${target.zh}加到当前项目的面板上,允许吗?`
|
|
2961
3071
|
});
|
|
2962
3072
|
};
|
|
2963
3073
|
//#endregion
|
|
@@ -3272,7 +3382,7 @@ function reconOf(commands, plugins, keywords) {
|
|
|
3272
3382
|
};
|
|
3273
3383
|
return {
|
|
3274
3384
|
...withNext,
|
|
3275
|
-
text: textOf$
|
|
3385
|
+
text: textOf$3(withNext)
|
|
3276
3386
|
};
|
|
3277
3387
|
}
|
|
3278
3388
|
function nextOf(answer) {
|
|
@@ -3285,7 +3395,7 @@ function nextOf(answer) {
|
|
|
3285
3395
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
3286
3396
|
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy action=probe(script 读接口 / css 读 DOM)";
|
|
3287
3397
|
}
|
|
3288
|
-
function textOf$
|
|
3398
|
+
function textOf$3(answer) {
|
|
3289
3399
|
const lines = [];
|
|
3290
3400
|
if (answer.sites.length > 0) {
|
|
3291
3401
|
lines.push(`opencli 自带这 ${String(answer.sites.length)} 组能力(不用装、不用点头):`);
|
|
@@ -3586,26 +3696,26 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3586
3696
|
}
|
|
3587
3697
|
const tool = {
|
|
3588
3698
|
name: "workbench_diy",
|
|
3589
|
-
description: "
|
|
3699
|
+
description: "DIY 工坊里的模块:看它走到哪一步,或把它装上 / 上板。只认工坊里造好的模块 —— 普通项目里装模块用 workbench_modules。status 是入口,照它给的\"下一步\"走。install 是这台机器上的持久变更,一个模块一辈子只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。",
|
|
3590
3700
|
parameters: withApprovalNote({
|
|
3591
3701
|
type: "object",
|
|
3592
3702
|
properties: {
|
|
3593
3703
|
action: {
|
|
3594
3704
|
type: "string",
|
|
3595
|
-
description: "list=工坊里有哪些模块(默认);status
|
|
3705
|
+
description: "list=工坊里有哪些模块(默认);status=某个模块走到哪一步、下一步做什么;panel=面板此刻的真实样子(宿主回的原文都在)—— 用户说\"卡片不对 / 一片空白 / 一直转圈\"就先调它;recon=先查 opencli 有没有现成的(自带 adapter / 本机插件)—— opencli 是什么自己查,别问用户;probe=用用户浏览器里现有的登录探一条读(script 或 css);open=只上板(不装);install=装上并上板(第一次,会让用户点头)。",
|
|
3596
3706
|
enum: [...ACTIONS$2]
|
|
3597
3707
|
},
|
|
3598
3708
|
unit: {
|
|
3599
3709
|
type: "string",
|
|
3600
|
-
description: "status / panel / open / install
|
|
3710
|
+
description: "status / panel / open / install 填:单元 id(list 会给)。"
|
|
3601
3711
|
},
|
|
3602
3712
|
script: {
|
|
3603
3713
|
type: "string",
|
|
3604
|
-
description: "probe 要(与 css
|
|
3714
|
+
description: "probe 要(与 css 二选一):读接口用这个 —— 在页面里跑一段回 JSON 的脚本。不在站点就回 { ok: false, code: \"not-on-site\" };登录过期回 { ok: false, code: \"not-logged-in\" };成功回 { ok: true, ...数据 }。"
|
|
3605
3715
|
},
|
|
3606
3716
|
css: {
|
|
3607
3717
|
type: "string",
|
|
3608
|
-
description: "probe 要(与 script
|
|
3718
|
+
description: "probe 要(与 script 二选一):读 DOM 用这个 —— 给一个 CSS 选择器,宿主回匹配到的元素文本(每行的单元格按换行拼在 text 里);不在站点会自己打开 url 再读一遍。"
|
|
3609
3719
|
},
|
|
3610
3720
|
limit: {
|
|
3611
3721
|
type: "number",
|
|
@@ -3617,11 +3727,11 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3617
3727
|
},
|
|
3618
3728
|
url: {
|
|
3619
3729
|
type: "string",
|
|
3620
|
-
description: "probe / recon 用:那套系统的地址(probe 不在站点上时自己打开它再读一遍;recon
|
|
3730
|
+
description: "probe / recon 用:那套系统的地址(probe 不在站点上时自己打开它再读一遍;recon 从它取域名)。"
|
|
3621
3731
|
},
|
|
3622
3732
|
keyword: {
|
|
3623
3733
|
type: "string",
|
|
3624
|
-
description: "recon 要:站点名 / 系统名 /
|
|
3734
|
+
description: "recon 要:站点名 / 系统名 / 域名(英文名或域名最准,例:bilibili、arxiv.org、cmdb)。"
|
|
3625
3735
|
},
|
|
3626
3736
|
loginUrl: {
|
|
3627
3737
|
type: "string",
|
|
@@ -3629,7 +3739,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3629
3739
|
},
|
|
3630
3740
|
session: {
|
|
3631
3741
|
type: "string",
|
|
3632
|
-
description: "probe
|
|
3742
|
+
description: "probe 用:会话名(默认 workbench,一般不用填)。"
|
|
3633
3743
|
},
|
|
3634
3744
|
open: {
|
|
3635
3745
|
type: "boolean",
|
|
@@ -4482,6 +4592,330 @@ async function installRouted(deps) {
|
|
|
4482
4592
|
});
|
|
4483
4593
|
}
|
|
4484
4594
|
//#endregion
|
|
4595
|
+
//#region src/shared/api/host/runtime-seed.ts
|
|
4596
|
+
var WORKSHOP_SKILLS_DIR = "workbench-module-diy";
|
|
4597
|
+
function runtimeSeedPaths(root) {
|
|
4598
|
+
const opencliRoot = join(root, "opencli");
|
|
4599
|
+
const opencliBinDir = join(opencliRoot, "bin");
|
|
4600
|
+
const workshop = join(root, "diy");
|
|
4601
|
+
return {
|
|
4602
|
+
root,
|
|
4603
|
+
entryDir: join(root, "bin"),
|
|
4604
|
+
opencliRoot,
|
|
4605
|
+
opencliBinDir,
|
|
4606
|
+
opencliBin: join(opencliBinDir, "opencli"),
|
|
4607
|
+
opencliHome: join(opencliRoot, "home"),
|
|
4608
|
+
workshop,
|
|
4609
|
+
workshopPackages: join(workshop, "packages"),
|
|
4610
|
+
workshopManifest: join(workshop, "package.json"),
|
|
4611
|
+
skillsLink: join(workshop, ".agents", "skills", WORKSHOP_SKILLS_DIR)
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
function runtimeSeedRootOf(ctx) {
|
|
4615
|
+
const homePath = ctx.get("dshHomePath");
|
|
4616
|
+
if (typeof homePath !== "function") return null;
|
|
4617
|
+
try {
|
|
4618
|
+
return homePath("workbench");
|
|
4619
|
+
} catch {
|
|
4620
|
+
return null;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
function packageRootOf(moduleUrl) {
|
|
4624
|
+
return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
|
|
4625
|
+
}
|
|
4626
|
+
function isInstalledCopy(packageRoot) {
|
|
4627
|
+
return packageRoot.split(sep).includes("node_modules");
|
|
4628
|
+
}
|
|
4629
|
+
function opencliWrapperScript(packageRoot, home) {
|
|
4630
|
+
const script = join(packageRoot, "bin", "opencli-isolated");
|
|
4631
|
+
return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
|
|
4632
|
+
}
|
|
4633
|
+
function workshopSeedPlan(packageRoot, home, root) {
|
|
4634
|
+
const script = join(packageRoot, "scripts", "seed-workshop.sh");
|
|
4635
|
+
const env = { DSH_HOME: home };
|
|
4636
|
+
const log = join(root, "seed.log");
|
|
4637
|
+
return {
|
|
4638
|
+
entry: {
|
|
4639
|
+
file: "bash",
|
|
4640
|
+
args: [script, "entry"],
|
|
4641
|
+
env,
|
|
4642
|
+
log
|
|
4643
|
+
},
|
|
4644
|
+
full: {
|
|
4645
|
+
file: "bash",
|
|
4646
|
+
args: [script],
|
|
4647
|
+
env,
|
|
4648
|
+
log
|
|
4649
|
+
}
|
|
4650
|
+
};
|
|
4651
|
+
}
|
|
4652
|
+
function runDetached(command, onError, spawnImpl = spawn) {
|
|
4653
|
+
let fd = null;
|
|
4654
|
+
try {
|
|
4655
|
+
fd = openSync(command.log, "a");
|
|
4656
|
+
const child = spawnImpl(command.file, [...command.args], {
|
|
4657
|
+
env: {
|
|
4658
|
+
...process.env,
|
|
4659
|
+
...command.env
|
|
4660
|
+
},
|
|
4661
|
+
detached: true,
|
|
4662
|
+
stdio: [
|
|
4663
|
+
"ignore",
|
|
4664
|
+
fd,
|
|
4665
|
+
fd
|
|
4666
|
+
]
|
|
4667
|
+
});
|
|
4668
|
+
child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
|
|
4669
|
+
child.unref();
|
|
4670
|
+
} catch (error) {
|
|
4671
|
+
onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
|
|
4672
|
+
} finally {
|
|
4673
|
+
if (fd !== null) closeQuietly(fd);
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
function closeQuietly(fd) {
|
|
4677
|
+
try {
|
|
4678
|
+
closeSync(fd);
|
|
4679
|
+
} catch {
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function prependPath(entryDir, env = process.env) {
|
|
4684
|
+
const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
|
|
4685
|
+
if (parts.includes(entryDir)) return false;
|
|
4686
|
+
env.PATH = [entryDir, ...parts].join(delimiter);
|
|
4687
|
+
return true;
|
|
4688
|
+
}
|
|
4689
|
+
function seedRuntimeFiles(options) {
|
|
4690
|
+
const paths = runtimeSeedPaths(options.root);
|
|
4691
|
+
const done = [];
|
|
4692
|
+
try {
|
|
4693
|
+
for (const dir of [
|
|
4694
|
+
paths.entryDir,
|
|
4695
|
+
paths.opencliBinDir,
|
|
4696
|
+
paths.opencliHome,
|
|
4697
|
+
paths.workshopPackages
|
|
4698
|
+
]) mkdirSync(dir, { recursive: true });
|
|
4699
|
+
const wanted = opencliWrapperScript(options.packageRoot, options.home);
|
|
4700
|
+
if (readText(paths.opencliBin) !== wanted) {
|
|
4701
|
+
rmSync(paths.opencliBin, { force: true });
|
|
4702
|
+
writeFileSync(paths.opencliBin, wanted, "utf8");
|
|
4703
|
+
chmodSync(paths.opencliBin, 493);
|
|
4704
|
+
done.push(paths.opencliBin);
|
|
4705
|
+
}
|
|
4706
|
+
if (linkSkills(paths.skillsLink, join(options.packageRoot, "skills", "workbench-module-diy"), options.log)) done.push(paths.skillsLink);
|
|
4707
|
+
} catch (error) {
|
|
4708
|
+
options.log?.(`自举没做完:${String(error)}`);
|
|
4709
|
+
}
|
|
4710
|
+
return done;
|
|
4711
|
+
}
|
|
4712
|
+
function readText(file) {
|
|
4713
|
+
try {
|
|
4714
|
+
return readFileSync(file, "utf8");
|
|
4715
|
+
} catch {
|
|
4716
|
+
return null;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
function readLink(link) {
|
|
4720
|
+
try {
|
|
4721
|
+
return readlinkSync(link);
|
|
4722
|
+
} catch {
|
|
4723
|
+
return null;
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
function linkSkills(link, source, log) {
|
|
4727
|
+
if (!existsSync(source) || readLink(link) === source) return false;
|
|
4728
|
+
try {
|
|
4729
|
+
mkdirSync(dirname(link), { recursive: true });
|
|
4730
|
+
rmSync(link, {
|
|
4731
|
+
force: true,
|
|
4732
|
+
recursive: true
|
|
4733
|
+
});
|
|
4734
|
+
symlinkSync(source, link, "dir");
|
|
4735
|
+
return true;
|
|
4736
|
+
} catch (error) {
|
|
4737
|
+
log?.(`DIY 指引没挂上(${link}):${String(error)}`);
|
|
4738
|
+
return false;
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
//#endregion
|
|
4742
|
+
//#region src/shared/api/host/self-update.ts
|
|
4743
|
+
function runningVersionOf(moduleUrl) {
|
|
4744
|
+
try {
|
|
4745
|
+
const read = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8"));
|
|
4746
|
+
return typeof read.version === "string" && read.version !== "" ? read.version : void 0;
|
|
4747
|
+
} catch {
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
function selfUpdatePaths(dshHome) {
|
|
4752
|
+
const dir = join(dshHome, "workbench");
|
|
4753
|
+
return {
|
|
4754
|
+
dir,
|
|
4755
|
+
script: join(dir, "self-update.sh"),
|
|
4756
|
+
marker: join(dir, "self-update.json"),
|
|
4757
|
+
log: join(dir, "self-update.log")
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
function recoveryCommand(input) {
|
|
4761
|
+
return `dsh plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
|
|
4762
|
+
}
|
|
4763
|
+
function selfUpdateMarker(request, state, version, at) {
|
|
4764
|
+
return {
|
|
4765
|
+
...request,
|
|
4766
|
+
state,
|
|
4767
|
+
version,
|
|
4768
|
+
at
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
function quoted(value) {
|
|
4772
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
4773
|
+
}
|
|
4774
|
+
function selfUpdateScript(request, paths) {
|
|
4775
|
+
const rescue = recoveryCommand({
|
|
4776
|
+
profile: request.profile,
|
|
4777
|
+
pkg: request.pkg,
|
|
4778
|
+
version: request.from
|
|
4779
|
+
});
|
|
4780
|
+
const lastsOne = recoveryCommand({
|
|
4781
|
+
profile: request.profile,
|
|
4782
|
+
pkg: request.pkg,
|
|
4783
|
+
version: request.to === "" ? "latest" : request.to
|
|
4784
|
+
});
|
|
4785
|
+
const target = request.to === "" ? "latest" : request.to;
|
|
4786
|
+
const landed = request.to === "" ? "is_other" : "is_to";
|
|
4787
|
+
return `#!/usr/bin/env bash
|
|
4788
|
+
set -uo pipefail
|
|
4789
|
+
DSH_HOME=${quoted(request.dshHome)}
|
|
4790
|
+
PKG=${quoted(request.pkg)}
|
|
4791
|
+
PROFILE=${quoted(request.profile)}
|
|
4792
|
+
FROM=${quoted(request.from)}
|
|
4793
|
+
TO=${quoted(target)}
|
|
4794
|
+
MARKER=${quoted(paths.marker)}
|
|
4795
|
+
LOG=${quoted(paths.log)}
|
|
4796
|
+
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
|
|
4797
|
+
say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
|
|
4798
|
+
on_disk() {
|
|
4799
|
+
node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
|
|
4800
|
+
"$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
|
|
4801
|
+
}
|
|
4802
|
+
write_marker() {
|
|
4803
|
+
printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
|
|
4804
|
+
"$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
|
|
4805
|
+
}
|
|
4806
|
+
install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }
|
|
4807
|
+
is_to() { [ "$(on_disk)" = "$TO" ]; }
|
|
4808
|
+
is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
|
|
4809
|
+
if ! command -v dsh >/dev/null 2>&1; then
|
|
4810
|
+
say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"
|
|
4811
|
+
write_marker failed "$FROM"
|
|
4812
|
+
exit 1
|
|
4813
|
+
fi
|
|
4814
|
+
say "开始 \${FROM} -> \${TO}(不卸任何一行)"
|
|
4815
|
+
write_marker running "$FROM"
|
|
4816
|
+
if install "$TO" && ${landed}; then
|
|
4817
|
+
say "换好了:$(on_disk) 在盘上(目标 \${TO}),重启一次 dsh 才用得上"
|
|
4818
|
+
write_marker ok "$(on_disk)"
|
|
4819
|
+
exit 0
|
|
4820
|
+
fi
|
|
4821
|
+
say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
|
|
4822
|
+
if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
|
|
4823
|
+
say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
|
|
4824
|
+
write_marker rolled-back "$FROM"
|
|
4825
|
+
exit 0
|
|
4826
|
+
fi
|
|
4827
|
+
say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
|
|
4828
|
+
write_marker failed "$(on_disk)"
|
|
4829
|
+
exit 1
|
|
4830
|
+
`;
|
|
4831
|
+
}
|
|
4832
|
+
function planSelfUpdate(request, now = /* @__PURE__ */ new Date()) {
|
|
4833
|
+
const paths = selfUpdatePaths(request.dshHome);
|
|
4834
|
+
return {
|
|
4835
|
+
paths,
|
|
4836
|
+
script: selfUpdateScript(request, paths),
|
|
4837
|
+
marker: selfUpdateMarker(request, "running", request.from, now.toISOString()),
|
|
4838
|
+
command: {
|
|
4839
|
+
file: "bash",
|
|
4840
|
+
args: [paths.script],
|
|
4841
|
+
env: {},
|
|
4842
|
+
log: paths.log
|
|
4843
|
+
}
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4846
|
+
function startSelfUpdate(request, onError, spawnImpl = spawn) {
|
|
4847
|
+
const plan = planSelfUpdate(request);
|
|
4848
|
+
mkdirSync(plan.paths.dir, { recursive: true });
|
|
4849
|
+
writeFileSync(plan.paths.script, plan.script, { mode: 493 });
|
|
4850
|
+
chmodSync(plan.paths.script, 493);
|
|
4851
|
+
writeFileSync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
|
|
4852
|
+
runDetached(plan.command, onError, spawnImpl);
|
|
4853
|
+
const forward = recoveryCommand({
|
|
4854
|
+
profile: request.profile,
|
|
4855
|
+
pkg: request.pkg,
|
|
4856
|
+
version: request.to === "" ? "latest" : request.to
|
|
4857
|
+
});
|
|
4858
|
+
return {
|
|
4859
|
+
started: true,
|
|
4860
|
+
paths: plan.paths,
|
|
4861
|
+
forward
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
//#endregion
|
|
4865
|
+
//#region src/modules/panels/model/market-workbench-update.ts
|
|
4866
|
+
async function updateWorkbenchOnce(deps) {
|
|
4867
|
+
const asked = deps.asked.trim();
|
|
4868
|
+
const target = asked === "" ? PLUGIN_ID : asked;
|
|
4869
|
+
if (target !== "@dsh-workbench/core") throw new Error(`这条路只更新工作台自己那一份(${PLUGIN_ID}):${target}`);
|
|
4870
|
+
const read = deps.workbench?.(target);
|
|
4871
|
+
if (read === void 0) throw new Error(`读不到 profile 里装的 ${target} —— 没法判断装的是哪一份,更新做不了。`);
|
|
4872
|
+
if (read.local === true) throw new Error("这份工作台是从本仓装进来的(开发副本),不从源上更新。");
|
|
4873
|
+
const shell = deps.shell;
|
|
4874
|
+
const registry = read.registry ?? await deps.profileRegistry?.();
|
|
4875
|
+
const askedLatest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
4876
|
+
shell,
|
|
4877
|
+
name: read.pkg,
|
|
4878
|
+
registry,
|
|
4879
|
+
force: true
|
|
4880
|
+
});
|
|
4881
|
+
if (shell !== void 0 && registry !== void 0 && askedLatest !== void 0) {
|
|
4882
|
+
const missing = await missingReleaseParts({
|
|
4883
|
+
shell,
|
|
4884
|
+
registry,
|
|
4885
|
+
version: askedLatest,
|
|
4886
|
+
force: true
|
|
4887
|
+
});
|
|
4888
|
+
if (missing.length > 0) throw new Error(`源上已经有 ${read.pkg}@${askedLatest},但这一份发布还没铺齐:${missing.join("、")} 在这一版上还拉不到。装上会缺依赖,稍等再试 —— 市场页那颗「重新检查」会重新探一次。`);
|
|
4889
|
+
}
|
|
4890
|
+
const home = deps.dshHome?.();
|
|
4891
|
+
const profile = deps.profileName?.();
|
|
4892
|
+
if (home === void 0 || profile === void 0 || profile === null) throw new Error("拿不到 DSH_HOME 或 profile 名字 —— 自更新要多一个进程在外头跑,这一步做不了。");
|
|
4893
|
+
const spec = askedLatest === void 0 ? `${read.pkg}@latest` : `${read.pkg}@${askedLatest}`;
|
|
4894
|
+
const started = (deps.selfUpdate ?? startSelfUpdate)({
|
|
4895
|
+
dshHome: home,
|
|
4896
|
+
profile,
|
|
4897
|
+
pkg: read.pkg,
|
|
4898
|
+
from: read.version,
|
|
4899
|
+
to: askedLatest ?? "",
|
|
4900
|
+
pinned: askedLatest !== void 0
|
|
4901
|
+
}, (message) => deps.logger?.warn?.(`[dsh-workbench] ${message}`));
|
|
4902
|
+
deps.logger?.info?.(`[dsh-workbench] 自更新交给外部执行器(${started.paths.script}):${read.version} → ${askedLatest ?? "最新版"},这一跳不掉自己那一行`);
|
|
4903
|
+
return {
|
|
4904
|
+
change: {
|
|
4905
|
+
application: "restart-required",
|
|
4906
|
+
stage: "install",
|
|
4907
|
+
changed: false,
|
|
4908
|
+
target: spec,
|
|
4909
|
+
bundle: read.pkg
|
|
4910
|
+
},
|
|
4911
|
+
module: deps.installed?.(read.pkg) ?? {
|
|
4912
|
+
kind: "unknown",
|
|
4913
|
+
pkg: read.pkg
|
|
4914
|
+
},
|
|
4915
|
+
supply: { state: "no-needs" }
|
|
4916
|
+
};
|
|
4917
|
+
}
|
|
4918
|
+
//#endregion
|
|
4485
4919
|
//#region src/modules/panels/model/plugin-actions.ts
|
|
4486
4920
|
function pluginKeptOf(retention, detail) {
|
|
4487
4921
|
if (retention === "keep-held") return { kind: "held" };
|
|
@@ -4722,7 +5156,7 @@ async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
|
4722
5156
|
//#endregion
|
|
4723
5157
|
//#region src/modules/panels/model/market.ts
|
|
4724
5158
|
function createMarketService(options) {
|
|
4725
|
-
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references } = options;
|
|
5159
|
+
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references, dshHome, profileName } = options;
|
|
4726
5160
|
const command = options.command ?? "opencli";
|
|
4727
5161
|
const catalog = () => manifestsNow?.() ?? manifests;
|
|
4728
5162
|
function requireEntry(unit) {
|
|
@@ -4874,7 +5308,11 @@ function createMarketService(options) {
|
|
|
4874
5308
|
async function removeInstalledBundle(manager, pkg) {
|
|
4875
5309
|
const first = await manager.remove(pkg);
|
|
4876
5310
|
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
4877
|
-
|
|
5311
|
+
const settled = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
5312
|
+
if (settled.application !== "failed" && settled.application !== "cancelled") return settled;
|
|
5313
|
+
const put = await manager.setEnabled?.(pkg, true);
|
|
5314
|
+
logger?.warn?.(`[dsh-workbench] ${pkg} 没卸成(${settled.errorCode ?? settled.application})—— 已把它那一行放回名单,别让一次失败留在"装着但关着"`);
|
|
5315
|
+
return put ?? settled;
|
|
4878
5316
|
}
|
|
4879
5317
|
function routeDeps(manager) {
|
|
4880
5318
|
return {
|
|
@@ -5065,31 +5503,23 @@ function createMarketService(options) {
|
|
|
5065
5503
|
return outcome;
|
|
5066
5504
|
},
|
|
5067
5505
|
async updateWorkbench(pkg) {
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5506
|
+
return updateWorkbenchOnce({
|
|
5507
|
+
asked: pkg,
|
|
5508
|
+
...workbench === void 0 ? {} : { workbench },
|
|
5509
|
+
...shell === void 0 ? {} : { shell },
|
|
5510
|
+
...installed === void 0 ? {} : { installed },
|
|
5511
|
+
...dshHome === void 0 ? {} : { dshHome },
|
|
5512
|
+
...profileName === void 0 ? {} : { profileName },
|
|
5513
|
+
profileRegistry: async () => {
|
|
5514
|
+
try {
|
|
5515
|
+
return (await requireBundles().registries())?.resolved ?? void 0;
|
|
5516
|
+
} catch {
|
|
5517
|
+
return;
|
|
5518
|
+
}
|
|
5519
|
+
},
|
|
5520
|
+
...options.selfUpdate === void 0 ? {} : { selfUpdate: options.selfUpdate },
|
|
5521
|
+
...logger === void 0 ? {} : { logger }
|
|
5079
5522
|
});
|
|
5080
|
-
if (shell !== void 0 && registry !== void 0 && latest !== void 0) {
|
|
5081
|
-
const missing = await missingReleaseParts({
|
|
5082
|
-
shell,
|
|
5083
|
-
registry,
|
|
5084
|
-
version: latest,
|
|
5085
|
-
force: true
|
|
5086
|
-
});
|
|
5087
|
-
if (missing.length > 0) throw new Error(`源上已经有 ${read.pkg}@${latest},但这一份发布还没铺齐:${missing.join("、")} 在这一版上还拉不到。装上会缺依赖,稍等再试 —— 市场页那颗「重新检查」会重新探一次。`);
|
|
5088
|
-
}
|
|
5089
|
-
const spec = latest === void 0 ? `${read.pkg}@latest` : `${read.pkg}@${latest}`;
|
|
5090
|
-
const outcome = await replaceDshBundle(routeDeps(requireBundles()), spec, { ...registry === void 0 ? {} : { registry } });
|
|
5091
|
-
logger?.info?.(`[dsh-workbench] 已更新工作台 ${read.version} → ${latest ?? "最新版"}`);
|
|
5092
|
-
return outcome;
|
|
5093
5523
|
},
|
|
5094
5524
|
async updatePlugin(pkg) {
|
|
5095
5525
|
const outcome = await plugins.update(pkg);
|
|
@@ -5293,30 +5723,30 @@ function registerMarketTools(ctx, market) {
|
|
|
5293
5723
|
}
|
|
5294
5724
|
const tool = {
|
|
5295
5725
|
name: "workbench_modules",
|
|
5296
|
-
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec
|
|
5726
|
+
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec 把模块包装进 profile;按模块 id 的 install / update 只装、升它声明的那份外部供给(opencli 插件与库),不换模块包本身;uninstall 连模块包一起卸。update-workbench 把工作台自己更新到源上的新版(一般自己换上,要重启时结果里会说;重启由用户来)。卸模块只动工作台运行时,不影响终端那套 dsh;共用依赖只有升级。",
|
|
5297
5727
|
parameters: withApprovalNote({
|
|
5298
5728
|
type: "object",
|
|
5299
5729
|
properties: {
|
|
5300
5730
|
action: {
|
|
5301
5731
|
type: "string",
|
|
5302
|
-
description: "install-bundle
|
|
5732
|
+
description: "list=看状态(默认);install / update / uninstall 要 id;install-bundle 要 spec(换源给 registry);update-dependency 要 id;update-plugin / uninstall-plugin 要 plugin。",
|
|
5303
5733
|
enum: [...ACTIONS$1]
|
|
5304
5734
|
},
|
|
5305
5735
|
id: {
|
|
5306
5736
|
type: "string",
|
|
5307
|
-
description: "模块 id(如 coding-issues);update-dependency
|
|
5737
|
+
description: "模块 id(如 coding-issues);update-dependency 用共用依赖 id(如 plugin:coding)。"
|
|
5308
5738
|
},
|
|
5309
5739
|
spec: {
|
|
5310
5740
|
type: "string",
|
|
5311
|
-
description: "
|
|
5741
|
+
description: "包名(可带 @版本),也可以是 git 地址 / tarball / 本地绝对路径。"
|
|
5312
5742
|
},
|
|
5313
5743
|
registry: {
|
|
5314
5744
|
type: "string",
|
|
5315
|
-
description: "
|
|
5745
|
+
description: "去哪个源装(不填就用这份 profile 自己配的源)。"
|
|
5316
5746
|
},
|
|
5317
5747
|
plugin: {
|
|
5318
5748
|
type: "string",
|
|
5319
|
-
description: "
|
|
5749
|
+
description: "opencli 插件的包名或插件名(如 cmdb 或 opencli-plugin-cmdb)。"
|
|
5320
5750
|
}
|
|
5321
5751
|
},
|
|
5322
5752
|
required: ["action"],
|
|
@@ -5466,16 +5896,16 @@ function applyPanelsPref(panels, payload) {
|
|
|
5466
5896
|
//#region src/modules/panels/model/handoff.ts
|
|
5467
5897
|
var PANELS_FACT = "panels";
|
|
5468
5898
|
var CAPABILITY_FACT = "capability";
|
|
5469
|
-
function textOf$
|
|
5899
|
+
function textOf$2(value) {
|
|
5470
5900
|
return typeof value === "string" ? value.trim() : "";
|
|
5471
5901
|
}
|
|
5472
5902
|
function actionOf(call) {
|
|
5473
|
-
return textOf$
|
|
5903
|
+
return textOf$2(call.args["action"]) || "list";
|
|
5474
5904
|
}
|
|
5475
5905
|
function requestOf(call) {
|
|
5476
5906
|
const action = actionOf(call);
|
|
5477
5907
|
if (!PROVISION_ACTIONS.includes(action)) return void 0;
|
|
5478
|
-
const target = textOf$
|
|
5908
|
+
const target = textOf$2(call.args["unit"]) || textOf$2(call.args["id"]);
|
|
5479
5909
|
if (target === "") return void 0;
|
|
5480
5910
|
const wanted = action;
|
|
5481
5911
|
return isDependencyAction(wanted) ? {
|
|
@@ -5487,7 +5917,7 @@ function requestOf(call) {
|
|
|
5487
5917
|
};
|
|
5488
5918
|
}
|
|
5489
5919
|
function panelsHandoffEntry(target = {}) {
|
|
5490
|
-
const path = textOf$
|
|
5920
|
+
const path = textOf$2(target.path);
|
|
5491
5921
|
return path === "" ? void 0 : {
|
|
5492
5922
|
fact: PANELS_FACT,
|
|
5493
5923
|
key: `path:${path}`
|
|
@@ -5872,13 +6302,13 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5872
6302
|
}
|
|
5873
6303
|
const tool = {
|
|
5874
6304
|
name: "workbench_panels",
|
|
5875
|
-
description: "
|
|
6305
|
+
description: "看或调右侧面板:哪些套件开着、什么顺序。两份板:本工作区那份优先,没提过的行看机器级默认板 —— 那只是还没配过的工作区的默认值(已经自己配过的工作区保持自己的选择);本工作区只记改过的那几行,所以那份板以后改了,没改过的工作区跟着变。收起 = 这个工作区明确不要它:卡片与 agent 工具一起在这个工作区下线,连机器级默认板上开着的套件也照样收起;别的项目不受影响、不删数据,要放回来用 show。在机器级默认板上 hide 是写下\"不要它\"这一笔 —— 比名单更管用,show 会把这一笔划掉。顺序只属于工作区:没排过的按各模块自己声明的次序(开着的在前、关着的在后),show 或 move 过就以那次为准。",
|
|
5876
6306
|
parameters: withApprovalNote({
|
|
5877
6307
|
type: "object",
|
|
5878
6308
|
properties: {
|
|
5879
6309
|
action: {
|
|
5880
6310
|
type: "string",
|
|
5881
|
-
description: "show=这个工作区要它(之前收起的也放回来);hide
|
|
6311
|
+
description: "list=看现在这份板(默认);show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它;move=调顺序(新开一行时排最后);reset=清掉这个工作区改过的行(改完全部回到跟着机器级默认板走);follow=某一行放回跟随(带 id = 只那一行,不带 = 全部)。",
|
|
5882
6312
|
enum: [
|
|
5883
6313
|
"list",
|
|
5884
6314
|
"show",
|
|
@@ -5890,7 +6320,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5890
6320
|
},
|
|
5891
6321
|
id: {
|
|
5892
6322
|
type: "string",
|
|
5893
|
-
description: "show / hide / move
|
|
6323
|
+
description: "show / hide / move 必填:要动的套件 id。"
|
|
5894
6324
|
},
|
|
5895
6325
|
index: {
|
|
5896
6326
|
type: "integer",
|
|
@@ -5898,7 +6328,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5898
6328
|
},
|
|
5899
6329
|
scope: {
|
|
5900
6330
|
type: "string",
|
|
5901
|
-
description: "workspace(默认)=只动当前工作区;machine
|
|
6331
|
+
description: "workspace(默认)=只动当前工作区;machine=动那份机器级默认板(只影响还没配过的工作区;没有顺序一说,move 会报错)。",
|
|
5902
6332
|
enum: ["workspace", "machine"]
|
|
5903
6333
|
}
|
|
5904
6334
|
},
|
|
@@ -6034,6 +6464,8 @@ var OWNED = [{
|
|
|
6034
6464
|
panels.accept(unit);
|
|
6035
6465
|
},
|
|
6036
6466
|
isReleased: (unit) => panels.isReleased(unit),
|
|
6467
|
+
dshHome: () => dshHome(),
|
|
6468
|
+
profileName: () => profileDir === null ? null : basename(profileDir),
|
|
6037
6469
|
logger
|
|
6038
6470
|
});
|
|
6039
6471
|
return {
|
|
@@ -6077,151 +6509,307 @@ function assembleHostUnits(registry) {
|
|
|
6077
6509
|
for (const face of OWNED) registry.registerOwned(face);
|
|
6078
6510
|
}
|
|
6079
6511
|
//#endregion
|
|
6080
|
-
//#region src/shared/api/host/
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
const
|
|
6084
|
-
const opencliBinDir = join(opencliRoot, "bin");
|
|
6085
|
-
const workshop = join(root, "diy");
|
|
6512
|
+
//#region src/shared/api/host/unit-readiness.ts
|
|
6513
|
+
function unitReadiness(manifest, lookup) {
|
|
6514
|
+
const missingHard = manifest.host.inject.filter((name) => lookup(name) === void 0);
|
|
6515
|
+
const missingSoft = (manifest.host.optional ?? []).filter((name) => lookup(name) === void 0);
|
|
6086
6516
|
return {
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
opencliBinDir,
|
|
6091
|
-
opencliBin: join(opencliBinDir, "opencli"),
|
|
6092
|
-
opencliHome: join(opencliRoot, "home"),
|
|
6093
|
-
workshop,
|
|
6094
|
-
workshopPackages: join(workshop, "packages"),
|
|
6095
|
-
workshopManifest: join(workshop, "package.json"),
|
|
6096
|
-
skillsLink: join(workshop, ".agents", "skills", WORKSHOP_SKILLS_DIR)
|
|
6517
|
+
canMount: missingHard.length === 0,
|
|
6518
|
+
missingHard,
|
|
6519
|
+
missingSoft
|
|
6097
6520
|
};
|
|
6098
6521
|
}
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
} catch {
|
|
6105
|
-
return null;
|
|
6106
|
-
}
|
|
6107
|
-
}
|
|
6108
|
-
function packageRootOf(moduleUrl) {
|
|
6109
|
-
return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
|
|
6110
|
-
}
|
|
6111
|
-
function isInstalledCopy(packageRoot) {
|
|
6112
|
-
return packageRoot.split(sep).includes("node_modules");
|
|
6113
|
-
}
|
|
6114
|
-
function opencliWrapperScript(packageRoot, home) {
|
|
6115
|
-
const script = join(packageRoot, "bin", "opencli-isolated");
|
|
6116
|
-
return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
|
|
6117
|
-
}
|
|
6118
|
-
function workshopSeedPlan(packageRoot, home, root) {
|
|
6119
|
-
const script = join(packageRoot, "scripts", "seed-workshop.sh");
|
|
6120
|
-
const env = { DSH_HOME: home };
|
|
6121
|
-
const log = join(root, "seed.log");
|
|
6522
|
+
//#endregion
|
|
6523
|
+
//#region src/shared/api/host/mount-units.ts
|
|
6524
|
+
function withRunsRoute(result, unit) {
|
|
6525
|
+
const runs = result.longTasks;
|
|
6526
|
+
if (runs === void 0) return result;
|
|
6122
6527
|
return {
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
log
|
|
6528
|
+
...result,
|
|
6529
|
+
routes: {
|
|
6530
|
+
...result.routes,
|
|
6531
|
+
[runsRouteOf(unit)]: (search) => {
|
|
6532
|
+
const path = (param(search, "path") ?? "").trim();
|
|
6533
|
+
return {
|
|
6534
|
+
ok: true,
|
|
6535
|
+
runs: path === "" ? [] : runs.current(path)
|
|
6536
|
+
};
|
|
6537
|
+
}
|
|
6134
6538
|
}
|
|
6135
6539
|
};
|
|
6136
6540
|
}
|
|
6137
|
-
function
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
...command.env
|
|
6145
|
-
},
|
|
6146
|
-
detached: true,
|
|
6147
|
-
stdio: [
|
|
6148
|
-
"ignore",
|
|
6149
|
-
fd,
|
|
6150
|
-
fd
|
|
6151
|
-
]
|
|
6541
|
+
function notedRegistry(registry, writesOf) {
|
|
6542
|
+
return { register(definition) {
|
|
6543
|
+
if (!writesOf(definition.name)) return registry.register(definition);
|
|
6544
|
+
const noted = withApprovalNote(definition.parameters, { required: true });
|
|
6545
|
+
return registry.register(noted === definition.parameters ? definition : {
|
|
6546
|
+
...definition,
|
|
6547
|
+
parameters: noted
|
|
6152
6548
|
});
|
|
6153
|
-
|
|
6154
|
-
child.unref();
|
|
6155
|
-
} catch (error) {
|
|
6156
|
-
onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
|
|
6157
|
-
} finally {
|
|
6158
|
-
if (fd !== null) closeQuietly(fd);
|
|
6159
|
-
}
|
|
6549
|
+
} };
|
|
6160
6550
|
}
|
|
6161
|
-
function
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
return;
|
|
6166
|
-
}
|
|
6551
|
+
function scopedMountCtx(ctx, registry, writesOf) {
|
|
6552
|
+
const tools = notedRegistry(registry, writesOf);
|
|
6553
|
+
const get = (name) => {
|
|
6554
|
+
const value = ctx.get(name);
|
|
6555
|
+
return name === "tools" && value !== void 0 ? tools : value;
|
|
6556
|
+
};
|
|
6557
|
+
return new Proxy(ctx, { get(target, key) {
|
|
6558
|
+
if (key === "get") return get;
|
|
6559
|
+
const value = Reflect.get(target, key, target);
|
|
6560
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
6561
|
+
} });
|
|
6167
6562
|
}
|
|
6168
|
-
function
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
env.PATH = [entryDir, ...parts].join(delimiter);
|
|
6172
|
-
return true;
|
|
6563
|
+
function failureReasonOf(error) {
|
|
6564
|
+
if (error instanceof Error) return error.message;
|
|
6565
|
+
return typeof error === "string" ? error : String(error);
|
|
6173
6566
|
}
|
|
6174
|
-
function
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6567
|
+
function fail(ledger, item, reason) {
|
|
6568
|
+
ledger.failed.add(item);
|
|
6569
|
+
ledger.failures.push({
|
|
6570
|
+
id: item.host.manifest.id,
|
|
6571
|
+
reason
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
function collectRoutes(mounted, ledger) {
|
|
6575
|
+
const routes = {};
|
|
6576
|
+
const owner = /* @__PURE__ */ new Map();
|
|
6577
|
+
const routesOfUnit = /* @__PURE__ */ new Map();
|
|
6578
|
+
for (const item of mounted) {
|
|
6579
|
+
if (ledger.failed.has(item)) continue;
|
|
6580
|
+
const unit = item.host.manifest.id;
|
|
6581
|
+
const accepted = [];
|
|
6582
|
+
let problem;
|
|
6583
|
+
for (const [key, handler] of Object.entries(item.result.routes)) {
|
|
6584
|
+
if (key !== unit && !key.startsWith(`${unit}/`)) {
|
|
6585
|
+
problem = `路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`;
|
|
6586
|
+
break;
|
|
6587
|
+
}
|
|
6588
|
+
const previous = owner.get(key);
|
|
6589
|
+
if (previous !== void 0) {
|
|
6590
|
+
problem = `路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`;
|
|
6591
|
+
break;
|
|
6592
|
+
}
|
|
6593
|
+
accepted.push([key, handler]);
|
|
6594
|
+
}
|
|
6595
|
+
if (problem !== void 0) {
|
|
6596
|
+
fail(ledger, item, problem);
|
|
6597
|
+
continue;
|
|
6598
|
+
}
|
|
6599
|
+
for (const [key, handler] of accepted) {
|
|
6600
|
+
owner.set(key, unit);
|
|
6601
|
+
routes[key] = handler;
|
|
6602
|
+
const own = routesOfUnit.get(unit);
|
|
6603
|
+
if (own === void 0) routesOfUnit.set(unit, [key]);
|
|
6604
|
+
else own.push(key);
|
|
6190
6605
|
}
|
|
6191
|
-
if (linkSkills(paths.skillsLink, join(options.packageRoot, "skills", "workbench-module-diy"), options.log)) done.push(paths.skillsLink);
|
|
6192
|
-
} catch (error) {
|
|
6193
|
-
options.log?.(`自举没做完:${String(error)}`);
|
|
6194
6606
|
}
|
|
6195
|
-
return
|
|
6607
|
+
return {
|
|
6608
|
+
routes,
|
|
6609
|
+
owners: owner,
|
|
6610
|
+
routesOfUnit
|
|
6611
|
+
};
|
|
6196
6612
|
}
|
|
6197
|
-
function
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6613
|
+
function collectNamed(mounted, ledger, pickMany, label) {
|
|
6614
|
+
const owner = /* @__PURE__ */ new Map();
|
|
6615
|
+
const out = [];
|
|
6616
|
+
for (const item of mounted) {
|
|
6617
|
+
if (ledger.failed.has(item)) continue;
|
|
6618
|
+
const unit = item.host.manifest.id;
|
|
6619
|
+
const entries = pickMany(item);
|
|
6620
|
+
if (entries.length === 0) continue;
|
|
6621
|
+
const clash = entries.find((entry) => owner.has(entry.resource));
|
|
6622
|
+
if (clash !== void 0) {
|
|
6623
|
+
fail(ledger, item, `${label}名冲突:${clash.resource} 由套件 ${owner.get(clash.resource)} 与 ${unit} 同时声明`);
|
|
6624
|
+
continue;
|
|
6625
|
+
}
|
|
6626
|
+
for (const entry of entries) {
|
|
6627
|
+
owner.set(entry.resource, unit);
|
|
6628
|
+
out.push({
|
|
6629
|
+
...entry,
|
|
6630
|
+
unit
|
|
6631
|
+
});
|
|
6632
|
+
}
|
|
6202
6633
|
}
|
|
6634
|
+
return out;
|
|
6203
6635
|
}
|
|
6204
|
-
function
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6636
|
+
function collectContextRenderers(mounted, ledger) {
|
|
6637
|
+
const table = /* @__PURE__ */ new Map();
|
|
6638
|
+
const owner = /* @__PURE__ */ new Map();
|
|
6639
|
+
for (const item of mounted) {
|
|
6640
|
+
if (ledger.failed.has(item)) continue;
|
|
6641
|
+
const unit = item.host.manifest.id;
|
|
6642
|
+
const context = item.result.context;
|
|
6643
|
+
if (context === void 0) continue;
|
|
6644
|
+
const clash = context.kinds.find((kind) => owner.has(kind));
|
|
6645
|
+
if (clash !== void 0) {
|
|
6646
|
+
fail(ledger, item, `对话引用的 kind 冲突:${clash} 由套件 ${owner.get(clash)} 与 ${unit} 同时认领`);
|
|
6647
|
+
continue;
|
|
6648
|
+
}
|
|
6649
|
+
for (const kind of context.kinds) {
|
|
6650
|
+
owner.set(kind, unit);
|
|
6651
|
+
table.set(kind, (ref) => context.render(ref));
|
|
6652
|
+
}
|
|
6209
6653
|
}
|
|
6654
|
+
return table;
|
|
6210
6655
|
}
|
|
6211
|
-
function
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6656
|
+
function pickProvider(mounted, ledger, pick, label) {
|
|
6657
|
+
let chosen;
|
|
6658
|
+
let chosenBy;
|
|
6659
|
+
for (const item of mounted) {
|
|
6660
|
+
if (ledger.failed.has(item)) continue;
|
|
6661
|
+
const value = pick(item);
|
|
6662
|
+
if (value === void 0) continue;
|
|
6663
|
+
if (chosen === void 0) {
|
|
6664
|
+
chosen = value;
|
|
6665
|
+
chosenBy = item.host.manifest.id;
|
|
6666
|
+
continue;
|
|
6667
|
+
}
|
|
6668
|
+
fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.host.manifest.id}`);
|
|
6669
|
+
}
|
|
6670
|
+
return chosen;
|
|
6671
|
+
}
|
|
6672
|
+
function collectWarmReads(mounted) {
|
|
6673
|
+
const declarations = mounted.map((item) => item.result.warmReads).filter((declare) => declare !== void 0);
|
|
6674
|
+
return () => {
|
|
6675
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6676
|
+
const reads = [];
|
|
6677
|
+
for (const read of declarations.flatMap((declare) => declare())) {
|
|
6678
|
+
const key = read.join(" ");
|
|
6679
|
+
if (seen.has(key)) continue;
|
|
6680
|
+
seen.add(key);
|
|
6681
|
+
reads.push(read);
|
|
6682
|
+
}
|
|
6683
|
+
return reads;
|
|
6684
|
+
};
|
|
6685
|
+
}
|
|
6686
|
+
function mountUnits(units, deps) {
|
|
6687
|
+
const store = deps.store ?? createWorkspaceStore({ root: null });
|
|
6688
|
+
const ownedUnits = deps.ownedUnits ?? (() => []);
|
|
6689
|
+
const mounted = [];
|
|
6690
|
+
const skipped = [];
|
|
6691
|
+
const longTaskDisposers = /* @__PURE__ */ new Map();
|
|
6692
|
+
const ledger = {
|
|
6693
|
+
failed: /* @__PURE__ */ new Set(),
|
|
6694
|
+
failures: []
|
|
6695
|
+
};
|
|
6696
|
+
for (const host of units) {
|
|
6697
|
+
const readiness = unitReadiness(host.manifest, (dep) => deps.ctx.get(dep));
|
|
6698
|
+
if (!readiness.canMount) {
|
|
6699
|
+
skipped.push({
|
|
6700
|
+
id: host.manifest.id,
|
|
6701
|
+
missing: readiness.missingHard
|
|
6702
|
+
});
|
|
6703
|
+
continue;
|
|
6704
|
+
}
|
|
6705
|
+
const unit = host.manifest.id;
|
|
6706
|
+
const tools = deps.ctx.get("tools");
|
|
6707
|
+
const writes = toolUnits(deps.manifests());
|
|
6708
|
+
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools, (name) => writes.get(name)?.writes === true);
|
|
6709
|
+
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
6710
|
+
scope: deps.longTasks.scope,
|
|
6711
|
+
unit,
|
|
6712
|
+
bump: deps.longTasks.bump
|
|
6713
|
+
});
|
|
6714
|
+
let result;
|
|
6715
|
+
try {
|
|
6716
|
+
result = host.face.mount({
|
|
6717
|
+
manifest: host.manifest,
|
|
6718
|
+
ctx: mountCtx,
|
|
6719
|
+
logger: deps.logger,
|
|
6720
|
+
manifests: deps.manifests,
|
|
6721
|
+
ownedUnits,
|
|
6722
|
+
...deps.installable === void 0 ? {} : { installable: deps.installable },
|
|
6723
|
+
...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
|
|
6724
|
+
...deps.probe === void 0 ? {} : { probe: deps.probe },
|
|
6725
|
+
...deps.values === void 0 ? {} : { values: deps.values },
|
|
6726
|
+
...longTasks === void 0 ? {} : { longTasks: longTasks.host },
|
|
6727
|
+
store
|
|
6728
|
+
});
|
|
6729
|
+
} catch (error) {
|
|
6730
|
+
longTasks?.dispose();
|
|
6731
|
+
ledger.failures.push({
|
|
6732
|
+
id: unit,
|
|
6733
|
+
reason: failureReasonOf(error)
|
|
6734
|
+
});
|
|
6735
|
+
continue;
|
|
6736
|
+
}
|
|
6737
|
+
const item = {
|
|
6738
|
+
host,
|
|
6739
|
+
readiness,
|
|
6740
|
+
result: withRunsRoute(result, unit)
|
|
6741
|
+
};
|
|
6742
|
+
mounted.push(item);
|
|
6743
|
+
if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
|
|
6744
|
+
}
|
|
6745
|
+
const collected = collectRoutes(mounted, ledger);
|
|
6746
|
+
const prefs = collectNamed(mounted, ledger, (item) => item.result.prefs === void 0 ? [] : [item.result.prefs], "偏好资源");
|
|
6747
|
+
const actions = collectNamed(mounted, ledger, (item) => item.result.actions ?? [], "动作资源");
|
|
6748
|
+
const owners = new Map(collected.owners);
|
|
6749
|
+
for (const entry of [...prefs, ...actions]) owners.set(entry.resource, entry.unit);
|
|
6750
|
+
const factsOf = /* @__PURE__ */ new Map();
|
|
6751
|
+
for (const item of mounted) {
|
|
6752
|
+
if (ledger.failed.has(item)) continue;
|
|
6753
|
+
const declare = item.result.factsOf;
|
|
6754
|
+
if (declare !== void 0) factsOf.set(item.host.manifest.id, declare);
|
|
6755
|
+
}
|
|
6756
|
+
const gate = pickProvider(mounted, ledger, (item) => item.result.gate, "启停闸门");
|
|
6757
|
+
const provision = pickProvider(mounted, ledger, (item) => item.result.provision, "机器供给");
|
|
6758
|
+
const contextRenderers = collectContextRenderers(mounted, ledger);
|
|
6759
|
+
const alive = mounted.filter((item) => !ledger.failed.has(item));
|
|
6760
|
+
for (const item of mounted) {
|
|
6761
|
+
if (!ledger.failed.has(item)) continue;
|
|
6762
|
+
longTaskDisposers.get(item)?.();
|
|
6763
|
+
longTaskDisposers.delete(item);
|
|
6224
6764
|
}
|
|
6765
|
+
return {
|
|
6766
|
+
mounted: alive,
|
|
6767
|
+
routes: collected.routes,
|
|
6768
|
+
owners,
|
|
6769
|
+
routesOfUnit: collected.routesOfUnit,
|
|
6770
|
+
factsOf,
|
|
6771
|
+
gate,
|
|
6772
|
+
prefs,
|
|
6773
|
+
actions,
|
|
6774
|
+
provision,
|
|
6775
|
+
contextRenderers,
|
|
6776
|
+
skipped,
|
|
6777
|
+
failed: ledger.failures,
|
|
6778
|
+
warmReads: collectWarmReads(alive),
|
|
6779
|
+
disposeLongTasks: () => {
|
|
6780
|
+
for (const dispose of [...longTaskDisposers.values()]) dispose();
|
|
6781
|
+
longTaskDisposers.clear();
|
|
6782
|
+
}
|
|
6783
|
+
};
|
|
6784
|
+
}
|
|
6785
|
+
//#endregion
|
|
6786
|
+
//#region src/shared/api/host/post-channels.ts
|
|
6787
|
+
function createPostChannels(deps) {
|
|
6788
|
+
const { ctx, mounted, facts, shellActions } = deps;
|
|
6789
|
+
let undo = [];
|
|
6790
|
+
const bind = (prefix, register) => {
|
|
6791
|
+
try {
|
|
6792
|
+
undo.push(register());
|
|
6793
|
+
} catch (error) {
|
|
6794
|
+
ctx.logger?.warn?.(`[dsh-workbench] ${prefix} 这条写通道没接上:${failureReasonOf(error)}`);
|
|
6795
|
+
}
|
|
6796
|
+
};
|
|
6797
|
+
return {
|
|
6798
|
+
rebind(wrote) {
|
|
6799
|
+
for (const dispose of undo.reverse()) dispose?.();
|
|
6800
|
+
undo = [];
|
|
6801
|
+
const prefs = [...mounted.values()].flatMap((item) => item.plan.prefs);
|
|
6802
|
+
const actions = [...shellActions, ...[...mounted.values()].flatMap((item) => item.plan.actions)];
|
|
6803
|
+
const provision = [...mounted.values()].find((item) => item.plan.provision !== void 0)?.plan.provision;
|
|
6804
|
+
if (prefs.length > 0) bind(PREFS_PREFIX, () => registerPrefs(ctx, prefs, wrote));
|
|
6805
|
+
if (actions.length > 0) bind(ACTIONS_PREFIX, () => registerActions(ctx, actions, wrote));
|
|
6806
|
+
if (provision !== void 0) bind(PROVISION_PREFIX, () => registerProvision(ctx, provision, facts));
|
|
6807
|
+
},
|
|
6808
|
+
dispose() {
|
|
6809
|
+
for (const dispose of undo) dispose?.();
|
|
6810
|
+
undo = [];
|
|
6811
|
+
}
|
|
6812
|
+
};
|
|
6225
6813
|
}
|
|
6226
6814
|
//#endregion
|
|
6227
6815
|
//#region src/shared/api/host/events.ts
|
|
@@ -6435,10 +7023,11 @@ async function workbenchVersion(input) {
|
|
|
6435
7023
|
pkg: read.pkg,
|
|
6436
7024
|
installed: read.version,
|
|
6437
7025
|
...latest === void 0 ? {} : { latest },
|
|
7026
|
+
...input.running === void 0 ? {} : { running: input.running },
|
|
6438
7027
|
...read.local === true ? { local: true } : {}
|
|
6439
7028
|
};
|
|
6440
7029
|
}
|
|
6441
|
-
function moduleVersionsHandler(ctx, shell) {
|
|
7030
|
+
function moduleVersionsHandler(ctx, shell, running) {
|
|
6442
7031
|
return async (search) => {
|
|
6443
7032
|
const manager = readBundleManager(ctx);
|
|
6444
7033
|
if (manager === void 0) return {
|
|
@@ -6483,6 +7072,7 @@ function moduleVersionsHandler(ctx, shell) {
|
|
|
6483
7072
|
shell,
|
|
6484
7073
|
fallback,
|
|
6485
7074
|
force,
|
|
7075
|
+
...running === void 0 ? {} : { running },
|
|
6486
7076
|
logger: ctx.logger
|
|
6487
7077
|
});
|
|
6488
7078
|
return {
|
|
@@ -7242,377 +7832,103 @@ function libsOfUnit(unit, libs) {
|
|
|
7242
7832
|
}
|
|
7243
7833
|
var unitCache = createTimedMemo(PROBE_TTL_MS);
|
|
7244
7834
|
//#endregion
|
|
7245
|
-
//#region src/shared/api/host/unit-
|
|
7246
|
-
function
|
|
7247
|
-
|
|
7248
|
-
|
|
7835
|
+
//#region src/shared/api/host/unit-registry.ts
|
|
7836
|
+
function installableUnitsOf(registry) {
|
|
7837
|
+
return registry.all().filter((host) => host.source.kind === "package").map((host) => host.unit);
|
|
7838
|
+
}
|
|
7839
|
+
function sourceLabel(source) {
|
|
7840
|
+
if (source.kind === "owned") return "外壳自己";
|
|
7841
|
+
if (source.kind === "plugin") return "按已装 opencli 插件合成";
|
|
7842
|
+
return source.pkg;
|
|
7843
|
+
}
|
|
7844
|
+
function createUnitRegistry(options) {
|
|
7845
|
+
const refusals = [];
|
|
7846
|
+
const hosts = [];
|
|
7847
|
+
const byUnit = /* @__PURE__ */ new Map();
|
|
7848
|
+
const failures = /* @__PURE__ */ new Map();
|
|
7849
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7850
|
+
function notify() {
|
|
7851
|
+
for (const listener of listeners) listener();
|
|
7852
|
+
}
|
|
7853
|
+
function add(host) {
|
|
7854
|
+
const taken = byUnit.get(host.unit);
|
|
7855
|
+
if (taken !== void 0) throw new Error(`套件 id 重复:${host.unit}(id 会进持久化配置,不能撞)—— 已被 ${sourceLabel(taken.source)} 占着`);
|
|
7856
|
+
byUnit.set(host.unit, host);
|
|
7857
|
+
hosts.push(host);
|
|
7858
|
+
notify();
|
|
7859
|
+
return () => {
|
|
7860
|
+
if (byUnit.get(host.unit) !== host) return;
|
|
7861
|
+
byUnit.delete(host.unit);
|
|
7862
|
+
const index = hosts.indexOf(host);
|
|
7863
|
+
if (index >= 0) hosts.splice(index, 1);
|
|
7864
|
+
notify();
|
|
7865
|
+
};
|
|
7866
|
+
}
|
|
7249
7867
|
return {
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7868
|
+
register(face) {
|
|
7869
|
+
const unit = typeof face?.unit === "string" ? face.unit.trim() : "";
|
|
7870
|
+
const refuse = (pkg, reasons) => {
|
|
7871
|
+
refusals.push({
|
|
7872
|
+
unit,
|
|
7873
|
+
...pkg === void 0 ? {} : { pkg },
|
|
7874
|
+
reasons
|
|
7875
|
+
});
|
|
7876
|
+
options.logger?.warn?.(`[dsh-workbench] ${pkg ?? "这个模块"}没挂上(unit ${unit === "" ? "没写" : unit}):${reasons.join(";")}`);
|
|
7877
|
+
notify();
|
|
7878
|
+
return () => {};
|
|
7879
|
+
};
|
|
7880
|
+
if (unit === "") return refuse(void 0, ["register({ unit, mount }) 里的 unit 是空的 —— U1b 之前的旧形状是 register({ manifest, mount });现在清单由外壳从**已装包**的 dsh.workbench 读出来,模块里的 id 从 deps.manifest.id 拿(ADR-0038 §2.2)"]);
|
|
7881
|
+
const source = options.declarationOf(unit);
|
|
7882
|
+
if (source === void 0) return refuse(void 0, [`没有哪个已装模块声明了 unit「${unit}」—— 查过这份 profile 的已装清单。要么 package.json 里 dsh.workbench.unit 写错了(改完要重启工作台),要么这个包根本没被装进这份 profile`]);
|
|
7883
|
+
const read = readDeclaration(source.pkg, source.declaration);
|
|
7884
|
+
if (read.manifest === void 0) return refuse(source.pkg, read.problems);
|
|
7885
|
+
return add({
|
|
7886
|
+
unit,
|
|
7887
|
+
manifest: read.manifest,
|
|
7888
|
+
face,
|
|
7889
|
+
source: {
|
|
7890
|
+
kind: "package",
|
|
7891
|
+
pkg: source.pkg
|
|
7892
|
+
}
|
|
7893
|
+
});
|
|
7894
|
+
},
|
|
7895
|
+
registerOwned(face) {
|
|
7896
|
+
return add({
|
|
7897
|
+
unit: face.manifest.id,
|
|
7898
|
+
manifest: face.manifest,
|
|
7899
|
+
face,
|
|
7900
|
+
source: { kind: "owned" }
|
|
7901
|
+
});
|
|
7902
|
+
},
|
|
7903
|
+
registerPlugin(face) {
|
|
7904
|
+
return add({
|
|
7905
|
+
unit: face.manifest.id,
|
|
7906
|
+
manifest: face.manifest,
|
|
7907
|
+
face,
|
|
7908
|
+
source: { kind: "plugin" }
|
|
7909
|
+
});
|
|
7910
|
+
},
|
|
7911
|
+
all: () => hosts,
|
|
7912
|
+
owned: () => hosts.filter((host) => host.source.kind === "owned").map((host) => host.unit),
|
|
7913
|
+
refusals: () => refusals,
|
|
7914
|
+
manifests: () => hosts.map((host) => host.manifest),
|
|
7915
|
+
subscribe(listener) {
|
|
7916
|
+
listeners.add(listener);
|
|
7917
|
+
return () => listeners.delete(listener);
|
|
7918
|
+
},
|
|
7919
|
+
fail(unit, reason) {
|
|
7920
|
+
failures.set(unit, reason);
|
|
7921
|
+
},
|
|
7922
|
+
recovered(unit) {
|
|
7923
|
+
failures.delete(unit);
|
|
7924
|
+
},
|
|
7925
|
+
failureOf: (unit) => failures.get(unit)
|
|
7253
7926
|
};
|
|
7254
7927
|
}
|
|
7255
7928
|
//#endregion
|
|
7256
|
-
//#region src/shared/api/host/
|
|
7257
|
-
function
|
|
7258
|
-
|
|
7259
|
-
if (runs === void 0) return result;
|
|
7260
|
-
return {
|
|
7261
|
-
...result,
|
|
7262
|
-
routes: {
|
|
7263
|
-
...result.routes,
|
|
7264
|
-
[runsRouteOf(unit)]: (search) => {
|
|
7265
|
-
const path = (param(search, "path") ?? "").trim();
|
|
7266
|
-
return {
|
|
7267
|
-
ok: true,
|
|
7268
|
-
runs: path === "" ? [] : runs.current(path)
|
|
7269
|
-
};
|
|
7270
|
-
}
|
|
7271
|
-
}
|
|
7272
|
-
};
|
|
7273
|
-
}
|
|
7274
|
-
function notedRegistry(registry, writesOf) {
|
|
7275
|
-
return { register(definition) {
|
|
7276
|
-
if (!writesOf(definition.name)) return registry.register(definition);
|
|
7277
|
-
const noted = withApprovalNote(definition.parameters, { required: true });
|
|
7278
|
-
return registry.register(noted === definition.parameters ? definition : {
|
|
7279
|
-
...definition,
|
|
7280
|
-
parameters: noted
|
|
7281
|
-
});
|
|
7282
|
-
} };
|
|
7283
|
-
}
|
|
7284
|
-
function scopedMountCtx(ctx, registry, writesOf) {
|
|
7285
|
-
const tools = notedRegistry(registry, writesOf);
|
|
7286
|
-
const get = (name) => {
|
|
7287
|
-
const value = ctx.get(name);
|
|
7288
|
-
return name === "tools" && value !== void 0 ? tools : value;
|
|
7289
|
-
};
|
|
7290
|
-
return new Proxy(ctx, { get(target, key) {
|
|
7291
|
-
if (key === "get") return get;
|
|
7292
|
-
const value = Reflect.get(target, key, target);
|
|
7293
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
7294
|
-
} });
|
|
7295
|
-
}
|
|
7296
|
-
function failureReasonOf(error) {
|
|
7297
|
-
if (error instanceof Error) return error.message;
|
|
7298
|
-
return typeof error === "string" ? error : String(error);
|
|
7299
|
-
}
|
|
7300
|
-
function fail(ledger, item, reason) {
|
|
7301
|
-
ledger.failed.add(item);
|
|
7302
|
-
ledger.failures.push({
|
|
7303
|
-
id: item.host.manifest.id,
|
|
7304
|
-
reason
|
|
7305
|
-
});
|
|
7306
|
-
}
|
|
7307
|
-
function collectRoutes(mounted, ledger) {
|
|
7308
|
-
const routes = {};
|
|
7309
|
-
const owner = /* @__PURE__ */ new Map();
|
|
7310
|
-
const routesOfUnit = /* @__PURE__ */ new Map();
|
|
7311
|
-
for (const item of mounted) {
|
|
7312
|
-
if (ledger.failed.has(item)) continue;
|
|
7313
|
-
const unit = item.host.manifest.id;
|
|
7314
|
-
const accepted = [];
|
|
7315
|
-
let problem;
|
|
7316
|
-
for (const [key, handler] of Object.entries(item.result.routes)) {
|
|
7317
|
-
if (key !== unit && !key.startsWith(`${unit}/`)) {
|
|
7318
|
-
problem = `路由名要住在自己那一格的命名空间里(${unit} 或 ${unit}/…):${key} 不属于 ${unit} —— 装 / 升 / 卸时点名不到它,那个模块的数据会停在旧值`;
|
|
7319
|
-
break;
|
|
7320
|
-
}
|
|
7321
|
-
const previous = owner.get(key);
|
|
7322
|
-
if (previous !== void 0) {
|
|
7323
|
-
problem = `路由名冲突:${key} 由套件 ${previous} 与 ${unit} 同时声明`;
|
|
7324
|
-
break;
|
|
7325
|
-
}
|
|
7326
|
-
accepted.push([key, handler]);
|
|
7327
|
-
}
|
|
7328
|
-
if (problem !== void 0) {
|
|
7329
|
-
fail(ledger, item, problem);
|
|
7330
|
-
continue;
|
|
7331
|
-
}
|
|
7332
|
-
for (const [key, handler] of accepted) {
|
|
7333
|
-
owner.set(key, unit);
|
|
7334
|
-
routes[key] = handler;
|
|
7335
|
-
const own = routesOfUnit.get(unit);
|
|
7336
|
-
if (own === void 0) routesOfUnit.set(unit, [key]);
|
|
7337
|
-
else own.push(key);
|
|
7338
|
-
}
|
|
7339
|
-
}
|
|
7340
|
-
return {
|
|
7341
|
-
routes,
|
|
7342
|
-
owners: owner,
|
|
7343
|
-
routesOfUnit
|
|
7344
|
-
};
|
|
7345
|
-
}
|
|
7346
|
-
function collectNamed(mounted, ledger, pickMany, label) {
|
|
7347
|
-
const owner = /* @__PURE__ */ new Map();
|
|
7348
|
-
const out = [];
|
|
7349
|
-
for (const item of mounted) {
|
|
7350
|
-
if (ledger.failed.has(item)) continue;
|
|
7351
|
-
const unit = item.host.manifest.id;
|
|
7352
|
-
const entries = pickMany(item);
|
|
7353
|
-
if (entries.length === 0) continue;
|
|
7354
|
-
const clash = entries.find((entry) => owner.has(entry.resource));
|
|
7355
|
-
if (clash !== void 0) {
|
|
7356
|
-
fail(ledger, item, `${label}名冲突:${clash.resource} 由套件 ${owner.get(clash.resource)} 与 ${unit} 同时声明`);
|
|
7357
|
-
continue;
|
|
7358
|
-
}
|
|
7359
|
-
for (const entry of entries) {
|
|
7360
|
-
owner.set(entry.resource, unit);
|
|
7361
|
-
out.push({
|
|
7362
|
-
...entry,
|
|
7363
|
-
unit
|
|
7364
|
-
});
|
|
7365
|
-
}
|
|
7366
|
-
}
|
|
7367
|
-
return out;
|
|
7368
|
-
}
|
|
7369
|
-
function collectContextRenderers(mounted, ledger) {
|
|
7370
|
-
const table = /* @__PURE__ */ new Map();
|
|
7371
|
-
const owner = /* @__PURE__ */ new Map();
|
|
7372
|
-
for (const item of mounted) {
|
|
7373
|
-
if (ledger.failed.has(item)) continue;
|
|
7374
|
-
const unit = item.host.manifest.id;
|
|
7375
|
-
const context = item.result.context;
|
|
7376
|
-
if (context === void 0) continue;
|
|
7377
|
-
const clash = context.kinds.find((kind) => owner.has(kind));
|
|
7378
|
-
if (clash !== void 0) {
|
|
7379
|
-
fail(ledger, item, `对话引用的 kind 冲突:${clash} 由套件 ${owner.get(clash)} 与 ${unit} 同时认领`);
|
|
7380
|
-
continue;
|
|
7381
|
-
}
|
|
7382
|
-
for (const kind of context.kinds) {
|
|
7383
|
-
owner.set(kind, unit);
|
|
7384
|
-
table.set(kind, (ref) => context.render(ref));
|
|
7385
|
-
}
|
|
7386
|
-
}
|
|
7387
|
-
return table;
|
|
7388
|
-
}
|
|
7389
|
-
function pickProvider(mounted, ledger, pick, label) {
|
|
7390
|
-
let chosen;
|
|
7391
|
-
let chosenBy;
|
|
7392
|
-
for (const item of mounted) {
|
|
7393
|
-
if (ledger.failed.has(item)) continue;
|
|
7394
|
-
const value = pick(item);
|
|
7395
|
-
if (value === void 0) continue;
|
|
7396
|
-
if (chosen === void 0) {
|
|
7397
|
-
chosen = value;
|
|
7398
|
-
chosenBy = item.host.manifest.id;
|
|
7399
|
-
continue;
|
|
7400
|
-
}
|
|
7401
|
-
fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.host.manifest.id}`);
|
|
7402
|
-
}
|
|
7403
|
-
return chosen;
|
|
7404
|
-
}
|
|
7405
|
-
function collectWarmReads(mounted) {
|
|
7406
|
-
const declarations = mounted.map((item) => item.result.warmReads).filter((declare) => declare !== void 0);
|
|
7407
|
-
return () => {
|
|
7408
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7409
|
-
const reads = [];
|
|
7410
|
-
for (const read of declarations.flatMap((declare) => declare())) {
|
|
7411
|
-
const key = read.join(" ");
|
|
7412
|
-
if (seen.has(key)) continue;
|
|
7413
|
-
seen.add(key);
|
|
7414
|
-
reads.push(read);
|
|
7415
|
-
}
|
|
7416
|
-
return reads;
|
|
7417
|
-
};
|
|
7418
|
-
}
|
|
7419
|
-
function mountUnits(units, deps) {
|
|
7420
|
-
const store = deps.store ?? createWorkspaceStore({ root: null });
|
|
7421
|
-
const ownedUnits = deps.ownedUnits ?? (() => []);
|
|
7422
|
-
const mounted = [];
|
|
7423
|
-
const skipped = [];
|
|
7424
|
-
const longTaskDisposers = /* @__PURE__ */ new Map();
|
|
7425
|
-
const ledger = {
|
|
7426
|
-
failed: /* @__PURE__ */ new Set(),
|
|
7427
|
-
failures: []
|
|
7428
|
-
};
|
|
7429
|
-
for (const host of units) {
|
|
7430
|
-
const readiness = unitReadiness(host.manifest, (dep) => deps.ctx.get(dep));
|
|
7431
|
-
if (!readiness.canMount) {
|
|
7432
|
-
skipped.push({
|
|
7433
|
-
id: host.manifest.id,
|
|
7434
|
-
missing: readiness.missingHard
|
|
7435
|
-
});
|
|
7436
|
-
continue;
|
|
7437
|
-
}
|
|
7438
|
-
const unit = host.manifest.id;
|
|
7439
|
-
const tools = deps.ctx.get("tools");
|
|
7440
|
-
const writes = toolUnits(deps.manifests());
|
|
7441
|
-
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools, (name) => writes.get(name)?.writes === true);
|
|
7442
|
-
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
7443
|
-
scope: deps.longTasks.scope,
|
|
7444
|
-
unit,
|
|
7445
|
-
bump: deps.longTasks.bump
|
|
7446
|
-
});
|
|
7447
|
-
let result;
|
|
7448
|
-
try {
|
|
7449
|
-
result = host.face.mount({
|
|
7450
|
-
manifest: host.manifest,
|
|
7451
|
-
ctx: mountCtx,
|
|
7452
|
-
logger: deps.logger,
|
|
7453
|
-
manifests: deps.manifests,
|
|
7454
|
-
ownedUnits,
|
|
7455
|
-
...deps.installable === void 0 ? {} : { installable: deps.installable },
|
|
7456
|
-
...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
|
|
7457
|
-
...deps.probe === void 0 ? {} : { probe: deps.probe },
|
|
7458
|
-
...deps.values === void 0 ? {} : { values: deps.values },
|
|
7459
|
-
...longTasks === void 0 ? {} : { longTasks: longTasks.host },
|
|
7460
|
-
store
|
|
7461
|
-
});
|
|
7462
|
-
} catch (error) {
|
|
7463
|
-
longTasks?.dispose();
|
|
7464
|
-
ledger.failures.push({
|
|
7465
|
-
id: unit,
|
|
7466
|
-
reason: failureReasonOf(error)
|
|
7467
|
-
});
|
|
7468
|
-
continue;
|
|
7469
|
-
}
|
|
7470
|
-
const item = {
|
|
7471
|
-
host,
|
|
7472
|
-
readiness,
|
|
7473
|
-
result: withRunsRoute(result, unit)
|
|
7474
|
-
};
|
|
7475
|
-
mounted.push(item);
|
|
7476
|
-
if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
|
|
7477
|
-
}
|
|
7478
|
-
const collected = collectRoutes(mounted, ledger);
|
|
7479
|
-
const prefs = collectNamed(mounted, ledger, (item) => item.result.prefs === void 0 ? [] : [item.result.prefs], "偏好资源");
|
|
7480
|
-
const actions = collectNamed(mounted, ledger, (item) => item.result.actions ?? [], "动作资源");
|
|
7481
|
-
const owners = new Map(collected.owners);
|
|
7482
|
-
for (const entry of [...prefs, ...actions]) owners.set(entry.resource, entry.unit);
|
|
7483
|
-
const factsOf = /* @__PURE__ */ new Map();
|
|
7484
|
-
for (const item of mounted) {
|
|
7485
|
-
if (ledger.failed.has(item)) continue;
|
|
7486
|
-
const declare = item.result.factsOf;
|
|
7487
|
-
if (declare !== void 0) factsOf.set(item.host.manifest.id, declare);
|
|
7488
|
-
}
|
|
7489
|
-
const gate = pickProvider(mounted, ledger, (item) => item.result.gate, "启停闸门");
|
|
7490
|
-
const provision = pickProvider(mounted, ledger, (item) => item.result.provision, "机器供给");
|
|
7491
|
-
const contextRenderers = collectContextRenderers(mounted, ledger);
|
|
7492
|
-
const alive = mounted.filter((item) => !ledger.failed.has(item));
|
|
7493
|
-
for (const item of mounted) {
|
|
7494
|
-
if (!ledger.failed.has(item)) continue;
|
|
7495
|
-
longTaskDisposers.get(item)?.();
|
|
7496
|
-
longTaskDisposers.delete(item);
|
|
7497
|
-
}
|
|
7498
|
-
return {
|
|
7499
|
-
mounted: alive,
|
|
7500
|
-
routes: collected.routes,
|
|
7501
|
-
owners,
|
|
7502
|
-
routesOfUnit: collected.routesOfUnit,
|
|
7503
|
-
factsOf,
|
|
7504
|
-
gate,
|
|
7505
|
-
prefs,
|
|
7506
|
-
actions,
|
|
7507
|
-
provision,
|
|
7508
|
-
contextRenderers,
|
|
7509
|
-
skipped,
|
|
7510
|
-
failed: ledger.failures,
|
|
7511
|
-
warmReads: collectWarmReads(alive),
|
|
7512
|
-
disposeLongTasks: () => {
|
|
7513
|
-
for (const dispose of [...longTaskDisposers.values()]) dispose();
|
|
7514
|
-
longTaskDisposers.clear();
|
|
7515
|
-
}
|
|
7516
|
-
};
|
|
7517
|
-
}
|
|
7518
|
-
//#endregion
|
|
7519
|
-
//#region src/shared/api/host/unit-registry.ts
|
|
7520
|
-
function installableUnitsOf(registry) {
|
|
7521
|
-
return registry.all().filter((host) => host.source.kind === "package").map((host) => host.unit);
|
|
7522
|
-
}
|
|
7523
|
-
function sourceLabel(source) {
|
|
7524
|
-
if (source.kind === "owned") return "外壳自己";
|
|
7525
|
-
if (source.kind === "plugin") return "按已装 opencli 插件合成";
|
|
7526
|
-
return source.pkg;
|
|
7527
|
-
}
|
|
7528
|
-
function createUnitRegistry(options) {
|
|
7529
|
-
const refusals = [];
|
|
7530
|
-
const hosts = [];
|
|
7531
|
-
const byUnit = /* @__PURE__ */ new Map();
|
|
7532
|
-
const failures = /* @__PURE__ */ new Map();
|
|
7533
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
7534
|
-
function notify() {
|
|
7535
|
-
for (const listener of listeners) listener();
|
|
7536
|
-
}
|
|
7537
|
-
function add(host) {
|
|
7538
|
-
const taken = byUnit.get(host.unit);
|
|
7539
|
-
if (taken !== void 0) throw new Error(`套件 id 重复:${host.unit}(id 会进持久化配置,不能撞)—— 已被 ${sourceLabel(taken.source)} 占着`);
|
|
7540
|
-
byUnit.set(host.unit, host);
|
|
7541
|
-
hosts.push(host);
|
|
7542
|
-
notify();
|
|
7543
|
-
return () => {
|
|
7544
|
-
if (byUnit.get(host.unit) !== host) return;
|
|
7545
|
-
byUnit.delete(host.unit);
|
|
7546
|
-
const index = hosts.indexOf(host);
|
|
7547
|
-
if (index >= 0) hosts.splice(index, 1);
|
|
7548
|
-
notify();
|
|
7549
|
-
};
|
|
7550
|
-
}
|
|
7551
|
-
return {
|
|
7552
|
-
register(face) {
|
|
7553
|
-
const unit = typeof face?.unit === "string" ? face.unit.trim() : "";
|
|
7554
|
-
const refuse = (pkg, reasons) => {
|
|
7555
|
-
refusals.push({
|
|
7556
|
-
unit,
|
|
7557
|
-
...pkg === void 0 ? {} : { pkg },
|
|
7558
|
-
reasons
|
|
7559
|
-
});
|
|
7560
|
-
options.logger?.warn?.(`[dsh-workbench] ${pkg ?? "这个模块"}没挂上(unit ${unit === "" ? "没写" : unit}):${reasons.join(";")}`);
|
|
7561
|
-
notify();
|
|
7562
|
-
return () => {};
|
|
7563
|
-
};
|
|
7564
|
-
if (unit === "") return refuse(void 0, ["register({ unit, mount }) 里的 unit 是空的 —— U1b 之前的旧形状是 register({ manifest, mount });现在清单由外壳从**已装包**的 dsh.workbench 读出来,模块里的 id 从 deps.manifest.id 拿(ADR-0038 §2.2)"]);
|
|
7565
|
-
const source = options.declarationOf(unit);
|
|
7566
|
-
if (source === void 0) return refuse(void 0, [`没有哪个已装模块声明了 unit「${unit}」—— 查过这份 profile 的已装清单。要么 package.json 里 dsh.workbench.unit 写错了(改完要重启工作台),要么这个包根本没被装进这份 profile`]);
|
|
7567
|
-
const read = readDeclaration(source.pkg, source.declaration);
|
|
7568
|
-
if (read.manifest === void 0) return refuse(source.pkg, read.problems);
|
|
7569
|
-
return add({
|
|
7570
|
-
unit,
|
|
7571
|
-
manifest: read.manifest,
|
|
7572
|
-
face,
|
|
7573
|
-
source: {
|
|
7574
|
-
kind: "package",
|
|
7575
|
-
pkg: source.pkg
|
|
7576
|
-
}
|
|
7577
|
-
});
|
|
7578
|
-
},
|
|
7579
|
-
registerOwned(face) {
|
|
7580
|
-
return add({
|
|
7581
|
-
unit: face.manifest.id,
|
|
7582
|
-
manifest: face.manifest,
|
|
7583
|
-
face,
|
|
7584
|
-
source: { kind: "owned" }
|
|
7585
|
-
});
|
|
7586
|
-
},
|
|
7587
|
-
registerPlugin(face) {
|
|
7588
|
-
return add({
|
|
7589
|
-
unit: face.manifest.id,
|
|
7590
|
-
manifest: face.manifest,
|
|
7591
|
-
face,
|
|
7592
|
-
source: { kind: "plugin" }
|
|
7593
|
-
});
|
|
7594
|
-
},
|
|
7595
|
-
all: () => hosts,
|
|
7596
|
-
owned: () => hosts.filter((host) => host.source.kind === "owned").map((host) => host.unit),
|
|
7597
|
-
refusals: () => refusals,
|
|
7598
|
-
manifests: () => hosts.map((host) => host.manifest),
|
|
7599
|
-
subscribe(listener) {
|
|
7600
|
-
listeners.add(listener);
|
|
7601
|
-
return () => listeners.delete(listener);
|
|
7602
|
-
},
|
|
7603
|
-
fail(unit, reason) {
|
|
7604
|
-
failures.set(unit, reason);
|
|
7605
|
-
},
|
|
7606
|
-
recovered(unit) {
|
|
7607
|
-
failures.delete(unit);
|
|
7608
|
-
},
|
|
7609
|
-
failureOf: (unit) => failures.get(unit)
|
|
7610
|
-
};
|
|
7611
|
-
}
|
|
7612
|
-
//#endregion
|
|
7613
|
-
//#region src/shared/api/host/unit-roster-facts.ts
|
|
7614
|
-
function rosterKeyOf(units) {
|
|
7615
|
-
return units.all().map((host) => host.unit).join("\0");
|
|
7929
|
+
//#region src/shared/api/host/unit-roster-facts.ts
|
|
7930
|
+
function rosterKeyOf(units) {
|
|
7931
|
+
return units.all().map((host) => host.unit).join("\0");
|
|
7616
7932
|
}
|
|
7617
7933
|
function bindRosterFacts(options) {
|
|
7618
7934
|
let seen = rosterKeyOf(options.units);
|
|
@@ -7628,7 +7944,7 @@ function bindRosterFacts(options) {
|
|
|
7628
7944
|
function recordOf(value) {
|
|
7629
7945
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
7630
7946
|
}
|
|
7631
|
-
function textOf(value) {
|
|
7947
|
+
function textOf$1(value) {
|
|
7632
7948
|
return typeof value === "string" ? value : "";
|
|
7633
7949
|
}
|
|
7634
7950
|
function stringsOf(value) {
|
|
@@ -7639,18 +7955,18 @@ function argsOf(raw) {
|
|
|
7639
7955
|
return raw.flatMap((item) => {
|
|
7640
7956
|
const record = recordOf(item);
|
|
7641
7957
|
if (record === void 0) return [];
|
|
7642
|
-
const name = textOf(record["name"]).trim();
|
|
7958
|
+
const name = textOf$1(record["name"]).trim();
|
|
7643
7959
|
if (name === "") return [];
|
|
7644
7960
|
const fallback = record["default"];
|
|
7645
7961
|
return [{
|
|
7646
7962
|
name,
|
|
7647
|
-
type: textOf(record["type"]) || "string",
|
|
7963
|
+
type: textOf$1(record["type"]) || "string",
|
|
7648
7964
|
required: record["required"] === true,
|
|
7649
7965
|
positional: record["positional"] === true,
|
|
7650
7966
|
valueRequired: record["valueRequired"] === true,
|
|
7651
7967
|
choices: stringsOf(record["choices"]),
|
|
7652
7968
|
...fallback === void 0 || fallback === null ? {} : { defaultValue: fallback },
|
|
7653
|
-
help: textOf(record["help"])
|
|
7969
|
+
help: textOf$1(record["help"])
|
|
7654
7970
|
}];
|
|
7655
7971
|
});
|
|
7656
7972
|
}
|
|
@@ -7661,11 +7977,11 @@ function parsePluginRows(stdout) {
|
|
|
7661
7977
|
for (const item of parsed) {
|
|
7662
7978
|
const record = recordOf(item);
|
|
7663
7979
|
if (record === void 0) return void 0;
|
|
7664
|
-
const name = textOf(record["name"]).trim();
|
|
7980
|
+
const name = textOf$1(record["name"]).trim();
|
|
7665
7981
|
if (name === "") return void 0;
|
|
7666
7982
|
rows.push({
|
|
7667
7983
|
name,
|
|
7668
|
-
version: textOf(record["version"]),
|
|
7984
|
+
version: textOf$1(record["version"]),
|
|
7669
7985
|
commands: stringsOf(record["commands"])
|
|
7670
7986
|
});
|
|
7671
7987
|
}
|
|
@@ -7678,17 +7994,17 @@ function parseCommandRows(stdout) {
|
|
|
7678
7994
|
for (const item of parsed) {
|
|
7679
7995
|
const record = recordOf(item);
|
|
7680
7996
|
if (record === void 0) return void 0;
|
|
7681
|
-
const site = textOf(record["site"]).trim();
|
|
7682
|
-
const name = textOf(record["name"]).trim();
|
|
7997
|
+
const site = textOf$1(record["site"]).trim();
|
|
7998
|
+
const name = textOf$1(record["name"]).trim();
|
|
7683
7999
|
if (site === "" || name === "") return void 0;
|
|
7684
8000
|
rows.push({
|
|
7685
8001
|
site,
|
|
7686
8002
|
name,
|
|
7687
|
-
description: textOf(record["description"]),
|
|
7688
|
-
access: textOf(record["access"]),
|
|
8003
|
+
description: textOf$1(record["description"]),
|
|
8004
|
+
access: textOf$1(record["access"]),
|
|
7689
8005
|
browser: record["browser"] === true,
|
|
7690
|
-
siteSession: textOf(record["siteSession"]),
|
|
7691
|
-
...textOf(record["domain"]) === "" ? {} : { domain: textOf(record["domain"]) },
|
|
8006
|
+
siteSession: textOf$1(record["siteSession"]),
|
|
8007
|
+
...textOf$1(record["domain"]) === "" ? {} : { domain: textOf$1(record["domain"]) },
|
|
7692
8008
|
args: argsOf(record["args"])
|
|
7693
8009
|
});
|
|
7694
8010
|
}
|
|
@@ -7876,6 +8192,7 @@ var SCHEMA_TYPE = {
|
|
|
7876
8192
|
string: "string"
|
|
7877
8193
|
};
|
|
7878
8194
|
var KEY_ARG_LIMIT = 3;
|
|
8195
|
+
var NO_KEY_ARGS = "没有带参数";
|
|
7879
8196
|
var RUN_CODE_TOOL = "run_code";
|
|
7880
8197
|
var PLUGIN_PROMPT_BUDGET = 4e3;
|
|
7881
8198
|
function pluginToolName(plugin, command) {
|
|
@@ -7953,13 +8270,15 @@ function pluginKeyArgs(command, raw) {
|
|
|
7953
8270
|
parts.push(`${arg.name}=${String(value)}`);
|
|
7954
8271
|
if (parts.length === KEY_ARG_LIMIT) break;
|
|
7955
8272
|
}
|
|
7956
|
-
return parts.length === 0 ?
|
|
8273
|
+
return parts.length === 0 ? NO_KEY_ARGS : parts.join("、");
|
|
7957
8274
|
}
|
|
7958
8275
|
function pluginApprovalQuestion(command, raw) {
|
|
7959
8276
|
const key = pluginKeyArgs(command, raw);
|
|
8277
|
+
const zhKey = key === NO_KEY_ARGS ? "这次没带参数" : key;
|
|
8278
|
+
const enKey = key === NO_KEY_ARGS ? "no arguments" : key;
|
|
7960
8279
|
return {
|
|
7961
|
-
zh:
|
|
7962
|
-
en: `
|
|
8280
|
+
zh: `agent 想执行工作台 [${command.plugin}] opencli 插件的 [${command.name}] 命令(${zhKey}),允许吗?`,
|
|
8281
|
+
en: `The agent wants to run the [${command.name}] command of the workbench opencli plugin [${command.plugin}] (${enKey}). Allow?`
|
|
7963
8282
|
};
|
|
7964
8283
|
}
|
|
7965
8284
|
function pluginApprovalFor(command) {
|
|
@@ -8221,6 +8540,330 @@ function createPluginUnits(options) {
|
|
|
8221
8540
|
};
|
|
8222
8541
|
}
|
|
8223
8542
|
//#endregion
|
|
8543
|
+
//#region src/shared/api/config-sync.ts
|
|
8544
|
+
var CONFIG_SYNC_ROUTE = "config-sync";
|
|
8545
|
+
var CONFIG_SYNC_RESOURCE = "configSync";
|
|
8546
|
+
//#endregion
|
|
8547
|
+
//#region src/shared/api/host/config-sync.ts
|
|
8548
|
+
var ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
|
|
8549
|
+
var INSERT_LINE = /^- insert:[ \t]*$/;
|
|
8550
|
+
var NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
|
|
8551
|
+
var NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
|
|
8552
|
+
var COMMENT_LINE = /^[ \t]*#/;
|
|
8553
|
+
function trimmedTail(lines) {
|
|
8554
|
+
let end = lines.length;
|
|
8555
|
+
while (end > 0 && (lines[end - 1] ?? "").trim() === "") end -= 1;
|
|
8556
|
+
return lines.slice(0, end);
|
|
8557
|
+
}
|
|
8558
|
+
function matched(text, pattern) {
|
|
8559
|
+
const found = pattern.exec(text);
|
|
8560
|
+
return found === null ? void 0 : found[1];
|
|
8561
|
+
}
|
|
8562
|
+
function blockOf(body, index) {
|
|
8563
|
+
const lines = body.split("\n");
|
|
8564
|
+
const first = lines[0] ?? "";
|
|
8565
|
+
const id = matched(first, ID_LINE);
|
|
8566
|
+
const nested = lines.flatMap((line) => {
|
|
8567
|
+
const one = matched(line, NESTED_ID);
|
|
8568
|
+
return one === void 0 ? [] : [one];
|
|
8569
|
+
});
|
|
8570
|
+
const insert = id === void 0 && INSERT_LINE.test(first);
|
|
8571
|
+
const label = id ?? nested.join(", ");
|
|
8572
|
+
const key = id !== void 0 ? `id:${id}` : insert ? `insert:${nested.join(", ")}` : `block:${String(index)}`;
|
|
8573
|
+
const names = lines.flatMap((line) => {
|
|
8574
|
+
const one = matched(line, NAME_LINE);
|
|
8575
|
+
return one === void 0 ? [] : [one];
|
|
8576
|
+
});
|
|
8577
|
+
return {
|
|
8578
|
+
key,
|
|
8579
|
+
id: label === "" ? key : label,
|
|
8580
|
+
insert,
|
|
8581
|
+
body,
|
|
8582
|
+
names
|
|
8583
|
+
};
|
|
8584
|
+
}
|
|
8585
|
+
function spannedBlocks(text) {
|
|
8586
|
+
const lines = text.split("\n");
|
|
8587
|
+
const starts = [];
|
|
8588
|
+
lines.forEach((line, index) => {
|
|
8589
|
+
if (/^- /.test(line) || line === "-") starts.push(index);
|
|
8590
|
+
});
|
|
8591
|
+
const blocks = [];
|
|
8592
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
8593
|
+
const from = starts[i] ?? 0;
|
|
8594
|
+
const to = i + 1 < starts.length ? starts[i + 1] ?? lines.length : lines.length;
|
|
8595
|
+
const body = trimmedTail(lines.slice(from, to)).join("\n");
|
|
8596
|
+
if (body.trim() === "") continue;
|
|
8597
|
+
blocks.push({
|
|
8598
|
+
...blockOf(body, blocks.length),
|
|
8599
|
+
from,
|
|
8600
|
+
to
|
|
8601
|
+
});
|
|
8602
|
+
}
|
|
8603
|
+
return blocks;
|
|
8604
|
+
}
|
|
8605
|
+
function patchBlocks(text) {
|
|
8606
|
+
return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
|
|
8607
|
+
}
|
|
8608
|
+
function normalizeBlock(body) {
|
|
8609
|
+
return body.split("\n").map((line) => line.replace(/\s+$/, "")).filter((line) => line.trim() !== "" && !COMMENT_LINE.test(line)).join("\n");
|
|
8610
|
+
}
|
|
8611
|
+
function syncRows(source, own, missing) {
|
|
8612
|
+
const mine = new Map(own.map((block) => [block.key, block]));
|
|
8613
|
+
const rows = [];
|
|
8614
|
+
for (const block of source) {
|
|
8615
|
+
const here = mine.get(block.key);
|
|
8616
|
+
if (here !== void 0 && normalizeBlock(here.body) === normalizeBlock(block.body)) continue;
|
|
8617
|
+
rows.push({
|
|
8618
|
+
key: block.key,
|
|
8619
|
+
id: block.id,
|
|
8620
|
+
insert: block.insert,
|
|
8621
|
+
state: here === void 0 ? "new" : "differs",
|
|
8622
|
+
body: block.body,
|
|
8623
|
+
ownBody: here === void 0 ? null : here.body,
|
|
8624
|
+
missing: missing(block)
|
|
8625
|
+
});
|
|
8626
|
+
}
|
|
8627
|
+
return rows;
|
|
8628
|
+
}
|
|
8629
|
+
function replaceBlocks(text, blocks) {
|
|
8630
|
+
const wanted = new Map(blocks.map((block) => [block.key, block]));
|
|
8631
|
+
const spans = spannedBlocks(text).filter((span) => wanted.has(span.key));
|
|
8632
|
+
const lines = text.split("\n");
|
|
8633
|
+
for (const span of [...spans].reverse()) {
|
|
8634
|
+
const replacement = (wanted.get(span.key)?.body ?? "").split("\n");
|
|
8635
|
+
lines.splice(span.from, span.to - span.from, ...replacement);
|
|
8636
|
+
}
|
|
8637
|
+
return lines.join("\n");
|
|
8638
|
+
}
|
|
8639
|
+
function appendBlocks(text, blocks) {
|
|
8640
|
+
const kept = text.split("\n").filter((line) => line.trim() !== "[]").join("\n").replace(/\s+$/, "");
|
|
8641
|
+
let out = kept === "" ? "" : `${kept}\n`;
|
|
8642
|
+
for (const block of blocks) out += `\n${block.body}\n`;
|
|
8643
|
+
return out;
|
|
8644
|
+
}
|
|
8645
|
+
function mergeBlocks(ownText, incoming, overwrite) {
|
|
8646
|
+
const own = patchBlocks(ownText);
|
|
8647
|
+
const mine = new Set(own.map((block) => block.key));
|
|
8648
|
+
const written = [];
|
|
8649
|
+
const skipped = [];
|
|
8650
|
+
const take = [];
|
|
8651
|
+
for (const block of incoming) {
|
|
8652
|
+
if (!mine.has(block.key) || overwrite.has(block.key)) {
|
|
8653
|
+
take.push(block);
|
|
8654
|
+
written.push(block.id);
|
|
8655
|
+
continue;
|
|
8656
|
+
}
|
|
8657
|
+
skipped.push(block.id);
|
|
8658
|
+
}
|
|
8659
|
+
if (take.length === 0) return {
|
|
8660
|
+
ok: true,
|
|
8661
|
+
text: ownText,
|
|
8662
|
+
written,
|
|
8663
|
+
skipped
|
|
8664
|
+
};
|
|
8665
|
+
const text = appendBlocks(replaceBlocks(ownText, take.filter((block) => mine.has(block.key))), take.filter((block) => !mine.has(block.key)));
|
|
8666
|
+
const check = patchBlocks(text);
|
|
8667
|
+
const expected = /* @__PURE__ */ new Set([...mine, ...take.map((block) => block.key)]);
|
|
8668
|
+
if (check.length !== expected.size || new Set(check.map((block) => block.key)).size !== expected.size) return {
|
|
8669
|
+
ok: false,
|
|
8670
|
+
reason: "写入前的自检没过:行的 id 会重复或丢行"
|
|
8671
|
+
};
|
|
8672
|
+
return {
|
|
8673
|
+
ok: true,
|
|
8674
|
+
text,
|
|
8675
|
+
written,
|
|
8676
|
+
skipped
|
|
8677
|
+
};
|
|
8678
|
+
}
|
|
8679
|
+
function readPatchBlocks(path) {
|
|
8680
|
+
try {
|
|
8681
|
+
const text = readFileSync(path, "utf8");
|
|
8682
|
+
return {
|
|
8683
|
+
ok: true,
|
|
8684
|
+
text,
|
|
8685
|
+
blocks: patchBlocks(text)
|
|
8686
|
+
};
|
|
8687
|
+
} catch (error) {
|
|
8688
|
+
return {
|
|
8689
|
+
ok: false,
|
|
8690
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
8691
|
+
};
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
function patchMode(path) {
|
|
8695
|
+
try {
|
|
8696
|
+
return statSync(path).mode & 511;
|
|
8697
|
+
} catch {
|
|
8698
|
+
return 420;
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
function writePatchFile(path, text) {
|
|
8702
|
+
const mode = patchMode(path);
|
|
8703
|
+
const tmp = `${path}.wb-sync-${String(process.pid)}`;
|
|
8704
|
+
writeFileSync(tmp, text, { mode });
|
|
8705
|
+
try {
|
|
8706
|
+
renameSync(tmp, path);
|
|
8707
|
+
} catch (error) {
|
|
8708
|
+
try {
|
|
8709
|
+
unlinkSync(tmp);
|
|
8710
|
+
} catch {
|
|
8711
|
+
throw error;
|
|
8712
|
+
}
|
|
8713
|
+
throw error;
|
|
8714
|
+
}
|
|
8715
|
+
}
|
|
8716
|
+
function configSyncHandler(deps) {
|
|
8717
|
+
return (search) => ({
|
|
8718
|
+
ok: true,
|
|
8719
|
+
...configSyncSnapshot(deps, search.get("source") ?? void 0)
|
|
8720
|
+
});
|
|
8721
|
+
}
|
|
8722
|
+
function textOf(value) {
|
|
8723
|
+
return typeof value === "string" ? value : "";
|
|
8724
|
+
}
|
|
8725
|
+
function profileDependencies(dir) {
|
|
8726
|
+
try {
|
|
8727
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
8728
|
+
return new Set(Object.keys(manifest.dependencies ?? {}));
|
|
8729
|
+
} catch {
|
|
8730
|
+
return /* @__PURE__ */ new Set();
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
8733
|
+
function profileNames(profilesDir) {
|
|
8734
|
+
try {
|
|
8735
|
+
return readdirSync(profilesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name).filter((name) => existsSync(join(profilesDir, name, "package.json"))).sort((a, b) => a.localeCompare(b));
|
|
8736
|
+
} catch {
|
|
8737
|
+
return [];
|
|
8738
|
+
}
|
|
8739
|
+
}
|
|
8740
|
+
function configSyncSnapshot(deps, asked) {
|
|
8741
|
+
const profilesDir = join(deps.home, "profiles");
|
|
8742
|
+
const others = profileNames(profilesDir).filter((name) => name !== deps.self);
|
|
8743
|
+
const base = {
|
|
8744
|
+
self: deps.self,
|
|
8745
|
+
home: deps.home
|
|
8746
|
+
};
|
|
8747
|
+
if (deps.profileDir === null) return {
|
|
8748
|
+
...base,
|
|
8749
|
+
source: "",
|
|
8750
|
+
profiles: [],
|
|
8751
|
+
rows: [],
|
|
8752
|
+
emptyProviders: false,
|
|
8753
|
+
reason: "拿不到当前 profile 目录"
|
|
8754
|
+
};
|
|
8755
|
+
const own = readPatchBlocks(join(deps.profileDir, "cordis.patch.yml"));
|
|
8756
|
+
const emptyProviders = own.ok && !own.blocks.some((block) => block.key.startsWith("id:llm-"));
|
|
8757
|
+
const ownBlocks = own.ok ? own.blocks : [];
|
|
8758
|
+
const profiles = others.map((name) => {
|
|
8759
|
+
const read = readPatchBlocks(join(profilesDir, name, "cordis.patch.yml"));
|
|
8760
|
+
return {
|
|
8761
|
+
name,
|
|
8762
|
+
readable: read.ok,
|
|
8763
|
+
candidates: read.ok ? syncRows(read.blocks, ownBlocks, () => []).length : 0
|
|
8764
|
+
};
|
|
8765
|
+
});
|
|
8766
|
+
if (others.length === 0) return {
|
|
8767
|
+
...base,
|
|
8768
|
+
source: "",
|
|
8769
|
+
profiles,
|
|
8770
|
+
rows: [],
|
|
8771
|
+
emptyProviders,
|
|
8772
|
+
reason: "这台机器上只有当前这个 profile,没有可同步的来源"
|
|
8773
|
+
};
|
|
8774
|
+
const source = others.includes(asked ?? "") ? asked ?? "" : others.includes("web") ? "web" : others[0] ?? "";
|
|
8775
|
+
const sourceDir = join(profilesDir, source);
|
|
8776
|
+
const read = readPatchBlocks(join(sourceDir, "cordis.patch.yml"));
|
|
8777
|
+
if (!read.ok) return {
|
|
8778
|
+
...base,
|
|
8779
|
+
source,
|
|
8780
|
+
profiles,
|
|
8781
|
+
rows: [],
|
|
8782
|
+
emptyProviders,
|
|
8783
|
+
reason: read.reason
|
|
8784
|
+
};
|
|
8785
|
+
if (!own.ok) return {
|
|
8786
|
+
...base,
|
|
8787
|
+
source,
|
|
8788
|
+
profiles,
|
|
8789
|
+
rows: [],
|
|
8790
|
+
emptyProviders,
|
|
8791
|
+
reason: own.reason
|
|
8792
|
+
};
|
|
8793
|
+
const declared = profileDependencies(sourceDir);
|
|
8794
|
+
const modulesDir = join(deps.profileDir, "node_modules");
|
|
8795
|
+
const rows = syncRows(read.blocks, own.blocks, (block) => block.names.filter((name) => declared.has(name) && !existsSync(join(modulesDir, name))));
|
|
8796
|
+
return {
|
|
8797
|
+
...base,
|
|
8798
|
+
source,
|
|
8799
|
+
profiles,
|
|
8800
|
+
rows,
|
|
8801
|
+
emptyProviders
|
|
8802
|
+
};
|
|
8803
|
+
}
|
|
8804
|
+
function configSyncApply(deps, payload) {
|
|
8805
|
+
if (deps.profileDir === null) return {
|
|
8806
|
+
ok: false,
|
|
8807
|
+
text: "拿不到当前 profile 目录,这次没写任何东西"
|
|
8808
|
+
};
|
|
8809
|
+
const body = typeof payload === "object" && payload !== null ? payload : {};
|
|
8810
|
+
const source = textOf(body["source"]);
|
|
8811
|
+
const keys = new Set((Array.isArray(body["keys"]) ? body["keys"] : []).map((item) => textOf(item)).filter((item) => item !== ""));
|
|
8812
|
+
const overwrite = new Set((Array.isArray(body["overwrite"]) ? body["overwrite"] : []).map((item) => textOf(item)));
|
|
8813
|
+
if (source === "" || keys.size === 0) return {
|
|
8814
|
+
ok: false,
|
|
8815
|
+
text: "没说清从哪个 profile 搬哪几行"
|
|
8816
|
+
};
|
|
8817
|
+
const profilesDir = join(deps.home, "profiles");
|
|
8818
|
+
if (!profileNames(profilesDir).includes(source)) return {
|
|
8819
|
+
ok: false,
|
|
8820
|
+
text: `没有这个 profile:${source}`
|
|
8821
|
+
};
|
|
8822
|
+
if (source === deps.self) return {
|
|
8823
|
+
ok: false,
|
|
8824
|
+
text: "来源不能是工作台自己"
|
|
8825
|
+
};
|
|
8826
|
+
const read = readPatchBlocks(join(profilesDir, source, "cordis.patch.yml"));
|
|
8827
|
+
if (!read.ok) return {
|
|
8828
|
+
ok: false,
|
|
8829
|
+
text: `读不到 ${source} 的配置:${read.reason}`
|
|
8830
|
+
};
|
|
8831
|
+
const own = readPatchBlocks(join(deps.profileDir, "cordis.patch.yml"));
|
|
8832
|
+
if (!own.ok) return {
|
|
8833
|
+
ok: false,
|
|
8834
|
+
text: `读不到工作台自己的配置:${own.reason}`
|
|
8835
|
+
};
|
|
8836
|
+
const picked = read.blocks.filter((block) => keys.has(block.key));
|
|
8837
|
+
if (picked.length === 0) return {
|
|
8838
|
+
ok: false,
|
|
8839
|
+
text: "选中的行在来源里已经找不到了,重新读一遍再试"
|
|
8840
|
+
};
|
|
8841
|
+
const merged = mergeBlocks(own.text, picked, overwrite);
|
|
8842
|
+
if (!merged.ok) return {
|
|
8843
|
+
ok: false,
|
|
8844
|
+
text: merged.reason
|
|
8845
|
+
};
|
|
8846
|
+
try {
|
|
8847
|
+
writePatchFile(join(deps.profileDir, "cordis.patch.yml"), merged.text);
|
|
8848
|
+
} catch (error) {
|
|
8849
|
+
return {
|
|
8850
|
+
ok: false,
|
|
8851
|
+
text: `写不进去:${error instanceof Error ? error.message : String(error)}`
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8854
|
+
return {
|
|
8855
|
+
ok: true,
|
|
8856
|
+
written: merged.written,
|
|
8857
|
+
skipped: merged.skipped
|
|
8858
|
+
};
|
|
8859
|
+
}
|
|
8860
|
+
function configSyncAction(deps) {
|
|
8861
|
+
return {
|
|
8862
|
+
resource: CONFIG_SYNC_RESOURCE,
|
|
8863
|
+
handle: (payload) => configSyncApply(deps, payload)
|
|
8864
|
+
};
|
|
8865
|
+
}
|
|
8866
|
+
//#endregion
|
|
8224
8867
|
//#region src/shared/api/host/tool-scope.ts
|
|
8225
8868
|
function agentOf(value) {
|
|
8226
8869
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -8345,11 +8988,17 @@ function apply(ctx) {
|
|
|
8345
8988
|
[DSH_PLUGINS_ROUTE]: dshPluginsHandler(ctx),
|
|
8346
8989
|
[BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
|
|
8347
8990
|
[BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
|
|
8348
|
-
[MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"))
|
|
8991
|
+
[MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), runningVersionOf(import.meta.url))
|
|
8349
8992
|
};
|
|
8350
8993
|
const renderers = /* @__PURE__ */ new Map();
|
|
8351
8994
|
const shell = ctx.get("shell");
|
|
8352
8995
|
const profileDir = profileDirOf(ctx);
|
|
8996
|
+
const configSync = {
|
|
8997
|
+
home: dshHome(),
|
|
8998
|
+
self: profileDir === null ? "" : basename(profileDir),
|
|
8999
|
+
profileDir
|
|
9000
|
+
};
|
|
9001
|
+
routes[CONFIG_SYNC_ROUTE] = configSyncHandler(configSync);
|
|
8353
9002
|
const units = createUnitRegistry({
|
|
8354
9003
|
declarationOf: createDeclarationLookup(profileDir),
|
|
8355
9004
|
logger: ctx.logger
|
|
@@ -8424,10 +9073,23 @@ function apply(ctx) {
|
|
|
8424
9073
|
const factsOfMap = /* @__PURE__ */ new Map();
|
|
8425
9074
|
const routesOfUnitMap = /* @__PURE__ */ new Map();
|
|
8426
9075
|
const ownersMap = /* @__PURE__ */ new Map();
|
|
8427
|
-
let undoPost = [];
|
|
8428
9076
|
const factsOfUnit = (unit) => [...routesOfUnitMap.get(unit) ?? [], "capability"];
|
|
8429
9077
|
const callFacts = (unit, call) => factsOfMap.get(unit)?.(call) ?? factsOfUnit(unit);
|
|
8430
9078
|
const deniedNow = (workspace) => [...new Set([...mounted.values()].flatMap((item) => item.plan.gate?.deniedTools(workspace) ?? []))];
|
|
9079
|
+
const wrote = (resource, write) => {
|
|
9080
|
+
const unit = ownersMap.get(resource);
|
|
9081
|
+
if (unit !== void 0) facts.bump(callFacts(unit, {
|
|
9082
|
+
source: resource,
|
|
9083
|
+
args: asArgs(write.args),
|
|
9084
|
+
result: write.result
|
|
9085
|
+
}));
|
|
9086
|
+
};
|
|
9087
|
+
const postChannels = createPostChannels({
|
|
9088
|
+
ctx,
|
|
9089
|
+
mounted,
|
|
9090
|
+
facts,
|
|
9091
|
+
shellActions: [configSyncAction(configSync)]
|
|
9092
|
+
});
|
|
8431
9093
|
function unmountUnit(id) {
|
|
8432
9094
|
const item = mounted.get(id);
|
|
8433
9095
|
if (item === void 0) return;
|
|
@@ -8517,22 +9179,7 @@ function apply(ctx) {
|
|
|
8517
9179
|
}
|
|
8518
9180
|
}
|
|
8519
9181
|
function rebindPost() {
|
|
8520
|
-
|
|
8521
|
-
undoPost = [];
|
|
8522
|
-
const wrote = (resource, write) => {
|
|
8523
|
-
const unit = ownersMap.get(resource);
|
|
8524
|
-
if (unit !== void 0) facts.bump(callFacts(unit, {
|
|
8525
|
-
source: resource,
|
|
8526
|
-
args: asArgs(write.args),
|
|
8527
|
-
result: write.result
|
|
8528
|
-
}));
|
|
8529
|
-
};
|
|
8530
|
-
const prefs = [...mounted.values()].flatMap((item) => item.plan.prefs);
|
|
8531
|
-
const actions = [...mounted.values()].flatMap((item) => item.plan.actions);
|
|
8532
|
-
const provision = [...mounted.values()].find((item) => item.plan.provision !== void 0)?.plan.provision;
|
|
8533
|
-
if (prefs.length > 0) undoPost.push(registerPrefs(ctx, prefs, wrote));
|
|
8534
|
-
if (actions.length > 0) undoPost.push(registerActions(ctx, actions, wrote));
|
|
8535
|
-
if (provision !== void 0) undoPost.push(registerProvision(ctx, provision, facts));
|
|
9182
|
+
postChannels.rebind(wrote);
|
|
8536
9183
|
}
|
|
8537
9184
|
function sync() {
|
|
8538
9185
|
const present = new Set(units.all().map((one) => one.unit));
|
|
@@ -8599,7 +9246,7 @@ function apply(ctx) {
|
|
|
8599
9246
|
}
|
|
8600
9247
|
ctx.effect(() => () => {
|
|
8601
9248
|
for (const id of [...mounted.keys()]) unmountUnit(id);
|
|
8602
|
-
|
|
9249
|
+
postChannels.dispose();
|
|
8603
9250
|
}, `${PLUGIN_ID}: 卸载时把单元摘干净`);
|
|
8604
9251
|
ctx.effect(() => units.subscribe(sync), `${PLUGIN_ID}: 注册表变了就增删(装/卸一个模块 = 挂/卸它的面)`);
|
|
8605
9252
|
ctx.effect(() => bindRosterFacts({
|