feat(connectd): native-binary connectors + companion sidecar lifecycle #2

Open
tim.lingo wants to merge 5 commits from feat/bin-native-connector into main
2 changed files with 12 additions and 1 deletions
Showing only changes of commit 995b9a8548 - Show all commits
+8 -1
View File
@@ -5,6 +5,8 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
import { createHash } from "node:crypto";
import { join, dirname } from "node:path";
import { spawn, type ChildProcess } from "node:child_process";
import { mkdirSync } from "node:fs";
import { homedir } from "node:os";
import type { ConnectorsConfig, ServerConfig } from "./config.js";
import { saveConfig } from "./config.js";
import { KeychainOAuthProvider, takePendingAuthUrl } from "./oauth.js";
@@ -190,10 +192,15 @@ export class ConnectorBridge {
};
if (await ping()) return; // already running (this session or a prior one)
if (this.companions.has(id)) return; // we already started it
let cwd: string | undefined;
if (comp.cwd) {
cwd = comp.cwd.replace(/^~(?=\/|$)/, homedir()).replace(/^\$HOME(?=\/|$)/, homedir());
try { mkdirSync(cwd, { recursive: true }); } catch { /* best effort — spawn will surface a real failure */ }
}
const child = spawn(
resolveRuntimeCommand(comp.command),
(comp.args ?? []).map(resolveBundledArg),
{ env: { ...process.env, ...(comp.env ?? {}) } as Record<string, string>, stdio: "ignore" },
{ env: { ...process.env, ...(comp.env ?? {}) } as Record<string, string>, stdio: "ignore", cwd },
);
this.companions.set(id, child);
child.on("exit", () => this.companions.delete(id));
+4
View File
@@ -20,6 +20,10 @@ export interface ServerConfig {
args?: string[];
env?: Record<string, string>;
healthUrl?: string;
// Working directory for the sidecar (created if missing). The WhatsApp bridge writes its session
// store relative to cwd, so this must be writable (e.g. ~/.neuron/whatsapp) — never the read-only
// app bundle. "~" / "$HOME" at the start are expanded to the user's home.
cwd?: string;
};
// http (Phase 3)
url?: string;