Resolved 7 conflicts and reconciled local patches with upstream's evolved API: - packages/core/src/plugin-loader.ts: dropped local createContextFor()/buildContext() in favor of upstream's createRouteContext() which provides the same TaskStore- override capability our patch added. - packages/dashboard/src/routes.ts: removed local manual plugin-route mounter (upstream's createPluginRouter handles this with richer response support); kept registerPluginExemptPath loop so external webhook plugins (telemetry- watcher, Grafana/Sentry) still bypass daemon-token auth. Removed obsolete resolveHeartbeatMonitorFor (replaced by upstream's resolveHeartbeatMonitor). - packages/dashboard/src/routes/register-agent-runtime-routes.ts: ported 4 call sites to upstream's isHeartbeatMonitorForProject + resolveHeartbeatMonitor dual-fallback pattern (multi-project monitor resolution). - packages/cli/package.json: combined upstream's pi-ai 0.73.0 + dockerode bumps with our cross-spawn dep for vendored pi-claude-cli. - pnpm-workspace.yaml: kept telemetry-watcher entry alongside upstream's new plugin entries (droid-runtime, cursor-runtime, agent-browser, whatsapp-chat, roadmap, even-realities-glasses, even-cards, reports). - packages/dashboard/app/components/SetupWizardModal.css: kept z-index:110 fix (wizard stacks above ModelOnboardingModal) and added upstream's overflow-y/overscroll-behavior on .setup-wizard-overlay. - pnpm-lock.yaml: took upstream verbatim; pnpm install regenerated to include cross-spawn. Typechecks: @fusion/core and @fusion/dashboard pass cleanly.
112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
const { mockResolveCliConfig, mockProbeBinary } = vi.hoisted(() => ({
|
|
mockResolveCliConfig: vi.fn().mockReturnValue({
|
|
binaryPath: "openclaw",
|
|
agentId: "main",
|
|
model: undefined,
|
|
thinking: "off",
|
|
cliTimeoutSec: 0,
|
|
cliTimeoutMs: 300_000,
|
|
useGateway: false,
|
|
}),
|
|
mockProbeBinary: vi.fn().mockResolvedValue({
|
|
available: true,
|
|
binaryPath: "/opt/homebrew/bin/openclaw",
|
|
version: "OpenClaw 2026.4.26",
|
|
probeDurationMs: 12,
|
|
}),
|
|
}));
|
|
vi.mock("../pi-module.js", async () => {
|
|
const actual = await vi.importActual("../pi-module.js");
|
|
return {
|
|
...actual,
|
|
resolveCliConfig: mockResolveCliConfig,
|
|
};
|
|
});
|
|
vi.mock("../probe.js", async () => {
|
|
const actual = await vi.importActual("../probe.js");
|
|
return {
|
|
...actual,
|
|
probeOpenClawBinary: mockProbeBinary,
|
|
};
|
|
});
|
|
import plugin, { openclawRuntimeMetadata, openclawRuntimeFactory, OPENCLAW_RUNTIME_ID, } from "../index.js";
|
|
import { OpenClawRuntimeAdapter } from "../runtime-adapter.js";
|
|
function createMockContext(settings = {}) {
|
|
const logger = {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
};
|
|
return {
|
|
pluginId: "fusion-plugin-openclaw-runtime",
|
|
settings,
|
|
logger,
|
|
emitEvent: vi.fn(),
|
|
taskStore: { getTask: vi.fn() },
|
|
};
|
|
}
|
|
describe("openclaw-runtime plugin", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockResolveCliConfig.mockReturnValue({
|
|
binaryPath: "openclaw",
|
|
agentId: "main",
|
|
model: undefined,
|
|
thinking: "off",
|
|
cliTimeoutSec: 0,
|
|
cliTimeoutMs: 300_000,
|
|
useGateway: false,
|
|
});
|
|
mockProbeBinary.mockResolvedValue({
|
|
available: true,
|
|
binaryPath: "/opt/homebrew/bin/openclaw",
|
|
version: "OpenClaw 2026.4.26",
|
|
probeDurationMs: 12,
|
|
});
|
|
});
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
it("manifest identity is stable", () => {
|
|
expect(plugin.manifest.id).toBe("fusion-plugin-openclaw-runtime");
|
|
expect(plugin.manifest.name).toBe("OpenClaw Runtime Plugin");
|
|
expect(plugin.state).toBe("installed");
|
|
expect(plugin.runtime?.metadata.runtimeId).toBe(OPENCLAW_RUNTIME_ID);
|
|
expect(plugin.manifest.runtime).toEqual(openclawRuntimeMetadata);
|
|
});
|
|
it("onLoad probes binary and logs binary path + version", async () => {
|
|
const ctx = createMockContext({});
|
|
await plugin.hooks.onLoad(ctx);
|
|
expect(mockProbeBinary).toHaveBeenCalledWith({ binaryPath: "openclaw" });
|
|
expect(ctx.logger.info).toHaveBeenCalledWith(expect.stringContaining("openclaw"));
|
|
expect(ctx.emitEvent).toHaveBeenCalledWith("openclaw-runtime:loaded", expect.objectContaining({
|
|
runtimeId: OPENCLAW_RUNTIME_ID,
|
|
binaryAvailable: true,
|
|
}));
|
|
});
|
|
it("onLoad logs warning when binary missing", async () => {
|
|
mockProbeBinary.mockResolvedValueOnce({
|
|
available: false,
|
|
probeDurationMs: 5,
|
|
reason: "`openclaw` not found on PATH",
|
|
});
|
|
const ctx = createMockContext({});
|
|
await plugin.hooks.onLoad(ctx);
|
|
expect(ctx.logger.info).toHaveBeenCalledWith(expect.stringContaining("not detected"));
|
|
});
|
|
it("factory returns an OpenClawRuntimeAdapter instance", async () => {
|
|
const runtime = (await openclawRuntimeFactory(createMockContext({ binaryPath: "/usr/bin/openclaw", agentId: "ops" })));
|
|
expect(runtime).toBeInstanceOf(OpenClawRuntimeAdapter);
|
|
expect(runtime.id).toBe("openclaw");
|
|
});
|
|
it("factory creation does not throw with empty settings", async () => {
|
|
await expect(openclawRuntimeFactory(createMockContext())).resolves.toBeInstanceOf(OpenClawRuntimeAdapter);
|
|
});
|
|
it("onUnload does not throw", () => {
|
|
const ctx = createMockContext();
|
|
expect(() => plugin.hooks.onUnload?.(ctx)).not.toThrow();
|
|
});
|
|
});
|
|
//# sourceMappingURL=index.test.js.map
|