#!/usr/bin/env node
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import * as fs from "fs";
import * as path from "path";
import { execSync } from "child_process";
class SessionDaemon {
  config;
  state;
  stackmemoryDir;
  sessionsDir;
  logsDir;
  pidFile;
  heartbeatFile;
  logFile;
  saveInterval = null;
  heartbeatInterval = null;
  activityCheckInterval = null;
  isShuttingDown = false;
  constructor(sessionId2, options2) {
    const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "";
    this.stackmemoryDir = path.join(homeDir, ".stackmemory");
    this.sessionsDir = path.join(this.stackmemoryDir, "sessions");
    this.logsDir = path.join(this.stackmemoryDir, "logs");
    this.config = {
      sessionId: sessionId2,
      saveIntervalMs: options2?.saveIntervalMs ?? 15 * 60 * 1e3,
      inactivityTimeoutMs: options2?.inactivityTimeoutMs ?? 30 * 60 * 1e3,
      heartbeatIntervalMs: options2?.heartbeatIntervalMs ?? 60 * 1e3
    };
    this.pidFile = path.join(this.sessionsDir, `${sessionId2}.pid`);
    this.heartbeatFile = path.join(this.sessionsDir, `${sessionId2}.heartbeat`);
    this.logFile = path.join(this.logsDir, "daemon.log");
    this.state = {
      startTime: Date.now(),
      lastSaveTime: Date.now(),
      lastActivityTime: Date.now(),
      saveCount: 0,
      errors: []
    };
    this.ensureDirectories();
  }
  ensureDirectories() {
    [this.sessionsDir, this.logsDir].forEach((dir) => {
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
    });
  }
  log(level, message, data) {
    const entry = {
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      level,
      sessionId: this.config.sessionId,
      message,
      data
    };
    const logLine = JSON.stringify(entry) + "\n";
    try {
      fs.appendFileSync(this.logFile, logLine);
    } catch {
      console.error(`[${entry.timestamp}] ${level}: ${message}`, data);
    }
  }
  checkIdempotency() {
    if (fs.existsSync(this.pidFile)) {
      try {
        const existingPid = fs.readFileSync(this.pidFile, "utf8").trim();
        const pid = parseInt(existingPid, 10);
        try {
          process.kill(pid, 0);
          this.log("WARN", "Daemon already running for this session", {
            existingPid: pid
          });
          return false;
        } catch {
          this.log("INFO", "Cleaning up stale PID file", { stalePid: pid });
          fs.unlinkSync(this.pidFile);
        }
      } catch {
        try {
          fs.unlinkSync(this.pidFile);
        } catch {
        }
      }
    }
    return true;
  }
  writePidFile() {
    fs.writeFileSync(this.pidFile, process.pid.toString());
    this.log("INFO", "PID file created", {
      pid: process.pid,
      file: this.pidFile
    });
  }
  updateHeartbeat() {
    const heartbeatData = {
      pid: process.pid,
      sessionId: this.config.sessionId,
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      uptime: Date.now() - this.state.startTime,
      saveCount: this.state.saveCount,
      lastSaveTime: new Date(this.state.lastSaveTime).toISOString()
    };
    try {
      fs.writeFileSync(
        this.heartbeatFile,
        JSON.stringify(heartbeatData, null, 2)
      );
    } catch (err) {
      this.log("ERROR", "Failed to update heartbeat file", {
        error: String(err)
      });
    }
  }
  saveContext() {
    if (this.isShuttingDown) return;
    try {
      const stackmemoryBin = path.join(
        this.stackmemoryDir,
        "bin",
        "stackmemory"
      );
      if (!fs.existsSync(stackmemoryBin)) {
        this.log("WARN", "StackMemory binary not found", {
          path: stackmemoryBin
        });
        return;
      }
      const message = `Auto-checkpoint #${this.state.saveCount + 1} at ${(/* @__PURE__ */ new Date()).toISOString()}`;
      execSync(`"${stackmemoryBin}" context add observation "${message}"`, {
        timeout: 3e4,
        encoding: "utf8",
        stdio: "pipe"
      });
      this.state.saveCount++;
      this.state.lastSaveTime = Date.now();
      this.log("INFO", "Context saved successfully", {
        saveCount: this.state.saveCount,
        intervalMs: this.config.saveIntervalMs
      });
    } catch (err) {
      const errorMsg = err instanceof Error ? err.message : String(err);
      if (!errorMsg.includes("EBUSY") && !errorMsg.includes("EAGAIN")) {
        this.state.errors.push(errorMsg);
        this.log("WARN", "Failed to save context", { error: errorMsg });
      }
      if (this.state.errors.length > 50) {
        this.log("ERROR", "Too many errors, initiating shutdown");
        this.shutdown("too_many_errors");
      }
    }
  }
  checkActivity() {
    if (this.isShuttingDown) return;
    const sessionFile = path.join(
      this.stackmemoryDir,
      "traces",
      "current-session.json"
    );
    try {
      if (fs.existsSync(sessionFile)) {
        const stats = fs.statSync(sessionFile);
        const lastModified = stats.mtimeMs;
        if (lastModified > this.state.lastActivityTime) {
          this.state.lastActivityTime = lastModified;
          this.log("DEBUG", "Activity detected", {
            lastModified: new Date(lastModified).toISOString()
          });
        }
      }
    } catch {
    }
    const inactiveTime = Date.now() - this.state.lastActivityTime;
    if (inactiveTime > this.config.inactivityTimeoutMs) {
      this.log("INFO", "Inactivity timeout reached", {
        inactiveTimeMs: inactiveTime,
        timeoutMs: this.config.inactivityTimeoutMs
      });
      this.shutdown("inactivity_timeout");
    }
  }
  setupSignalHandlers() {
    const handleSignal = (signal) => {
      this.log("INFO", `Received ${signal}, shutting down gracefully`);
      this.shutdown(signal.toLowerCase());
    };
    process.on("SIGTERM", () => handleSignal("SIGTERM"));
    process.on("SIGINT", () => handleSignal("SIGINT"));
    process.on("SIGHUP", () => handleSignal("SIGHUP"));
    process.on("uncaughtException", (err) => {
      this.log("ERROR", "Uncaught exception", {
        error: err.message,
        stack: err.stack
      });
      this.shutdown("uncaught_exception");
    });
    process.on("unhandledRejection", (reason) => {
      this.log("ERROR", "Unhandled rejection", { reason: String(reason) });
    });
  }
  cleanup() {
    try {
      if (fs.existsSync(this.pidFile)) {
        fs.unlinkSync(this.pidFile);
        this.log("INFO", "PID file removed");
      }
    } catch (e) {
      this.log("WARN", "Failed to remove PID file", { error: String(e) });
    }
    try {
      const finalHeartbeat = {
        pid: process.pid,
        sessionId: this.config.sessionId,
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        status: "shutdown",
        uptime: Date.now() - this.state.startTime,
        totalSaves: this.state.saveCount
      };
      fs.writeFileSync(
        this.heartbeatFile,
        JSON.stringify(finalHeartbeat, null, 2)
      );
    } catch {
    }
  }
  shutdown(reason) {
    if (this.isShuttingDown) return;
    this.isShuttingDown = true;
    this.log("INFO", "Daemon shutting down", {
      reason,
      uptime: Date.now() - this.state.startTime,
      totalSaves: this.state.saveCount,
      errors: this.state.errors.length
    });
    if (this.saveInterval) {
      clearInterval(this.saveInterval);
      this.saveInterval = null;
    }
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
    if (this.activityCheckInterval) {
      clearInterval(this.activityCheckInterval);
      this.activityCheckInterval = null;
    }
    try {
      this.saveContext();
    } catch {
    }
    this.cleanup();
    process.exit(
      reason === "inactivity_timeout" || reason === "sigterm" ? 0 : 1
    );
  }
  start() {
    if (!this.checkIdempotency()) {
      this.log("INFO", "Exiting - daemon already running");
      process.exit(0);
    }
    this.writePidFile();
    this.setupSignalHandlers();
    this.log("INFO", "Session daemon started", {
      sessionId: this.config.sessionId,
      pid: process.pid,
      saveIntervalMs: this.config.saveIntervalMs,
      inactivityTimeoutMs: this.config.inactivityTimeoutMs
    });
    this.updateHeartbeat();
    this.heartbeatInterval = setInterval(() => {
      this.updateHeartbeat();
    }, this.config.heartbeatIntervalMs);
    this.saveInterval = setInterval(() => {
      this.saveContext();
    }, this.config.saveIntervalMs);
    this.activityCheckInterval = setInterval(() => {
      this.checkActivity();
    }, 60 * 1e3);
    this.saveContext();
  }
}
function parseArgs() {
  const args = process.argv.slice(2);
  let sessionId2 = `session-${Date.now()}`;
  const options2 = {};
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--session-id" && args[i + 1]) {
      sessionId2 = args[i + 1];
      i++;
    } else if (arg === "--save-interval" && args[i + 1]) {
      options2.saveIntervalMs = parseInt(args[i + 1], 10) * 1e3;
      i++;
    } else if (arg === "--inactivity-timeout" && args[i + 1]) {
      options2.inactivityTimeoutMs = parseInt(args[i + 1], 10) * 1e3;
      i++;
    } else if (arg === "--heartbeat-interval" && args[i + 1]) {
      options2.heartbeatIntervalMs = parseInt(args[i + 1], 10) * 1e3;
      i++;
    } else if (!arg.startsWith("--")) {
      sessionId2 = arg;
    }
  }
  return { sessionId: sessionId2, options: options2 };
}
const { sessionId, options } = parseArgs();
const daemon = new SessionDaemon(sessionId, options);
daemon.start();
//# sourceMappingURL=session-daemon.js.map
