## 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 -->
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
/*
|
|
FNXC:OmpAcp 2026-07-14-00:05:
|
|
Convert engine-resolved MCP server definitions (FN-7022 three-transport shape)
|
|
into ACP `session/new.mcpServers` entries so omp acp receives the same
|
|
operator-approved MCP set as other Fusion AI lanes. Env/header secrets are
|
|
already materialized by the engine; this module only reshapes them and never logs
|
|
server contents. Shared shape with fusion-plugin-grok-runtime.
|
|
*/
|
|
|
|
export type AcpMcpServer =
|
|
| {
|
|
name: string;
|
|
command: string;
|
|
args: string[];
|
|
env: { name: string; value: string }[];
|
|
}
|
|
| {
|
|
type: "http";
|
|
name: string;
|
|
url: string;
|
|
headers: { name: string; value: string }[];
|
|
}
|
|
| {
|
|
type: "sse";
|
|
name: string;
|
|
url: string;
|
|
headers: { name: string; value: string }[];
|
|
};
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Record<string, unknown>)
|
|
: undefined;
|
|
}
|
|
|
|
function mapEntries(map: Record<string, string> | undefined): { name: string; value: string }[] {
|
|
if (!map) return [];
|
|
return Object.entries(map)
|
|
.filter((entry): entry is [string, string] => typeof entry[0] === "string" && typeof entry[1] === "string")
|
|
.map(([name, value]) => ({ name, value }));
|
|
}
|
|
|
|
/**
|
|
* Normalize engine `mcpServers` (ResolvedMcpServerDefinition or legacy ACP
|
|
* stdio shape) into the ACP wire format omp accepts.
|
|
*/
|
|
export function toAcpMcpServers(servers: unknown): AcpMcpServer[] {
|
|
if (!Array.isArray(servers) || servers.length === 0) return [];
|
|
const out: AcpMcpServer[] = [];
|
|
|
|
for (const raw of servers) {
|
|
const server = asRecord(raw);
|
|
if (!server) continue;
|
|
const name = typeof server.name === "string" ? server.name.trim() : "";
|
|
if (!name || server.enabled === false) continue;
|
|
|
|
// Legacy ACP stdio shape: { name, command, args, env: [{name,value}] }
|
|
if (typeof server.command === "string" && server.command.trim() && !("transport" in server) && !("type" in server) && !("url" in server)) {
|
|
const envPairs = Array.isArray(server.env)
|
|
? server.env
|
|
.map((entry) => asRecord(entry))
|
|
.filter((entry): entry is Record<string, unknown> => Boolean(entry))
|
|
.filter((entry) => typeof entry.name === "string" && typeof entry.value === "string")
|
|
.map((entry) => ({ name: String(entry.name), value: String(entry.value) }))
|
|
: mapEntries(asRecord(server.env) as Record<string, string> | undefined);
|
|
out.push({
|
|
name,
|
|
command: server.command.trim(),
|
|
args: Array.isArray(server.args) ? server.args.filter((a): a is string => typeof a === "string") : [],
|
|
env: envPairs,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
const transport = typeof server.transport === "string" ? server.transport : typeof server.type === "string" ? server.type : "stdio";
|
|
|
|
if (transport === "stdio") {
|
|
const command = typeof server.command === "string" ? server.command.trim() : "";
|
|
if (!command) continue;
|
|
out.push({
|
|
name,
|
|
command,
|
|
args: Array.isArray(server.args) ? server.args.filter((a): a is string => typeof a === "string") : [],
|
|
env: mapEntries(asRecord(server.env) as Record<string, string> | undefined),
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (transport === "http" || transport === "streamable-http") {
|
|
const url = typeof server.url === "string" ? server.url.trim() : "";
|
|
if (!url) continue;
|
|
out.push({
|
|
type: "http",
|
|
name,
|
|
url,
|
|
headers: mapEntries(asRecord(server.headers) as Record<string, string> | undefined),
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (transport === "sse") {
|
|
const url = typeof server.url === "string" ? server.url.trim() : "";
|
|
if (!url) continue;
|
|
out.push({
|
|
type: "sse",
|
|
name,
|
|
url,
|
|
headers: mapEntries(asRecord(server.headers) as Record<string, string> | undefined),
|
|
});
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|