






















@@ -1,3 +1,9 @@
1+/**
2+ * Onboarding plugin installation flow.
3+ *
4+ * It selects local, ClawHub, npm, or override install sources; records durable
5+ * install metadata; and enables plugins requested by setup workflows.
6+ */
17import fs from "node:fs";
28import path from "node:path";
39import { uniqueStrings } from "@openclaw/normalization-core/string-normalization";
@@ -50,6 +56,7 @@ type InstallPluginFromClawHubResult = Awaited<
5056const ONBOARDING_PLUGIN_INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
5157const ONBOARDING_PLUGIN_INSTALL_WATCHDOG_TIMEOUT_MS = ONBOARDING_PLUGIN_INSTALL_TIMEOUT_MS + 5_000;
525859+/** Catalog entry used by onboarding to offer or require a plugin install. */
5360export type OnboardingPluginInstallEntry = {
5461pluginId: string;
5562label: string;
@@ -58,8 +65,10 @@ export type OnboardingPluginInstallEntry = {
5865preferRemoteInstall?: boolean;
5966};
606768+/** Outcome status for a single onboarding plugin install attempt. */
6169export type OnboardingPluginInstallStatus = "installed" | "skipped" | "failed" | "timed_out";
627071+/** Config and status returned after attempting an onboarding plugin install. */
6372export type OnboardingPluginInstallResult = {
6473cfg: OpenClawConfig;
6574installed: boolean;
@@ -74,6 +83,8 @@ function shouldFallbackClawHubToNpm(params: {
7483if (!isOpenClawOrgNpmSpec(params.npmSpec)) {
7584return false;
7685}
86+// Only official OpenClaw npm packages are safe fallback targets for ClawHub
87+// availability failures; arbitrary npm fallbacks would change trust source.
7788return (
7889params.result.code === CLAWHUB_INSTALL_ERROR_CODE.PACKAGE_NOT_FOUND ||
7990params.result.code === CLAWHUB_INSTALL_ERROR_CODE.VERSION_NOT_FOUND ||
@@ -238,6 +249,8 @@ function resolveLocalPath(params: {
238249for (const candidate of candidates) {
239250try {
240251const resolved = fs.realpathSync(candidate);
252+// Local plugin paths must stay inside the current repo/workspace roots so
253+// catalog metadata cannot point setup at arbitrary filesystem locations.
241254if (
242255!bases.some((base) => {
243256const realBase = resolveRealDirectory(base);
@@ -334,6 +347,8 @@ function resolveInstallDefaultChoice(params: {
334347return "local";
335348}
336349const updateChannel = cfg.update?.channel;
350+// Dev builds prefer checked-out local plugins; stable/beta prefer published
351+// artifacts so installed records match the user's release channel.
337352if (updateChannel === "dev") {
338353return "local";
339354}
@@ -411,6 +426,8 @@ async function promptInstallChoice(params: {
411426realSources.push("local");
412427}
413428if (realSources.length === 1) {
429+// Callers that already selected a plugin/channel can skip an extra prompt
430+// when there is only one viable source.
414431return realSources[0];
415432}
416433}
@@ -785,6 +802,8 @@ async function installPluginFromOverride(params: {
785802runtime.log?.(
786803`Using plugin install override for ${sanitizeTerminalText(entry.pluginId)} from ${PLUGIN_INSTALL_OVERRIDES_ENV} (${ALLOW_PLUGIN_INSTALL_OVERRIDES_ENV}=1).`,
787804);
805+// Overrides are explicit operator/developer input and intentionally bypass
806+// catalog trust defaults while still recording the resulting install source.
788807const installOutcome =
789808params.override.kind === "npm"
790809 ? await installPluginFromNpmSpecWithProgress({
@@ -965,6 +984,7 @@ async function installPluginFromClawHubSpecWithProgress(params: {
965984}
966985}
967986987+/** Ensures an onboarding plugin is installed, enabled, and recorded in config. */
968988export async function ensureOnboardingPluginInstalled(params: {
969989cfg: OpenClawConfig;
970990entry: OnboardingPluginInstallEntry;
@@ -978,6 +998,8 @@ export async function ensureOnboardingPluginInstalled(params: {
978998let next = params.cfg;
979999const installOverride = resolvePluginInstallOverride({ pluginId: entry.pluginId });
9801000if (installOverride) {
1001+// Any install override mutates config/install records, so guard it with the
1002+// same write-mode check as normal installs.
9811003assertConfigWriteAllowedInCurrentMode();
9821004return await installPluginFromOverride({
9831005cfg: next,
@@ -1053,6 +1075,8 @@ export async function ensureOnboardingPluginInstalled(params: {
10531075assertConfigWriteAllowedInCurrentMode();
1054107610551077if (choice === "local" && localPath) {
1078+// Bundled plugin sources are already part of the host checkout; enabling is
1079+// enough and no install record/load path should be added for that case.
10561080const enableResult = await applyPluginEnablement({
10571081cfg: next,
10581082pluginId: entry.pluginId,
@@ -1164,6 +1188,8 @@ export async function ensureOnboardingPluginInstalled(params: {
11641188};
11651189}
116611901191+// ClawHub package/version misses for official packages can recover through
1192+// npm, but keep the operator in control before changing install source.
11671193shouldTryNpm = await prompter.confirm({
11681194message: t("wizard.plugins.useNpmPackageInstead", {
11691195spec: sanitizeTerminalText(npmInstallSpec),
@@ -1274,6 +1300,8 @@ export async function ensureOnboardingPluginInstalled(params: {
12741300);
1275130112761302if (localPath) {
1303+// If npm fails and a trusted local checkout exists, offer it as a recovery
1304+// path instead of leaving setup stuck on the remote artifact.
12771305const fallback = await prompter.confirm({
12781306message: t("wizard.plugins.useLocalPluginPathInstead", {
12791307path: sanitizeTerminalText(localPath),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。