@dsh-workbench/core 0.2.6 → 0.2.8
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 +11 -9
- package/bin/dsh-workbench +42 -4
- package/client.js +252 -36
- package/dist/client/client.body.js +252 -36
- package/dist/client/index.js +252 -36
- package/dist/host/index.js +1054 -737
- package/dist/types/app/client.d.ts +21 -15
- 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/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/shared/api/bundle-install.d.ts +1 -0
- package/dist/types/shared/api/config-sync.d.ts +0 -2
- 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 +2 -2
- 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 +12 -5
- package/docs/OPERATIONS.md +35 -4
- package/package.json +4 -4
- package/scripts/seed-profile.sh +61 -1
package/dist/host/index.js
CHANGED
|
@@ -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
|
|
@@ -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"],
|
|
@@ -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,155 +6509,311 @@ 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
|
-
paths.opencliBinDir,
|
|
6181
|
-
paths.opencliHome,
|
|
6182
|
-
paths.workshopPackages
|
|
6183
|
-
]) mkdirSync(dir, { recursive: true });
|
|
6184
|
-
const wanted = opencliWrapperScript(options.packageRoot, options.home);
|
|
6185
|
-
if (readText(paths.opencliBin) !== wanted) {
|
|
6186
|
-
rmSync(paths.opencliBin, { force: true });
|
|
6187
|
-
writeFileSync(paths.opencliBin, wanted, "utf8");
|
|
6188
|
-
chmodSync(paths.opencliBin, 493);
|
|
6189
|
-
done.push(paths.opencliBin);
|
|
6190
|
-
}
|
|
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
|
-
}
|
|
6195
|
-
return done;
|
|
6567
|
+
function fail(ledger, item, reason) {
|
|
6568
|
+
ledger.failed.add(item);
|
|
6569
|
+
ledger.failures.push({
|
|
6570
|
+
id: item.host.manifest.id,
|
|
6571
|
+
reason
|
|
6572
|
+
});
|
|
6196
6573
|
}
|
|
6197
|
-
function
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
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);
|
|
6605
|
+
}
|
|
6202
6606
|
}
|
|
6607
|
+
return {
|
|
6608
|
+
routes,
|
|
6609
|
+
owners: owner,
|
|
6610
|
+
routesOfUnit
|
|
6611
|
+
};
|
|
6203
6612
|
}
|
|
6204
|
-
function
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
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
|
+
}
|
|
6209
6633
|
}
|
|
6634
|
+
return out;
|
|
6210
6635
|
}
|
|
6211
|
-
function
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
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
|
+
}
|
|
6224
6653
|
}
|
|
6654
|
+
return table;
|
|
6225
6655
|
}
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
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);
|
|
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
|
+
};
|
|
6813
|
+
}
|
|
6814
|
+
//#endregion
|
|
6815
|
+
//#region src/shared/api/host/events.ts
|
|
6816
|
+
var HEARTBEAT_MS = 25e3;
|
|
6229
6817
|
function frame(payload) {
|
|
6230
6818
|
return `data: ${JSON.stringify(payload)}\n\n`;
|
|
6231
6819
|
}
|
|
@@ -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 {
|
|
@@ -7036,485 +7626,211 @@ async function probeLibs(options) {
|
|
|
7036
7626
|
const installed = installedLibVersion(need.name);
|
|
7037
7627
|
if (installed === void 0) {
|
|
7038
7628
|
found.set(need.name, {
|
|
7039
|
-
...base,
|
|
7040
|
-
status: "missing",
|
|
7041
|
-
command: libInstallCommand({ need })
|
|
7042
|
-
});
|
|
7043
|
-
continue;
|
|
7044
|
-
}
|
|
7045
|
-
if (checkVersionRange(need.version, installed) !== "satisfied") {
|
|
7046
|
-
found.set(need.name, {
|
|
7047
|
-
...base,
|
|
7048
|
-
status: "unsatisfied",
|
|
7049
|
-
installed,
|
|
7050
|
-
command: libInstallCommand({ need })
|
|
7051
|
-
});
|
|
7052
|
-
continue;
|
|
7053
|
-
}
|
|
7054
|
-
const registry = process.env[need.registryEnv]?.trim() || need.registryDefault;
|
|
7055
|
-
const latest = await latestVersion({
|
|
7056
|
-
shell,
|
|
7057
|
-
name: need.name,
|
|
7058
|
-
registry,
|
|
7059
|
-
force
|
|
7060
|
-
});
|
|
7061
|
-
const behind = latest !== void 0 && isNewerVersion(latest, installed);
|
|
7062
|
-
found.set(need.name, {
|
|
7063
|
-
...base,
|
|
7064
|
-
status: behind ? "outdated" : "ok",
|
|
7065
|
-
installed,
|
|
7066
|
-
...latest === void 0 ? {} : { latest },
|
|
7067
|
-
...behind ? { command: libInstallCommand({
|
|
7068
|
-
need,
|
|
7069
|
-
version: latest
|
|
7070
|
-
}) } : {}
|
|
7071
|
-
});
|
|
7072
|
-
}
|
|
7073
|
-
return found;
|
|
7074
|
-
}
|
|
7075
|
-
//#endregion
|
|
7076
|
-
//#region src/shared/api/host/probe-verdict.ts
|
|
7077
|
-
function evaluateUnit(list, unit, failure, command = "opencli", releases = /* @__PURE__ */ new Map(), libs = []) {
|
|
7078
|
-
const isPlugin = (item) => typeof item === "object" && item !== null;
|
|
7079
|
-
const needs = unit.plugins ?? [];
|
|
7080
|
-
const required = needs.flatMap((need) => need.commands ?? []);
|
|
7081
|
-
const primary = needs[0]?.name;
|
|
7082
|
-
if (failure !== void 0 || list === void 0) return {
|
|
7083
|
-
unit: unit.unit,
|
|
7084
|
-
...primary === void 0 ? {} : { plugin: primary },
|
|
7085
|
-
status: "unchecked",
|
|
7086
|
-
required,
|
|
7087
|
-
missing: [],
|
|
7088
|
-
detail: failure ?? "探不动",
|
|
7089
|
-
...libs.length === 0 ? {} : { libs }
|
|
7090
|
-
};
|
|
7091
|
-
const plugins = list.filter(isPlugin);
|
|
7092
|
-
const installed = new Map(plugins.map((item) => [String(item.name), item]));
|
|
7093
|
-
const states = needs.map((need) => {
|
|
7094
|
-
const hit = installed.get(need.name);
|
|
7095
|
-
if (hit === void 0) {
|
|
7096
|
-
const source = resolvePluginSource(need.installSource, process.env, {
|
|
7097
|
-
command,
|
|
7098
|
-
plugin: need.name
|
|
7099
|
-
});
|
|
7100
|
-
return {
|
|
7101
|
-
name: need.name,
|
|
7102
|
-
status: "missing",
|
|
7103
|
-
missing: [],
|
|
7104
|
-
install: source.ok ? "ready" : source.problem,
|
|
7105
|
-
command: source.ok ? source.line : pluginInstallCommand(need.name)
|
|
7106
|
-
};
|
|
7107
|
-
}
|
|
7108
|
-
const available = new Set((Array.isArray(hit.commands) ? hit.commands : []).map(String));
|
|
7109
|
-
const missing = (need.commands ?? []).filter((name) => !available.has(name));
|
|
7110
|
-
const release = releases.get(need.name);
|
|
7111
|
-
const behind = release?.latest !== void 0 && isNewerVersion(release.latest, release.installed);
|
|
7112
|
-
const commandsBehind = missing.length > 0;
|
|
7113
|
-
const upgrade = behind && release?.latest !== void 0 ? upgradeLine(need, command, release.latest) : void 0;
|
|
7114
|
-
return {
|
|
7115
|
-
name: need.name,
|
|
7116
|
-
status: commandsBehind || behind ? "outdated" : "ok",
|
|
7117
|
-
missing,
|
|
7118
|
-
...release === void 0 ? {} : { release },
|
|
7119
|
-
...commandsBehind || behind ? { command: upgrade ?? pluginUpdateCommand(need.name) ?? pluginInstallCommand(need.name) } : {}
|
|
7120
|
-
};
|
|
7121
|
-
});
|
|
7122
|
-
const absent = states.filter((state) => state.status === "missing");
|
|
7123
|
-
const outdated = states.filter((state) => state.status === "outdated");
|
|
7124
|
-
const missing = states.flatMap((state) => state.missing);
|
|
7125
|
-
const libMissing = libs.filter((state) => state.status === "missing");
|
|
7126
|
-
const libMismatch = libs.filter((state) => state.status === "unsatisfied");
|
|
7127
|
-
const libBehind = libs.filter((state) => state.status === "outdated");
|
|
7128
|
-
const status = absent.length > 0 || libMissing.length > 0 || libMismatch.length > 0 ? "missing-plugin" : missing.length > 0 ? "missing-commands" : "ok";
|
|
7129
|
-
const blocked = absent.find((state) => state.install !== "ready");
|
|
7130
|
-
const install = absent.length === 0 ? libMissing.length > 0 || libMismatch.length > 0 ? "ready" : void 0 : blocked === void 0 ? "ready" : blocked.install;
|
|
7131
|
-
const fix = status === "missing-plugin" ? install === "ready" ? "install" : void 0 : outdated.length > 0 || libBehind.length > 0 ? "update" : void 0;
|
|
7132
|
-
const first = absent[0] ?? outdated[0] ?? libMissing[0] ?? libMismatch[0] ?? libBehind[0];
|
|
7133
|
-
const gaps = [...missing.length === 0 ? [] : [`缺必需:${missing.join("、")}`]];
|
|
7134
|
-
const newer = states.filter((state) => state.release?.latest !== void 0 && isNewerVersion(state.release.latest, state.release.installed));
|
|
7135
|
-
const versionText = newer.length === 0 ? "" : `(源上有新版:${newer.map((state) => `${state.release?.installed ?? ""} → ${state.release?.latest ?? ""}`).join("、")})`;
|
|
7136
|
-
const absentNames = [...absent.map((state) => state.name), ...libMissing.map((state) => state.name)];
|
|
7137
|
-
const mismatchText = libMismatch.map((state) => {
|
|
7138
|
-
const declared = unit.libs?.find((need) => need.name === state.name)?.version?.trim();
|
|
7139
|
-
return `${state.name}(装的是 ${state.installed ?? "未知"},声明要 ${declared === void 0 || declared === "" ? "任意版本" : declared})`;
|
|
7140
|
-
});
|
|
7141
|
-
return {
|
|
7142
|
-
unit: unit.unit,
|
|
7143
|
-
...primary === void 0 ? {} : { plugin: primary },
|
|
7144
|
-
status,
|
|
7145
|
-
required,
|
|
7146
|
-
missing,
|
|
7147
|
-
detail: absentNames.length > 0 || mismatchText.length > 0 ? [...absentNames.length === 0 ? [] : [`${plugins.length} 个已装插件里没有 ${absentNames.join("、")}`], ...mismatchText].join(";") : `${needs.length} 份安装都在${gapsText(gaps)}${versionText}${libs.length === 0 ? "" : `;交叉依赖:${libs.map((state) => `${state.name}${state.installed === void 0 ? " 未装" : ` ${state.installed}`}`).join("、")}${libBehind.length === 0 ? "" : "(源上有新版)"}`}`,
|
|
7148
|
-
...fix === void 0 && first === void 0 ? {} : { command: fix === "install" ? (absent[0] ?? first)?.command : first?.command },
|
|
7149
|
-
...fix === void 0 ? {} : { fix },
|
|
7150
|
-
...install === void 0 ? {} : { install },
|
|
7151
|
-
plugins: states,
|
|
7152
|
-
...libs.length === 0 ? {} : { libs }
|
|
7153
|
-
};
|
|
7154
|
-
}
|
|
7155
|
-
function gapsText(gaps) {
|
|
7156
|
-
return gaps.length === 0 ? "" : `(${gaps.join(";")})`;
|
|
7157
|
-
}
|
|
7158
|
-
function pluginUpdateCommand(plugin) {
|
|
7159
|
-
return plugin === void 0 ? void 0 : `opencli plugin update ${plugin}`;
|
|
7160
|
-
}
|
|
7161
|
-
function pluginInstallCommand(plugin) {
|
|
7162
|
-
return `opencli plugin install <${plugin ?? "插件"}源>`;
|
|
7163
|
-
}
|
|
7164
|
-
function upgradeLine(need, command, latest) {
|
|
7165
|
-
const source = resolvePluginSource(need.installSource, process.env, {
|
|
7166
|
-
command,
|
|
7167
|
-
plugin: need.name,
|
|
7168
|
-
version: latest,
|
|
7169
|
-
mode: "update"
|
|
7170
|
-
});
|
|
7171
|
-
return source.ok ? source.line : void 0;
|
|
7172
|
-
}
|
|
7173
|
-
//#endregion
|
|
7174
|
-
//#region src/shared/api/host/probe-units.ts
|
|
7175
|
-
function parsePluginList(stdout) {
|
|
7176
|
-
const parsed = parseJsonLoose(stdout);
|
|
7177
|
-
if (Array.isArray(parsed)) return parsed;
|
|
7178
|
-
return /no plugins? installed/i.test(stdout) ? [] : void 0;
|
|
7179
|
-
}
|
|
7180
|
-
async function probeReleases(units, options) {
|
|
7181
|
-
const needs = /* @__PURE__ */ new Map();
|
|
7182
|
-
for (const unit of units) for (const need of unit.plugins ?? []) {
|
|
7183
|
-
const source = need.installSource;
|
|
7184
|
-
if (source?.kind === "npm") needs.set(need.name, source);
|
|
7185
|
-
}
|
|
7186
|
-
const found = /* @__PURE__ */ new Map();
|
|
7187
|
-
for (const [plugin, source] of needs) {
|
|
7188
|
-
const registry = npmRegistryOf(source, process.env);
|
|
7189
|
-
const signal = await probeRelease({
|
|
7190
|
-
shell: options.shell,
|
|
7191
|
-
package: source.package,
|
|
7192
|
-
...registry.ok ? { registry: registry.value } : {},
|
|
7193
|
-
force: options.force
|
|
7194
|
-
});
|
|
7195
|
-
if (signal !== void 0) found.set(plugin, signal);
|
|
7196
|
-
}
|
|
7197
|
-
return found;
|
|
7198
|
-
}
|
|
7199
|
-
async function probeUnits(options) {
|
|
7200
|
-
const { shell, command = "opencli", units, force = false } = options;
|
|
7201
|
-
const hits = units.map((unit) => unitCache.get(`${command}:${unit.unit}`));
|
|
7202
|
-
if (!force && hits.every((hit) => hit !== void 0)) return hits;
|
|
7203
|
-
const result = await runShell(shell, `${command} plugin list -f json`, { timeoutMs: OPENCLI_TIMEOUT_MS }).then((value) => ({
|
|
7204
|
-
kind: "ok",
|
|
7205
|
-
stdout: value.exitCode === 0 ? value.stdout : "",
|
|
7206
|
-
stderr: value.stderr
|
|
7207
|
-
}), (error) => ({
|
|
7208
|
-
kind: "error",
|
|
7209
|
-
stdout: "",
|
|
7210
|
-
stderr: String(error)
|
|
7211
|
-
}));
|
|
7212
|
-
const list = result.kind === "ok" ? parsePluginList(result.stdout) : void 0;
|
|
7213
|
-
const failure = result.kind === "error" ? result.stderr : list === void 0 ? "plugin list 的输出读不懂(不是 JSON 数组)" : void 0;
|
|
7214
|
-
if (failure !== void 0) return units.map((unit) => {
|
|
7215
|
-
const capability = {
|
|
7216
|
-
unit: unit.unit,
|
|
7217
|
-
...unit.plugins?.[0] === void 0 ? {} : { plugin: unit.plugins[0].name },
|
|
7218
|
-
status: "unchecked",
|
|
7219
|
-
required: (unit.plugins ?? []).flatMap((need) => need.commands ?? []),
|
|
7220
|
-
missing: [],
|
|
7221
|
-
detail: failure
|
|
7222
|
-
};
|
|
7223
|
-
unitCache.set(`${command}:${unit.unit}`, capability);
|
|
7224
|
-
return capability;
|
|
7225
|
-
});
|
|
7226
|
-
const [releases, libs] = await Promise.all([probeReleases(units, {
|
|
7227
|
-
shell,
|
|
7228
|
-
force
|
|
7229
|
-
}), probeLibs({
|
|
7230
|
-
needs: units.flatMap((unit) => unit.libs ?? []),
|
|
7231
|
-
shell,
|
|
7232
|
-
force
|
|
7233
|
-
})]);
|
|
7234
|
-
return units.map((unit) => {
|
|
7235
|
-
const capability = evaluateUnit(list, unit, void 0, command, releases, libsOfUnit(unit, libs));
|
|
7236
|
-
unitCache.set(`${command}:${unit.unit}`, capability);
|
|
7237
|
-
return capability;
|
|
7238
|
-
});
|
|
7239
|
-
}
|
|
7240
|
-
function libsOfUnit(unit, libs) {
|
|
7241
|
-
return (unit.libs ?? []).map((need) => libs.get(need.name)).filter((state) => state !== void 0);
|
|
7242
|
-
}
|
|
7243
|
-
var unitCache = createTimedMemo(PROBE_TTL_MS);
|
|
7244
|
-
//#endregion
|
|
7245
|
-
//#region src/shared/api/host/unit-readiness.ts
|
|
7246
|
-
function unitReadiness(manifest, lookup) {
|
|
7247
|
-
const missingHard = manifest.host.inject.filter((name) => lookup(name) === void 0);
|
|
7248
|
-
const missingSoft = (manifest.host.optional ?? []).filter((name) => lookup(name) === void 0);
|
|
7249
|
-
return {
|
|
7250
|
-
canMount: missingHard.length === 0,
|
|
7251
|
-
missingHard,
|
|
7252
|
-
missingSoft
|
|
7253
|
-
};
|
|
7254
|
-
}
|
|
7255
|
-
//#endregion
|
|
7256
|
-
//#region src/shared/api/host/mount-units.ts
|
|
7257
|
-
function withRunsRoute(result, unit) {
|
|
7258
|
-
const runs = result.longTasks;
|
|
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
|
|
7629
|
+
...base,
|
|
7630
|
+
status: "missing",
|
|
7631
|
+
command: libInstallCommand({ need })
|
|
7461
7632
|
});
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7633
|
+
continue;
|
|
7634
|
+
}
|
|
7635
|
+
if (checkVersionRange(need.version, installed) !== "satisfied") {
|
|
7636
|
+
found.set(need.name, {
|
|
7637
|
+
...base,
|
|
7638
|
+
status: "unsatisfied",
|
|
7639
|
+
installed,
|
|
7640
|
+
command: libInstallCommand({ need })
|
|
7467
7641
|
});
|
|
7468
7642
|
continue;
|
|
7469
7643
|
}
|
|
7470
|
-
const
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
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);
|
|
7644
|
+
const registry = process.env[need.registryEnv]?.trim() || need.registryDefault;
|
|
7645
|
+
const latest = await latestVersion({
|
|
7646
|
+
shell,
|
|
7647
|
+
name: need.name,
|
|
7648
|
+
registry,
|
|
7649
|
+
force
|
|
7650
|
+
});
|
|
7651
|
+
const behind = latest !== void 0 && isNewerVersion(latest, installed);
|
|
7652
|
+
found.set(need.name, {
|
|
7653
|
+
...base,
|
|
7654
|
+
status: behind ? "outdated" : "ok",
|
|
7655
|
+
installed,
|
|
7656
|
+
...latest === void 0 ? {} : { latest },
|
|
7657
|
+
...behind ? { command: libInstallCommand({
|
|
7658
|
+
need,
|
|
7659
|
+
version: latest
|
|
7660
|
+
}) } : {}
|
|
7661
|
+
});
|
|
7497
7662
|
}
|
|
7498
|
-
return
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7663
|
+
return found;
|
|
7664
|
+
}
|
|
7665
|
+
//#endregion
|
|
7666
|
+
//#region src/shared/api/host/probe-verdict.ts
|
|
7667
|
+
function evaluateUnit(list, unit, failure, command = "opencli", releases = /* @__PURE__ */ new Map(), libs = []) {
|
|
7668
|
+
const isPlugin = (item) => typeof item === "object" && item !== null;
|
|
7669
|
+
const needs = unit.plugins ?? [];
|
|
7670
|
+
const required = needs.flatMap((need) => need.commands ?? []);
|
|
7671
|
+
const primary = needs[0]?.name;
|
|
7672
|
+
if (failure !== void 0 || list === void 0) return {
|
|
7673
|
+
unit: unit.unit,
|
|
7674
|
+
...primary === void 0 ? {} : { plugin: primary },
|
|
7675
|
+
status: "unchecked",
|
|
7676
|
+
required,
|
|
7677
|
+
missing: [],
|
|
7678
|
+
detail: failure ?? "探不动",
|
|
7679
|
+
...libs.length === 0 ? {} : { libs }
|
|
7680
|
+
};
|
|
7681
|
+
const plugins = list.filter(isPlugin);
|
|
7682
|
+
const installed = new Map(plugins.map((item) => [String(item.name), item]));
|
|
7683
|
+
const states = needs.map((need) => {
|
|
7684
|
+
const hit = installed.get(need.name);
|
|
7685
|
+
if (hit === void 0) {
|
|
7686
|
+
const source = resolvePluginSource(need.installSource, process.env, {
|
|
7687
|
+
command,
|
|
7688
|
+
plugin: need.name
|
|
7689
|
+
});
|
|
7690
|
+
return {
|
|
7691
|
+
name: need.name,
|
|
7692
|
+
status: "missing",
|
|
7693
|
+
missing: [],
|
|
7694
|
+
install: source.ok ? "ready" : source.problem,
|
|
7695
|
+
command: source.ok ? source.line : pluginInstallCommand(need.name)
|
|
7696
|
+
};
|
|
7515
7697
|
}
|
|
7698
|
+
const available = new Set((Array.isArray(hit.commands) ? hit.commands : []).map(String));
|
|
7699
|
+
const missing = (need.commands ?? []).filter((name) => !available.has(name));
|
|
7700
|
+
const release = releases.get(need.name);
|
|
7701
|
+
const behind = release?.latest !== void 0 && isNewerVersion(release.latest, release.installed);
|
|
7702
|
+
const commandsBehind = missing.length > 0;
|
|
7703
|
+
const upgrade = behind && release?.latest !== void 0 ? upgradeLine(need, command, release.latest) : void 0;
|
|
7704
|
+
return {
|
|
7705
|
+
name: need.name,
|
|
7706
|
+
status: commandsBehind || behind ? "outdated" : "ok",
|
|
7707
|
+
missing,
|
|
7708
|
+
...release === void 0 ? {} : { release },
|
|
7709
|
+
...commandsBehind || behind ? { command: upgrade ?? pluginUpdateCommand(need.name) ?? pluginInstallCommand(need.name) } : {}
|
|
7710
|
+
};
|
|
7711
|
+
});
|
|
7712
|
+
const absent = states.filter((state) => state.status === "missing");
|
|
7713
|
+
const outdated = states.filter((state) => state.status === "outdated");
|
|
7714
|
+
const missing = states.flatMap((state) => state.missing);
|
|
7715
|
+
const libMissing = libs.filter((state) => state.status === "missing");
|
|
7716
|
+
const libMismatch = libs.filter((state) => state.status === "unsatisfied");
|
|
7717
|
+
const libBehind = libs.filter((state) => state.status === "outdated");
|
|
7718
|
+
const status = absent.length > 0 || libMissing.length > 0 || libMismatch.length > 0 ? "missing-plugin" : missing.length > 0 ? "missing-commands" : "ok";
|
|
7719
|
+
const blocked = absent.find((state) => state.install !== "ready");
|
|
7720
|
+
const install = absent.length === 0 ? libMissing.length > 0 || libMismatch.length > 0 ? "ready" : void 0 : blocked === void 0 ? "ready" : blocked.install;
|
|
7721
|
+
const fix = status === "missing-plugin" ? install === "ready" ? "install" : void 0 : outdated.length > 0 || libBehind.length > 0 ? "update" : void 0;
|
|
7722
|
+
const first = absent[0] ?? outdated[0] ?? libMissing[0] ?? libMismatch[0] ?? libBehind[0];
|
|
7723
|
+
const gaps = [...missing.length === 0 ? [] : [`缺必需:${missing.join("、")}`]];
|
|
7724
|
+
const newer = states.filter((state) => state.release?.latest !== void 0 && isNewerVersion(state.release.latest, state.release.installed));
|
|
7725
|
+
const versionText = newer.length === 0 ? "" : `(源上有新版:${newer.map((state) => `${state.release?.installed ?? ""} → ${state.release?.latest ?? ""}`).join("、")})`;
|
|
7726
|
+
const absentNames = [...absent.map((state) => state.name), ...libMissing.map((state) => state.name)];
|
|
7727
|
+
const mismatchText = libMismatch.map((state) => {
|
|
7728
|
+
const declared = unit.libs?.find((need) => need.name === state.name)?.version?.trim();
|
|
7729
|
+
return `${state.name}(装的是 ${state.installed ?? "未知"},声明要 ${declared === void 0 || declared === "" ? "任意版本" : declared})`;
|
|
7730
|
+
});
|
|
7731
|
+
return {
|
|
7732
|
+
unit: unit.unit,
|
|
7733
|
+
...primary === void 0 ? {} : { plugin: primary },
|
|
7734
|
+
status,
|
|
7735
|
+
required,
|
|
7736
|
+
missing,
|
|
7737
|
+
detail: absentNames.length > 0 || mismatchText.length > 0 ? [...absentNames.length === 0 ? [] : [`${plugins.length} 个已装插件里没有 ${absentNames.join("、")}`], ...mismatchText].join(";") : `${needs.length} 份安装都在${gapsText(gaps)}${versionText}${libs.length === 0 ? "" : `;交叉依赖:${libs.map((state) => `${state.name}${state.installed === void 0 ? " 未装" : ` ${state.installed}`}`).join("、")}${libBehind.length === 0 ? "" : "(源上有新版)"}`}`,
|
|
7738
|
+
...fix === void 0 && first === void 0 ? {} : { command: fix === "install" ? (absent[0] ?? first)?.command : first?.command },
|
|
7739
|
+
...fix === void 0 ? {} : { fix },
|
|
7740
|
+
...install === void 0 ? {} : { install },
|
|
7741
|
+
plugins: states,
|
|
7742
|
+
...libs.length === 0 ? {} : { libs }
|
|
7516
7743
|
};
|
|
7517
7744
|
}
|
|
7745
|
+
function gapsText(gaps) {
|
|
7746
|
+
return gaps.length === 0 ? "" : `(${gaps.join(";")})`;
|
|
7747
|
+
}
|
|
7748
|
+
function pluginUpdateCommand(plugin) {
|
|
7749
|
+
return plugin === void 0 ? void 0 : `opencli plugin update ${plugin}`;
|
|
7750
|
+
}
|
|
7751
|
+
function pluginInstallCommand(plugin) {
|
|
7752
|
+
return `opencli plugin install <${plugin ?? "插件"}源>`;
|
|
7753
|
+
}
|
|
7754
|
+
function upgradeLine(need, command, latest) {
|
|
7755
|
+
const source = resolvePluginSource(need.installSource, process.env, {
|
|
7756
|
+
command,
|
|
7757
|
+
plugin: need.name,
|
|
7758
|
+
version: latest,
|
|
7759
|
+
mode: "update"
|
|
7760
|
+
});
|
|
7761
|
+
return source.ok ? source.line : void 0;
|
|
7762
|
+
}
|
|
7763
|
+
//#endregion
|
|
7764
|
+
//#region src/shared/api/host/probe-units.ts
|
|
7765
|
+
function parsePluginList(stdout) {
|
|
7766
|
+
const parsed = parseJsonLoose(stdout);
|
|
7767
|
+
if (Array.isArray(parsed)) return parsed;
|
|
7768
|
+
return /no plugins? installed/i.test(stdout) ? [] : void 0;
|
|
7769
|
+
}
|
|
7770
|
+
async function probeReleases(units, options) {
|
|
7771
|
+
const needs = /* @__PURE__ */ new Map();
|
|
7772
|
+
for (const unit of units) for (const need of unit.plugins ?? []) {
|
|
7773
|
+
const source = need.installSource;
|
|
7774
|
+
if (source?.kind === "npm") needs.set(need.name, source);
|
|
7775
|
+
}
|
|
7776
|
+
const found = /* @__PURE__ */ new Map();
|
|
7777
|
+
for (const [plugin, source] of needs) {
|
|
7778
|
+
const registry = npmRegistryOf(source, process.env);
|
|
7779
|
+
const signal = await probeRelease({
|
|
7780
|
+
shell: options.shell,
|
|
7781
|
+
package: source.package,
|
|
7782
|
+
...registry.ok ? { registry: registry.value } : {},
|
|
7783
|
+
force: options.force
|
|
7784
|
+
});
|
|
7785
|
+
if (signal !== void 0) found.set(plugin, signal);
|
|
7786
|
+
}
|
|
7787
|
+
return found;
|
|
7788
|
+
}
|
|
7789
|
+
async function probeUnits(options) {
|
|
7790
|
+
const { shell, command = "opencli", units, force = false } = options;
|
|
7791
|
+
const hits = units.map((unit) => unitCache.get(`${command}:${unit.unit}`));
|
|
7792
|
+
if (!force && hits.every((hit) => hit !== void 0)) return hits;
|
|
7793
|
+
const result = await runShell(shell, `${command} plugin list -f json`, { timeoutMs: OPENCLI_TIMEOUT_MS }).then((value) => ({
|
|
7794
|
+
kind: "ok",
|
|
7795
|
+
stdout: value.exitCode === 0 ? value.stdout : "",
|
|
7796
|
+
stderr: value.stderr
|
|
7797
|
+
}), (error) => ({
|
|
7798
|
+
kind: "error",
|
|
7799
|
+
stdout: "",
|
|
7800
|
+
stderr: String(error)
|
|
7801
|
+
}));
|
|
7802
|
+
const list = result.kind === "ok" ? parsePluginList(result.stdout) : void 0;
|
|
7803
|
+
const failure = result.kind === "error" ? result.stderr : list === void 0 ? "plugin list 的输出读不懂(不是 JSON 数组)" : void 0;
|
|
7804
|
+
if (failure !== void 0) return units.map((unit) => {
|
|
7805
|
+
const capability = {
|
|
7806
|
+
unit: unit.unit,
|
|
7807
|
+
...unit.plugins?.[0] === void 0 ? {} : { plugin: unit.plugins[0].name },
|
|
7808
|
+
status: "unchecked",
|
|
7809
|
+
required: (unit.plugins ?? []).flatMap((need) => need.commands ?? []),
|
|
7810
|
+
missing: [],
|
|
7811
|
+
detail: failure
|
|
7812
|
+
};
|
|
7813
|
+
unitCache.set(`${command}:${unit.unit}`, capability);
|
|
7814
|
+
return capability;
|
|
7815
|
+
});
|
|
7816
|
+
const [releases, libs] = await Promise.all([probeReleases(units, {
|
|
7817
|
+
shell,
|
|
7818
|
+
force
|
|
7819
|
+
}), probeLibs({
|
|
7820
|
+
needs: units.flatMap((unit) => unit.libs ?? []),
|
|
7821
|
+
shell,
|
|
7822
|
+
force
|
|
7823
|
+
})]);
|
|
7824
|
+
return units.map((unit) => {
|
|
7825
|
+
const capability = evaluateUnit(list, unit, void 0, command, releases, libsOfUnit(unit, libs));
|
|
7826
|
+
unitCache.set(`${command}:${unit.unit}`, capability);
|
|
7827
|
+
return capability;
|
|
7828
|
+
});
|
|
7829
|
+
}
|
|
7830
|
+
function libsOfUnit(unit, libs) {
|
|
7831
|
+
return (unit.libs ?? []).map((need) => libs.get(need.name)).filter((state) => state !== void 0);
|
|
7832
|
+
}
|
|
7833
|
+
var unitCache = createTimedMemo(PROBE_TTL_MS);
|
|
7518
7834
|
//#endregion
|
|
7519
7835
|
//#region src/shared/api/host/unit-registry.ts
|
|
7520
7836
|
function installableUnitsOf(registry) {
|
|
@@ -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) {
|
|
@@ -8423,8 +8742,7 @@ function configSyncSnapshot(deps, asked) {
|
|
|
8423
8742
|
const others = profileNames(profilesDir).filter((name) => name !== deps.self);
|
|
8424
8743
|
const base = {
|
|
8425
8744
|
self: deps.self,
|
|
8426
|
-
home: deps.home
|
|
8427
|
-
restart: true
|
|
8745
|
+
home: deps.home
|
|
8428
8746
|
};
|
|
8429
8747
|
if (deps.profileDir === null) return {
|
|
8430
8748
|
...base,
|
|
@@ -8536,8 +8854,13 @@ function configSyncApply(deps, payload) {
|
|
|
8536
8854
|
return {
|
|
8537
8855
|
ok: true,
|
|
8538
8856
|
written: merged.written,
|
|
8539
|
-
skipped: merged.skipped
|
|
8540
|
-
|
|
8857
|
+
skipped: merged.skipped
|
|
8858
|
+
};
|
|
8859
|
+
}
|
|
8860
|
+
function configSyncAction(deps) {
|
|
8861
|
+
return {
|
|
8862
|
+
resource: CONFIG_SYNC_RESOURCE,
|
|
8863
|
+
handle: (payload) => configSyncApply(deps, payload)
|
|
8541
8864
|
};
|
|
8542
8865
|
}
|
|
8543
8866
|
//#endregion
|
|
@@ -8665,7 +8988,7 @@ function apply(ctx) {
|
|
|
8665
8988
|
[DSH_PLUGINS_ROUTE]: dshPluginsHandler(ctx),
|
|
8666
8989
|
[BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
|
|
8667
8990
|
[BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
|
|
8668
|
-
[MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"))
|
|
8991
|
+
[MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), runningVersionOf(import.meta.url))
|
|
8669
8992
|
};
|
|
8670
8993
|
const renderers = /* @__PURE__ */ new Map();
|
|
8671
8994
|
const shell = ctx.get("shell");
|
|
@@ -8750,10 +9073,23 @@ function apply(ctx) {
|
|
|
8750
9073
|
const factsOfMap = /* @__PURE__ */ new Map();
|
|
8751
9074
|
const routesOfUnitMap = /* @__PURE__ */ new Map();
|
|
8752
9075
|
const ownersMap = /* @__PURE__ */ new Map();
|
|
8753
|
-
let undoPost = [];
|
|
8754
9076
|
const factsOfUnit = (unit) => [...routesOfUnitMap.get(unit) ?? [], "capability"];
|
|
8755
9077
|
const callFacts = (unit, call) => factsOfMap.get(unit)?.(call) ?? factsOfUnit(unit);
|
|
8756
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
|
+
});
|
|
8757
9093
|
function unmountUnit(id) {
|
|
8758
9094
|
const item = mounted.get(id);
|
|
8759
9095
|
if (item === void 0) return;
|
|
@@ -8843,22 +9179,7 @@ function apply(ctx) {
|
|
|
8843
9179
|
}
|
|
8844
9180
|
}
|
|
8845
9181
|
function rebindPost() {
|
|
8846
|
-
|
|
8847
|
-
undoPost = [];
|
|
8848
|
-
const wrote = (resource, write) => {
|
|
8849
|
-
const unit = ownersMap.get(resource);
|
|
8850
|
-
if (unit !== void 0) facts.bump(callFacts(unit, {
|
|
8851
|
-
source: resource,
|
|
8852
|
-
args: asArgs(write.args),
|
|
8853
|
-
result: write.result
|
|
8854
|
-
}));
|
|
8855
|
-
};
|
|
8856
|
-
const prefs = [...mounted.values()].flatMap((item) => item.plan.prefs);
|
|
8857
|
-
const actions = [...mounted.values()].flatMap((item) => item.plan.actions);
|
|
8858
|
-
const provision = [...mounted.values()].find((item) => item.plan.provision !== void 0)?.plan.provision;
|
|
8859
|
-
if (prefs.length > 0) undoPost.push(registerPrefs(ctx, prefs, wrote));
|
|
8860
|
-
if (actions.length > 0) undoPost.push(registerActions(ctx, actions, wrote));
|
|
8861
|
-
if (provision !== void 0) undoPost.push(registerProvision(ctx, provision, facts));
|
|
9182
|
+
postChannels.rebind(wrote);
|
|
8862
9183
|
}
|
|
8863
9184
|
function sync() {
|
|
8864
9185
|
const present = new Set(units.all().map((one) => one.unit));
|
|
@@ -8925,7 +9246,7 @@ function apply(ctx) {
|
|
|
8925
9246
|
}
|
|
8926
9247
|
ctx.effect(() => () => {
|
|
8927
9248
|
for (const id of [...mounted.keys()]) unmountUnit(id);
|
|
8928
|
-
|
|
9249
|
+
postChannels.dispose();
|
|
8929
9250
|
}, `${PLUGIN_ID}: 卸载时把单元摘干净`);
|
|
8930
9251
|
ctx.effect(() => units.subscribe(sync), `${PLUGIN_ID}: 注册表变了就增删(装/卸一个模块 = 挂/卸它的面)`);
|
|
8931
9252
|
ctx.effect(() => bindRosterFacts({
|
|
@@ -9013,10 +9334,6 @@ function apply(ctx) {
|
|
|
9013
9334
|
return () => off?.();
|
|
9014
9335
|
}, `${PLUGIN_ID}: 对话引用注入`);
|
|
9015
9336
|
ctx.effect(() => registerFactStream(ctx, facts), `${PLUGIN_ID}: 变更流`);
|
|
9016
|
-
ctx.effect(() => registerActions(ctx, [{
|
|
9017
|
-
resource: CONFIG_SYNC_RESOURCE,
|
|
9018
|
-
handle: (payload) => configSyncApply(configSync, payload)
|
|
9019
|
-
}]), `${PLUGIN_ID}: 配置同步那一跳(把来源 profile 的行写进工作台自己的 patch)`);
|
|
9020
9337
|
ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
|
|
9021
9338
|
ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
|
|
9022
9339
|
}
|