// src/peers/migrate-from-identity-anchor.ts
import { promises as fs2, constants as fsConstants2 } from "fs";
import path2 from "path";

// src/peers/storage.ts
import { promises as fs, constants as fsConstants } from "fs";
import path from "path";

// src/peers/types.ts
var PEER_ID_PATTERN = /^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/;
var PEER_ID_MAX_LENGTH = 64;

// src/peers/storage.ts
async function openNoFollow(file, flags) {
  return fs.open(file, flags | fsConstants.O_NOFOLLOW);
}
async function readFileNoFollow(file) {
  await assertParentDirInodeStable(file);
  const fh = await openNoFollow(file, fsConstants.O_RDONLY);
  try {
    return await fh.readFile("utf8");
  } finally {
    await fh.close();
  }
}
async function assertParentDirInodeStable(filePath) {
  const parent = path.dirname(filePath);
  const dh = await fs.open(
    parent,
    fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW
  );
  try {
    const fstatInfo = await dh.stat();
    const lstatInfo = await fs.lstat(parent);
    if (fstatInfo.ino !== lstatInfo.ino || fstatInfo.dev !== lstatInfo.dev) {
      throw new Error(
        `parent directory "${parent}" was swapped between checks (inode mismatch)`
      );
    }
    if (lstatInfo.isSymbolicLink()) {
      throw new Error(`parent directory "${parent}" is a symlink and is rejected`);
    }
  } finally {
    await dh.close();
  }
}
async function writeFileNoFollow(file, data) {
  await assertParentDirInodeStable(file);
  const fh = await openNoFollow(
    file,
    fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC
  );
  try {
    await fh.writeFile(data, "utf8");
  } finally {
    await fh.close();
  }
}
async function appendFileNoFollow(file, data) {
  await assertParentDirInodeStable(file);
  const fh = await openNoFollow(
    file,
    fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND
  );
  try {
    await fh.writeFile(data, "utf8");
  } finally {
    await fh.close();
  }
}
var ALLOWED_KINDS = /* @__PURE__ */ new Set([
  "self",
  "human",
  "agent",
  "integration"
]);
function assertValidPeerId(peerId) {
  if (typeof peerId !== "string") {
    throw new Error("peerId must be a string");
  }
  if (peerId.length === 0) {
    throw new Error("peerId must not be empty");
  }
  if (peerId.length > PEER_ID_MAX_LENGTH) {
    throw new Error(`peerId must be \u2264 ${PEER_ID_MAX_LENGTH} characters`);
  }
  if (!PEER_ID_PATTERN.test(peerId)) {
    throw new Error(
      `peerId "${peerId}" is invalid \u2014 must match ${PEER_ID_PATTERN}`
    );
  }
  if (/[.\-_]{2,}/.test(peerId)) {
    throw new Error(
      `peerId "${peerId}" is invalid \u2014 must not contain consecutive separators`
    );
  }
}
function isPlainObject(value) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    return false;
  }
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}
function assertValidKind(kind) {
  if (typeof kind !== "string" || !ALLOWED_KINDS.has(kind)) {
    throw new Error(
      `peer kind must be one of ${Array.from(ALLOWED_KINDS).join(", ")}`
    );
  }
}
var PEERS_DIR_NAME = "peers";
function peersRoot(memoryDir) {
  return path.join(memoryDir, PEERS_DIR_NAME);
}
function peerDir(memoryDir, peerId) {
  assertValidPeerId(peerId);
  const candidate = path.join(peersRoot(memoryDir), peerId);
  const root = peersRoot(memoryDir);
  const relative = path.relative(root, candidate);
  if (relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`peerId "${peerId}" resolves outside peers root`);
  }
  return candidate;
}
async function assertPeersRootNotSymlink(memoryDir) {
  const root = peersRoot(memoryDir);
  let rootStat = null;
  try {
    rootStat = await fs.lstat(root);
  } catch (err) {
    if (err.code !== "ENOENT") throw err;
  }
  if (rootStat && rootStat.isSymbolicLink()) {
    throw new Error(`peers root "${root}" is a symlink and is rejected`);
  }
}
async function mkdirPeerDirAtomic(memoryDir, peerId) {
  const root = peersRoot(memoryDir);
  await fs.mkdir(memoryDir, { recursive: true });
  try {
    await fs.mkdir(root);
  } catch (err) {
    if (err.code !== "EEXIST") throw err;
  }
  const rootLstat = await fs.lstat(root);
  if (rootLstat.isSymbolicLink()) {
    throw new Error(`peers root "${root}" is a symlink and is rejected`);
  }
  if (!rootLstat.isDirectory()) {
    throw new Error(`peers root "${root}" exists but is not a directory`);
  }
  const rootHandle = await fs.open(
    root,
    fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW
  );
  try {
    const candidate = peerDir(memoryDir, peerId);
    await fs.mkdir(candidate, { recursive: true });
    const fstatRoot = await rootHandle.stat();
    const lstatRoot = await fs.lstat(root);
    if (fstatRoot.ino !== lstatRoot.ino || fstatRoot.dev !== lstatRoot.dev) {
      throw new Error(`peers root "${root}" was swapped during mkdir`);
    }
    const peerLstat = await fs.lstat(candidate);
    if (peerLstat.isSymbolicLink()) {
      throw new Error(`peer directory "${peerId}" is a symlink and is rejected`);
    }
  } finally {
    await rootHandle.close();
  }
}
async function assertPeerDirNotEscaped(memoryDir, peerId) {
  const candidate = peerDir(memoryDir, peerId);
  const root = peersRoot(memoryDir);
  await assertPeersRootNotSymlink(memoryDir);
  let candidateStat = null;
  try {
    candidateStat = await fs.lstat(candidate);
  } catch (err) {
    if (err.code !== "ENOENT") throw err;
  }
  if (candidateStat && candidateStat.isSymbolicLink()) {
    throw new Error(`peer directory "${peerId}" is a symlink and is rejected`);
  }
  if (candidateStat) {
    const realRoot = await fs.realpath(root);
    const realCandidate = await fs.realpath(candidate);
    const rel = path.relative(realRoot, realCandidate);
    if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) {
      throw new Error(`peer directory "${peerId}" escapes the peers root`);
    }
  }
}
function identityPath(memoryDir, peerId) {
  return path.join(peerDir(memoryDir, peerId), "identity.md");
}
function profilePath(memoryDir, peerId) {
  return path.join(peerDir(memoryDir, peerId), "profile.md");
}
function interactionsPath(memoryDir, peerId) {
  return path.join(peerDir(memoryDir, peerId), "interactions.log.md");
}
function escapeYamlString(value) {
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
}
function unescapeYamlString(quoted) {
  const body = quoted.slice(1, -1);
  let out = "";
  for (let i = 0; i < body.length; i++) {
    const ch = body[i];
    if (ch === "\\" && i + 1 < body.length) {
      const next = body[i + 1];
      if (next === "\\" || next === '"') {
        out += next;
        i++;
        continue;
      }
      if (next === "n") {
        out += "\n";
        i++;
        continue;
      }
      if (next === "r") {
        out += "\r";
        i++;
        continue;
      }
      if (next === "t") {
        out += "	";
        i++;
        continue;
      }
    }
    out += ch;
  }
  return out;
}
function parsePeerFrontmatter(raw) {
  const text = raw.replace(/^\uFEFF/, "");
  if (!text.startsWith("---")) {
    throw new Error("peer file is missing YAML frontmatter delimiter");
  }
  const after = text.slice(3);
  const close = after.indexOf("\n---");
  if (close === -1) {
    throw new Error("peer file frontmatter is not terminated");
  }
  const fmBlock = after.slice(0, close).replace(/^\n/, "");
  const body = after.slice(close + 4).replace(/^\n/, "");
  const fields = {};
  for (const lineRaw of fmBlock.split("\n")) {
    const line = lineRaw.trim();
    if (line === "" || line.startsWith("#")) continue;
    const colon = line.indexOf(":");
    if (colon === -1) {
      throw new Error(`peer frontmatter line is malformed: ${line}`);
    }
    const key = line.slice(0, colon).trim();
    const valueRaw = line.slice(colon + 1).trim();
    if (key === "") {
      throw new Error(`peer frontmatter has empty key: ${line}`);
    }
    let value;
    if (valueRaw.startsWith('"') && valueRaw.endsWith('"') && valueRaw.length >= 2) {
      value = unescapeYamlString(valueRaw);
    } else {
      value = valueRaw;
    }
    fields[key] = value;
  }
  return { fields, body };
}
function emitPeerIdentity(peer) {
  const lines = ["---"];
  lines.push(`id: ${escapeYamlString(peer.id)}`);
  lines.push(`kind: ${peer.kind}`);
  lines.push(`displayName: ${escapeYamlString(peer.displayName)}`);
  lines.push(`createdAt: ${escapeYamlString(peer.createdAt)}`);
  lines.push(`updatedAt: ${escapeYamlString(peer.updatedAt)}`);
  lines.push("---");
  lines.push("");
  lines.push(peer.notes ?? "");
  let out = lines.join("\n");
  while (out.endsWith("\n")) out = out.slice(0, -1);
  return out + "\n";
}
async function readPeer(memoryDir, peerId) {
  assertValidPeerId(peerId);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const file = identityPath(memoryDir, peerId);
  let raw;
  try {
    raw = await readFileNoFollow(file);
  } catch (err) {
    if (err.code === "ENOENT") {
      return null;
    }
    throw err;
  }
  const { fields, body } = parsePeerFrontmatter(raw);
  const id = fields.id ?? peerId;
  if (id !== peerId) {
    throw new Error(
      `peer identity file mismatch \u2014 expected id "${peerId}", file claims "${id}"`
    );
  }
  const kind = fields.kind;
  assertValidKind(kind);
  const displayName = fields.displayName ?? "";
  const createdAt = fields.createdAt ?? "";
  if (createdAt === "") {
    throw new Error(`peer "${peerId}" is missing createdAt`);
  }
  const rawUpdatedAt = fields.updatedAt;
  const updatedAt = typeof rawUpdatedAt === "string" && rawUpdatedAt.length > 0 ? rawUpdatedAt : createdAt;
  let trimmedBody = body;
  if (trimmedBody.startsWith("\r\n")) trimmedBody = trimmedBody.slice(2);
  else if (trimmedBody.startsWith("\n")) trimmedBody = trimmedBody.slice(1);
  if (trimmedBody.endsWith("\r\n")) trimmedBody = trimmedBody.slice(0, -2);
  else if (trimmedBody.endsWith("\n")) trimmedBody = trimmedBody.slice(0, -1);
  return {
    id: peerId,
    kind,
    displayName,
    createdAt,
    updatedAt,
    notes: trimmedBody === "" ? void 0 : trimmedBody
  };
}
async function writePeer(memoryDir, peer) {
  assertValidPeerId(peer.id);
  assertValidKind(peer.kind);
  if (typeof peer.displayName !== "string") {
    throw new Error("peer.displayName must be a string");
  }
  if (typeof peer.createdAt !== "string" || peer.createdAt === "") {
    throw new Error("peer.createdAt must be a non-empty ISO-8601 string");
  }
  if (typeof peer.updatedAt !== "string" || peer.updatedAt === "") {
    throw new Error("peer.updatedAt must be a non-empty ISO-8601 string");
  }
  if (peer.notes !== void 0 && typeof peer.notes !== "string") {
    throw new Error("peer.notes must be a string when provided");
  }
  await mkdirPeerDirAtomic(memoryDir, peer.id);
  await assertPeerDirNotEscaped(memoryDir, peer.id);
  const file = identityPath(memoryDir, peer.id);
  await writeFileNoFollow(file, emitPeerIdentity(peer));
}
async function deletePeer(memoryDir, peerId) {
  assertValidPeerId(peerId);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const file = identityPath(memoryDir, peerId);
  let lstatBefore;
  try {
    lstatBefore = await fs.lstat(file);
  } catch (err) {
    if (err.code === "ENOENT") {
      return false;
    }
    throw err;
  }
  if (lstatBefore.isSymbolicLink()) {
    throw new Error(`refusing to unlink "${file}": target is a symlink`);
  }
  if (!lstatBefore.isFile()) {
    throw new Error(`refusing to unlink "${file}": target is not a regular file`);
  }
  await assertParentDirInodeStable(file);
  try {
    await fs.unlink(file);
  } catch (err) {
    if (err.code === "ENOENT") {
      return false;
    }
    throw err;
  }
  return true;
}
async function forgetPeer(memoryDir, peerId, opts) {
  if (opts.confirm !== "yes") {
    throw new Error("forgetPeer requires confirm: 'yes'");
  }
  assertValidPeerId(peerId);
  await assertPeersRootNotSymlink(memoryDir);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const dir = peerDir(memoryDir, peerId);
  let dirStat;
  try {
    dirStat = await fs.lstat(dir);
  } catch (err) {
    if (err.code === "ENOENT") {
      return { purged: false };
    }
    throw err;
  }
  if (dirStat.isSymbolicLink()) {
    throw new Error(
      `refusing to purge peer directory "${peerId}": target is a symlink`
    );
  }
  if (!dirStat.isDirectory()) {
    throw new Error(
      `refusing to purge peer directory "${peerId}": target is not a directory`
    );
  }
  await fs.rm(dir, { recursive: true, force: true });
  return { purged: true };
}
async function listPeers(memoryDir) {
  const root = peersRoot(memoryDir);
  let entries;
  let dh = null;
  try {
    dh = await fs.open(
      root,
      fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW
    );
    const fstatBefore = await dh.stat();
    entries = await fs.readdir(root);
    const lstatAfter = await fs.lstat(root);
    if (fstatBefore.ino !== lstatAfter.ino || fstatBefore.dev !== lstatAfter.dev || lstatAfter.isSymbolicLink()) {
      throw new Error(`peers root "${root}" was swapped during readdir`);
    }
  } catch (err) {
    if (err.code === "ENOENT") {
      return [];
    }
    throw err;
  } finally {
    if (dh) await dh.close();
  }
  const peers = [];
  entries.sort();
  for (const name of entries) {
    if (!PEER_ID_PATTERN.test(name) || name.length > PEER_ID_MAX_LENGTH) {
      continue;
    }
    let stat;
    try {
      stat = await fs.lstat(path.join(root, name));
    } catch (err) {
      if (err.code === "ENOENT") continue;
      throw err;
    }
    if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
    let peer = null;
    try {
      peer = await readPeer(memoryDir, name);
    } catch (err) {
      const code = err.code;
      const message = err instanceof Error ? err.message : String(err);
      const isSecurityFailure = message.startsWith("peers root") || message.startsWith("peer directory") || message.startsWith("parent directory") || message.startsWith("path ") || message.includes("escapes the peers root") || message.includes("inode mismatch") || message.includes("is a symlink and is rejected") || message.includes("was swapped");
      if (isSecurityFailure) throw err;
      if (code && code !== "ENOENT") throw err;
      continue;
    }
    if (peer !== null) {
      peers.push(peer);
    }
  }
  return peers;
}
function sanitizeLogField(value) {
  return value.replace(/[\r\n\t]+/g, " ").trim();
}
function formatLogEntry(entry) {
  const ts = sanitizeLogField(entry.timestamp);
  const kind = sanitizeLogField(entry.kind);
  const summary = sanitizeLogField(entry.summary);
  const session = entry.sessionId ? ` [session=${sanitizeLogField(entry.sessionId)}]` : "";
  return `- [${ts}] (${kind})${session} ${summary}`;
}
async function appendInteractionLog(memoryDir, peerId, entry) {
  assertValidPeerId(peerId);
  if (typeof entry.timestamp !== "string" || entry.timestamp.trim() === "") {
    throw new Error("interaction entry must have a non-whitespace timestamp");
  }
  if (typeof entry.kind !== "string" || entry.kind.trim() === "") {
    throw new Error("interaction entry must have a non-whitespace kind");
  }
  if (typeof entry.summary !== "string") {
    throw new Error("interaction entry must have a string summary");
  }
  if (entry.sessionId !== void 0) {
    if (typeof entry.sessionId !== "string" || entry.sessionId.trim() === "") {
      throw new Error("interaction entry sessionId must be a non-whitespace string when provided");
    }
  }
  await mkdirPeerDirAtomic(memoryDir, peerId);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const file = interactionsPath(memoryDir, peerId);
  const line = formatLogEntry(entry) + "\n";
  await appendFileNoFollow(file, line);
  return file;
}
function emitPeerProfile(profile) {
  const payload = {
    updatedAt: profile.updatedAt,
    fields: { ...profile.fields },
    provenance: Object.fromEntries(
      Object.entries(profile.provenance).map(([k, v]) => [k, [...v]])
    )
  };
  const json = JSON.stringify(payload, null, 2);
  return [
    "---",
    `peerId: ${escapeYamlString(profile.peerId)}`,
    `updatedAt: ${escapeYamlString(profile.updatedAt)}`,
    "---",
    "",
    "<!-- peer profile \u2014 managed by the async reasoner. Manual edits will be overwritten. -->",
    "",
    "```json",
    json,
    "```",
    ""
  ].join("\n");
}
function parsePeerProfile(raw, peerId) {
  const { fields: fm, body } = parsePeerFrontmatter(raw);
  if (fm.peerId !== void 0 && fm.peerId !== peerId) {
    throw new Error(
      `peer profile mismatch \u2014 expected "${peerId}", file claims "${fm.peerId}"`
    );
  }
  const fenceMatch = body.match(/```json\s*\n([\s\S]*?)\n```/);
  if (!fenceMatch) {
    throw new Error(`peer profile for "${peerId}" is missing JSON payload`);
  }
  let parsed;
  try {
    parsed = JSON.parse(fenceMatch[1]);
  } catch (err) {
    throw new Error(
      `peer profile for "${peerId}" has invalid JSON: ${err.message}`
    );
  }
  if (typeof parsed !== "object" || parsed === null) {
    throw new Error(`peer profile for "${peerId}" is not an object`);
  }
  const payload = parsed;
  const payloadUpdatedAt = typeof payload.updatedAt === "string" ? payload.updatedAt : void 0;
  const updatedAt = payloadUpdatedAt ?? fm.updatedAt ?? "";
  if (typeof updatedAt !== "string" || updatedAt === "") {
    throw new Error(`peer profile for "${peerId}" is missing updatedAt`);
  }
  let fieldsObj;
  if (payload.fields === void 0) {
    fieldsObj = {};
  } else if (typeof payload.fields === "object" && payload.fields !== null && !Array.isArray(payload.fields)) {
    fieldsObj = payload.fields;
  } else {
    throw new Error(`peer profile for "${peerId}" has malformed fields section`);
  }
  let provenanceObj;
  if (payload.provenance === void 0) {
    provenanceObj = {};
  } else if (typeof payload.provenance === "object" && payload.provenance !== null && !Array.isArray(payload.provenance)) {
    provenanceObj = payload.provenance;
  } else {
    throw new Error(`peer profile for "${peerId}" has malformed provenance section`);
  }
  const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
  const fields = {};
  for (const [k, v] of Object.entries(fieldsObj)) {
    if (DANGEROUS_KEYS.has(k)) continue;
    if (typeof v === "string") fields[k] = v;
  }
  const provenance = {};
  for (const [k, v] of Object.entries(provenanceObj)) {
    if (DANGEROUS_KEYS.has(k)) continue;
    if (!Array.isArray(v)) continue;
    const list = [];
    for (const item of v) {
      if (typeof item !== "object" || item === null || Array.isArray(item)) {
        continue;
      }
      const r = item;
      if (typeof r.observedAt !== "string" || r.observedAt === "") continue;
      if (typeof r.signal !== "string" || r.signal === "") continue;
      const clean = {
        observedAt: r.observedAt,
        signal: r.signal,
        ...typeof r.sourceSessionId === "string" && r.sourceSessionId.length > 0 ? { sourceSessionId: r.sourceSessionId } : {},
        ...typeof r.note === "string" && r.note.length > 0 ? { note: r.note } : {}
      };
      list.push(clean);
    }
    provenance[k] = list;
  }
  return { peerId, updatedAt, fields, provenance };
}
async function readPeerProfile(memoryDir, peerId) {
  assertValidPeerId(peerId);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const file = profilePath(memoryDir, peerId);
  let raw;
  try {
    raw = await readFileNoFollow(file);
  } catch (err) {
    if (err.code === "ENOENT") {
      return null;
    }
    throw err;
  }
  return parsePeerProfile(raw, peerId);
}
async function writePeerProfile(memoryDir, profile) {
  assertValidPeerId(profile.peerId);
  if (typeof profile.updatedAt !== "string" || profile.updatedAt === "") {
    throw new Error("profile.updatedAt must be a non-empty ISO-8601 string");
  }
  if (!isPlainObject(profile.fields)) {
    throw new Error("profile.fields must be a plain object");
  }
  const RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
  for (const [key, value] of Object.entries(profile.fields)) {
    if (RESERVED_KEYS.has(key)) {
      throw new Error(`profile.fields key "${key}" is reserved and cannot be persisted`);
    }
    if (typeof value !== "string") {
      throw new Error(`profile.fields["${key}"] must be a string`);
    }
  }
  if (!isPlainObject(profile.provenance)) {
    throw new Error("profile.provenance must be a plain object");
  }
  for (const [key, list] of Object.entries(profile.provenance)) {
    if (RESERVED_KEYS.has(key)) {
      throw new Error(`profile.provenance key "${key}" is reserved and cannot be persisted`);
    }
    if (!Array.isArray(list)) {
      throw new Error(`profile.provenance["${key}"] must be an array`);
    }
    for (let i = 0; i < list.length; i++) {
      const item = list[i];
      if (typeof item !== "object" || item === null || Array.isArray(item)) {
        throw new Error(`profile.provenance["${key}"][${i}] must be an object`);
      }
      const r = item;
      if (typeof r.observedAt !== "string" || r.observedAt === "") {
        throw new Error(`profile.provenance["${key}"][${i}].observedAt must be a non-empty string`);
      }
      if (typeof r.signal !== "string" || r.signal === "") {
        throw new Error(`profile.provenance["${key}"][${i}].signal must be a non-empty string`);
      }
      if (r.sourceSessionId !== void 0) {
        if (typeof r.sourceSessionId !== "string" || r.sourceSessionId === "") {
          throw new Error(`profile.provenance["${key}"][${i}].sourceSessionId must be a non-empty string when provided`);
        }
      }
      if (r.note !== void 0) {
        if (typeof r.note !== "string" || r.note === "") {
          throw new Error(`profile.provenance["${key}"][${i}].note must be a non-empty string when provided`);
        }
      }
    }
  }
  await mkdirPeerDirAtomic(memoryDir, profile.peerId);
  await assertPeerDirNotEscaped(memoryDir, profile.peerId);
  const file = profilePath(memoryDir, profile.peerId);
  await writeFileNoFollow(file, emitPeerProfile(profile));
}
async function readInteractionLogRaw(memoryDir, peerId) {
  assertValidPeerId(peerId);
  await assertPeerDirNotEscaped(memoryDir, peerId);
  const file = interactionsPath(memoryDir, peerId);
  try {
    return await readFileNoFollow(file);
  } catch (err) {
    if (err.code === "ENOENT") {
      return "";
    }
    throw err;
  }
}
function parseLogLine(line) {
  if (!line.startsWith("- [")) return null;
  const tsClose = line.indexOf("]", 3);
  if (tsClose === -1) return null;
  const timestamp = line.slice(3, tsClose).trim();
  if (timestamp === "") return null;
  let cursor = tsClose + 1;
  while (cursor < line.length && line[cursor] === " ") cursor += 1;
  if (line[cursor] !== "(") return null;
  const kindOpen = cursor;
  const kindClose = line.indexOf(")", kindOpen + 1);
  if (kindClose === -1) return null;
  const kind = line.slice(kindOpen + 1, kindClose).trim();
  if (kind === "") return null;
  cursor = kindClose + 1;
  let sessionId;
  while (cursor < line.length && line[cursor] === " ") cursor += 1;
  if (line.startsWith("[session=", cursor)) {
    const close = line.indexOf("]", cursor + "[session=".length);
    if (close > -1) {
      const sid = line.slice(cursor + "[session=".length, close).trim();
      if (sid.length > 0) sessionId = sid;
      cursor = close + 1;
    }
  }
  while (cursor < line.length && line[cursor] === " ") cursor += 1;
  const summary = line.slice(cursor);
  return sessionId === void 0 ? { timestamp, kind, summary } : { timestamp, kind, sessionId, summary };
}
async function readPeerInteractionLog(memoryDir, peerId, options = {}) {
  const raw = await readInteractionLogRaw(memoryDir, peerId);
  if (raw === "") return [];
  const entries = [];
  for (const lineRaw of raw.split(/\r?\n/)) {
    const line = lineRaw.trimEnd();
    if (line === "") continue;
    const parsed = parseLogLine(line);
    if (parsed === null) continue;
    entries.push(parsed);
  }
  let filtered = entries;
  if (typeof options.afterTimestamp === "string" && options.afterTimestamp.length > 0) {
    const cutoff = options.afterTimestamp;
    filtered = filtered.filter((e) => e.timestamp > cutoff);
  }
  if (typeof options.limit === "number" && options.limit > 0) {
    if (filtered.length > options.limit) {
      filtered = filtered.slice(filtered.length - options.limit);
    }
  }
  return filtered;
}

// src/peers/migrate-from-identity-anchor.ts
async function safeReadLegacyFile(filePath) {
  const parent = path2.dirname(filePath);
  try {
    const parentStat = await fs2.lstat(parent);
    if (parentStat.isSymbolicLink()) {
      return { content: null, filePath: null };
    }
  } catch (err) {
    if (err.code === "ENOENT") {
      return { content: null, filePath: null };
    }
    throw err;
  }
  let fh;
  try {
    fh = await fs2.open(filePath, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
  } catch (err) {
    const code = err.code;
    if (code === "ENOENT" || code === "ELOOP" || code === "ENOTDIR") {
      return { content: null, filePath: null };
    }
    throw err;
  }
  try {
    const stat = await fh.stat();
    if (!stat.isFile()) {
      return { content: null, filePath: null };
    }
    const content = await fh.readFile("utf8");
    return { content, filePath };
  } finally {
    await fh.close();
  }
}
function buildSelfNotes(anchorContent, identityMdContent) {
  const parts = [];
  if (anchorContent !== null && anchorContent.trim().length > 0) {
    parts.push(
      "## Migrated from identity-anchor.md\n\n" + anchorContent.trim()
    );
  }
  if (identityMdContent !== null && identityMdContent.trim().length > 0) {
    parts.push(
      "## Migrated from IDENTITY.md\n\n" + identityMdContent.trim()
    );
  }
  if (parts.length === 0) return void 0;
  return parts.join("\n\n");
}
async function migrateFromIdentityAnchor(options) {
  const { memoryDir, dryRun = false } = options;
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const createdAt = options.createdAt ?? now;
  const displayName = options.displayName ?? "Self";
  const existing = await readPeer(memoryDir, "self");
  if (existing !== null) {
    return {
      peer: existing,
      written: false,
      skipped: true,
      // P2 (codex): preserve the caller-requested dryRun flag even on the
      // skip path. Previously hard-coded `false`, which contradicted the
      // MigrateFromIdentityAnchorResult contract for dry-run callers.
      dryRun,
      identityAnchorSource: null,
      identityMdSource: null
    };
  }
  const anchorPath = path2.join(memoryDir, "identity", "identity-anchor.md");
  const identityMdPath = path2.join(memoryDir, "IDENTITY.md");
  const [anchorResult, identityMdResult] = await Promise.all([
    safeReadLegacyFile(anchorPath),
    safeReadLegacyFile(identityMdPath)
  ]);
  const notes = buildSelfNotes(
    anchorResult.content,
    identityMdResult.content
  );
  const peer = {
    id: "self",
    kind: "self",
    displayName,
    createdAt,
    updatedAt: now,
    ...notes !== void 0 ? { notes } : {}
  };
  if (!dryRun) {
    const selfPeerDir = path2.join(memoryDir, PEERS_DIR_NAME, "self");
    await fs2.mkdir(selfPeerDir, { recursive: true });
    const identityFilePath = path2.join(selfPeerDir, "identity.md");
    let exclusiveFh = null;
    try {
      exclusiveFh = await fs2.open(
        identityFilePath,
        fsConstants2.O_WRONLY | fsConstants2.O_CREAT | fsConstants2.O_EXCL | fsConstants2.O_NOFOLLOW
      );
    } catch (err) {
      const code = err.code;
      if (code === "EEXIST") {
        const raceWinner = await readPeer(memoryDir, "self");
        return {
          peer: raceWinner ?? peer,
          written: false,
          skipped: true,
          dryRun,
          identityAnchorSource: null,
          identityMdSource: null
        };
      }
      throw err;
    } finally {
      if (exclusiveFh !== null) await exclusiveFh.close();
    }
    await writePeer(memoryDir, peer);
  }
  return {
    peer,
    written: !dryRun,
    skipped: false,
    dryRun,
    identityAnchorSource: anchorResult.filePath,
    identityMdSource: identityMdResult.filePath
  };
}

export {
  PEER_ID_PATTERN,
  PEER_ID_MAX_LENGTH,
  assertValidPeerId,
  PEERS_DIR_NAME,
  readPeer,
  writePeer,
  deletePeer,
  forgetPeer,
  listPeers,
  appendInteractionLog,
  readPeerProfile,
  writePeerProfile,
  readInteractionLogRaw,
  readPeerInteractionLog,
  migrateFromIdentityAnchor
};
//# sourceMappingURL=chunk-4DWOBS2A.js.map