Commits merged: - docs(FN-3717): update openclaw runtime and settings docs - test(FN-3717): fix openclaw engine test typing - test(FN-3717): strengthen openclaw bridge coverage - feat(FN-3717): complete Step 2 — wire engine and bundle delivery - fix(FN-3717): address openclaw bridge review feedback - feat(FN-3717): complete Step 1 — add OpenClaw MCP bridge Files changed: .changeset/fn-3717-openclaw-tool-bridge.md | 5 ++ docs/settings-reference.md | 28 +++++--- packages/cli/src/__tests__/bundle-output.test.ts | 9 +++ packages/cli/tsup.config.ts | 7 ++ .../src/__tests__/openclaw-runtime-e2e.test.ts | 28 ++++++++ .../__tests__/openclaw-runtime-integration.test.ts | 10 +++ plugins/fusion-plugin-openclaw-runtime/README.md | 13 ++++ .../src/__tests__/cli-spawn.test.ts | 82 +++++++++++++++++++--- .../src/__tests__/mcp-config.test.ts | 45 ++++++++++++ .../src/__tests__/runtime-adapter.test.ts | 46 ++++++++++++ .../fusion-plugin-openclaw-runtime/src/index.ts | 5 ++ .../src/mcp-config.ts | 60 ++++++++++++++++ .../src/mcp-schema-server.cjs | 59 ++++++++++++++++ .../src/pi-module.ts | 52 ++++++++++++-- .../src/runtime-adapter.ts | 26 +++++++ .../fusion-plugin-openclaw-runtime/src/types.ts | 4 ++ 16 files changed, 457 insertions(+), 22 deletions(-) Fusion-Task-Id: FN-3717
99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
/**
|
|
* OpenClaw Runtime Adapter — drives the local `openclaw` CLI as a subprocess.
|
|
*
|
|
* Each call to `promptWithFallback` invokes
|
|
* `openclaw --no-color agent --local --json --session-id <uuid> --message <prompt>`
|
|
* (and `--model`, `--thinking`, `--timeout`, `--agent` if configured),
|
|
* parses the JSON document on stdout, and forwards visible/reasoning text
|
|
* via the session callbacks.
|
|
*
|
|
* Session continuity: the UUID minted on session create is reused on every
|
|
* subsequent prompt as `--session-id`, so openclaw resumes the same agent
|
|
* conversation server-side.
|
|
*/
|
|
|
|
import type {
|
|
AgentRuntime,
|
|
AgentRuntimeOptions,
|
|
AgentSessionResult,
|
|
CliConfig,
|
|
GatewaySession,
|
|
} from "./types.js";
|
|
import {
|
|
configureOpenClawMcpServer,
|
|
createCliSession,
|
|
describeCliModel,
|
|
promptCli,
|
|
resolveCliConfig,
|
|
} from "./pi-module.js";
|
|
import { toolsToMcpToolDefs, writeOpenClawMcpBridgeFiles, type ToolLike } from "./mcp-config.js";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
export class OpenClawRuntimeAdapter implements AgentRuntime {
|
|
readonly id = "openclaw";
|
|
readonly name = "OpenClaw Runtime";
|
|
|
|
private readonly config: CliConfig;
|
|
|
|
constructor(settings?: Partial<CliConfig> | Record<string, unknown>) {
|
|
this.config = resolveCliConfig(settings as Record<string, unknown> | undefined);
|
|
}
|
|
|
|
async createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult> {
|
|
const contextTools = [
|
|
...(Array.isArray(options.tools) ? (options.tools as ToolLike[]) : []),
|
|
...(Array.isArray(options.customTools) ? (options.customTools as ToolLike[]) : []),
|
|
];
|
|
const toolDefs = toolsToMcpToolDefs(contextTools);
|
|
|
|
let mcpProfile: string | undefined;
|
|
let mcpConfigPath: string | undefined;
|
|
|
|
if (toolDefs.length > 0) {
|
|
const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID());
|
|
mcpProfile = `fusion-${randomUUID()}`;
|
|
mcpConfigPath = bridge.serverConfigPath;
|
|
await configureOpenClawMcpServer({
|
|
binaryPath: this.config.binaryPath,
|
|
profile: mcpProfile,
|
|
serverName: bridge.serverName,
|
|
serverConfigPath: bridge.serverConfigPath,
|
|
});
|
|
}
|
|
|
|
const session = createCliSession({
|
|
systemPrompt: options.systemPrompt,
|
|
agentId: this.config.agentId,
|
|
mcpProfile,
|
|
mcpConfigPath,
|
|
callbacks: {
|
|
onText: options.onText,
|
|
onThinking: options.onThinking,
|
|
onToolStart: options.onToolStart,
|
|
onToolEnd: options.onToolEnd,
|
|
},
|
|
});
|
|
|
|
return { session, sessionFile: undefined };
|
|
}
|
|
|
|
async promptWithFallback(
|
|
session: GatewaySession,
|
|
prompt: string,
|
|
options?: unknown,
|
|
): Promise<void> {
|
|
const overrideCallbacks = (options ?? undefined) as
|
|
| Parameters<typeof promptCli>[3]
|
|
| undefined;
|
|
await promptCli(session, prompt, this.config, overrideCallbacks);
|
|
}
|
|
|
|
describeModel(session: GatewaySession): string {
|
|
return describeCliModel(session);
|
|
}
|
|
|
|
async dispose(_session: GatewaySession): Promise<void> {
|
|
// No persistent resources — each prompt spawns a fresh subprocess.
|
|
}
|
|
}
|