fix(electrobun): sidecar runner path resolution for dev build output + debug logging

This commit is contained in:
Hibryda 2026-03-26 02:04:02 +01:00
parent 0eb4ba9396
commit 541b6eda46

View file

@ -3,9 +3,18 @@
import { join } from "path";
import { homedir } from "os";
import { existsSync } from "fs";
import { existsSync, appendFileSync, mkdirSync } from "fs";
import { parseMessage, type AgentMessage, type ProviderId } from "./message-adapter.ts";
// Debug log to file (always on in dev, check AGOR_DEBUG for prod)
const DEBUG_LOG = join(homedir(), ".local", "share", "agor", "sidecar-debug.log");
try { mkdirSync(join(homedir(), ".local", "share", "agor"), { recursive: true }); } catch {}
function dbg(msg: string) {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { appendFileSync(DEBUG_LOG, line); } catch {}
console.log(`[sidecar] ${msg}`);
}
// ── Types ────────────────────────────────────────────────────────────────────
export type SessionStatus = "running" | "idle" | "done" | "error";
@ -111,8 +120,29 @@ function findClaudeCli(): string | undefined {
// ── Runner resolution ────────────────────────────────────────────────────────
function resolveRunnerPath(provider: ProviderId): string {
const repoRoot = join(import.meta.dir, "..", "..", "..");
return join(repoRoot, "sidecar", "dist", `${provider}-runner.mjs`);
// In dev mode, import.meta.dir is inside the Electrobun build output,
// not the source tree. Walk up until we find sidecar/dist/ or use env var.
const envRoot = process.env.AGOR_ROOT;
if (envRoot) return join(envRoot, "sidecar", "dist", `${provider}-runner.mjs`);
// Try multiple candidate roots (dev build output vs source tree)
const candidates = [
join(import.meta.dir, "..", "..", ".."), // source: src/bun/ → repo root
join(import.meta.dir, "..", "..", "..", "..", "..", ".."), // build: build/dev-linux-x64/App/... → repo root
];
for (const root of candidates) {
const path = join(root, "sidecar", "dist", `${provider}-runner.mjs`);
if (existsSync(path)) {
dbg(`Runner found at: ${path} (root: ${root})`);
return path;
}
}
// Fallback: try finding from cwd
const cwdPath = join(process.cwd(), "sidecar", "dist", `${provider}-runner.mjs`);
dbg(`Trying cwd fallback: ${cwdPath}`);
return cwdPath;
}
function findNodeRuntime(): string {
@ -175,12 +205,15 @@ export class SidecarManager {
}
const runnerPath = resolveRunnerPath(provider);
dbg(`startSession: id=${sessionId} provider=${provider} runner=${runnerPath}`);
if (!existsSync(runnerPath)) {
dbg(`ERROR: runner not found at ${runnerPath}`);
return { ok: false, error: `Runner not found: ${runnerPath}` };
}
const controller = new AbortController();
const env = buildCleanEnv(options.extraEnv, options.claudeConfigDir);
dbg(`Spawning: ${this.nodeRuntime} ${runnerPath} cwd=${options.cwd || 'default'}`);
const proc = Bun.spawn([this.nodeRuntime, runnerPath], {
stdin: "pipe",
@ -218,6 +251,7 @@ export class SidecarManager {
// Monitor process exit
proc.exited.then((exitCode) => {
dbg(`Process exited: session=${sessionId} code=${exitCode}`);
const s = this.sessions.get(sessionId);
if (s) {
s.state.status = exitCode === 0 ? "done" : "error";
@ -248,8 +282,10 @@ export class SidecarManager {
queryMsg.worktreeName = options.worktreeName;
}
dbg(`Sending query: ${JSON.stringify(queryMsg).slice(0, 200)}...`);
this.writeToProcess(sessionId, queryMsg);
dbg(`Session ${sessionId} started successfully`);
return { ok: true };
}
@ -419,6 +455,7 @@ export class SidecarManager {
const text = decoder.decode(chunk, { stream: true });
for (const line of text.split("\n")) {
if (line.trim()) {
dbg(`STDERR [${sessionId}]: ${line.trim()}`);
console.log(`[sidecar:${sessionId}] ${line.trim()}`);
}
}
@ -429,16 +466,19 @@ export class SidecarManager {
}
private handleNdjsonLine(sessionId: string, session: ActiveSession, line: string): void {
dbg(`NDJSON [${sessionId}]: ${line.slice(0, 200)}`);
let raw: Record<string, unknown>;
try {
raw = JSON.parse(line);
} catch {
dbg(`Invalid JSON from ${sessionId}: ${line.slice(0, 100)}`);
console.warn(`[sidecar] Invalid JSON from ${sessionId}: ${line.slice(0, 100)}`);
return;
}
// Handle sidecar-level events (not forwarded to message adapter)
const type = raw.type;
dbg(`Event type: ${type} for ${sessionId}`);
if (type === "ready" || type === "pong") return;
if (type === "agent_started") {