## 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 -->
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
import { runOmpCommand } from "./cli-spawn.js";
|
|
|
|
/*
|
|
FNXC:OmpAcp 2026-07-11-23:35:
|
|
Model discovery for the omp-cli provider card. Prefer a structured list when
|
|
available; fall soft to an empty list with a clear reason so the picker stays
|
|
usable without inventing model ids.
|
|
*/
|
|
|
|
export interface OmpModelDiscoveryResult {
|
|
models: string[];
|
|
source: string;
|
|
fallbackUsed: boolean;
|
|
reason?: string;
|
|
}
|
|
|
|
/**
|
|
* Attempt to list models from the local omp install.
|
|
* Tries `omp models` then falls back to empty (CLI default still works via ACP).
|
|
*/
|
|
export async function discoverOmpModels(
|
|
binary: string,
|
|
timeoutMs = 8000,
|
|
): Promise<OmpModelDiscoveryResult> {
|
|
const result = await runOmpCommand(binary, ["models"], timeoutMs);
|
|
if (result.code === 0) {
|
|
const models = parseModelList(result.stdout || result.stderr);
|
|
if (models.length > 0) {
|
|
return { models, source: "omp models", fallbackUsed: false };
|
|
}
|
|
}
|
|
|
|
// Some installs may only expose models via help text; do not invent ids.
|
|
return {
|
|
models: [],
|
|
source: "probe",
|
|
fallbackUsed: true,
|
|
reason:
|
|
result.code === 0
|
|
? "omp models returned no parseable model ids"
|
|
: `omp models failed (code ${result.code ?? "null"})`,
|
|
};
|
|
}
|
|
|
|
function parseModelList(text: string): string[] {
|
|
const lines = text.split(/\r?\n/);
|
|
const models: string[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
for (const raw of lines) {
|
|
const line = raw.trim();
|
|
if (!line || line.startsWith("#") || line.startsWith("┌") || line.startsWith("├") || line.startsWith("└") || line.startsWith("│ model")) {
|
|
continue;
|
|
}
|
|
|
|
// omp models table rows: │ claude-sonnet-4-5 │ 200K │ ...
|
|
const tableCell = line.match(/^│\s*([a-zA-Z0-9][\w./+-]*)\s*│/);
|
|
// Common shapes: "* model-id (default)", "- model-id", "model-id", "provider/model-id"
|
|
const bullet = line.match(/^[-*•]\s+(\S+)/);
|
|
const bare = !tableCell && !bullet && !line.includes(" ") ? line : undefined;
|
|
const candidate = (tableCell?.[1] ?? bullet?.[1] ?? bare)?.replace(/[(),]/g, "") ?? "";
|
|
if (!candidate || candidate.length < 2) continue;
|
|
if (/^(available|default|models?|provider|context|max-out|thinking|images)$/i.test(candidate)) continue;
|
|
if (seen.has(candidate)) continue;
|
|
seen.add(candidate);
|
|
models.push(candidate);
|
|
}
|
|
|
|
return models;
|
|
}
|