import { createHash } from "node:crypto"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; export const LOG_DIR = join(homedir(), ".agent-relay", "logs"); export const STATE_FILE = join(homedir(), ".agent-relay", "orchestrator-sessions.json"); export const SESSION_DIR = join(homedir(), ".agent-relay", "sessions"); export const RUNNER_INFO_DIR = join(homedir(), ".agent-relay", "runners"); // #1514 — durable positive-death evidence. When a runner is confirmed dead its runner-info // file is DELETED by cleanupSessionRecord, but its owned tmux session can still linger; a // tombstone here (keyed by session name) survives that cleanup so the wedged-session reaper // retains proof-of-death to reap the orphan. Separate dir so cleanup never removes it. export const TOMBSTONE_DIR = join(homedir(), ".agent-relay", "tombstones"); export const GUEST_TTL_MS = 60 * 60 * 1000; export const GUEST_STATE_FILE = join(homedir(), ".agent-relay", "orchestrator-guests.json"); export const terminalGuests = new Map(); export let guestStateHydrated = false; export function markGuestStateHydrated(): void { guestStateHydrated = true; } // #1514 (review finding 4) — a stable identity for THIS orchestrator's state home. // The orchestrator stamps it onto every tmux session it spawns (@agent-relay-owner) // and the wedged-session reaper reaps ONLY sessions carrying a matching id, so two // orchestrators that share a UID + tmux prefix on one host but keep SEPARATE state // homes can never reap each other's sessions. Derived from the absolute state-file // path: identical across restarts of the same state home, distinct for a different // one. Not a secret — just a per-state-home discriminator. export function orchestratorStateOwnerId(stateFile: string = STATE_FILE): string { return createHash("sha256").update(resolve(stateFile)).digest("hex").slice(0, 16); }