import {
  StorageManager,
  normalizeEntityName
} from "./chunk-DCE6SQLA.js";
import {
  readEnvVar,
  resolveHomeDir
} from "./chunk-MARWOCVP.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/briefing.ts
import { readFile } from "fs/promises";
import os from "os";
import path from "path";
var BRIEFING_FORMAT_ALLOWED = ["markdown", "json"];
var BRIEFING_FOLLOWUP_DEFAULT_MODEL = "gpt-5.2";
function validateBriefingFormat(value) {
  if (value === void 0) return null;
  if (BRIEFING_FORMAT_ALLOWED.includes(value)) return null;
  return `Invalid --format value: "${value}". Accepted: ${BRIEFING_FORMAT_ALLOWED.join(", ")}.`;
}
var DAY_MS = 24 * 60 * 60 * 1e3;
var MAX_WINDOW_MS = 100 * 365 * DAY_MS;
function parseBriefingWindow(token, now = /* @__PURE__ */ new Date()) {
  const raw = typeof token === "string" ? token.trim().toLowerCase() : "";
  if (raw.length === 0) return null;
  if (raw === "yesterday") {
    const startOfToday = startOfUtcDay(now);
    const from2 = new Date(startOfToday.getTime() - DAY_MS);
    return { from: from2, to: startOfToday, label: "yesterday" };
  }
  if (raw === "today") {
    return { from: startOfUtcDay(now), to: now, label: "today" };
  }
  const match = raw.match(/^(\d+)\s*(h|d|w)$/);
  if (!match) return null;
  const value = parseInt(match[1], 10);
  if (!Number.isFinite(value) || value <= 0) return null;
  const unit = match[2];
  let ms = 0;
  if (unit === "h") ms = value * 60 * 60 * 1e3;
  else if (unit === "d") ms = value * DAY_MS;
  else if (unit === "w") ms = value * 7 * DAY_MS;
  if (ms === 0) return null;
  if (ms > MAX_WINDOW_MS || !Number.isFinite(ms)) return null;
  const from = new Date(now.getTime() - ms);
  if (!Number.isFinite(from.getTime())) return null;
  return {
    from,
    to: now,
    label: `last ${value}${unit}`
  };
}
function startOfUtcDay(date) {
  const d = new Date(date.getTime());
  d.setUTCHours(0, 0, 0, 0);
  return d;
}
function parseBriefingFocus(token) {
  if (typeof token !== "string") return null;
  const trimmed = token.trim();
  if (trimmed.length === 0) return null;
  const [maybeType, ...rest] = trimmed.split(":");
  if (rest.length === 0) {
    return { type: "topic", value: maybeType };
  }
  const rawType = maybeType.toLowerCase();
  if (rawType === "person" || rawType === "project" || rawType === "topic") {
    const value = rest.join(":").trim();
    if (value.length === 0) return null;
    return { type: rawType, value };
  }
  return { type: "topic", value: trimmed };
}
function focusMatchesMemory(memory, focus) {
  const needle = focus.value.toLowerCase();
  const entityRef = (memory.frontmatter.entityRef ?? "").toLowerCase();
  const rawHaystackParts = [
    memory.content,
    ...memory.frontmatter.tags ?? []
  ];
  if (focus.type === "topic" && entityRef) {
    rawHaystackParts.push(entityRef);
  }
  const rawHaystack = rawHaystackParts.join(" ").toLowerCase();
  if (rawHaystack.includes(needle)) return true;
  if (!entityRef) return false;
  const focusCanonical = normalizeEntityName(focus.value, focus.type);
  let refForNormalize = entityRef;
  const typeDelimMatch = refForNormalize.match(
    new RegExp(`^${focus.type}[^a-z0-9]+`, "i")
  );
  if (typeDelimMatch) {
    refForNormalize = refForNormalize.slice(typeDelimMatch[0].length);
  }
  const refCanonical = normalizeEntityName(refForNormalize, focus.type);
  return refCanonical === focusCanonical;
}
function focusMatchesEntity(entity, focus) {
  const needle = focus.value.toLowerCase();
  if (focus.type === "person" && entity.type.toLowerCase() !== "person") return false;
  if (focus.type === "project" && entity.type.toLowerCase() !== "project") return false;
  const haystack = [
    entity.name,
    entity.synthesis || entity.summary || "",
    ...entity.facts,
    ...entity.aliases ?? [],
    ...(entity.structuredSections ?? []).flatMap((section) => [section.title, ...section.facts])
  ].join(" ").toLowerCase();
  return haystack.includes(needle);
}
var FileCalendarSource = class {
  constructor(filePath) {
    this.filePath = filePath;
  }
  filePath;
  async eventsForDate(dateIso) {
    let raw;
    try {
      raw = await readFile(this.filePath, "utf-8");
    } catch (err) {
      log.warn(`briefing: calendar source unreadable at ${this.filePath}: ${err}`);
      return [];
    }
    const events = this.filePath.toLowerCase().endsWith(".ics") ? parseIcsEvents(raw) : parseJsonEvents(raw);
    return events.filter((event) => eventFallsOnDate(event, dateIso));
  }
};
function parseJsonEvents(raw) {
  try {
    const parsed = JSON.parse(raw);
    const arr = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.events) ? parsed.events : [];
    const events = [];
    for (const entry of arr) {
      if (!entry || typeof entry !== "object") continue;
      const e = entry;
      const id = typeof e.id === "string" ? e.id : typeof e.uid === "string" ? e.uid : cryptoRandomId();
      const title = typeof e.title === "string" ? e.title : typeof e.summary === "string" ? e.summary : "";
      const start = typeof e.start === "string" ? e.start : typeof e.dtstart === "string" ? e.dtstart : "";
      if (!title || !start) continue;
      events.push({
        id,
        title,
        start,
        end: typeof e.end === "string" ? e.end : typeof e.dtend === "string" ? e.dtend : void 0,
        location: typeof e.location === "string" ? e.location : void 0,
        notes: typeof e.notes === "string" ? e.notes : typeof e.description === "string" ? e.description : void 0
      });
    }
    return events;
  } catch (err) {
    log.warn(`briefing: calendar JSON parse failed: ${err}`);
    return [];
  }
}
function parseIcsLine(line) {
  const colonIdx = line.indexOf(":");
  if (colonIdx <= 0) return null;
  const head = line.slice(0, colonIdx);
  const value = line.slice(colonIdx + 1).trim();
  const headParts = head.split(";");
  const property = headParts[0].toUpperCase();
  if (!/^[A-Z0-9-]+$/.test(property)) return null;
  const params = {};
  for (let i = 1; i < headParts.length; i++) {
    const segment = headParts[i];
    const eqIdx = segment.indexOf("=");
    if (eqIdx <= 0) continue;
    const name = segment.slice(0, eqIdx).toUpperCase();
    let val = segment.slice(eqIdx + 1);
    if (val.startsWith('"') && val.endsWith('"') && val.length >= 2) {
      val = val.slice(1, -1);
    }
    params[name] = val;
  }
  return { property, params, value };
}
function parseIcsEvents(raw) {
  const events = [];
  const normalized = raw.replace(/\r\n/g, "\n").replace(/\n[ \t]/g, "");
  const blocks = normalized.split(/BEGIN:VEVENT/i).slice(1);
  for (const block of blocks) {
    const endIdx = block.search(/END:VEVENT/i);
    const body = endIdx === -1 ? block : block.slice(0, endIdx);
    const simpleFields = {};
    const dateFields = {};
    for (const line of body.split("\n")) {
      const parsed = parseIcsLine(line);
      if (!parsed) continue;
      const { property, params, value } = parsed;
      if (property === "DTSTART" || property === "DTEND") {
        if (dateFields[property] === void 0) {
          dateFields[property] = { raw: value, params };
        }
      } else if (simpleFields[property] === void 0) {
        simpleFields[property] = value;
      }
    }
    const title = simpleFields.SUMMARY;
    const dtstart = dateFields.DTSTART;
    if (!title || !dtstart) continue;
    const dtend = dateFields.DTEND;
    events.push({
      id: simpleFields.UID ?? cryptoRandomId(),
      title,
      start: normalizeIcsDate(dtstart.raw, dtstart.params),
      end: dtend ? normalizeIcsDate(dtend.raw, dtend.params) : void 0,
      location: simpleFields.LOCATION,
      notes: simpleFields.DESCRIPTION
    });
  }
  return events;
}
function normalizeIcsDate(value, params = {}) {
  if (/^\d{8}T\d{6}Z$/.test(value)) {
    const y = value.slice(0, 4);
    const m = value.slice(4, 6);
    const d = value.slice(6, 8);
    const hh = value.slice(9, 11);
    const mm = value.slice(11, 13);
    const ss = value.slice(13, 15);
    return `${y}-${m}-${d}T${hh}:${mm}:${ss}Z`;
  }
  if (/^\d{8}T\d{6}$/.test(value)) {
    const y = value.slice(0, 4);
    const m = value.slice(4, 6);
    const d = value.slice(6, 8);
    const hh = value.slice(9, 11);
    const mm = value.slice(11, 13);
    const ss = value.slice(13, 15);
    const local = `${y}-${m}-${d}T${hh}:${mm}:${ss}`;
    const tzid = params.TZID;
    if (tzid) {
      const utcIso = icsWallclockToUtc(local, tzid);
      if (utcIso) return utcIso;
      log.warn(
        `briefing: unsupported TZID "${tzid}" \u2014 treating as UTC for ${local}`
      );
      return `${local}Z`;
    }
    return local;
  }
  if (/^\d{8}$/.test(value)) {
    return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}T00:00:00Z`;
  }
  return value;
}
function icsWallclockToUtc(local, tzid) {
  const match = local.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})$/);
  if (!match) return null;
  const [, y, mo, d, hh, mm, ss] = match;
  const naiveUtcMs = Date.UTC(
    Number(y),
    Number(mo) - 1,
    Number(d),
    Number(hh),
    Number(mm),
    Number(ss)
  );
  if (!Number.isFinite(naiveUtcMs)) return null;
  let formatter;
  try {
    formatter = new Intl.DateTimeFormat("en-US", {
      timeZone: tzid,
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
      hour12: false
    });
  } catch {
    return null;
  }
  const zonedMs = zonedFormatToMs(formatter, new Date(naiveUtcMs));
  if (zonedMs === null) return null;
  const offsetMs = naiveUtcMs - zonedMs;
  const realUtcMs = naiveUtcMs + offsetMs;
  const zonedMs2 = zonedFormatToMs(formatter, new Date(realUtcMs));
  if (zonedMs2 !== null) {
    const offsetMs2 = realUtcMs - zonedMs2;
    if (offsetMs2 !== offsetMs) {
      return new Date(naiveUtcMs + offsetMs2).toISOString();
    }
  }
  return new Date(realUtcMs).toISOString();
}
function zonedFormatToMs(formatter, date) {
  const parts = formatter.formatToParts(date);
  const get = (type) => parts.find((p) => p.type === type)?.value;
  const y = get("year");
  const mo = get("month");
  const d = get("day");
  const hh = get("hour");
  const mm = get("minute");
  const ss = get("second");
  if (!y || !mo || !d || !hh || !mm || !ss) return null;
  const normalizedHour = Number(hh) === 24 ? 0 : Number(hh);
  const ms = Date.UTC(Number(y), Number(mo) - 1, Number(d), normalizedHour, Number(mm), Number(ss));
  return Number.isFinite(ms) ? ms : null;
}
function isoHasTimezone(isoStr) {
  return /(Z|[+-]\d{2}:?\d{2})$/.test(isoStr);
}
function isoToMs(isoStr) {
  if (!isoStr) return null;
  let src = isoStr;
  if (!isoHasTimezone(src)) {
    src = src + "Z";
  }
  const ms = new Date(src).getTime();
  return Number.isFinite(ms) ? ms : null;
}
function eventFallsOnDate(event, dateIso) {
  const target = dateIso.slice(0, 10);
  const start = event.start;
  const startIsFloating = !isoHasTimezone(start);
  if (startIsFloating) {
    const startShapeOk = typeof start === "string" && /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?$/.test(start);
    if (!startShapeOk) {
      log.debug(`briefing: skipping calendar event with invalid start value: ${JSON.stringify(start)}`);
      return false;
    }
    const startDateStr = start.slice(0, 10);
    const startDateProbe = /* @__PURE__ */ new Date(startDateStr + "T00:00:00Z");
    if (Number.isNaN(startDateProbe.getTime()) || startDateProbe.toISOString().slice(0, 10) !== startDateStr) {
      log.warn(
        `briefing: skipping calendar event "${event.title}" with impossible start date ${JSON.stringify(startDateStr)}`
      );
      return false;
    }
    const startRawTime = start.indexOf("T") !== -1 ? start.slice(start.indexOf("T") + 1) : "";
    if (startRawTime !== "") {
      const startTimeParts = startRawTime.split(":").map(Number);
      const shh = startTimeParts[0] ?? 0;
      const smm = startTimeParts[1] ?? 0;
      const sss = Math.floor(startTimeParts[2] ?? 0);
      if (shh > 23 || smm > 59 || sss > 59) {
        log.warn(
          `briefing: skipping calendar event "${event.title}" with out-of-range start time ${JSON.stringify(startRawTime)}`
        );
        return false;
      }
    }
    const startDate = startDateStr;
    const end2 = event.end;
    if (!end2) return startDate === target;
    const endIsValid = typeof end2 === "string" && /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?$/.test(end2);
    if (!endIsValid) {
      log.warn(
        `briefing: event "${event.title}" has malformed end timestamp ${JSON.stringify(end2)}; treating as single-day event at ${startDate}`
      );
      return startDate === target;
    }
    const endDate = end2.slice(0, 10);
    const endDateProbe = /* @__PURE__ */ new Date(endDate + "T00:00:00Z");
    if (Number.isNaN(endDateProbe.getTime()) || endDateProbe.toISOString().slice(0, 10) !== endDate) {
      log.warn(
        `briefing: event "${event.title}" has impossible end date ${JSON.stringify(endDate)}; treating as single-day event at ${startDate}`
      );
      return startDate === target;
    }
    const rawTime = end2.indexOf("T") !== -1 ? end2.slice(end2.indexOf("T") + 1) : "";
    if (rawTime !== "") {
      const timeParts = rawTime.split(":").map(Number);
      const hh = timeParts[0] ?? 0;
      const mm = timeParts[1] ?? 0;
      const ss = Math.floor(timeParts[2] ?? 0);
      const timeIsValid = hh <= 23 && mm <= 59 && ss <= 59;
      if (!timeIsValid) {
        log.warn(
          `briefing: event "${event.title}" has out-of-range end time ${JSON.stringify(rawTime)}; treating as single-day event at ${startDate}`
        );
        return startDate === target;
      }
    }
    const endTime = end2.slice(11);
    const endIsDateOnly = endTime === "";
    const endAtExactMidnight = endIsDateOnly || /^00(:00){1,2}(\.0+)?$/.test(endTime);
    const endActiveOnEndDay = !endAtExactMidnight;
    if (endActiveOnEndDay) {
      return startDate <= target && target <= endDate;
    }
    return startDate <= target && target < endDate;
  }
  const startMs = isoToMs(start);
  if (startMs === null) {
    log.debug(`briefing: skipping calendar event with invalid start value: ${JSON.stringify(start)}`);
    return false;
  }
  const dayStart = Date.UTC(
    Number(target.slice(0, 4)),
    Number(target.slice(5, 7)) - 1,
    Number(target.slice(8, 10))
  );
  const dayEnd = dayStart + 864e5;
  const end = event.end;
  if (!end) {
    return startMs >= dayStart && startMs < dayEnd;
  }
  const endMs = isoToMs(end);
  if (endMs === null) {
    return startMs >= dayStart && startMs < dayEnd;
  }
  return startMs < dayEnd && endMs > dayStart;
}
function cryptoRandomId() {
  return `evt-${Math.random().toString(36).slice(2, 10)}`;
}
var MAX_ACTIVE_THREADS = 8;
var MAX_RECENT_ENTITIES = 8;
var MAX_OPEN_COMMITMENTS = 8;
async function buildBriefing(options) {
  const now = options.now ?? /* @__PURE__ */ new Date();
  const window = options.window ?? defaultWindow(now);
  const maxFollowups = clampFollowups(options.maxFollowups);
  const focus = options.focus ?? null;
  const [allMemories, allEntities] = await Promise.all([
    safeReadMemories(options.storage),
    safeReadEntities(options.storage)
  ]);
  const memoriesInWindow = filterMemoriesByWindow(allMemories, window);
  const focusedMemories = focus ? memoriesInWindow.filter((m) => focusMatchesMemory(m, focus)) : memoriesInWindow;
  const activeThreads = buildActiveThreads(focusedMemories);
  const recentEntities = buildRecentEntities(allEntities, window, focus);
  const openCommitments = buildOpenCommitments(focusedMemories);
  const calendarLoadResult = options.calendarSource ? await loadTodayCalendar(options.calendarSource, now) : void 0;
  const calendarSourceErrors = calendarLoadResult?.error ? [calendarLoadResult.error] : [];
  const sectionsBase = {
    activeThreads,
    recentEntities,
    openCommitments,
    suggestedFollowups: [],
    todayCalendar: calendarLoadResult?.events
  };
  let followups = [];
  let followupsUnavailableReason;
  if (maxFollowups === 0 || options.allowLlm === false) {
    followupsUnavailableReason = "LLM follow-ups disabled by configuration";
  } else if (!options.openaiApiKey && !options.followupGenerator) {
    followupsUnavailableReason = "OPENAI_API_KEY not configured";
  } else {
    try {
      const generator = options.followupGenerator ?? buildOpenAiFollowupGenerator({
        apiKey: options.openaiApiKey,
        model: options.model ?? BRIEFING_FOLLOWUP_DEFAULT_MODEL,
        baseURL: options.openaiBaseUrl
      });
      const generated = await generator({
        sections: sectionsBase,
        windowLabel: window.label,
        maxFollowups
      });
      followups = generated.slice(0, maxFollowups);
    } catch (err) {
      const errMsg = stringifyError(err);
      const modelName = options.model ?? BRIEFING_FOLLOWUP_DEFAULT_MODEL;
      if (/model/i.test(errMsg) && (/not found/i.test(errMsg) || /does not exist/i.test(errMsg) || /invalid/i.test(errMsg))) {
        followupsUnavailableReason = `configured follow-up model '${modelName}' is not available in the Responses API`;
      } else {
        followupsUnavailableReason = `LLM follow-ups failed: ${errMsg}`;
      }
      log.warn(`briefing: ${followupsUnavailableReason}`);
    }
  }
  const sections = {
    ...sectionsBase,
    suggestedFollowups: followups
  };
  const windowIso = { from: window.from.toISOString(), to: window.to.toISOString() };
  const markdown = renderBriefingMarkdown({
    sections,
    windowLabel: window.label,
    focus,
    followupsUnavailableReason,
    generatedAt: now,
    namespace: options.namespace
  });
  const json = {
    generatedAt: now.toISOString(),
    window: windowIso,
    focus,
    namespace: options.namespace ?? null,
    sections,
    followupsUnavailableReason: followupsUnavailableReason ?? null,
    calendarSourceErrors: calendarSourceErrors.length > 0 ? calendarSourceErrors : null
  };
  const result = {
    markdown,
    json,
    sections,
    followupsUnavailableReason,
    window: windowIso
  };
  if (calendarSourceErrors.length > 0) {
    result.calendarSourceErrors = calendarSourceErrors;
  }
  return result;
}
function clampFollowups(value) {
  if (typeof value !== "number" || !Number.isFinite(value)) return 5;
  return Math.max(0, Math.min(10, Math.floor(value)));
}
function defaultWindow(now) {
  const parsed = parseBriefingWindow("yesterday", now);
  if (parsed) return parsed;
  return { from: new Date(now.getTime() - DAY_MS), to: now, label: "yesterday" };
}
async function safeReadMemories(storage) {
  try {
    return await storage.readAllMemories();
  } catch (err) {
    log.warn(`briefing: readAllMemories failed: ${err}`);
    return [];
  }
}
async function safeReadEntities(storage) {
  try {
    return await storage.readAllEntityFiles();
  } catch (err) {
    log.warn(`briefing: readAllEntityFiles failed: ${err}`);
    return [];
  }
}
function memoryTimestamp(memory) {
  const raw = memory.frontmatter.updated || memory.frontmatter.created;
  if (!raw) return 0;
  const t = Date.parse(raw);
  return Number.isFinite(t) ? t : 0;
}
function filterMemoriesByWindow(memories, window) {
  const fromMs = window.from.getTime();
  const toMs = window.to.getTime();
  return memories.filter((m) => {
    const status = m.frontmatter.status;
    if (status === "superseded" || status === "archived" || status === "rejected" || status === "quarantined" || status === "forgotten") {
      return false;
    }
    const ts = memoryTimestamp(m);
    return ts >= fromMs && ts < toMs;
  });
}
function buildActiveThreads(memories) {
  const buckets = /* @__PURE__ */ new Map();
  for (const memory of memories) {
    const threadKey = extractThreadKey(memory);
    const updatedAt = memory.frontmatter.updated || memory.frontmatter.created || "";
    const existing = buckets.get(threadKey);
    if (!existing || updatedAt > existing.updatedAt) {
      buckets.set(threadKey, {
        id: threadKey,
        title: summarizeContentTitle(memory),
        updatedAt,
        // Always derive reason from the newest memory so the description
        // reflects the most-recent activity type, not the first memory seen.
        reason: describeReason(memory)
      });
    }
  }
  return Array.from(buckets.values()).sort((a, b) => {
    if (a.updatedAt > b.updatedAt) return -1;
    if (a.updatedAt < b.updatedAt) return 1;
    return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
  }).slice(0, MAX_ACTIVE_THREADS);
}
function extractThreadKey(memory) {
  const entityRef = memory.frontmatter.entityRef?.trim();
  if (entityRef) return `entity:${entityRef}`;
  const tags = memory.frontmatter.tags ?? [];
  const topicTag = tags.find((t) => t.startsWith("topic:"));
  if (topicTag) return topicTag;
  if (tags.length > 0) return `tag:${tags[0]}`;
  return `memory:${memory.frontmatter.id}`;
}
function summarizeContentTitle(memory) {
  const firstLine = (memory.content || "").split("\n").find((line) => line.trim().length > 0) ?? "";
  const trimmed = firstLine.trim();
  if (trimmed.length === 0) return memory.frontmatter.id;
  return trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed;
}
function describeReason(memory) {
  const cat = memory.frontmatter.category;
  if (cat === "commitment") return "open commitment";
  if (cat === "decision") return "recent decision";
  if (cat === "correction") return "recent correction";
  return "recent activity";
}
function buildRecentEntities(entities, window, focus) {
  const fromMs = window.from.getTime();
  const scored = [];
  const now = window.to;
  for (const entity of entities) {
    if (focus && !focusMatchesEntity(entity, focus)) continue;
    const toMs = window.to.getTime();
    const updatedMs = entity.updated ? Date.parse(entity.updated) : 0;
    if (!Number.isFinite(updatedMs) || updatedMs < fromMs || updatedMs >= toMs) continue;
    const score = StorageManager.scoreEntity(entity, now);
    scored.push({
      name: entity.name,
      type: entity.type,
      updatedAt: entity.updated,
      score: Number(score.toFixed(4)),
      summary: entity.synthesis || entity.summary
    });
  }
  return scored.sort((a, b) => {
    const scoreDiff = b.score - a.score;
    if (scoreDiff !== 0) return scoreDiff;
    if (a.updatedAt > b.updatedAt) return -1;
    if (a.updatedAt < b.updatedAt) return 1;
    return 0;
  }).slice(0, MAX_RECENT_ENTITIES);
}
function buildOpenCommitments(memories) {
  const commitments = [];
  for (const memory of memories) {
    const tags = memory.frontmatter.tags ?? [];
    const isPending = tags.some((t) => t.toLowerCase() === "pending");
    const isCommitment = memory.frontmatter.category === "commitment";
    const isUnresolvedQuestion = /(?:\?$|\bfollow[- ]up\b|\btodo\b)/i.test(memory.content);
    if (isPending || isCommitment || isUnresolvedQuestion) {
      const kind = isCommitment ? "commitment" : isUnresolvedQuestion ? "question" : "pending_memory";
      commitments.push({
        id: memory.frontmatter.id,
        kind,
        text: summarizeContentTitle(memory),
        source: memory.frontmatter.source,
        createdAt: memory.frontmatter.created
      });
    }
  }
  return commitments.sort((a, b) => {
    if (!a.createdAt && !b.createdAt) return 0;
    if (!a.createdAt) return 1;
    if (!b.createdAt) return -1;
    if (a.createdAt > b.createdAt) return -1;
    if (a.createdAt < b.createdAt) return 1;
    return 0;
  }).slice(0, MAX_OPEN_COMMITMENTS);
}
async function loadTodayCalendar(source, now) {
  const sourceLabel = source.filePath ?? "calendar";
  try {
    const dateIso = now.toISOString().slice(0, 10);
    const events = await source.eventsForDate(dateIso);
    return { events, error: void 0 };
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    log.warn(`briefing: calendar source error (${sourceLabel}): ${message}`);
    return {
      events: void 0,
      error: { source: sourceLabel, error: message }
    };
  }
}
function buildOpenAiFollowupGenerator(cfg) {
  return async ({ sections, windowLabel, maxFollowups }) => {
    const { OpenAI } = await import("openai");
    const clientOpts = { apiKey: cfg.apiKey };
    if (cfg.baseURL) clientOpts.baseURL = cfg.baseURL;
    const client = new OpenAI(clientOpts);
    const prompt = buildFollowupPrompt(sections, windowLabel, maxFollowups);
    const response = await client.responses.create({
      model: cfg.model,
      instructions: FOLLOWUP_INSTRUCTIONS,
      input: prompt,
      max_output_tokens: 512
    });
    const text = typeof response.output_text === "string" ? response.output_text : "";
    return parseFollowupResponse(text, maxFollowups);
  };
}
var FOLLOWUP_INSTRUCTIONS = `You suggest short follow-up prompts for a daily context briefing.
Return strict JSON of the form { "followups": [{ "text": "...", "rationale": "..." }] }.
Rules:
- Never invent facts absent from the input.
- Keep each "text" under 140 characters.
- Prefer concrete, action-oriented phrasing.
- Omit duplicates. Avoid filler.`;
function buildFollowupPrompt(sections, windowLabel, maxFollowups) {
  const lines = [];
  lines.push(`Window: ${windowLabel}`);
  lines.push(`Desired follow-ups: ${maxFollowups}`);
  lines.push("");
  lines.push("Active threads:");
  for (const t of sections.activeThreads) lines.push(`- ${t.title} (${t.reason})`);
  lines.push("");
  lines.push("Recent entities:");
  for (const e of sections.recentEntities) lines.push(`- ${e.name} [${e.type}]`);
  lines.push("");
  lines.push("Open commitments:");
  for (const c of sections.openCommitments) lines.push(`- [${c.kind}] ${c.text}`);
  return lines.join("\n");
}
function parseFollowupResponse(raw, max) {
  const parsed = JSON.parse(raw);
  if (!parsed || typeof parsed !== "object") {
    throw new Error(`LLM returned non-object JSON: ${typeof parsed}`);
  }
  const arr = parsed.followups;
  if (!Array.isArray(arr)) {
    throw new Error(`LLM response missing "followups" array`);
  }
  const out = [];
  for (const entry of arr) {
    if (!entry || typeof entry !== "object") continue;
    const text = entry.text;
    if (typeof text !== "string" || text.trim().length === 0) continue;
    const rationale = entry.rationale;
    out.push({
      text: text.trim(),
      rationale: typeof rationale === "string" ? rationale.trim() : void 0
    });
    if (out.length >= max) break;
  }
  return out;
}
function stringifyError(err) {
  if (err instanceof Error) return err.message;
  return String(err);
}
function renderBriefingMarkdown(ctx) {
  const lines = [];
  lines.push(`# Daily Context Briefing`);
  lines.push("");
  lines.push(`_Generated ${ctx.generatedAt.toISOString()} (window: ${ctx.windowLabel})_`);
  if (ctx.focus) {
    lines.push(`_Focus: ${ctx.focus.type}:${ctx.focus.value}_`);
  }
  if (ctx.namespace) {
    lines.push(`_Namespace: ${ctx.namespace}_`);
  }
  lines.push("");
  lines.push(`## Active threads`);
  if (ctx.sections.activeThreads.length === 0) {
    lines.push(`_No active threads in window._`);
  } else {
    for (const t of ctx.sections.activeThreads) {
      lines.push(`- **${t.title}** \u2014 ${t.reason} (updated ${t.updatedAt})`);
    }
  }
  lines.push("");
  lines.push(`## Recent entities`);
  if (ctx.sections.recentEntities.length === 0) {
    lines.push(`_No entities updated in window._`);
  } else {
    for (const e of ctx.sections.recentEntities) {
      const summary = e.summary ? ` \u2014 ${e.summary}` : "";
      lines.push(`- **${e.name}** (${e.type}, score ${e.score})${summary}`);
    }
  }
  lines.push("");
  lines.push(`## Open commitments`);
  if (ctx.sections.openCommitments.length === 0) {
    lines.push(`_No open commitments detected._`);
  } else {
    for (const c of ctx.sections.openCommitments) {
      lines.push(`- [${c.kind}] ${c.text}`);
    }
  }
  lines.push("");
  lines.push(`## Suggested follow-ups`);
  if (ctx.followupsUnavailableReason) {
    lines.push(`_Unavailable: ${ctx.followupsUnavailableReason}_`);
  } else if (ctx.sections.suggestedFollowups.length === 0) {
    lines.push(`_No follow-ups suggested._`);
  } else {
    for (const f of ctx.sections.suggestedFollowups) {
      const rationale = f.rationale ? ` _(${f.rationale})_` : "";
      lines.push(`- ${f.text}${rationale}`);
    }
  }
  lines.push("");
  if (ctx.sections.todayCalendar !== void 0) {
    lines.push(`## Today's calendar`);
    if (ctx.sections.todayCalendar.length === 0) {
      lines.push(`_No events on the calendar today._`);
    } else {
      for (const ev of ctx.sections.todayCalendar) {
        const end = ev.end ? ` \u2192 ${ev.end}` : "";
        const loc = ev.location ? ` @ ${ev.location}` : "";
        lines.push(`- **${ev.title}** (${ev.start}${end})${loc}`);
      }
    }
    lines.push("");
  }
  return lines.join("\n").trimEnd() + "\n";
}
function resolveBriefingSaveDir(configOverride, env) {
  if (typeof configOverride === "string" && configOverride.trim().length > 0) {
    return path.resolve(configOverride.trim());
  }
  const remnicHome = (env === void 0 ? readEnvVar("REMNIC_HOME") : env.REMNIC_HOME)?.trim();
  if (remnicHome && remnicHome.length > 0) {
    return path.join(remnicHome, "briefings");
  }
  const home = env === void 0 ? resolveHomeDir() : env.HOME ?? env.USERPROFILE ?? os.homedir();
  return path.join(home, ".remnic", "briefings");
}
function briefingFilename(date, format = "markdown") {
  const day = date.toISOString().slice(0, 10);
  return format === "json" ? `${day}.json` : `${day}.md`;
}

export {
  BRIEFING_FORMAT_ALLOWED,
  BRIEFING_FOLLOWUP_DEFAULT_MODEL,
  validateBriefingFormat,
  parseBriefingWindow,
  parseBriefingFocus,
  focusMatchesMemory,
  focusMatchesEntity,
  FileCalendarSource,
  parseIcsEvents,
  eventFallsOnDate,
  buildBriefing,
  filterMemoriesByWindow,
  buildActiveThreads,
  buildRecentEntities,
  renderBriefingMarkdown,
  resolveBriefingSaveDir,
  briefingFilename
};
//# sourceMappingURL=chunk-SS253RXF.js.map