import {
  EngramMcpServer
} from "./chunk-Y5KDIOKF.js";
import {
  EngramAccessInputError
} from "./chunk-WGK4VHGP.js";
import {
  isTrustZoneName
} from "./chunk-EQINRHYR.js";
import {
  subscribeGraphEvents
} from "./chunk-WIICJPET.js";
import {
  expandTildePath
} from "./chunk-IXEJRKCZ.js";
import {
  isRecallDisclosure
} from "./chunk-VQXK37XA.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";
import {
  validateRequest
} from "./chunk-A4ACKWIW.js";

// src/access-http.ts
import { createServer } from "http";
import { randomUUID, timingSafeEqual } from "crypto";
import { AsyncLocalStorage } from "async_hooks";
import { existsSync } from "fs";
import { readFile } from "fs/promises";
import path from "path";
import { fileURLToPath, URL } from "url";

// src/adapters/types.ts
function headerValue(headers, key) {
  const raw = headers[key];
  const value = Array.isArray(raw) ? raw[0] : raw;
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
}

// src/adapters/claude-code.ts
var ClaudeCodeAdapter = class {
  id = "claude-code";
  matches(context) {
    if (context.clientInfo?.name === "claude-code") return true;
    const ua = headerValue(context.headers, "user-agent");
    if (ua && ua.toLowerCase().startsWith("claude-code/")) return true;
    const clientId = headerValue(context.headers, "x-engram-client-id");
    if (clientId?.toLowerCase() === "claude-code") return true;
    return false;
  }
  resolveIdentity(context) {
    const mcpSessionId = headerValue(context.headers, "mcp-session-id");
    const principal = headerValue(context.headers, "x-engram-principal") || "claude-code";
    const namespace = headerValue(context.headers, "x-engram-namespace") || "claude-code";
    return {
      namespace,
      principal,
      sessionKey: mcpSessionId ?? context.sessionKey,
      adapterId: this.id
    };
  }
};

// src/adapters/codex.ts
var CodexAdapter = class {
  id = "codex";
  matches(context) {
    if (context.clientInfo?.name === "codex-mcp-client") return true;
    const clientName = context.clientInfo?.name?.toLowerCase() ?? "";
    if (clientName.includes("codex") && clientName !== "codex-mcp-client") return true;
    const clientId = headerValue(context.headers, "x-engram-client-id");
    if (clientId?.toLowerCase() === "codex") return true;
    return false;
  }
  resolveIdentity(context) {
    const mcpSessionId = headerValue(context.headers, "mcp-session-id");
    const principal = headerValue(context.headers, "x-engram-principal") || "codex";
    const namespace = headerValue(context.headers, "x-engram-namespace") || "codex";
    return {
      namespace,
      principal,
      sessionKey: mcpSessionId ?? context.sessionKey,
      adapterId: this.id
    };
  }
};

// src/adapters/replit.ts
var ReplitAdapter = class {
  id = "replit";
  matches(context) {
    const clientId = headerValue(context.headers, "x-engram-client-id");
    if (clientId?.toLowerCase() === "replit") return true;
    const clientName = context.clientInfo?.name?.toLowerCase() ?? "";
    if (clientName.includes("replit")) return true;
    return false;
  }
  resolveIdentity(context) {
    const mcpSessionId = headerValue(context.headers, "mcp-session-id");
    const principal = headerValue(context.headers, "x-engram-principal") || "replit-agent";
    const namespace = headerValue(context.headers, "x-engram-namespace") || "replit";
    return {
      namespace,
      principal,
      sessionKey: mcpSessionId ?? context.sessionKey,
      adapterId: this.id
    };
  }
};

// src/adapters/hermes.ts
var HermesAdapter = class {
  id = "hermes";
  matches(context) {
    if (headerValue(context.headers, "x-hermes-session-id")) return true;
    const clientId = headerValue(context.headers, "x-engram-client-id");
    if (clientId?.toLowerCase() === "hermes") return true;
    const clientName = context.clientInfo?.name?.toLowerCase() ?? "";
    if (clientName.includes("hermes")) return true;
    return false;
  }
  resolveIdentity(context) {
    const sessionId = headerValue(context.headers, "x-hermes-session-id");
    const principal = headerValue(context.headers, "x-engram-principal") || "hermes-agent";
    const namespace = headerValue(context.headers, "x-engram-namespace") || "hermes";
    return {
      namespace,
      principal,
      sessionKey: sessionId ?? context.sessionKey,
      adapterId: this.id
    };
  }
};

// src/adapters/registry.ts
var AdapterRegistry = class {
  adapters;
  constructor(adapters) {
    this.adapters = adapters ?? [
      new HermesAdapter(),
      new ReplitAdapter(),
      new CodexAdapter(),
      new ClaudeCodeAdapter()
    ];
  }
  /**
   * Try each adapter in order. Return the first match, or null if
   * no adapter recognizes the request context.
   */
  resolve(context) {
    for (const adapter of this.adapters) {
      if (adapter.matches(context)) {
        return adapter.resolveIdentity(context);
      }
    }
    return null;
  }
  /** List registered adapter IDs */
  list() {
    return this.adapters.map((a) => a.id);
  }
};

// src/access-http.ts
function resolveDefaultAdminConsolePublicDir() {
  const thisDir = path.dirname(fileURLToPath(import.meta.url));
  const candidates = [
    // Standard: admin-console sibling to src/ (development layout)
    path.resolve(thisDir, "../admin-console/public"),
    // Bundled: admin-console inside dist/ alongside the bundle
    path.resolve(thisDir, "./admin-console/public"),
    // Package root: walk up from dist/ to the package root
    path.resolve(thisDir, "../../admin-console/public")
  ];
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
}
var defaultAdminConsolePublicDir = resolveDefaultAdminConsolePublicDir();
var correlationIdStore = new AsyncLocalStorage();
var WRITE_RATE_LIMIT_WINDOW_MS = 6e4;
var WRITE_RATE_LIMIT_MAX_REQUESTS = 30;
var TRUST_ZONE_RECORD_KINDS = ["memory", "artifact", "state", "trajectory", "external"];
var TRUST_ZONE_SOURCE_CLASSES = ["tool_output", "web_content", "subagent_trace", "system_memory", "user_input", "manual"];
var HttpError = class extends Error {
  constructor(status, message, code, details) {
    super(message);
    this.status = status;
    this.code = code ?? `http_${status}`;
    this.details = details;
  }
  status;
  code;
  details;
};
function hostToUrlAuthority(host) {
  if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
    return `[${host}]`;
  }
  return host;
}
function parseTrustZoneKindFilter(raw) {
  if (raw === null) return void 0;
  if (TRUST_ZONE_RECORD_KINDS.includes(raw)) {
    return raw;
  }
  throw new HttpError(400, `kind must be one of ${TRUST_ZONE_RECORD_KINDS.join("|")}`, "invalid_kind_filter");
}
function parseTrustZoneSourceClassFilter(raw) {
  if (raw === null) return void 0;
  if (TRUST_ZONE_SOURCE_CLASSES.includes(raw)) {
    return raw;
  }
  throw new HttpError(400, `sourceClass must be one of ${TRUST_ZONE_SOURCE_CLASSES.join("|")}`, "invalid_source_class_filter");
}
function parseTrustZoneFilter(raw) {
  if (raw === null) return void 0;
  if (isTrustZoneName(raw)) {
    return raw;
  }
  throw new HttpError(400, "zone must be one of quarantine|working|trusted", "invalid_zone_filter");
}
function decodePeerIdSegment(raw) {
  try {
    return decodeURIComponent(raw);
  } catch {
    throw new EngramAccessInputError("peerId path segment is not valid percent-encoded input");
  }
}
var EngramAccessHttpServer = class {
  service;
  host;
  requestedPort;
  authToken;
  authTokens;
  authTokensGetter;
  authenticatedPrincipal;
  maxBodyBytes;
  adminConsoleEnabled;
  adminConsolePublicDir;
  trustPrincipalHeader;
  adapterRegistry;
  writeRequestTimestamps = [];
  mcpServer;
  server = null;
  boundPort = 0;
  /** Active SSE response objects for /engram/v1/graph/events. */
  sseClients = /* @__PURE__ */ new Set();
  /** Throttle batch: pending SSE event batches per client. */
  sseBatchTimers = /* @__PURE__ */ new Map();
  ssePendingBatches = /* @__PURE__ */ new Map();
  /**
   * Per-client cleanup callbacks: clear heartbeat interval, flush timer,
   * unsubscribe from bus, and end the response.  Stored here so `stop()`
   * can invoke them even when the client hasn't disconnected yet
   * (Cursor review thread `access-http.ts:232`).
   */
  sseCleanupFns = /* @__PURE__ */ new Set();
  constructor(options) {
    this.service = options.service;
    this.host = options.host?.trim() || "127.0.0.1";
    this.requestedPort = Number.isFinite(options.port) ? Math.max(0, Math.floor(options.port ?? 0)) : 0;
    this.authToken = options.authToken?.trim() || void 0;
    this.authTokens = (options.authTokens ?? []).map((t) => t.trim()).filter(Boolean);
    this.authTokensGetter = options.authTokensGetter;
    this.authenticatedPrincipal = options.principal?.trim() || void 0;
    this.maxBodyBytes = Number.isFinite(options.maxBodyBytes) ? Math.max(1, Math.floor(options.maxBodyBytes ?? 131072)) : 131072;
    this.adminConsoleEnabled = options.adminConsoleEnabled !== false;
    this.adminConsolePublicDir = options.adminConsolePublicDir ?? defaultAdminConsolePublicDir;
    this.trustPrincipalHeader = options.trustPrincipalHeader === true;
    this.adapterRegistry = options.enableAdapters !== false ? options.adapterRegistry ?? new AdapterRegistry() : null;
    this.mcpServer = new EngramMcpServer(this.service, {
      principal: options.principal,
      citationsEnabled: options.citationsEnabled,
      citationsAutoDetect: options.citationsAutoDetect
    });
  }
  async start() {
    if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) {
      throw new Error("engram access HTTP requires authToken or authTokens");
    }
    if (this.server) return this.status();
    const server = createServer((req, res) => {
      const correlationId = randomUUID();
      correlationIdStore.run(correlationId, () => {
        void this.handle(req, res, correlationId).catch((err) => {
          log.debug(`engram access HTTP request failed [${correlationId}]: ${err}`);
          if (err instanceof HttpError) {
            const payload = { error: err.message, code: err.code };
            if (err.details) payload.details = err.details;
            this.respondJson(res, err.status, payload);
            return;
          }
          if (err instanceof EngramAccessInputError) {
            this.respondJson(res, 400, { error: err.message, code: "input_error" });
            return;
          }
          if (res.headersSent) {
            res.destroy(err);
            return;
          }
          this.respondJson(res, 500, { error: "internal_error", code: "internal_error" });
        });
      });
    });
    try {
      await new Promise((resolve, reject) => {
        const onError = (err) => {
          server.off("listening", onListening);
          reject(err);
        };
        const onListening = () => {
          server.off("error", onError);
          resolve();
        };
        server.once("error", onError);
        server.once("listening", onListening);
        server.listen(this.requestedPort, this.host);
      });
    } catch (err) {
      server.close();
      throw err;
    }
    this.server = server;
    const address = server.address();
    this.boundPort = typeof address === "object" && address ? address.port : this.requestedPort;
    return this.status();
  }
  async stop() {
    if (!this.server) return;
    const server = this.server;
    this.server = null;
    this.boundPort = 0;
    for (const cleanup of this.sseCleanupFns) {
      try {
        cleanup();
      } catch {
      }
    }
    this.sseCleanupFns.clear();
    for (const [res, timer] of this.sseBatchTimers.entries()) {
      clearTimeout(timer);
      this.sseBatchTimers.delete(res);
    }
    this.ssePendingBatches.clear();
    for (const res of this.sseClients) {
      try {
        res.end();
      } catch {
      }
    }
    this.sseClients.clear();
    await new Promise((resolve, reject) => {
      server.close((err) => err ? reject(err) : resolve());
    });
  }
  status() {
    return {
      running: this.server !== null,
      host: this.host,
      port: this.boundPort,
      maxBodyBytes: this.maxBodyBytes
    };
  }
  /**
   * Resolve the adapter identity for the incoming request.
   * Includes MCP clientInfo from the last initialize handshake if available.
   * Returns null if no adapter matches or adapters are disabled.
   */
  resolveAdapterIdentity(req) {
    if (!this.adapterRegistry) return null;
    const sessionId = (() => {
      const raw = req.headers["mcp-session-id"];
      return typeof raw === "string" ? raw.trim() : void 0;
    })();
    return this.adapterRegistry.resolve({
      headers: req.headers,
      clientInfo: this.mcpServer.getClientInfo(sessionId)
    });
  }
  /** Cache for per-request identity resolution (avoids double adapter resolution) */
  identityCache = /* @__PURE__ */ new WeakMap();
  /** Resolve principal and namespace from request headers and adapter identity */
  resolveRequestIdentity(req) {
    const cached = this.identityCache.get(req);
    if (cached) return cached;
    let principal;
    let namespace;
    if (this.trustPrincipalHeader) {
      const headerVal = req.headers["x-engram-principal"];
      const raw = Array.isArray(headerVal) ? headerVal[0] : headerVal;
      if (typeof raw === "string") {
        const trimmed = raw.trim();
        if (trimmed.length > 0) {
          principal = trimmed;
        }
      }
    }
    const adapterIdentity = this.resolveAdapterIdentity(req);
    if (adapterIdentity) {
      if (!principal) {
        principal = adapterIdentity.principal;
      }
      namespace = adapterIdentity.namespace;
    }
    if (!principal) {
      principal = this.authenticatedPrincipal;
    }
    const result = { principal, namespace };
    this.identityCache.set(req, result);
    return result;
  }
  resolveRequestPrincipal(req) {
    return this.resolveRequestIdentity(req).principal;
  }
  /** Resolve namespace: only use the explicit body value. Adapter-inferred namespace
   *  is intentionally NOT used as a fallback for REST requests — omitting namespace
   *  should default to the server's global namespace, not silently scope to an adapter. */
  resolveNamespace(_req, bodyNamespace) {
    return bodyNamespace || void 0;
  }
  /**
   * Resolve the recall disclosure depth from the request (issue #677 PR
   * 2/4).  Explicit body value wins; otherwise we accept a
   * `?disclosure=...` query parameter so curl/browser tooling can use the
   * three-tier surface without rewriting JSON.  Invalid query values
   * throw `EngramAccessInputError` (CLAUDE.md rule 51 — no silent
   * fallback).  An absent body field AND an absent query param yields
   * `undefined`, which the service maps to `DEFAULT_RECALL_DISCLOSURE`.
   */
  resolveRecallDisclosure(bodyDisclosure, parsed) {
    if (bodyDisclosure !== void 0) {
      return bodyDisclosure;
    }
    const queryDisclosure = parsed.searchParams.get("disclosure");
    if (queryDisclosure === null) {
      return void 0;
    }
    if (!isRecallDisclosure(queryDisclosure)) {
      throw new EngramAccessInputError(
        `disclosure must be one of: chunk, section, raw (got: ${queryDisclosure})`
      );
    }
    return queryDisclosure;
  }
  async handle(req, res, correlationId) {
    const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
    const pathname = parsed.pathname;
    if (this.adminConsoleEnabled && await this.handleAdminConsole(req, res, pathname)) {
      return;
    }
    if (!this.isAuthorized(req, pathname)) {
      const body = JSON.stringify({ error: "unauthorized", code: "unauthorized" });
      res.writeHead(401, {
        "content-type": "application/json; charset=utf-8",
        "www-authenticate": "Bearer",
        "x-request-id": correlationId
      });
      res.end(body);
      return;
    }
    if (req.method === "POST" && pathname === "/mcp") {
      await this.handleMcpRequest(req, res);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/health") {
      this.respondJson(res, 200, await this.service.health());
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/adapters") {
      const identity = this.resolveAdapterIdentity(req);
      this.respondJson(res, 200, {
        adaptersEnabled: this.adapterRegistry !== null,
        registered: this.adapterRegistry?.list() ?? [],
        resolved: identity
      });
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/recall") {
      const body = await this.readValidatedBody(req, "recall");
      const codingContext = "codingContext" in body ? body.codingContext : void 0;
      const disclosure = this.resolveRecallDisclosure(body.disclosure, parsed);
      const asOfQueryRaw = parsed.searchParams.get("as_of");
      const bodyHasAsOf = typeof body.asOf === "string" && body.asOf.length > 0;
      if (!bodyHasAsOf && asOfQueryRaw !== null && asOfQueryRaw.length === 0) {
        throw new EngramAccessInputError(
          "as_of must be a non-empty timestamp (got empty value)"
        );
      }
      const asOf = body.asOf ?? (asOfQueryRaw !== null && asOfQueryRaw.length > 0 ? asOfQueryRaw : void 0);
      const bodyHasTagsField = body !== null && typeof body === "object" && "tags" in body;
      const bodyTagsValue = bodyHasTagsField ? body.tags : void 0;
      const bodyTags = Array.isArray(bodyTagsValue) ? bodyTagsValue : void 0;
      const queryTags = parsed.searchParams.getAll("tag");
      const tags = bodyHasTagsField ? bodyTags : queryTags.length > 0 ? queryTags : void 0;
      const bodyTagMatch = body.tagMatch;
      let tagMatch;
      if (bodyTagMatch !== void 0) {
        if (bodyTagMatch === "any" || bodyTagMatch === "all") {
          tagMatch = bodyTagMatch;
        }
      } else {
        const queryTagMatch = parsed.searchParams.get("tag_match");
        if (queryTagMatch !== null) {
          if (queryTagMatch !== "any" && queryTagMatch !== "all") {
            throw new EngramAccessInputError(
              `tag_match must be one of: any, all (got: ${queryTagMatch})`
            );
          }
          tagMatch = queryTagMatch;
        }
      }
      const bodyIncludeLowConfidence = body.includeLowConfidence;
      const queryIncludeLowConfidence = parsed.searchParams.get("include_low_confidence");
      if (bodyIncludeLowConfidence === void 0 && queryIncludeLowConfidence !== null && queryIncludeLowConfidence !== "true" && queryIncludeLowConfidence !== "false") {
        throw new EngramAccessInputError(
          `include_low_confidence must be one of: true, false (got: ${queryIncludeLowConfidence})`
        );
      }
      const includeLowConfidence = bodyIncludeLowConfidence === true || bodyIncludeLowConfidence === void 0 && queryIncludeLowConfidence === "true";
      const response = await this.service.recall({
        query: body.query ?? "",
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace),
        topK: body.topK,
        mode: body.mode,
        includeDebug: body.includeDebug === true,
        // Forward the validated disclosure depth to the service layer
        // (issue #677).  The zod schema accepts/rejects body values;
        // `resolveRecallDisclosure()` validates the query-param fallback.
        disclosure,
        codingContext,
        // Forward cwd/projectTag for auto git-context resolution (issue #569).
        cwd: body.cwd,
        projectTag: body.projectTag,
        ...asOf !== void 0 ? { asOf } : {},
        ...tags !== void 0 ? { tags } : {},
        ...tagMatch !== void 0 ? { tagMatch } : {},
        ...includeLowConfidence ? { includeLowConfidence: true } : {}
      });
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/coding-context") {
      const body = await this.readValidatedBody(req, "setCodingContext");
      this.service.setCodingContext({
        sessionKey: body.sessionKey,
        codingContext: body.codingContext
      });
      this.respondJson(res, 200, { ok: true });
      return;
    }
    if (req.method === "POST" && (pathname === "/engram/v1/capsules/export" || pathname === "/remnic/v1/capsules/export")) {
      const body = await this.readValidatedBody(req, "capsuleExport");
      this.ensureWriteRateLimitAvailable();
      const result = await this.service.capsuleExport({
        name: body.name,
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        since: body.since,
        includeKinds: body.includeKinds,
        peerIds: body.peerIds,
        includeTranscripts: body.includeTranscripts,
        encrypt: body.encrypt
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "POST" && (pathname === "/engram/v1/capsules/import" || pathname === "/remnic/v1/capsules/import")) {
      const body = await this.readValidatedBody(req, "capsuleImport");
      this.ensureWriteRateLimitAvailable();
      const result = await this.service.capsuleImport({
        archivePath: expandTildePath(body.archivePath),
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        mode: body.mode
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
      const body = await this.readValidatedBody(req, "recallExplain");
      const response = await this.service.recallExplain({
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace)
      });
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/recall/tier-explain") {
      const sessionParam = parsed.searchParams.get("session");
      const sessionKey = sessionParam && sessionParam.length > 0 ? sessionParam : void 0;
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
      );
      const payload = await this.service.recallTierExplain(
        sessionKey,
        namespace,
        this.resolveRequestPrincipal(req)
      );
      this.respondJson(res, 200, payload);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/recall/xray") {
      const queryParam = parsed.searchParams.get("q");
      if (!queryParam || queryParam.trim().length === 0) {
        this.respondJson(res, 400, {
          error: "missing_query",
          code: "missing_query",
          message: "q search parameter is required and must be non-empty"
        });
        return;
      }
      const sessionParam = parsed.searchParams.get("session");
      const sessionKey = sessionParam && sessionParam.length > 0 ? sessionParam : void 0;
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
      );
      const budgetParam = parsed.searchParams.get("budget");
      let budget;
      if (budgetParam !== null && budgetParam !== "") {
        const parsedBudget = Number(budgetParam);
        if (!Number.isFinite(parsedBudget) || parsedBudget <= 0 || !Number.isInteger(parsedBudget)) {
          this.respondJson(res, 400, {
            error: "invalid_budget",
            code: "invalid_budget",
            message: "budget expects a positive integer"
          });
          return;
        }
        budget = parsedBudget;
      }
      const disclosureParam = parsed.searchParams.get("disclosure");
      let disclosure;
      if (disclosureParam !== null && disclosureParam.length > 0) {
        if (!isRecallDisclosure(disclosureParam)) {
          this.respondJson(res, 400, {
            error: "invalid_disclosure",
            code: "invalid_disclosure",
            message: "disclosure must be one of: chunk, section, raw"
          });
          return;
        }
        disclosure = disclosureParam;
      }
      let payload;
      try {
        payload = await this.service.recallXray({
          query: queryParam,
          sessionKey,
          namespace,
          budget,
          authenticatedPrincipal: this.resolveRequestPrincipal(req),
          ...disclosure !== void 0 ? { disclosure } : {}
        });
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        if (message.startsWith("recallXray:")) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message
          });
          return;
        }
        throw err;
      }
      this.respondJson(res, 200, payload);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/observe") {
      const body = await this.readValidatedBody(req, "observe");
      this.ensureWriteRateLimitAvailable();
      const response = await this.service.observe({
        sessionKey: body.sessionKey,
        messages: body.messages.map((message) => ({
          role: message.role,
          content: message.content,
          sourceFormat: message.sourceFormat ?? void 0,
          rawContent: message.rawContent ?? void 0,
          parts: message.parts ?? void 0
        })),
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        skipExtraction: body.skipExtraction === true,
        // Forward cwd/projectTag for auto git-context resolution (issue #569).
        cwd: body.cwd,
        projectTag: body.projectTag
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 202, response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/lcm/search") {
      const body = await this.readValidatedBody(req, "lcmSearch");
      const response = await this.service.lcmSearch({
        query: body.query,
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        limit: body.limit
      });
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/lcm/status") {
      this.respondJson(res, 200, await this.service.lcmStatus());
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/memories") {
      const body = await this.readValidatedBody(req, "memoryStore");
      const request = {
        schemaVersion: body.schemaVersion,
        idempotencyKey: body.idempotencyKey,
        dryRun: body.dryRun === true,
        sessionKey: body.sessionKey,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        content: body.content,
        category: body.category,
        confidence: body.confidence,
        namespace: this.resolveNamespace(req, body.namespace),
        tags: body.tags,
        entityRef: body.entityRef,
        ttl: body.ttl,
        sourceReason: body.sourceReason
      };
      const idempotencyStatus = await this.service.peekMemoryStoreIdempotency(request);
      if (idempotencyStatus === "miss" && request.dryRun !== true) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.memoryStore(request);
      if (this.shouldCountWriteRateLimit(response)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, this.writeResponseStatus(response), response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/suggestions") {
      const body = await this.readValidatedBody(req, "suggestionSubmit");
      const request = {
        schemaVersion: body.schemaVersion,
        idempotencyKey: body.idempotencyKey,
        dryRun: body.dryRun === true,
        sessionKey: body.sessionKey,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        content: body.content,
        category: body.category,
        confidence: body.confidence,
        namespace: this.resolveNamespace(req, body.namespace),
        tags: body.tags,
        entityRef: body.entityRef,
        ttl: body.ttl,
        sourceReason: body.sourceReason
      };
      const idempotencyStatus = await this.service.peekSuggestionSubmitIdempotency(request);
      if (idempotencyStatus === "miss" && request.dryRun !== true) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.suggestionSubmit(request);
      if (this.shouldCountWriteRateLimit(response)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, this.writeResponseStatus(response), response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/memories") {
      const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
      const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
      const sortParam = parsed.searchParams.get("sort") ?? void 0;
      const sort = sortParam === "updated_desc" || sortParam === "updated_asc" || sortParam === "created_desc" || sortParam === "created_asc" ? sortParam : void 0;
      const response = await this.service.memoryBrowse({
        query: parsed.searchParams.get("q") ?? void 0,
        status: parsed.searchParams.get("status") ?? void 0,
        category: parsed.searchParams.get("category") ?? void 0,
        namespace: parsed.searchParams.get("namespace") ?? void 0,
        sort,
        limit: Number.isFinite(limitRaw) ? limitRaw : 50,
        offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
      });
      this.respondJson(res, 200, response);
      return;
    }
    const memoryMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)$/);
    if (req.method === "GET" && memoryMatch) {
      const memoryId = decodeURIComponent(memoryMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? void 0;
      const response = await this.service.memoryGet(memoryId, namespace, this.resolveRequestPrincipal(req));
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }
    const timelineMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)\/timeline$/);
    if (req.method === "GET" && timelineMatch) {
      const memoryId = decodeURIComponent(timelineMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? void 0;
      const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "200", 10);
      const limit = Number.isFinite(limitRaw) ? limitRaw : 200;
      const response = await this.service.memoryTimeline(memoryId, namespace, limit, this.resolveRequestPrincipal(req));
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/entities") {
      const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
      const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
      const response = await this.service.entityList({
        namespace: parsed.searchParams.get("namespace") ?? void 0,
        query: parsed.searchParams.get("q") ?? void 0,
        limit: Number.isFinite(limitRaw) ? limitRaw : 50,
        offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
      });
      this.respondJson(res, 200, response);
      return;
    }
    const entityMatch = pathname.match(/^\/engram\/v1\/entities\/([^/]+)$/);
    if (req.method === "GET" && entityMatch) {
      const entityName = decodeURIComponent(entityMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? void 0;
      const response = await this.service.entityGet(entityName, namespace);
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/review-queue") {
      const response = await this.service.reviewQueue(
        parsed.searchParams.get("runId") ?? void 0,
        parsed.searchParams.get("namespace") ?? void 0,
        this.resolveRequestPrincipal(req)
      );
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/maintenance") {
      this.respondJson(res, 200, await this.service.maintenance(parsed.searchParams.get("namespace") ?? void 0, this.resolveRequestPrincipal(req)));
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/quality") {
      this.respondJson(res, 200, await this.service.quality(parsed.searchParams.get("namespace") ?? void 0, this.resolveRequestPrincipal(req)));
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/trust-zones/status") {
      this.respondJson(
        res,
        200,
        await this.service.trustZoneStatus(parsed.searchParams.get("namespace") ?? void 0, this.resolveRequestPrincipal(req))
      );
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/procedural/stats") {
      const namespaceParam = parsed.searchParams.get("namespace");
      this.respondJson(
        res,
        200,
        await this.service.procedureStats(
          {
            namespace: this.resolveNamespace(
              req,
              namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
            )
          },
          this.resolveRequestPrincipal(req)
        )
      );
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/trust-zones/records") {
      const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "25", 10);
      const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
      const response = await this.service.trustZoneBrowse({
        query: parsed.searchParams.get("q") ?? void 0,
        zone: parseTrustZoneFilter(parsed.searchParams.get("zone")),
        kind: parseTrustZoneKindFilter(parsed.searchParams.get("kind")),
        sourceClass: parseTrustZoneSourceClassFilter(parsed.searchParams.get("sourceClass")),
        namespace: parsed.searchParams.get("namespace") ?? void 0,
        limit: Number.isFinite(limitRaw) ? limitRaw : 25,
        offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
      }, this.resolveRequestPrincipal(req));
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/review-disposition") {
      const body = await this.readValidatedBody(req, "reviewDisposition");
      this.ensureWriteRateLimitAvailable();
      const response = await this.service.reviewDisposition({
        memoryId: body.memoryId,
        status: body.status,
        reasonCode: body.reasonCode,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req)
      });
      if (this.shouldCountWriteRateLimit(response)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/trust-zones/promote") {
      const body = await this.readValidatedBody(req, "trustZonePromote");
      const dryRun = body.dryRun === true;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.trustZonePromote({
        recordId: body.recordId,
        targetZone: body.targetZone,
        promotionReason: body.promotionReason,
        recordedAt: body.recordedAt,
        summary: body.summary,
        dryRun,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req)
      });
      if (this.shouldCountWriteRateLimit(response)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, response.dryRun ? 200 : 201, response);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/trust-zones/demo-seed") {
      const body = await this.readValidatedBody(req, "trustZoneDemoSeed");
      const dryRun = body.dryRun === true;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.trustZoneDemoSeed({
        scenario: body.scenario,
        recordedAt: body.recordedAt,
        dryRun,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req)
      });
      if (this.shouldCountWriteRateLimit(response)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, response.dryRun ? 200 : 201, response);
      return;
    }
    if (req.method === "POST" && pathname === "/v1/citations/observed") {
      const body = await this.readJsonBody(req);
      if (!body || typeof body !== "object" || Array.isArray(body)) {
        throw new HttpError(400, "request body must be a JSON object", "invalid_body");
      }
      const payload = body;
      const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : void 0;
      const namespace = typeof payload.namespace === "string" ? payload.namespace : void 0;
      const citationsRaw = payload.citations;
      if (!citationsRaw || typeof citationsRaw !== "object" || Array.isArray(citationsRaw)) {
        throw new HttpError(400, "citations must be a JSON object with entries and rolloutIds", "invalid_citations");
      }
      const citObj = citationsRaw;
      const entries = [];
      if (Array.isArray(citObj.entries)) {
        for (const raw of citObj.entries) {
          if (raw && typeof raw === "object" && !Array.isArray(raw)) {
            const e = raw;
            if (typeof e.path === "string" && typeof e.lineStart === "number" && typeof e.lineEnd === "number") {
              entries.push({
                path: e.path,
                lineStart: e.lineStart,
                lineEnd: e.lineEnd,
                note: typeof e.note === "string" ? e.note : ""
              });
            }
          }
        }
      }
      const rolloutIds = [];
      if (Array.isArray(citObj.rolloutIds)) {
        for (const id of citObj.rolloutIds) {
          if (typeof id === "string" && id.length > 0) {
            rolloutIds.push(id);
          }
        }
      }
      let matched = 0;
      let submitted = 0;
      if (typeof this.service.recordCitationUsage === "function") {
        const result = await this.service.recordCitationUsage({
          sessionId,
          namespace: this.resolveNamespace(req, namespace),
          authenticatedPrincipal: this.resolveRequestPrincipal(req),
          entries,
          rolloutIds
        });
        submitted = result.submitted;
        matched = result.matched;
      }
      this.respondJson(res, 200, {
        ok: true,
        submitted,
        matched,
        entriesReceived: entries.length,
        rolloutIdsReceived: rolloutIds.length
      });
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/review/contradictions") {
      const VALID_FILTERS = /* @__PURE__ */ new Set(["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"]);
      const rawFilter = parsed.searchParams.get("filter") ?? "unresolved";
      if (!VALID_FILTERS.has(rawFilter)) {
        this.respondJson(res, 400, { error: `Invalid filter '${rawFilter}'. Valid: ${[...VALID_FILTERS].join(", ")}` });
        return;
      }
      const namespace = parsed.searchParams.get("namespace") ?? void 0;
      const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
      const { listPairs } = await import("./contradiction-review-5LTTVDQV.js");
      const result = listPairs(this.service.memoryDir, {
        filter: rawFilter,
        namespace,
        limit: Number.isFinite(limitRaw) ? limitRaw : 50
      });
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "GET" && pathname.startsWith("/engram/v1/review/contradictions/")) {
      const pairId = pathname.split("/").pop() ?? "";
      const { readPair } = await import("./contradiction-review-5LTTVDQV.js");
      const pair = readPair(this.service.memoryDir, pairId);
      if (!pair) {
        this.respondJson(res, 404, { error: "pair_not_found" });
        return;
      }
      this.respondJson(res, 200, pair);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/review/resolve") {
      const body = await this.readJsonBody(req);
      const pairId = typeof body.pairId === "string" ? body.pairId : "";
      const verb = typeof body.verb === "string" ? body.verb : "";
      if (!pairId || !verb) {
        this.respondJson(res, 400, { error: "pairId and verb are required" });
        return;
      }
      const { isValidResolutionVerb, executeResolution } = await import("./resolution-YGIBORXI.js");
      if (!isValidResolutionVerb(verb)) {
        this.respondJson(res, 400, { error: `Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context` });
        return;
      }
      const result = await executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb);
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/graph/snapshot") {
      const limitRaw = parsed.searchParams.get("limit");
      let limit;
      if (limitRaw !== null && limitRaw.length > 0) {
        const parsedLimit = Number(limitRaw);
        if (!Number.isFinite(parsedLimit) || !Number.isInteger(parsedLimit) || parsedLimit <= 0) {
          this.respondJson(res, 400, {
            error: "invalid_limit",
            code: "invalid_limit",
            message: "limit must be a positive integer"
          });
          return;
        }
        limit = parsedLimit;
      }
      const sinceRaw = parsed.searchParams.get("since");
      let since;
      if (sinceRaw !== null && sinceRaw.length > 0) {
        if (!Number.isFinite(Date.parse(sinceRaw))) {
          this.respondJson(res, 400, {
            error: "invalid_since",
            code: "invalid_since",
            message: "since must be a parseable ISO timestamp"
          });
          return;
        }
        since = sinceRaw;
      }
      const focusNodeIdRaw = parsed.searchParams.get("focusNodeId");
      const focusNodeId = focusNodeIdRaw && focusNodeIdRaw.length > 0 ? focusNodeIdRaw : void 0;
      const categoriesRaw = parsed.searchParams.get("categories");
      let categories;
      if (categoriesRaw !== null && categoriesRaw.length > 0) {
        categories = categoriesRaw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
        if (categories.length === 0) {
          this.respondJson(res, 400, {
            error: "invalid_categories",
            code: "invalid_categories",
            message: "categories must be a comma-separated list with at least one non-empty value"
          });
          return;
        }
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0
      );
      try {
        const snapshot = await this.service.graphSnapshot(
          {
            namespace,
            ...limit !== void 0 ? { limit } : {},
            ...since !== void 0 ? { since } : {},
            ...focusNodeId !== void 0 ? { focusNodeId } : {},
            ...categories !== void 0 ? { categories } : {}
          },
          this.resolveRequestPrincipal(req)
        );
        this.respondJson(res, 200, snapshot);
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        if (message.startsWith("graphSnapshot:")) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message
          });
          return;
        }
        throw err;
      }
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/contradiction-scan") {
      const body = await this.readJsonBody(req);
      const { runContradictionScan } = await import("./contradiction-scan-3Z6YW7YA.js");
      const result = await runContradictionScan({
        storage: this.service.storageRef,
        config: this.service.configRef,
        memoryDir: this.service.memoryDir,
        embeddingLookupFactory: this.service.embeddingLookupFactoryRef,
        localLlm: this.service.localLlmRef,
        fallbackLlm: this.service.fallbackLlmRef,
        namespace: typeof body.namespace === "string" ? body.namespace : void 0
      });
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/graph/events") {
      await this.handleGraphEventsSSE(req, res);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/console/state") {
      const namespace = parsed.searchParams.get("namespace") ?? void 0;
      const snapshot = await this.service.consoleState(namespace, this.resolveRequestPrincipal(req));
      this.respondJson(res, 200, snapshot);
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/peers") {
      const result = await this.service.peerList();
      this.respondJson(res, 200, result);
      return;
    }
    const peerProfileMatch = /^\/engram\/v1\/peers\/([^/]+)\/profile$/.exec(pathname);
    if (peerProfileMatch) {
      if (req.method !== "GET") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const peerId = decodePeerIdSegment(peerProfileMatch[1] ?? "");
      const result = await this.service.peerProfileGet(peerId);
      if (!result.found) {
        this.respondJson(res, 404, { error: "peer_profile_not_found", code: "peer_profile_not_found" });
        return;
      }
      this.respondJson(res, 200, result);
      return;
    }
    const peerIdMatch = /^\/engram\/v1\/peers\/([^/]+)$/.exec(pathname);
    if (peerIdMatch) {
      const peerId = decodePeerIdSegment(peerIdMatch[1] ?? "");
      if (req.method === "GET") {
        const result = await this.service.peerGet(peerId);
        if (!result.found) {
          this.respondJson(res, 404, { error: "peer_not_found", code: "peer_not_found" });
          return;
        }
        this.respondJson(res, 200, result);
        return;
      }
      if (req.method === "PUT") {
        const body = await this.readJsonBody(req);
        if ("kind" in body && body.kind !== void 0 && typeof body.kind !== "string") {
          throw new EngramAccessInputError("kind must be a string when provided");
        }
        if ("displayName" in body && body.displayName !== void 0 && typeof body.displayName !== "string") {
          throw new EngramAccessInputError("displayName must be a string when provided");
        }
        if ("notes" in body && body.notes !== void 0 && typeof body.notes !== "string") {
          throw new EngramAccessInputError("notes must be a string when provided");
        }
        const result = await this.service.peerSet({
          id: peerId,
          kind: typeof body.kind === "string" ? body.kind : void 0,
          displayName: typeof body.displayName === "string" ? body.displayName : void 0,
          notes: typeof body.notes === "string" ? body.notes : void 0
        });
        this.respondJson(res, result.created ? 201 : 200, result);
        return;
      }
      if (req.method === "DELETE") {
        const forgetParam = parsed.searchParams.get("forget");
        if (forgetParam === "true") {
          const body = await this.readJsonBody(req);
          const confirm = typeof body.confirm === "string" ? body.confirm : "";
          if (confirm !== "yes") {
            this.respondJson(res, 400, {
              error: "confirm_required",
              code: "confirm_required",
              message: "DELETE ?forget=true requires { confirm: 'yes' } in the request body"
            });
            return;
          }
          const result2 = await this.service.peerForget(peerId, { confirm: "yes" });
          this.respondJson(res, 200, result2);
          return;
        }
        const result = await this.service.peerDelete(peerId);
        this.respondJson(res, 200, result);
        return;
      }
      this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
      return;
    }
    if (req.method === "GET" && pathname === "/engram/v1/dreams/status") {
      const { normalizeDreamsStatusWindowHours } = await import("./dreams-ledger-LR2NBAZE.js");
      const windowHoursRaw = parsed.searchParams.get("windowHours");
      let windowHours;
      try {
        windowHours = normalizeDreamsStatusWindowHours(
          windowHoursRaw !== null ? Number(windowHoursRaw) : void 0
        );
      } catch {
        this.respondJson(res, 400, { error: "windowHours must be a positive integer" });
        return;
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0;
      const result = await this.service.dreamsStatus({
        windowHours,
        namespace,
        principal: this.resolveRequestPrincipal(req)
      });
      this.respondJson(res, 200, result);
      return;
    }
    if (req.method === "POST" && pathname === "/engram/v1/dreams/run") {
      const body = await this.readJsonBody(req);
      const VALID_PHASES = ["lightSleep", "rem", "deepSleep"];
      const phase = typeof body.phase === "string" ? body.phase : void 0;
      if (!phase || !VALID_PHASES.includes(phase)) {
        this.respondJson(res, 400, {
          error: `phase is required and must be one of: ${VALID_PHASES.join(", ")}`
        });
        return;
      }
      if ("dryRun" in body && body.dryRun !== void 0 && typeof body.dryRun !== "boolean") {
        this.respondJson(res, 400, {
          error: "dryRun must be a boolean when provided"
        });
        return;
      }
      if ("namespace" in body && body.namespace !== void 0 && typeof body.namespace !== "string") {
        this.respondJson(res, 400, {
          error: "namespace must be a string when provided"
        });
        return;
      }
      const dryRun = body.dryRun === true;
      const namespace = typeof body.namespace === "string" ? body.namespace : void 0;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const result = await this.service.dreamsRun({
        phase,
        dryRun,
        namespace,
        authenticatedPrincipal: this.resolveRequestPrincipal(req)
      });
      if (this.shouldCountWriteRateLimit(result)) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, result);
      return;
    }
    this.respondJson(res, 404, { error: "not_found", code: "not_found" });
  }
  /**
   * SSE handler for /engram/v1/graph/events.
   *
   * Lifecycle:
   *  1. Write SSE headers (Content-Type: text/event-stream).
   *  2. Register this response in `sseClients`.
   *  3. Resolve the namespace from the request and subscribe to THAT
   *     namespace's graph event bus (Codex P1: in multi-namespace
   *     deployments each namespace has its own bus keyed by its storage
   *     dir — subscribing to the global root leaks events across tenants).
   *  4. On each event, add to a 200 ms batch; flush batch as a single SSE frame.
   *  5. Send heartbeat every 25 s.
   *  6. On client disconnect (req "close"), clean up timers and unsubscribe.
   *  7. Register the cleanup callback in `sseCleanupFns` so `stop()` can
   *     release the heartbeat interval and bus subscription even when the
   *     client never disconnects (Cursor review thread `access-http.ts:232`).
   */
  async handleGraphEventsSSE(req, res) {
    const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
    const namespaceParam = parsed.searchParams.get("namespace");
    const namespace = namespaceParam && namespaceParam.length > 0 ? namespaceParam : void 0;
    const principal = this.resolveRequestPrincipal(req);
    const memoryDir = await this.service.getMemoryDirForNamespace(namespace, principal);
    res.writeHead(200, {
      "content-type": "text/event-stream; charset=utf-8",
      "cache-control": "no-cache, no-store, must-revalidate",
      "connection": "keep-alive",
      "x-accel-buffering": "no",
      // prevent nginx buffering
      "transfer-encoding": "chunked"
    });
    const writeSSE = (payload) => {
      try {
        res.write(`data: ${JSON.stringify(payload)}

`);
      } catch {
      }
    };
    writeSSE({ type: "connected" });
    this.sseClients.add(res);
    const flushBatch = () => {
      const batch = this.ssePendingBatches.get(res);
      if (!batch || batch.length === 0) return;
      this.ssePendingBatches.delete(res);
      this.sseBatchTimers.delete(res);
      writeSSE({ type: "batch", events: batch });
    };
    const unsubscribe = subscribeGraphEvents(memoryDir, (event) => {
      let batch = this.ssePendingBatches.get(res);
      if (!batch) {
        batch = [];
        this.ssePendingBatches.set(res, batch);
      }
      batch.push(event);
      if (!this.sseBatchTimers.has(res)) {
        this.sseBatchTimers.set(res, setTimeout(flushBatch, 200));
      }
    });
    const heartbeatInterval = setInterval(() => {
      writeSSE({ type: "heartbeat" });
    }, 25e3);
    const cleanup = () => {
      clearInterval(heartbeatInterval);
      const timer = this.sseBatchTimers.get(res);
      if (timer !== void 0) {
        clearTimeout(timer);
        this.sseBatchTimers.delete(res);
      }
      this.ssePendingBatches.delete(res);
      unsubscribe();
      this.sseClients.delete(res);
      this.sseCleanupFns.delete(cleanup);
      try {
        res.end();
      } catch {
      }
    };
    this.sseCleanupFns.add(cleanup);
    req.once("close", cleanup);
    req.once("error", cleanup);
  }
  async handleMcpRequest(req, res) {
    const body = await this.readJsonBody(req);
    const request = body;
    const toolName = typeof request.params?.name === "string" ? request.params.name : "";
    const toolArgs = request.params?.arguments;
    const dreamsRunDryRun = (toolName === "engram.dreams_run" || toolName === "remnic.dreams_run") && toolArgs !== null && typeof toolArgs === "object" && !Array.isArray(toolArgs) && toolArgs.dryRun === true;
    const memoryActionApplyDryRun = (toolName === "engram.memory_action_apply" || toolName === "remnic.memory_action_apply") && toolArgs !== null && typeof toolArgs === "object" && !Array.isArray(toolArgs) && toolArgs.dryRun === true;
    const isMcpWrite = request.method === "tools/call" && (toolName === "engram.memory_store" || toolName === "remnic.memory_store" || toolName === "engram.suggestion_submit" || toolName === "remnic.suggestion_submit" || toolName === "engram.observe" || toolName === "remnic.observe" || toolName === "engram.capsule_export" || toolName === "remnic.capsule_export" || toolName === "engram.capsule_import" || toolName === "remnic.capsule_import" || !dreamsRunDryRun && (toolName === "engram.dreams_run" || toolName === "remnic.dreams_run") || !memoryActionApplyDryRun && (toolName === "engram.memory_action_apply" || toolName === "remnic.memory_action_apply"));
    if (isMcpWrite) {
      this.ensureWriteRateLimitAvailable();
    }
    const sessionId = (() => {
      const raw = req.headers["mcp-session-id"];
      return typeof raw === "string" ? raw.trim() : void 0;
    })();
    const mcpCorrelationId = correlationIdStore.getStore() ?? randomUUID();
    const response = await this.mcpServer.handleRequest(request, {
      principalOverride: this.resolveRequestPrincipal(req),
      sessionId,
      correlationId: mcpCorrelationId
    });
    if (isMcpWrite && response !== null) {
      const result = response.result;
      const isError = result?.isError === true;
      const structured = result?.structuredContent;
      if (!isError && structured && this.shouldCountWriteRateLimit(structured)) {
        this.recordWriteRateLimitHit();
      }
    }
    if (response === null) {
      res.statusCode = 202;
      res.end();
      return;
    }
    const assignedSessionId = this.mcpServer.popInitSessionId(mcpCorrelationId);
    if (assignedSessionId) {
      res.setHeader("mcp-session-id", assignedSessionId);
    }
    this.respondJson(res, 200, response);
  }
  respondJson(res, status, payload) {
    const body = JSON.stringify(payload, null, 2);
    res.statusCode = status;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.setHeader("content-length", String(Buffer.byteLength(body)));
    const cid = correlationIdStore.getStore();
    if (cid) {
      res.setHeader("x-request-id", cid);
    }
    res.end(body);
  }
  async handleAdminConsole(req, res, pathname) {
    if (req.method !== "GET") return false;
    if (pathname === "/remnic/ui" || pathname === "/engram/ui") {
      res.statusCode = 301;
      res.setHeader("location", pathname + "/");
      res.end();
      return true;
    }
    if (pathname === "/remnic/ui/" || pathname === "/engram/ui/") {
      await this.respondStatic(res, path.join(this.adminConsolePublicDir, "index.html"), "text/html; charset=utf-8");
      return true;
    }
    if (pathname === "/remnic/ui/app.js" || pathname === "/engram/ui/app.js") {
      await this.respondStatic(res, path.join(this.adminConsolePublicDir, "app.js"), "application/javascript; charset=utf-8");
      return true;
    }
    return false;
  }
  async respondStatic(res, filePath, contentType) {
    try {
      const body = await readFile(filePath, "utf-8");
      res.statusCode = 200;
      res.setHeader("content-type", contentType);
      res.setHeader("content-length", String(Buffer.byteLength(body)));
      res.end(body);
    } catch {
      this.respondJson(res, 404, { error: "not_found" });
    }
  }
  async readJsonBody(req) {
    const chunks = [];
    let total = 0;
    for await (const chunk of req) {
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      total += buffer.length;
      if (total > this.maxBodyBytes) {
        throw new HttpError(413, "request_body_too_large", "request_body_too_large");
      }
      chunks.push(buffer);
    }
    if (chunks.length === 0) return {};
    const raw = Buffer.concat(chunks).toString("utf-8").trim();
    if (raw.length === 0) return {};
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      throw new HttpError(400, "invalid_json", "invalid_json");
    }
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      throw new HttpError(400, "invalid_json_object", "invalid_json_object");
    }
    return parsed;
  }
  async readValidatedBody(req, schemaName) {
    const raw = await this.readJsonBody(req);
    const result = validateRequest(schemaName, raw);
    if (!result.success) {
      throw new HttpError(400, result.error.error, "validation_error", result.error.details);
    }
    return result.data;
  }
  isAuthorized(req, pathname) {
    if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) return false;
    const raw = req.headers.authorization;
    let candidate = null;
    if (raw) {
      const separator = raw.indexOf(" ");
      if (separator > 0) {
        const scheme = raw.slice(0, separator).toLowerCase();
        if (scheme === "bearer") {
          candidate = raw.slice(separator + 1).trim();
        }
      }
    }
    if (!candidate && pathname === "/engram/v1/graph/events") {
      try {
        const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
        const queryToken = parsed.searchParams.get("token");
        if (queryToken && queryToken.length > 0) {
          candidate = queryToken;
        }
      } catch {
      }
    }
    if (!candidate) return false;
    const token = candidate;
    if (this.authToken && this.timingSafeStringEqual(token, this.authToken)) return true;
    for (const valid of this.authTokens) {
      if (this.timingSafeStringEqual(token, valid)) return true;
    }
    if (this.authTokensGetter) {
      for (const valid of this.authTokensGetter()) {
        if (this.timingSafeStringEqual(token, valid)) return true;
      }
    }
    return false;
  }
  timingSafeStringEqual(a, b) {
    const left = this.encodeSecret(a);
    const right = this.encodeSecret(b);
    if (!left || !right) return false;
    return timingSafeEqual(left, right);
  }
  encodeSecret(value) {
    const encoded = Buffer.from(value, "utf-8");
    if (encoded.length > 1024) return null;
    const out = Buffer.alloc(2 + 1024);
    out.writeUInt16BE(encoded.length, 0);
    encoded.copy(out, 2);
    return out;
  }
  writeResponseStatus(response) {
    if (response.dryRun === true) return 200;
    if (response.status === "stored" || response.status === "queued_for_review") return 201;
    return 200;
  }
  ensureWriteRateLimitAvailable() {
    const now = Date.now();
    while (this.writeRequestTimestamps.length > 0 && now - (this.writeRequestTimestamps[0] ?? 0) > WRITE_RATE_LIMIT_WINDOW_MS) {
      this.writeRequestTimestamps.shift();
    }
    if (this.writeRequestTimestamps.length >= WRITE_RATE_LIMIT_MAX_REQUESTS) {
      throw new HttpError(429, "write_rate_limited", "write_rate_limited");
    }
  }
  recordWriteRateLimitHit() {
    this.writeRequestTimestamps.push(Date.now());
  }
  shouldCountWriteRateLimit(response) {
    return response.dryRun !== true && response.idempotencyReplay !== true;
  }
};

export {
  EngramAccessHttpServer
};
//# sourceMappingURL=chunk-36CTNQY7.js.map