feat(FN-2706): merge fusion/fn-2706 (auto-resolved)

- feat(FN-2706): complete Step 6 — document Paperclip REST runtime behavior
- test(FN-2706): cover getAgentIdentity success and request payload assertions
- fix(FN-2706): align promptWithFallback signature with runtime contract
- fix(FN-2706): add session dispose compatibility for engine callers
- fix(FN-2706): refine Paperclip API client error and config handling
- test(FN-2706): complete Step 4 — cover paperclip api client and adapter flow
- feat(FN-2706): complete Step 3 — wire plugin settings and remove engine guard
- feat(FN-2706): complete Step 2 — rewrite paperclip runtime adapter
- fix(FN-2706): restore compatibility exports during runtime migration
- feat(FN-2706): complete Step 1 — add Paperclip REST client
This commit is contained in:
Fusion
2026-04-27 10:28:51 -07:00
committed by gsxdsm
parent 9ab2beb0ae
commit 0aa2bf621d
18 changed files with 1307 additions and 904 deletions

View File

@@ -1,13 +1,21 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
const { mockCreateFnAgent, mockPromptWithFallback, mockDescribeModel } = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
mockPromptWithFallback: vi.fn(),
mockDescribeModel: vi.fn().mockReturnValue("unknown model"),
const { mockResolveGatewayConfig, mockCreateGatewaySession, mockPromptGateway, mockDescribeGatewayModel, mockProbeGateway, } = vi.hoisted(() => ({
mockResolveGatewayConfig: vi.fn().mockReturnValue({
gatewayUrl: "http://127.0.0.1:18789",
gatewayToken: undefined,
agentId: "main",
}),
mockCreateGatewaySession: vi.fn(),
mockPromptGateway: vi.fn(),
mockDescribeGatewayModel: vi.fn().mockReturnValue("openclaw/main"),
mockProbeGateway: vi.fn().mockResolvedValue(true),
}));
vi.mock("../pi-module.js", () => ({
createFnAgent: mockCreateFnAgent,
promptWithFallback: mockPromptWithFallback,
describeModel: mockDescribeModel,
resolveGatewayConfig: mockResolveGatewayConfig,
createGatewaySession: mockCreateGatewaySession,
promptGateway: mockPromptGateway,
describeGatewayModel: mockDescribeGatewayModel,
probeGateway: mockProbeGateway,
}));
import plugin, { openclawRuntimeMetadata, openclawRuntimeFactory, OPENCLAW_RUNTIME_ID } from "../index.js";
import { OpenClawRuntimeAdapter } from "../runtime-adapter.js";
@@ -31,6 +39,7 @@ function createMockContext(overrides = {}) {
describe("openclaw-runtime plugin", () => {
beforeEach(() => {
vi.clearAllMocks();
mockProbeGateway.mockResolvedValue(true);
});
afterEach(() => {
vi.restoreAllMocks();
@@ -59,13 +68,22 @@ describe("openclaw-runtime plugin", () => {
});
});
describe("hooks", () => {
it("onLoad should log startup message and emit loaded event", async () => {
it("onLoad should probe gateway, log startup message, and emit loaded event", async () => {
const ctx = createMockContext();
mockResolveGatewayConfig.mockReturnValue({
gatewayUrl: "http://localhost:18789",
gatewayToken: "secret-token",
agentId: "main",
});
await plugin.hooks.onLoad?.(ctx);
expect(ctx.logger.info).toHaveBeenCalledWith("OpenClaw Runtime Plugin loaded");
expect(mockProbeGateway).toHaveBeenCalledWith("http://localhost:18789");
expect(ctx.logger.info).toHaveBeenCalledWith("OpenClaw Runtime Plugin loaded (gateway: http://localhost:18789, reachable: yes)");
expect(ctx.logger.info.mock.calls.join(" ")).not.toContain("secret-token");
expect(ctx.emitEvent).toHaveBeenCalledWith("openclaw-runtime:loaded", {
runtimeId: OPENCLAW_RUNTIME_ID,
version: "0.1.0",
gatewayUrl: "http://localhost:18789",
gatewayReachable: true,
});
});
it("onUnload should not throw", () => {
@@ -79,7 +97,18 @@ describe("openclaw-runtime plugin", () => {
expect(typeof openclawRuntimeFactory).toBe("function");
});
it("runtime factory should return executable runtime adapter", async () => {
const runtime = (await openclawRuntimeFactory(createMockContext()));
const runtime = (await openclawRuntimeFactory(createMockContext({
settings: {
gatewayUrl: "http://settings-gateway:18789",
gatewayToken: "plugin-token",
agentId: "ops",
},
})));
expect(mockResolveGatewayConfig).toHaveBeenCalledWith({
gatewayUrl: "http://settings-gateway:18789",
gatewayToken: "plugin-token",
agentId: "ops",
});
expect(runtime).toBeInstanceOf(OpenClawRuntimeAdapter);
expect(runtime.id).toBe("openclaw");
expect(runtime.name).toBe("OpenClaw Runtime");

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,6BAA6B,EAC9B,MAAM,oBAAoB,CAAC;AAE5B,QAAA,MAAM,mBAAmB,aAAa,CAAC;AAGvC,QAAA,MAAM,uBAAuB,EAAE,6BAK9B,CAAC;AAEF,QAAA,MAAM,sBAAsB,EAAE,oBAE7B,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YA2BZ,CAAC;AAEH,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,YAAY,EAEZ,oBAAoB,EACpB,6BAA6B,EAC9B,MAAM,oBAAoB,CAAC;AAE5B,QAAA,MAAM,mBAAmB,aAAa,CAAC;AAGvC,QAAA,MAAM,uBAAuB,EAAE,6BAK9B,CAAC;AAEF,QAAA,MAAM,sBAAsB,EAAE,oBAG7B,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAkCZ,CAAC;AAEH,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,CAAC"}

View File

@@ -6,16 +6,18 @@
*/
import { definePlugin } from "@fusion/plugin-sdk";
import { OpenClawRuntimeAdapter } from "./runtime-adapter.js";
import { probeGateway, resolveGatewayConfig } from "./pi-module.js";
const OPENCLAW_RUNTIME_ID = "openclaw";
const OPENCLAW_RUNTIME_VERSION = "0.1.0";
const openclawRuntimeMetadata = {
runtimeId: OPENCLAW_RUNTIME_ID,
name: "OpenClaw Runtime",
description: "OpenClaw-backed AI session using the user's configured pi provider and model",
description: "OpenClaw-backed AI session using the local OpenClaw gateway",
version: OPENCLAW_RUNTIME_VERSION,
};
const openclawRuntimeFactory = async () => {
return new OpenClawRuntimeAdapter();
const openclawRuntimeFactory = async (ctx) => {
const config = resolveGatewayConfig(ctx?.settings);
return new OpenClawRuntimeAdapter(config);
};
const plugin = definePlugin({
manifest: {
@@ -29,11 +31,15 @@ const plugin = definePlugin({
},
state: "installed",
hooks: {
onLoad: (ctx) => {
ctx.logger.info("OpenClaw Runtime Plugin loaded");
onLoad: async (ctx) => {
const config = resolveGatewayConfig(ctx.settings);
const gatewayReachable = await probeGateway(config.gatewayUrl);
ctx.logger.info(`OpenClaw Runtime Plugin loaded (gateway: ${config.gatewayUrl}, reachable: ${gatewayReachable ? "yes" : "no"})`);
ctx.emitEvent("openclaw-runtime:loaded", {
runtimeId: OPENCLAW_RUNTIME_ID,
version: OPENCLAW_RUNTIME_VERSION,
gatewayUrl: config.gatewayUrl,
gatewayReachable,
});
},
onUnload: () => {

View File

@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAO9D,MAAM,mBAAmB,GAAG,UAAU,CAAC;AACvC,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAEzC,MAAM,uBAAuB,GAAkC;IAC7D,SAAS,EAAE,mBAAmB;IAC9B,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,8EAA8E;IAC3F,OAAO,EAAE,wBAAwB;CAClC,CAAC;AAEF,MAAM,sBAAsB,GAAyB,KAAK,IAAI,EAAE;IAC9D,OAAO,IAAI,sBAAsB,EAAE,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,MAAM,GAAiB,YAAY,CAAC;IACxC,QAAQ,EAAE;QACR,EAAE,EAAE,gCAAgC;QACpC,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,gDAAgD;QAC7D,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,kCAAkC;QAC5C,OAAO,EAAE,uBAAuB;KACjC;IACD,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE;QACL,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAClD,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE;gBACvC,SAAS,EAAE,mBAAmB;gBAC9B,OAAO,EAAE,wBAAwB;aAClC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE;YACb,qCAAqC;QACvC,CAAC;KACF;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,uBAAuB;QACjC,OAAO,EAAE,sBAAsB;KAChC;CACF,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAQpE,MAAM,mBAAmB,GAAG,UAAU,CAAC;AACvC,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAEzC,MAAM,uBAAuB,GAAkC;IAC7D,SAAS,EAAE,mBAAmB;IAC9B,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,6DAA6D;IAC1E,OAAO,EAAE,wBAAwB;CAClC,CAAC;AAEF,MAAM,sBAAsB,GAAyB,KAAK,EAAE,GAAmB,EAAE,EAAE;IACjF,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnD,OAAO,IAAI,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,MAAM,GAAiB,YAAY,CAAC;IACxC,QAAQ,EAAE;QACR,EAAE,EAAE,gCAAgC;QACpC,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,gDAAgD;QAC7D,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,kCAAkC;QAC5C,OAAO,EAAE,uBAAuB;KACjC;IACD,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE;QACL,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAClD,MAAM,gBAAgB,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAE/D,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,4CAA4C,MAAM,CAAC,UAAU,gBAAgB,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAChH,CAAC;YACF,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE;gBACvC,SAAS,EAAE,mBAAmB;gBAC9B,OAAO,EAAE,wBAAwB;gBACjC,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,gBAAgB;aACjB,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE;YACb,qCAAqC;QACvC,CAAC;KACF;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,uBAAuB;QACjC,OAAO,EAAE,sBAAsB;KAChC;CACF,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,CAAC"}