Files
qiming/qimingclaw/crates/agent-electron-client/src/main/services/packages/persistentMcpBridge.ts

121 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* PersistentMcpBridge — thin wrapper
*
* The implementation lives in qiming-mcp-stdio-proxy, installed to ~/.qimingclaw/node_modules.
* This module creates a singleton with electron-log injected as the logger.
* Uses dynamic require from ~/.qimingclaw so the app does not bundle the package.
*/
import * as path from "path";
import log from "electron-log";
import { getAppPaths, getBundledMcpProxyDir } from "./packageLocator";
const PKG_NAME = "qiming-mcp-stdio-proxy";
/**
* Wrap electron-log so high-frequency messages (e.g. "New HTTP session")
* are downgraded from info → debug to reduce log noise.
*/
const NOISY_PATTERNS = [/New HTTP session/];
function createQuietLogger(): typeof log {
const quiet = Object.create(log) as typeof log;
const originalInfo = log.info.bind(log);
quiet.info = (...args: Parameters<typeof log.info>) => {
const first = args[0];
if (
typeof first === "string" &&
NOISY_PATTERNS.some((p) => p.test(first))
) {
log.debug(...args);
return;
}
originalInfo(...args);
};
return quiet;
}
/** Lazy-loaded singleton instance (from qiming-mcp-stdio-proxy) */
let instance: {
start: (args: unknown) => Promise<void>;
stop: () => Promise<void>;
isRunning: () => boolean;
getBridgeUrl: (name: string) => string | null;
} | null = null;
function getInstance(): NonNullable<typeof instance> {
if (instance) return instance;
// 1. 应用内集成版本bundled resources
const bundledDir = getBundledMcpProxyDir();
if (bundledDir) {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const pkg = require(bundledDir);
if (pkg.PersistentMcpBridge) {
log.info(
`[PersistentMcpBridge] Using bundled integration: ${bundledDir}`,
);
instance = new pkg.PersistentMcpBridge(
createQuietLogger(),
) as NonNullable<typeof instance>;
return instance;
}
} catch (err) {
log.warn(
`[PersistentMcpBridge] Bundled integration load failed, falling back to node_modules:`,
err instanceof Error ? err.message : String(err),
);
}
}
// 2. 回退兼容: ~/.qimingbot/node_modules
const nodeModules = getAppPaths().nodeModules;
const pkgPath = path.join(nodeModules, PKG_NAME);
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const pkg = require(pkgPath);
if (!pkg.PersistentMcpBridge) {
throw new Error(`${PKG_NAME}: PersistentMcpBridge export not found`);
}
log.info(
`[PersistentMcpBridge] Using ~/.qimingbot path (legacy fallback): ${pkgPath}`,
);
instance = new pkg.PersistentMcpBridge(createQuietLogger()) as NonNullable<
typeof instance
>;
return instance;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
log.error(`[PersistentMcpBridge] Failed to load ${PKG_NAME}:`, msg);
throw new Error(
`${PKG_NAME} not installed or failed to load.${msg ? ` (${msg})` : ""}`,
);
}
}
export const persistentMcpBridge = {
async start(
args: Parameters<NonNullable<typeof instance>["start"]>[0],
): Promise<void> {
return getInstance().start(args);
},
async stop(): Promise<void> {
return getInstance().stop();
},
isRunning(): boolean {
try {
return getInstance().isRunning();
} catch {
return false;
}
},
getBridgeUrl(name: string): string | null {
try {
return getInstance().getBridgeUrl(name);
} catch {
return null;
}
},
};