























@@ -5080,6 +5080,131 @@ module.exports = {
50805080expect(fs.existsSync(runtimeMarker)).toBe(false);
50815081});
508250825083+it("invokes setChannelRuntime from a non-bundled setup entry for a configured deferred channel during startup Phase 1", () => {
5084+// Regression test for #77779. When a configured external channel plugin opts into
5085+// deferred full loading (startupDeferConfiguredChannelFullLoadUntilAfterListen) the
5086+// loader runs in setup-runtime mode during Phase 1 (before gateway listen). In that
5087+// phase api.registerChannel is active and writes the plugin into registry.channels,
5088+// so the channel provider starts immediately — before Phase 2 runs register(). Any
5089+// runtime initializer (e.g. setWeixinRuntime) that the provider polls for must
5090+// therefore be invoked via setChannelRuntime in the setup entry. Before this fix,
5091+// resolveSetupChannelRegistration silently dropped setChannelRuntime from non-bundled
5092+// {plugin, setChannelRuntime} exports, leaving the runtime unset and causing
5093+// waitForWeixinRuntime() to time out.
5094+useNoBundledPlugins();
5095+const pluginDir = makeTempDir();
5096+const runtimeMarker = path.join(makeTempDir(), "deferred-configured-setup-runtime-applied.txt");
5097+5098+fs.writeFileSync(
5099+path.join(pluginDir, "package.json"),
5100+JSON.stringify(
5101+{
5102+name: "@openclaw/non-bundled-deferred-setup-runtime-test",
5103+openclaw: {
5104+extensions: ["./index.cjs"],
5105+setupEntry: "./setup-entry.cjs",
5106+startup: {
5107+deferConfiguredChannelFullLoadUntilAfterListen: true,
5108+},
5109+},
5110+},
5111+null,
5112+2,
5113+),
5114+"utf-8",
5115+);
5116+fs.writeFileSync(
5117+path.join(pluginDir, "openclaw.plugin.json"),
5118+JSON.stringify(
5119+{
5120+id: "non-bundled-deferred-setup-runtime-test",
5121+configSchema: { type: "object", properties: {} },
5122+channels: ["non-bundled-deferred-setup-runtime-test"],
5123+},
5124+null,
5125+2,
5126+),
5127+"utf-8",
5128+);
5129+fs.writeFileSync(
5130+path.join(pluginDir, "index.cjs"),
5131+`module.exports = {
5132+ id: "non-bundled-deferred-setup-runtime-test",
5133+ register(api) {
5134+ api.registerChannel({
5135+ plugin: {
5136+ id: "non-bundled-deferred-setup-runtime-test",
5137+ meta: {
5138+ id: "non-bundled-deferred-setup-runtime-test",
5139+ label: "Non-Bundled Deferred Setup Runtime Test",
5140+ selectionLabel: "Non-Bundled Deferred Setup Runtime Test",
5141+ docsPath: "/channels/non-bundled-deferred-setup-runtime-test",
5142+ blurb: "full channel entry",
5143+ },
5144+ capabilities: { chatTypes: ["direct"] },
5145+ config: { listAccountIds: () => ["default"], resolveAccount: () => ({ accountId: "default", token: "configured" }) },
5146+ outbound: { deliveryMode: "direct" },
5147+ },
5148+ });
5149+ },
5150+};`,
5151+"utf-8",
5152+);
5153+fs.writeFileSync(
5154+path.join(pluginDir, "setup-entry.cjs"),
5155+`module.exports = {
5156+ plugin: {
5157+ id: "non-bundled-deferred-setup-runtime-test",
5158+ meta: {
5159+ id: "non-bundled-deferred-setup-runtime-test",
5160+ label: "Non-Bundled Deferred Setup Runtime Test",
5161+ selectionLabel: "Non-Bundled Deferred Setup Runtime Test",
5162+ docsPath: "/channels/non-bundled-deferred-setup-runtime-test",
5163+ blurb: "setup entry",
5164+ },
5165+ capabilities: { chatTypes: ["direct"] },
5166+ config: { listAccountIds: () => ["default"], resolveAccount: () => ({ accountId: "default", token: "configured" }) },
5167+ outbound: { deliveryMode: "direct" },
5168+ },
5169+ setChannelRuntime: () => {
5170+ require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "applied", "utf-8");
5171+ },
5172+};`,
5173+"utf-8",
5174+);
5175+5176+// Phase 1: preferSetupRuntimeForChannelPlugins=true simulates gateway startup when
5177+// at least one deferred configured channel plugin is present. The configured channel
5178+// opts into deferral, so setup-runtime mode is used. setup-entry.cjs is loaded and
5179+// setChannelRuntime must be invoked. The channel is also written into registry.channels
5180+// (runtimeChannel=true in setup-runtime), so the provider starts in Phase 1.
5181+const registry = loadOpenClawPlugins({
5182+cache: false,
5183+preferSetupRuntimeForChannelPlugins: true,
5184+config: {
5185+channels: {
5186+"non-bundled-deferred-setup-runtime-test": {
5187+enabled: true,
5188+token: "configured",
5189+},
5190+},
5191+plugins: {
5192+load: { paths: [pluginDir] },
5193+allow: ["non-bundled-deferred-setup-runtime-test"],
5194+},
5195+},
5196+});
5197+5198+// setChannelRuntime must have been called so that any runtime initializer the
5199+// provider polls for (e.g. waitForWeixinRuntime) is satisfied before the provider
5200+// times out.
5201+expect(fs.existsSync(runtimeMarker)).toBe(true);
5202+// The channel is registered in registry.channels during Phase 1 (not deferred to
5203+// Phase 2), confirming the provider would start and need setChannelRuntime.
5204+expect(registry.channels).toHaveLength(1);
5205+expect(registry.channels[0]?.plugin.id).toBe("non-bundled-deferred-setup-runtime-test");
5206+});
5207+50835208it("isolates loadSetupPlugin errors as per-plugin diagnostics instead of crashing registry load", () => {
50845209useNoBundledPlugins();
50855210const pluginDir = makeTempDir();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。