feat(FN-3998): add task lineage commit associations API, UI, and tests

Adds task lineage commit associations: a new API route stores and exposes which commits belong to which task, the `TaskChangesTab` surfaces these lineage links visually, and documentation covers the reconciliation model. Includes comprehensive tests for both the route and component.

Fusion-Task-Id: FN-3998
This commit is contained in:
Fusion
2026-05-11 04:58:02 -07:00
committed by gsxdsm
parent f870f07cab
commit fe8c186527
19 changed files with 1217 additions and 12 deletions

View File

@@ -0,0 +1,95 @@
/**
* OpenClaw Runtime Plugin
*
* Drives the local `openclaw` CLI as a subprocess (via
* `openclaw --no-color agent --local --json`). No daemon required.
*/
import { definePlugin } from "@fusion/plugin-sdk";
import { OpenClawRuntimeAdapter } from "./runtime-adapter.js";
import { resolveCliConfig } from "./pi-module.js";
import { probeOpenClawBinary } from "./probe.js";
import type {
FusionPlugin,
PluginContext,
PluginRuntimeFactory,
PluginRuntimeManifestMetadata,
} from "@fusion/plugin-sdk";
const OPENCLAW_RUNTIME_ID = "openclaw";
const OPENCLAW_RUNTIME_VERSION = "0.2.0";
const openclawRuntimeMetadata: PluginRuntimeManifestMetadata = {
runtimeId: OPENCLAW_RUNTIME_ID,
name: "OpenClaw Runtime",
description: "Drives the local `openclaw` CLI (openclaw/openclaw)",
version: OPENCLAW_RUNTIME_VERSION,
};
const openclawRuntimeFactory: PluginRuntimeFactory = async (ctx?: PluginContext) => {
return new OpenClawRuntimeAdapter(ctx?.settings as Record<string, unknown> | undefined);
};
const plugin: FusionPlugin = definePlugin({
manifest: {
id: "fusion-plugin-openclaw-runtime",
name: "OpenClaw Runtime Plugin",
version: OPENCLAW_RUNTIME_VERSION,
description:
"Drives the local `openclaw` CLI for Fusion agents — embedded `--local` mode by default; gateway optional.",
author: "Fusion Team",
homepage: "https://docs.openclaw.ai/",
runtime: openclawRuntimeMetadata,
},
state: "installed",
hooks: {
onLoad: async (ctx) => {
const config = resolveCliConfig(ctx.settings);
const probe = await probeOpenClawBinary({ binaryPath: config.binaryPath });
ctx.logger.info(
probe.available
? `OpenClaw Runtime Plugin loaded — binary=${config.binaryPath}${probe.version ? ` (${probe.version})` : ""}`
: `OpenClaw Runtime Plugin loaded but binary not detected: ${probe.reason ?? "unknown"}`,
);
ctx.emitEvent("openclaw-runtime:loaded", {
runtimeId: OPENCLAW_RUNTIME_ID,
version: OPENCLAW_RUNTIME_VERSION,
binaryAvailable: probe.available,
binaryPath: probe.binaryPath ?? config.binaryPath,
});
},
onUnload: () => {
// No persistent state to clean up — each prompt spawns a fresh subprocess.
},
},
runtime: {
metadata: openclawRuntimeMetadata,
factory: openclawRuntimeFactory,
},
});
export default plugin;
// ── Public exports ────────────────────────────────────────────────────────────
export { openclawRuntimeMetadata, openclawRuntimeFactory, OPENCLAW_RUNTIME_ID };
export { OpenClawRuntimeAdapter } from "./runtime-adapter.js";
export {
resolveCliConfig,
buildOpenClawArgs,
createCliSession,
promptCli,
describeCliModel,
extractStderrError,
configureOpenClawMcpServer,
} from "./pi-module.js";
export type { CliConfig, GatewaySession, OpenClawAgentJson } from "./types.js";
export {
toolsToMcpToolDefs,
writeOpenClawMcpBridgeFiles,
} from "./mcp-config.js";
// Probe re-export for the dashboard's runtime-provider-probes façade.
export { probeOpenClawBinary } from "./probe.js";
export type { OpenClawBinaryStatus } from "./probe.js";