## Summary - Add `fusion-plugin-omp-runtime` so Fusion agents can run through operator-installed **Oh My Pi (`omp`)** over the [Agent Client Protocol](https://omp.sh/docs/acp) (`omp acp`). - Wire staged/bundled install, Settings → Authentication card (enable + binary path), model discovery (`omp models` → `omp-cli/*`), and MCP eligibility for runtime id `omp`. - Forward Fusion `systemPrompt` via ACP `session/new` `_meta.systemPromptOverride`. ## How operators use it 1. Install/auth `omp` (credentials under `~/.omp`). 2. Enable **Oh My Pi — via omp ACP** in Settings → Authentication (optional binary path). 3. Set agent **Runtime Source → OMP Runtime** (`runtimeHint: "omp"`), or pick an `omp-cli/*` model when enabled. ## Known v1 gaps - No Grok-style Fusion `fn_*` loopback tool bridge yet (operator MCP is forwarded; in-process custom tools are not). - Model is fixed at spawn (`omp --model … acp`); no mid-session Fusion model switch. ## Test plan - [x] `pnpm --filter @fusion-plugin-examples/omp-runtime test` (unit + live ACP when `omp` is on PATH) - [x] Auth routes: `POST /api/auth/omp-cli`, `GET /api/providers/omp-cli/status` - [x] Engine `runtimeSupportsMcp("omp")` - [ ] Manual: enable card in dashboard, select OMP runtime on an agent, run a short chat turn <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Oh My Pi (OMP) CLI support as an ACP-backed runtime and model provider, including model discovery and probing. * Added dashboard auth/status controls to enable OMP, check readiness, and configure the local binary path (with validation). * Exposed OMP custom `fn_*` tools via an MCP loopback bridge, plus optional filesystem capabilities and stricter tool permission gating. * **Documentation** * Added/expanded OMP runtime contract and integration docs (including the ACP session/handshake flow). * **Tests** * Added Vitest coverage for settings wiring, provider status, model discovery, runtime sessions, permissions, MCP bridging, and live connectivity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
import { runOmpCommand } from "./cli-spawn.js";
|
|
import type { OmpBinaryStatus } from "./types.js";
|
|
|
|
const CANDIDATES = ["omp"] as const;
|
|
const MAX_FAILURE_DETAIL_LENGTH = 180;
|
|
|
|
function buildCandidates(binaryPath?: string): { candidates: string[]; configuredBinaryPath?: string } {
|
|
/*
|
|
FNXC:OmpAcp 2026-07-11-23:35:
|
|
Manual operator paths must be tried before PATH candidates without deleting
|
|
the fallback order. Deduping keeps an `omp` override from probing twice.
|
|
*/
|
|
const configuredBinaryPath = binaryPath?.trim() || undefined;
|
|
const ordered = configuredBinaryPath ? [configuredBinaryPath, ...CANDIDATES] : [...CANDIDATES];
|
|
return { candidates: Array.from(new Set(ordered)), configuredBinaryPath };
|
|
}
|
|
|
|
function summarizeFailure(binary: string, stdout: string, stderr: string): string | undefined {
|
|
const detail = `${stderr || stdout}`.replace(/\s+/g, " ").trim();
|
|
if (!detail) return undefined;
|
|
const truncated =
|
|
detail.length > MAX_FAILURE_DETAIL_LENGTH
|
|
? `${detail.slice(0, MAX_FAILURE_DETAIL_LENGTH - 1)}…`
|
|
: detail;
|
|
return `${binary}: ${truncated}`;
|
|
}
|
|
|
|
export async function probeOmpBinary(options?: {
|
|
timeoutMs?: number;
|
|
binaryPath?: string;
|
|
}): Promise<OmpBinaryStatus> {
|
|
const startedAt = Date.now();
|
|
const timeoutMs = options?.timeoutMs ?? 3000;
|
|
const { candidates, configuredBinaryPath } = buildCandidates(options?.binaryPath);
|
|
const failureDetails: string[] = [];
|
|
|
|
for (const binary of candidates) {
|
|
const version = await runOmpCommand(binary, ["--version"], timeoutMs);
|
|
const failureDetail = summarizeFailure(binary, version.stdout, version.stderr);
|
|
if (failureDetail) failureDetails.push(failureDetail);
|
|
const common = {
|
|
binaryName: binary,
|
|
binaryPath: binary,
|
|
configuredBinaryPath,
|
|
usingConfiguredBinaryPath: configuredBinaryPath === binary,
|
|
diagnostics: failureDetails.length > 0 ? [...failureDetails] : undefined,
|
|
probeDurationMs: Date.now() - startedAt,
|
|
};
|
|
if (version.code === 0) {
|
|
// FNXC:OmpAcp 2026-07-11-23:35: readiness = binary available; omp owns auth (~/.omp).
|
|
return {
|
|
available: true,
|
|
authenticated: true,
|
|
...common,
|
|
version: version.stdout.trim() || version.stderr.trim() || undefined,
|
|
reason: undefined,
|
|
};
|
|
}
|
|
}
|
|
|
|
const baseReason = configuredBinaryPath
|
|
? `Configured OMP CLI binary '${configuredBinaryPath}' failed; PATH fallback omp also failed`
|
|
: "omp not found on PATH";
|
|
return {
|
|
available: false,
|
|
authenticated: false,
|
|
configuredBinaryPath,
|
|
usingConfiguredBinaryPath: false,
|
|
diagnostics: failureDetails.length > 0 ? failureDetails : undefined,
|
|
reason: failureDetails.length > 0 ? `${baseReason} (${failureDetails.join("; ")})` : baseReason,
|
|
probeDurationMs: Date.now() - startedAt,
|
|
};
|
|
}
|