


























@@ -11,6 +11,12 @@ import {
1111triggerInternalHook,
1212} from "../hooks/internal-hooks.js";
1313import { emitDiagnosticEvent } from "../infra/diagnostic-events.js";
14+import {
15+clearDetachedTaskLifecycleRuntimeRegistration,
16+getDetachedTaskLifecycleRuntimeRegistration,
17+registerDetachedTaskLifecycleRuntime,
18+type DetachedTaskLifecycleRuntime,
19+} from "../tasks/detached-task-runtime-state.js";
1420import { withEnv } from "../test-utils/env.js";
1521import { clearPluginCommands, getPluginCommandSpecs } from "./command-registry-state.js";
1622import { getGlobalHookRunner, resetGlobalHookRunner } from "./hook-runner-global.js";
@@ -65,6 +71,26 @@ import {
6571import type { PluginSdkResolutionPreference } from "./sdk-alias.js";
6672let cachedBundledTelegramDir = "";
6773let cachedBundledMemoryDir = "";
74+75+function createDetachedTaskRuntimeStub(id: string): DetachedTaskLifecycleRuntime {
76+const fail = (name: string): never => {
77+throw new Error(`detached runtime ${id} should not execute ${name} in this test`);
78+};
79+return {
80+createQueuedTaskRun: () => fail("createQueuedTaskRun"),
81+createRunningTaskRun: () => fail("createRunningTaskRun"),
82+startTaskRunByRunId: () => fail("startTaskRunByRunId"),
83+recordTaskRunProgressByRunId: () => fail("recordTaskRunProgressByRunId"),
84+completeTaskRunByRunId: () => fail("completeTaskRunByRunId"),
85+failTaskRunByRunId: () => fail("failTaskRunByRunId"),
86+setDetachedTaskDeliveryStatusByRunId: () => fail("setDetachedTaskDeliveryStatusByRunId"),
87+cancelDetachedTaskRunById: async () => ({
88+found: true,
89+cancelled: true,
90+}),
91+};
92+}
93+6894const BUNDLED_TELEGRAM_PLUGIN_BODY = `module.exports = {
6995 id: "telegram",
7096 register(api) {
@@ -2025,6 +2051,155 @@ module.exports = { id: "throws-after-import", register() {} };`,
20252051expect(listMemoryEmbeddingProviders()).toEqual([]);
20262052});
202720532054+it("does not replace the active detached task runtime during non-activating loads", () => {
2055+useNoBundledPlugins();
2056+const activeRuntime = createDetachedTaskRuntimeStub("active");
2057+registerDetachedTaskLifecycleRuntime("active-runtime", activeRuntime);
2058+2059+const plugin = writePlugin({
2060+id: "snapshot-detached-runtime",
2061+filename: "snapshot-detached-runtime.cjs",
2062+body: `module.exports = {
2063+ id: "snapshot-detached-runtime",
2064+ register(api) {
2065+ api.registerDetachedTaskRuntime({
2066+ createQueuedTaskRun() { throw new Error("snapshot createQueuedTaskRun should not run"); },
2067+ createRunningTaskRun() { throw new Error("snapshot createRunningTaskRun should not run"); },
2068+ startTaskRunByRunId() { throw new Error("snapshot startTaskRunByRunId should not run"); },
2069+ recordTaskRunProgressByRunId() { throw new Error("snapshot recordTaskRunProgressByRunId should not run"); },
2070+ completeTaskRunByRunId() { throw new Error("snapshot completeTaskRunByRunId should not run"); },
2071+ failTaskRunByRunId() { throw new Error("snapshot failTaskRunByRunId should not run"); },
2072+ setDetachedTaskDeliveryStatusByRunId() { throw new Error("snapshot setDetachedTaskDeliveryStatusByRunId should not run"); },
2073+ async cancelDetachedTaskRunById() { return { found: true, cancelled: true }; },
2074+ });
2075+ },
2076+ };`,
2077+});
2078+2079+const scoped = loadOpenClawPlugins({
2080+cache: false,
2081+activate: false,
2082+workspaceDir: plugin.dir,
2083+config: {
2084+plugins: {
2085+load: { paths: [plugin.file] },
2086+allow: ["snapshot-detached-runtime"],
2087+},
2088+},
2089+onlyPluginIds: ["snapshot-detached-runtime"],
2090+});
2091+2092+expect(scoped.plugins.find((entry) => entry.id === "snapshot-detached-runtime")?.status).toBe(
2093+"loaded",
2094+);
2095+expect(getDetachedTaskLifecycleRuntimeRegistration()).toMatchObject({
2096+pluginId: "active-runtime",
2097+runtime: activeRuntime,
2098+});
2099+});
2100+2101+it("clears newly-registered detached task runtimes when plugin register fails", () => {
2102+useNoBundledPlugins();
2103+const plugin = writePlugin({
2104+id: "failing-detached-runtime",
2105+filename: "failing-detached-runtime.cjs",
2106+body: `module.exports = {
2107+ id: "failing-detached-runtime",
2108+ register(api) {
2109+ api.registerDetachedTaskRuntime({
2110+ createQueuedTaskRun() { throw new Error("failing createQueuedTaskRun should not run"); },
2111+ createRunningTaskRun() { throw new Error("failing createRunningTaskRun should not run"); },
2112+ startTaskRunByRunId() { throw new Error("failing startTaskRunByRunId should not run"); },
2113+ recordTaskRunProgressByRunId() { throw new Error("failing recordTaskRunProgressByRunId should not run"); },
2114+ completeTaskRunByRunId() { throw new Error("failing completeTaskRunByRunId should not run"); },
2115+ failTaskRunByRunId() { throw new Error("failing failTaskRunByRunId should not run"); },
2116+ setDetachedTaskDeliveryStatusByRunId() { throw new Error("failing setDetachedTaskDeliveryStatusByRunId should not run"); },
2117+ async cancelDetachedTaskRunById() { return { found: true, cancelled: true }; },
2118+ });
2119+ throw new Error("detached runtime register failed");
2120+ },
2121+ };`,
2122+});
2123+2124+const registry = loadOpenClawPlugins({
2125+cache: false,
2126+workspaceDir: plugin.dir,
2127+config: {
2128+plugins: {
2129+load: { paths: [plugin.file] },
2130+allow: ["failing-detached-runtime"],
2131+},
2132+},
2133+onlyPluginIds: ["failing-detached-runtime"],
2134+});
2135+2136+expect(registry.plugins.find((entry) => entry.id === "failing-detached-runtime")?.status).toBe(
2137+"error",
2138+);
2139+expect(getDetachedTaskLifecycleRuntimeRegistration()).toBeUndefined();
2140+});
2141+2142+it("restores cached detached task runtime registrations on cache hits", () => {
2143+useNoBundledPlugins();
2144+const plugin = writePlugin({
2145+id: "cached-detached-runtime",
2146+filename: "cached-detached-runtime.cjs",
2147+body: `module.exports = {
2148+ id: "cached-detached-runtime",
2149+ register(api) {
2150+ api.registerDetachedTaskRuntime({
2151+ createQueuedTaskRun() { throw new Error("cached createQueuedTaskRun should not run"); },
2152+ createRunningTaskRun() { throw new Error("cached createRunningTaskRun should not run"); },
2153+ startTaskRunByRunId() { throw new Error("cached startTaskRunByRunId should not run"); },
2154+ recordTaskRunProgressByRunId() { throw new Error("cached recordTaskRunProgressByRunId should not run"); },
2155+ completeTaskRunByRunId() { throw new Error("cached completeTaskRunByRunId should not run"); },
2156+ failTaskRunByRunId() { throw new Error("cached failTaskRunByRunId should not run"); },
2157+ setDetachedTaskDeliveryStatusByRunId() { throw new Error("cached setDetachedTaskDeliveryStatusByRunId should not run"); },
2158+ async cancelDetachedTaskRunById() { return { found: true, cancelled: true }; },
2159+ });
2160+ },
2161+ };`,
2162+});
2163+2164+const loadOptions = {
2165+workspaceDir: plugin.dir,
2166+config: {
2167+plugins: {
2168+load: { paths: [plugin.file] },
2169+allow: ["cached-detached-runtime"],
2170+},
2171+},
2172+onlyPluginIds: ["cached-detached-runtime"],
2173+} satisfies Parameters<typeof loadOpenClawPlugins>[0];
2174+2175+loadOpenClawPlugins(loadOptions);
2176+expect(getDetachedTaskLifecycleRuntimeRegistration()?.pluginId).toBe("cached-detached-runtime");
2177+2178+clearDetachedTaskLifecycleRuntimeRegistration();
2179+expect(getDetachedTaskLifecycleRuntimeRegistration()).toBeUndefined();
2180+2181+loadOpenClawPlugins(loadOptions);
2182+2183+expect(getDetachedTaskLifecycleRuntimeRegistration()?.pluginId).toBe("cached-detached-runtime");
2184+});
2185+2186+it("clears stale detached task runtime registrations on active reloads when no plugin re-registers one", () => {
2187+useNoBundledPlugins();
2188+registerDetachedTaskLifecycleRuntime("stale-runtime", createDetachedTaskRuntimeStub("stale"));
2189+2190+loadOpenClawPlugins({
2191+cache: false,
2192+config: {
2193+plugins: {
2194+load: { paths: [] },
2195+allow: [],
2196+},
2197+},
2198+});
2199+2200+expect(getDetachedTaskLifecycleRuntimeRegistration()).toBeUndefined();
2201+});
2202+20282203it("restores cached memory capability public artifacts on cache hits", async () => {
20292204useNoBundledPlugins();
20302205const workspaceDir = makeTempDir();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。