fix(plugins): re-export probe symbols + declare plugin deps in dashboard

- Hermes / OpenClaw plugin index.ts now re-export `probeHermesBinary` /
  `probeOpenClawBinary` and their status types so the dashboard's
  `runtime-provider-probes.ts` façade can import them via the public
  package entry instead of deep paths.
- Dashboard `package.json` adds `@fusion-plugin-examples/hermes-runtime`,
  `…/openclaw-runtime`, `…/paperclip-runtime` as workspace deps so
  pnpm symlinks them into `packages/dashboard/node_modules/`. Without
  these, the new probe imports failed with "Cannot find module" during
  `pnpm typecheck`.

This clears 6 of the 9 outstanding typecheck errors. The remaining 3 are
in the in-flight Hermes plugin rewrite (runtime-adapter still imports
from a deleted `./pi-module.js`; the new `index.ts` calls a factory
with the wrong arg type) and should be resolved by the same change set
that landed the rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fusion
2026-04-27 20:22:18 -07:00
committed by gsxdsm
parent 1a1bec1fb4
commit d3e1c28cef
82 changed files with 8481 additions and 2833 deletions

View File

@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { EventEmitter } from "node:events";
import { mkdtempSync } from "node:fs";
import { rm } from "node:fs/promises";
import { join } from "node:path";
@@ -9,10 +10,16 @@ import { PluginRunner } from "../plugin-runner.js";
import { resolveRuntime } from "../runtime-resolution.js";
import { createResolvedAgentSession } from "../agent-session-helpers.js";
const { mockCreateFnAgent, mockPromptWithFallback, mockDescribeModel } = vi.hoisted(() => ({
const {
mockCreateFnAgent,
mockPromptWithFallback,
mockDescribeModel,
mockSpawn,
} = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
mockPromptWithFallback: vi.fn(),
mockDescribeModel: vi.fn(),
mockSpawn: vi.fn(),
}));
vi.mock("../logger.js", () => ({
@@ -34,6 +41,10 @@ vi.mock("../pi.js", () => ({
describeModel: mockDescribeModel,
}));
vi.mock("node:child_process", () => ({
spawn: (...args: unknown[]) => mockSpawn(...args),
}));
function createTaskStoreMock(rootDir: string): TaskStore {
return {
getRootDir: () => rootDir,
@@ -52,6 +63,22 @@ async function preloadOpenClawPluginModule(): Promise<void> {
await import(pathToFileURL(openClawPluginModulePath()).href);
}
function createFakeChildProcess(): EventEmitter & {
stdout: EventEmitter;
stderr: EventEmitter;
kill: ReturnType<typeof vi.fn>;
} {
const child = new EventEmitter() as EventEmitter & {
stdout: EventEmitter;
stderr: EventEmitter;
kill: ReturnType<typeof vi.fn>;
};
child.stdout = new EventEmitter();
child.stderr = new EventEmitter();
child.kill = vi.fn();
return child;
}
describe("OpenClaw runtime E2E pipeline", () => {
const originalEnv = { ...process.env };
let testRoot: string;
@@ -73,35 +100,50 @@ describe("OpenClaw runtime E2E pipeline", () => {
mockPromptWithFallback.mockResolvedValue(undefined);
mockDescribeModel.mockReturnValue("pi/default");
const fetchMock = vi.fn().mockImplementation(async (input: string | URL, init?: RequestInit) => {
const url = typeof input === "string" ? input : input.toString();
const method = (init?.method ?? "GET").toUpperCase();
mockSpawn.mockImplementation((command: string, args: string[] = []) => {
const child = createFakeChildProcess();
if (method === "HEAD" && url === "http://127.0.0.1:18789") {
return new Response(null, { status: 200 });
}
queueMicrotask(() => {
if (command === "which" || command === "where") {
child.stdout.emit("data", Buffer.from("/usr/local/bin/openclaw\n"));
child.emit("close", 0);
return;
}
if (method === "POST" && url === "http://127.0.0.1:18789/v1/chat/completions") {
const ssePayload =
'data: {"choices":[{"delta":{"content":"OpenClaw response"}}]}\n\n' +
"data: [DONE]\\n\\n";
return new Response(ssePayload, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
if (args[0] === "--version") {
child.stdout.emit("data", Buffer.from("OpenClaw 2026.4.27\n"));
child.emit("close", 0);
return;
}
return new Response(`Unexpected request: ${method} ${url}`, { status: 500 });
if (args.includes("agent") && args.includes("--json")) {
const payload = JSON.stringify({
payloads: [{ text: "OpenClaw response" }],
meta: {
agentMeta: {
provider: "openclaw",
model: "openclaw-agent",
usage: { input: 1, output: 1, total: 2 },
},
},
});
child.stdout.emit("data", Buffer.from(payload));
child.emit("close", 0);
return;
}
child.stderr.emit("data", Buffer.from(`Unexpected spawn: ${command} ${args.join(" ")}`));
child.emit("close", 1);
});
return child;
});
vi.stubGlobal("fetch", fetchMock);
await preloadOpenClawPluginModule();
});
afterEach(async () => {
process.env = { ...originalEnv };
vi.unstubAllGlobals();
await rm(testRoot, { recursive: true, force: true });
});
@@ -163,7 +205,9 @@ describe("OpenClaw runtime E2E pipeline", () => {
expect(created.session).toBeTruthy();
await expect(resolved.runtime.promptWithFallback(created.session, "Hello from e2e")).resolves.toBeUndefined();
expect(resolved.runtime.describeModel(created.session)).toBe("openclaw/openclaw-agent");
expect(resolved.runtime.describeModel(created.session)).toBe(
"openclaw/openclaw-agent/openclaw/openclaw-agent",
);
expect(mockCreateFnAgent).not.toHaveBeenCalled();
});