{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/feeds/server/host.ts","../../../src/feeds/server/registry.ts","../../../src/feeds/server/retrievers/index.ts","../../../../common/dist/ssrf.js","../../../src/feeds/server/fetch/httpClient.ts","../../../src/feeds/server/fetch/rssParser.ts","../../../src/feeds/server/pathResolver.ts","../../../src/feeds/server/projectItem.ts","../../../src/feeds/server/retrievers/rss.ts","../../../src/feeds/server/retrievers/httpJson.ts","../../../src/feeds/server/state.ts","../../../src/feeds/server/agentIngest.ts","../../../src/feeds/server/engine.ts","../../../src/feeds/server/scheduledRefresh.ts"],"sourcesContent":["// Host injection seam for the Feeds engine — mirrors `configureCollectionHost`\n// / `configureScheduler`. The engine is host-agnostic: the workspace root, the\n// logger, an atomic file writer, and the hidden/visible agent-ingest worker\n// launcher are injected once at boot via `configureFeedsHost`. Everything else\n// the engine needs (collection IO, the notifier) is a sibling `@mulmoclaude/core`\n// subpath, imported directly. Both MulmoClaude and MulmoTerminal supply their\n// own host shim.\n\nimport { createForwardingLogger, createHostSlot, type StructuredLogger } from \"../../host/hostSlot.js\";\n\n/** Outcome of launching one hidden/visible agent-ingest worker. `chatId` lets\n *  the caller register a completion hook so a failed refresh doesn't die\n *  silently. */\nexport type AgentWorkerResult = { ok: true; chatId: string } | { ok: false; error: string };\n\n/** Launches a worker chat. Injected at boot to keep the feeds engine from\n *  importing a host's routes/session layer. `hidden` chooses an invisible\n *  system worker (scheduled refresh) vs a visible session the user can watch\n *  (manual Refresh — debuggable). `onComplete` is a one-shot completion hook\n *  (only honoured for hidden workers) so the dispatcher learns success/failure.\n *  Returns `ok:false` on the concurrency-cap miss or a launch error — the caller\n *  leaves state untouched and retries next tick. */\nexport type AgentWorkerRunner = (args: {\n  message: string;\n  roleId: string;\n  hidden: boolean;\n  /** Absolute root this refresh is for — the root `refreshViaAgent` was called\n   *  with, forwarded because the seed prompt's `dataPath` is RELATIVE to it.\n   *\n   *  A multi-root host must spawn the worker THERE (as its cwd), or the worker\n   *  resolves `data/collections/<slug>/items` against whatever root it happens\n   *  to run in and writes another project's records — silently, since both\n   *  paths exist and neither side errors.\n   *\n   *  Optional so a single-workspace host's runner (MulmoClaude's) can ignore it\n   *  and behave exactly as before: there is only one root, and it is the one the\n   *  worker already runs in. */\n  workspaceRoot?: string | undefined;\n  onComplete?: ((outcome: { didError: boolean }) => void | Promise<void>) | undefined;\n}) => Promise<AgentWorkerResult>;\n\n/** Public alias of the shared `StructuredLogger` — same shape as `CollectionLogger`. */\nexport type FeedsLogger = StructuredLogger;\n\nexport interface FeedsHost {\n  /** Absolute workspace root — the default for `refreshDue()` and state paths. */\n  workspaceRoot: string;\n  /** Host logger. */\n  log: FeedsLogger;\n  /** Host atomic file writer (state files). */\n  writeFileAtomic: (filePath: string, content: string) => Promise<void>;\n  /** Launches the agent-ingest worker (was `setAgentWorkerRunner`). */\n  spawnWorker: AgentWorkerRunner;\n}\n\nconst hostSlot = createHostSlot<FeedsHost>(\"@mulmoclaude/core/feeds: configureFeedsHost()\");\n\n/** Wire the feeds engine to a host. Call once at startup, before any refresh. */\nexport function configureFeedsHost(host: FeedsHost): void {\n  hostSlot.set(host);\n}\n\n/** The configured host, or throw if `configureFeedsHost` was never called. */\nexport function requireFeedsHost(): FeedsHost {\n  return hostSlot.get();\n}\n\n/** Test-only: clear the configured host. */\nexport function resetFeedsHostForTesting(): void {\n  hostSlot.reset();\n}\n\n/** Forwarding logger so engine modules can `import { log }` without each\n *  reaching for `requireFeedsHost().log`. Non-critical: calls before the host\n *  is wired are dropped (not thrown), matching the collection/google seams. */\nexport const log: FeedsLogger = createForwardingLogger(() => hostSlot.peek()?.log ?? null);\n","// List the registered data-source feeds. Feeds are CREATED / REMOVED by\n// the agent writing / deleting `feeds/<slug>/schema.json` directly (see\n// config/helps/feeds.md) — the host only discovers + retrieves them.\n// icon / dataPath defaults for agent-authored feed schemas are applied in\n// `collection/server/discovery.ts` (source === \"feed\").\n\nimport { rm } from \"node:fs/promises\";\nimport { discoverCollections, resolveDataDir, safeSlugName, type LoadedCollection } from \"../../collection/server/index.js\";\nimport { log, requireFeedsHost } from \"./host.js\";\nimport { feedDir } from \"../paths.js\";\n\n/** Every registered feed, as a discovered collection (carrying its\n *  validated schema, `ingest`, and resolved `dataDir`). */\nexport async function listFeeds(workspaceRoot: string = requireFeedsHost().workspaceRoot): Promise<LoadedCollection[]> {\n  const all = await discoverCollections({ workspaceRoot });\n  return all.filter((collection) => collection.source === \"feed\");\n}\n\n/** Delete a feed entirely: its records AND its `feeds/<slug>/` directory\n *  (schema + state). Idempotent. Host-side only (backs the UI delete\n *  button); the agent removes a feed by deleting both directories itself.\n *\n *  The records dir is derived from the SLUG (`data/feeds/<slug>`), never\n *  from the schema's `dataPath` — feeds are forced into that namespace at\n *  discovery, so a malformed/hostile `dataPath` can't redirect this delete\n *  at another app's data (e.g. `data/wiki`). `resolveDataDir` also rejects\n *  any path that escapes the workspace. */\nexport async function removeFeed(workspaceRoot: string, slug: string): Promise<boolean> {\n  const safe = safeSlugName(slug);\n  if (safe === null) return false;\n  const recordsDir = resolveDataDir(`data/feeds/${safe}`, workspaceRoot);\n  try {\n    if (recordsDir) await rm(recordsDir, { recursive: true, force: true });\n    await rm(feedDir(safe, workspaceRoot), { recursive: true, force: true });\n    log.info(\"feeds\", \"feed + records removed\", { slug: safe });\n    return true;\n  } catch (error) {\n    log.warn(\"feeds\", \"feed remove failed\", { slug: safe, error: String(error) });\n    return false;\n  }\n}\n","// Pluggable retriever registry. Each declarative `ingest.kind` maps to one\n// RetrieveFn that fetches the endpoint and returns projected records.\n// Side-effect registration keeps the engine decoupled from the kinds.\n// The `agent` kind is NOT a retriever (it dispatches a hidden worker before\n// the engine consults this registry); a future `code` kind would register here.\n\nimport type { CollectionItem, CollectionSchema } from \"../../../collection/index.js\";\nimport type { DeclarativeIngestSpec } from \"../../ingestTypes.js\";\nimport type { FeedState } from \"../state.js\";\n\nexport interface RetrieveResult {\n  /** Projected records, keyed by primaryKey (the engine upserts them). */\n  items: CollectionItem[];\n  /** Updated retriever cursor to persist (incremental fetches). */\n  cursor: Record<string, string>;\n}\n\n// Declarative-only: the engine branches `agent` ingest off BEFORE looking up a\n// retriever, so a RetrieveFn never sees a non-fetch spec (and rss/http-json can\n// read `ingest.url`/`map` without union narrowing).\nexport type RetrieveFn = (ingest: DeclarativeIngestSpec, schema: CollectionSchema, state: FeedState) => Promise<RetrieveResult>;\n\nconst registry = new Map<string, RetrieveFn>();\n\nexport function registerRetriever(kind: string, retriever: RetrieveFn): void {\n  registry.set(kind, retriever);\n}\n\nexport function getRetriever(kind: string): RetrieveFn | undefined {\n  return registry.get(kind);\n}\n","// The single SSRF deny-list for the monorepo: address classification and\n// URL-shape checks shared by every guard that fetches model-authored /\n// remote-user-supplied URLs (mastodon bridge urlGuard, core feeds httpClient).\n// Before #2459 the CIDR table was hand-copied per guard and had drifted.\n//\n// Everything here is pure and isomorphic — no `node:` builtins. The parts that\n// need the network (DNS resolution, per-redirect re-checks) stay in each\n// consumer's thin wrapper.\n/* eslint-disable sonarjs/no-hardcoded-ip -- the blocked ranges ARE this module's\n   specification; writing them as literals is the point, not an oversight. */\nconst ALLOWED_PROTOCOLS = new Set([\"http:\", \"https:\"]);\nconst BLOCKED_HOSTNAMES = new Set([\"localhost\", \"ip6-localhost\", \"ip6-loopback\"]);\nconst BLOCKED_HOSTNAME_SUFFIXES = [\".localhost\", \".local\", \".internal\"];\n/** IPv4 ranges that must never be fetched, as [firstAddress, prefixLength].\n *  The union of the pre-#2459 per-guard tables — removing an entry weakens\n *  every guard in the repo at once, so treat edits as security changes. */\nexport const BLOCKED_IPV4_RANGES = [\n    [\"0.0.0.0\", 8], // \"this network\"\n    [\"10.0.0.0\", 8], // RFC1918\n    [\"100.64.0.0\", 10], // CGNAT\n    [\"127.0.0.0\", 8], // loopback\n    [\"169.254.0.0\", 16], // link-local — includes the 169.254.169.254 metadata endpoint\n    [\"172.16.0.0\", 12], // RFC1918\n    [\"192.0.0.0\", 24], // IETF protocol assignments\n    [\"192.168.0.0\", 16], // RFC1918\n    [\"198.18.0.0\", 15], // benchmarking\n    [\"224.0.0.0\", 4], // multicast\n    [\"240.0.0.0\", 4], // reserved / broadcast\n];\n/** Dotted-decimal IPv4 → unsigned 32-bit value; null for anything else\n *  (hex / octal octets, whitespace, wrong part count). */\nexport function ipv4ToInt(address) {\n    const parts = address.split(\".\");\n    if (parts.length !== 4)\n        return null;\n    const octets = parts.map((part) => (/^\\d{1,3}$/.test(part) ? Number(part) : Number.NaN));\n    if (octets.some((octet) => Number.isNaN(octet) || octet > 255))\n        return null;\n    return octets.reduce((value, octet) => value * 256 + octet, 0);\n}\nfunction isBlockedIpv4Value(value) {\n    return BLOCKED_IPV4_RANGES.some(([base, prefix]) => {\n        const baseValue = ipv4ToInt(base);\n        if (baseValue === null)\n            return false;\n        // `>>> 0` keeps the mask unsigned; a /0 shift would be a no-op anyway.\n        const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;\n        return (value & mask) === (baseValue & mask);\n    });\n}\n/** True when this parses as IPv4 AND falls inside a blocked range. Non-IPv4\n *  input is false — \"not a blocked literal\", not \"safe\"; a caller that must\n *  reject non-IP input entirely does so at its own boundary. */\nexport function isBlockedIpv4(address) {\n    const value = ipv4ToInt(address);\n    if (value === null)\n        return false;\n    return isBlockedIpv4Value(value);\n}\n/** `URL.hostname` keeps the brackets on an IPv6 literal (`[::1]`); strip them\n *  so the address checks see the bare address. */\nexport function stripIpv6Brackets(hostname) {\n    return hostname.replace(/^\\[|\\]$/g, \"\");\n}\n/** The 32-bit value inside an IPv4-mapped IPv6 literal, or null when the\n *  address is not v4-mapped. The hex spelling matters: WHATWG URL serializes\n *  `[::ffff:127.0.0.1]` as `::ffff:7f00:1`, so matching only the dotted form\n *  would wave the mapped loopback through any URL-sourced check. */\nfunction mappedIpv4Value(lower) {\n    const dotted = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/.exec(lower)?.[1];\n    if (dotted !== undefined)\n        return ipv4ToInt(dotted);\n    const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower);\n    const [, high, low] = hex ?? [];\n    if (high !== undefined && low !== undefined)\n        return Number.parseInt(high, 16) * 0x10000 + Number.parseInt(low, 16);\n    return null;\n}\n/** Blocks loopback/unspecified, fc00::/7 unique-local, fe80::/10 link-local,\n *  and IPv4-mapped forms of blocked v4 addresses. Mask-based on the leading\n *  group — prefix string matching would miss e.g. fe9a:: (inside fe80::/10). */\nexport function isBlockedIpv6(address) {\n    const lower = stripIpv6Brackets(address.toLowerCase());\n    if (lower === \"::1\" || lower === \"::\")\n        return true;\n    // IPv4-mapped re-enters the v4 rules.\n    const mapped = mappedIpv4Value(lower);\n    if (mapped !== null)\n        return isBlockedIpv4Value(mapped);\n    const [head = \"\"] = lower.split(\":\");\n    if (head.length === 0)\n        return false;\n    const leading = Number.parseInt(head, 16);\n    if (Number.isNaN(leading))\n        return false;\n    if ((leading & 0xfe00) === 0xfc00)\n        return true; // fc00::/7 unique-local\n    if ((leading & 0xffc0) === 0xfe80)\n        return true; // fe80::/10 link-local\n    return false;\n}\n/** True when this literal address must never be fetched. Non-IP input is\n *  false — see `isBlockedIpv4`. */\nexport function isBlockedIp(address) {\n    return address.includes(\":\") ? isBlockedIpv6(address) : isBlockedIpv4(address);\n}\n/** Names that are internal by convention (localhost, *.local, *.internal…),\n *  rejected without waiting for DNS to prove it. */\nexport function isBlockedHostname(hostname) {\n    const lower = hostname.toLowerCase().replace(/\\.$/, \"\");\n    if (BLOCKED_HOSTNAMES.has(lower))\n        return true;\n    return BLOCKED_HOSTNAME_SUFFIXES.some((suffix) => lower.endsWith(suffix));\n}\nfunction parseUrl(raw) {\n    try {\n        return new URL(raw);\n    }\n    catch {\n        return null;\n    }\n}\n/** Parse and apply every check that doesn't need the network: shape, scheme,\n *  obviously-internal hostname, and literal addresses. Returns null on reject.\n *  Callers must still vet what non-literal hostnames resolve to. */\nexport function parseSafeUrlShape(raw) {\n    const url = parseUrl(raw);\n    if (url === null)\n        return null;\n    if (!ALLOWED_PROTOCOLS.has(url.protocol))\n        return null;\n    const hostname = stripIpv6Brackets(url.hostname);\n    if (hostname.length === 0)\n        return null;\n    if (isBlockedHostname(hostname))\n        return null;\n    if (isBlockedIp(hostname))\n        return null;\n    return url;\n}\n","// Minimal HTTP client for feed retrievers. Feed URLs are model-authored /\n// user-supplied, so beyond a User-Agent + timeout this guards against SSRF:\n// every URL (and every redirect hop) is DNS-resolved and rejected if it\n// points at a loopback / private / link-local / cloud-metadata address.\n// The deny-list itself is the repo-wide one in `@mulmoclaude/common/ssrf`\n// (#2459); this wrapper adds the DNS resolution and manual redirect loop.\n// Redirects are followed MANUALLY so a public URL can't 302 to an internal\n// one and bypass the guard. It does NOT do robots.txt / rate limiting (the\n// engine fetches feeds sequentially to stay gentle).\n//\n// Residual risk (same as the mastodon urlGuard): the DNS answer we validate is\n// not necessarily the one the subsequent fetch() uses — it re-resolves — so a\n// rebinding attacker with a very short TTL can still slip through. Pinning the\n// resolved address into the connection would need a custom agent.\n\nimport { lookup } from \"node:dns/promises\";\nimport { isIP } from \"node:net\";\nimport { isBlockedIpv4, isBlockedIpv6, parseSafeUrlShape, stripIpv6Brackets } from \"@mulmoclaude/common/ssrf\";\n\n// Inlined — the client needs only this one time constant and must stay free of\n// host-side time-constant modules.\nconst ONE_SECOND_MS = 1_000;\n\n/** Identifies the bot to site operators. */\nexport const FEED_USER_AGENT = \"MulmoClaude-FeedBot/1.0 (+https://github.com/receptron/mulmoclaude)\";\n\n/** Per-request wall-clock cap so a hung server can't wedge a refresh. */\nexport const DEFAULT_FEED_TIMEOUT_MS = 30 * ONE_SECOND_MS;\n\n/** Cap on redirect hops followed (each re-checked for SSRF). */\nconst MAX_REDIRECTS = 5;\n\n/** DNS answers should be IP literals; anything else is blocked outright. */\nfunction isBlockedResolvedIp(address: string): boolean {\n  const kind = isIP(address);\n  if (kind === 4) return isBlockedIpv4(address);\n  if (kind === 6) return isBlockedIpv6(address);\n  return true;\n}\n\nasync function resolveAddresses(host: string): Promise<{ address: string }[]> {\n  try {\n    return await lookup(host, { all: true });\n  } catch (err) {\n    throw new Error(`could not resolve host '${host}': ${String(err)}`);\n  }\n}\n\n/** Reject non-http(s) URLs, internal-by-convention hostnames, and URLs whose\n *  host is / resolves to a private/loopback address (SSRF guard). Throws with\n *  a clear reason; returns void on pass. */\nasync function assertFetchableUrl(rawUrl: string): Promise<void> {\n  if (!/^https?:\\/\\//i.test(rawUrl)) throw new Error(`refusing non-http(s) URL: ${rawUrl}`);\n  const url = parseSafeUrlShape(rawUrl);\n  if (url === null) throw new Error(`refusing to fetch a private/loopback/internal address: ${rawUrl}`);\n  const host = stripIpv6Brackets(url.hostname);\n  // A literal address was already vetted by the shape check above.\n  if (isIP(host)) return;\n  const blocked = (await resolveAddresses(host)).find((entry) => isBlockedResolvedIp(entry.address));\n  if (blocked) throw new Error(`refusing to fetch '${host}' — resolves to a private/loopback address (${blocked.address})`);\n}\n\nasync function fetchOnce(url: string, timeoutMs: number): Promise<Response> {\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(new DOMException(`feed fetch timed out after ${timeoutMs}ms`, \"TimeoutError\")), timeoutMs);\n  try {\n    return await fetch(url, { headers: { \"User-Agent\": FEED_USER_AGENT }, signal: controller.signal, redirect: \"manual\" });\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\n// Follow redirects manually, re-running the SSRF guard on every hop so a\n// public URL cannot bounce to an internal target.\nasync function fetchGuarded(rawUrl: string, timeoutMs: number): Promise<Response> {\n  let current = rawUrl;\n  for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n    await assertFetchableUrl(current);\n    const response = await fetchOnce(current, timeoutMs);\n    const redirect = response.status >= 300 && response.status < 400 && response.status !== 304 ? response.headers.get(\"location\") : null;\n    if (!redirect) return response;\n    current = new URL(redirect, current).toString();\n  }\n  throw new Error(`too many redirects (>${MAX_REDIRECTS}) starting from ${rawUrl}`);\n}\n\n/** Fetch a URL as text, throwing on guard rejection, network error, or non-2xx. */\nexport async function fetchText(url: string, timeoutMs: number = DEFAULT_FEED_TIMEOUT_MS): Promise<string> {\n  const response = await fetchGuarded(url, timeoutMs);\n  if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText} fetching ${url}`);\n  return response.text();\n}\n\n/** Fetch a URL as parsed JSON, throwing on guard rejection, network error, or non-2xx. */\nexport async function fetchJson(url: string, timeoutMs: number = DEFAULT_FEED_TIMEOUT_MS): Promise<unknown> {\n  const response = await fetchGuarded(url, timeoutMs);\n  if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText} fetching ${url}`);\n  return response.json();\n}\n","// RSS 2.0 + Atom 1.0 + RSS 1.0 (RDF) parser for the Feeds engine.\n//\n// Deliberately NON-normalizing: it locates the feed's items and hands\n// each one back as its RAW parsed XML element. The host hard-codes no\n// per-feed field list — `ingest.map` resolves source paths against the\n// raw item (tags are keys; attributes are prefixed `@_`; namespaced tags\n// keep their prefix), and the caller inspects the feed to decide what to\n// map. Generic value handling (text-node unwrapping, date parsing by the\n// target field's declared type) lives in `projectItem.ts`, not here.\n//\n// Own copy of the XML plumbing — the Feeds tree does not import the\n// legacy `sources` tree. Pure; unit-testable with fixture strings.\n\nimport { XMLParser } from \"fast-xml-parser\";\n\n// Tiny inline type guards (the host's shared `utils/types` is not available in\n// this shared package — these are the only two we need here).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction isNonEmptyString(value: unknown): value is string {\n  return typeof value === \"string\" && value.trim().length > 0;\n}\n\nexport interface ParsedFeedItem {\n  /** The raw parsed XML <item>/<entry> object, verbatim. */\n  raw: Record<string, unknown>;\n}\n\nexport interface ParsedFeed {\n  kind: \"rss\" | \"atom\";\n  title: string | null;\n  items: ParsedFeedItem[];\n}\n\nconst xml = new XMLParser({\n  ignoreAttributes: false,\n  attributeNamePrefix: \"@_\",\n  cdataPropName: \"#cdata\",\n  parseTagValue: false,\n  parseAttributeValue: false,\n  trimValues: true,\n  // Only the item containers are forced to arrays (single vs. many).\n  // Everything else stays in its natural parsed shape so `ingest.map`\n  // resolves paths the way the caller sees them in the feed.\n  isArray: (name) => name === \"item\" || name === \"entry\",\n});\n\n/** Parse an RSS/Atom/RDF feed body. Returns null when the input doesn't\n *  look like a feed we understand. */\nexport function parseFeed(body: string): ParsedFeed | null {\n  const text = stripBom(body);\n  if (!text.trim()) return null;\n  let parsed: unknown;\n  try {\n    parsed = xml.parse(text);\n  } catch {\n    return null;\n  }\n  if (!isRecord(parsed)) return null;\n  if (isRecord(parsed.rss)) return parseRss(parsed.rss);\n  if (isRecord(parsed.feed)) return parseAtom(parsed.feed);\n  const rdf = parsed[\"rdf:RDF\"] ?? parsed.RDF;\n  if (isRecord(rdf)) return parseRss10(rdf);\n  return null;\n}\n\n// Collect the raw item objects, skipping anything that isn't a record or\n// carries no <title> (drops feed-level noise / empty entries).\nfunction collectItems(value: unknown): ParsedFeedItem[] {\n  const rawItems = Array.isArray(value) ? value : [];\n  const items: ParsedFeedItem[] = [];\n  for (const raw of rawItems) {\n    if (isRecord(raw) && readString(raw.title) !== null) items.push({ raw });\n  }\n  return items;\n}\n\nfunction parseRss(rss: Record<string, unknown>): ParsedFeed | null {\n  const { channel } = rss;\n  if (!isRecord(channel)) return null;\n  return { kind: \"rss\", title: readString(channel.title), items: collectItems(channel.item) };\n}\n\nfunction parseRss10(rdf: Record<string, unknown>): ParsedFeed | null {\n  const channel = isRecord(rdf.channel) ? rdf.channel : null;\n  return { kind: \"rss\", title: channel ? readString(channel.title) : null, items: collectItems(rdf.item) };\n}\n\nfunction parseAtom(feed: Record<string, unknown>): ParsedFeed | null {\n  return { kind: \"atom\", title: readString(feed.title), items: collectItems(feed.entry) };\n}\n\n// --- helpers ------------------------------------------------------------\n\n// Extract a string from a plain string, a `{ \"#text\" }` / `{ \"#cdata\" }`\n// node, or an array (first non-empty). Used only to read the feed/item\n// title for the channel name + the empty-item filter.\nfunction readString(value: unknown): string | null {\n  if (isNonEmptyString(value)) return value;\n  if (typeof value === \"string\") return null;\n  if (isRecord(value)) return readStringFromRecord(value);\n  if (Array.isArray(value)) return readStringFromArray(value);\n  return null;\n}\n\nfunction readStringFromRecord(record: Record<string, unknown>): string | null {\n  const text = record[\"#text\"];\n  if (isNonEmptyString(text)) return text;\n  const cdata = record[\"#cdata\"];\n  if (isNonEmptyString(cdata)) return cdata;\n  return null;\n}\n\nfunction readStringFromArray(array: readonly unknown[]): string | null {\n  for (const entry of array) {\n    const resolved = readString(entry);\n    if (resolved !== null) return resolved;\n  }\n  return null;\n}\n\nfunction stripBom(text: string): string {\n  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n","// Tiny dot/bracket path resolver used by the declarative `ingest.map`\n// and `ingest.itemsAt`. Pure, no I/O, exhaustively unit-testable.\n//\n// Supported syntax:\n//   \"title\"                → root.title\n//   \"data.name\"            → root.data.name\n//   \"results[0].id\"        → root.results[0].id\n//   \"hourly[]\"             → root.hourly        (trailing [] = \"the array\n//                            here\"; the marker is a no-op for value reads)\n//   \"data.results[]\"       → root.data.results\n//\n// Any miss (wrong type, out-of-range index, absent key) yields\n// `undefined` rather than throwing — declarative configs fail soft.\n\nimport { isRecord, isUnknownArray } from \"@mulmoclaude/common\";\n\ninterface KeyToken {\n  kind: \"key\";\n  key: string;\n}\ninterface IndexToken {\n  kind: \"index\";\n  index: number;\n}\ntype PathToken = KeyToken | IndexToken;\n\nconst BRACKET_RE = /\\[(\\d*)\\]/g;\n\n/** Split one dot-segment (`results[0]`, `hourly[]`, `name`) into tokens.\n *  An empty `[]` is an array-identity marker and emits no token — the\n *  array value is read by the preceding key. */\nfunction parseSegment(segment: string, tokens: PathToken[]): void {\n  const bracketStart = segment.indexOf(\"[\");\n  const name = bracketStart === -1 ? segment : segment.slice(0, bracketStart);\n  if (name.length > 0) tokens.push({ kind: \"key\", key: name });\n  if (bracketStart === -1) return;\n  for (const match of segment.slice(bracketStart).matchAll(BRACKET_RE)) {\n    const [, inner = \"\"] = match;\n    if (inner.length > 0) tokens.push({ kind: \"index\", index: Number(inner) });\n  }\n}\n\nfunction tokenize(path: string): PathToken[] {\n  const tokens: PathToken[] = [];\n  for (const segment of path.split(\".\")) {\n    if (segment.length > 0) parseSegment(segment, tokens);\n  }\n  return tokens;\n}\n\nfunction step(current: unknown, token: PathToken): unknown {\n  if (current === null || current === undefined) return undefined;\n  if (token.kind === \"key\") {\n    if (!isRecord(current)) return undefined;\n    // A `__proto__` / `constructor` / `toString` key must miss (→ undefined),\n    // not read an inherited Object.prototype member into a `naturalKey`.\n    return Object.hasOwn(current, token.key) ? current[token.key] : undefined;\n  }\n  return isUnknownArray(current) ? current[token.index] : undefined;\n}\n\n/** Resolve a dot/bracket path against `root`, or `undefined` on any miss. */\nexport function getByPath(root: unknown, path: string): unknown {\n  let current: unknown = root;\n  for (const token of tokenize(path)) {\n    current = step(current, token);\n  }\n  return current;\n}\n\n/** Locate the array of raw items for a fetch. With `itemsAt` set, walk\n *  to it; without it, the response itself must be the array. Non-arrays\n *  yield `[]` so a malformed response is a no-op, not a crash. */\nexport function getItemsArray(root: unknown, itemsAt: string | undefined): unknown[] {\n  if (!itemsAt) return Array.isArray(root) ? root : [];\n  const value = getByPath(root, itemsAt);\n  return Array.isArray(value) ? value : [];\n}\n","// Project one raw retrieved item (a parsed RSS entry, or a JSON object)\n// into a CollectionItem whose keys match the schema's fields, using the\n// declarative `ingest.map`. Also derives the record's id.\n//\n// Id rule: a collection record's filename IS its primaryKey value, and\n// that value must be a safe slug. Feed-native keys (RSS guids/URLs,\n// ISO datetimes) usually are NOT slug-safe, so we slugify the natural\n// key into a deterministic, stable id — same natural key → same id, so\n// re-fetches upsert in place. The natural key comes from the mapped\n// primaryKey value, then `ingest.idFrom`, then a content hash.\n\nimport { isRecord } from \"@mulmoclaude/common\";\nimport { createHash } from \"node:crypto\";\nimport { getByPath } from \"./pathResolver.js\";\nimport type { CollectionItem, CollectionSchema } from \"../../collection/index.js\";\nimport type { DeclarativeIngestSpec } from \"../ingestTypes.js\";\n\nfunction asKeyString(value: unknown): string | null {\n  if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n  if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n  return null;\n}\n\n/** Collapse an XML element object that carries body text alongside\n *  attributes (`{ \"#text\": \"v\", \"@_x\": \"...\" }`) or CDATA to its text.\n *  Generic — the host knows no field names, only this fast-xml-parser\n *  shape — so a tag like `<guid isPermaLink=\"false\">id</guid>` maps to\n *  its text without the caller needing to write `.#text`. */\nfunction unwrapTextNode(value: unknown): unknown {\n  if (isRecord(value)) {\n    if (typeof value[\"#text\"] === \"string\") return value[\"#text\"];\n    if (typeof value[\"#cdata\"] === \"string\") return value[\"#cdata\"];\n  }\n  return value;\n}\n\n/** Normalize a mapped value generically — driven by the SCHEMA's declared\n *  field type, never by the source field name. Today: unwrap XML text\n *  nodes, and coerce anything mapped into a `date` field to a `YYYY-MM-DD`\n *  civil date (the collection `date` type + calendar are day-granularity\n *  and parse strictly — a full RFC-3339 timestamp would be rejected). */\nfunction normalizeValue(value: unknown, fieldType: string | undefined): unknown {\n  const unwrapped = unwrapTextNode(value);\n  if (fieldType === \"date\" && typeof unwrapped === \"string\") {\n    const millis = Date.parse(unwrapped);\n    if (Number.isFinite(millis)) return new Date(millis).toISOString().slice(0, 10);\n  }\n  return unwrapped;\n}\n\n/** Slugify a natural key into a stable, filename-safe id. Short, safe\n *  keys pass through (lowercased); long or unsafe keys collapse to a\n *  hash so the filename stays bounded and valid. */\nfunction toSafeId(natural: string): string {\n  const collapsed = natural\n    .trim()\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, \"-\");\n  // eslint-disable-next-line sonarjs/super-linear-regex -- anchored hyphen trim, linear, no catastrophic backtracking\n  const slug = collapsed.replace(/^-+|-+$/g, \"\");\n  if (slug.length > 0 && slug.length <= 80) return slug;\n  const hash = createHash(\"sha256\")\n    .update(natural || \"item\", \"utf-8\")\n    .digest(\"hex\")\n    .slice(0, 16);\n  return slug.length > 80 ? `${slug.slice(0, 60)}-${hash}` : `feed-${hash}`;\n}\n\nfunction naturalKey(record: CollectionItem, rawItem: unknown, ingest: DeclarativeIngestSpec, schema: CollectionSchema): string {\n  const fromMapped = asKeyString(record[schema.primaryKey]);\n  if (fromMapped) return fromMapped;\n  if (ingest.idFrom) {\n    const fromId = asKeyString(unwrapTextNode(getByPath(rawItem, ingest.idFrom)));\n    if (fromId) return fromId;\n  }\n  return JSON.stringify(record);\n}\n\n/** Build a record from a raw fetched item (a parsed RSS/Atom element or a\n *  JSON object) using `ingest.map`. Each source path is resolved against\n *  the raw item and normalized per the target field's declared type. The\n *  returned record's primaryKey is set to the derived safe id (so it\n *  doubles as the filename). */\nexport function projectRecord(rawItem: unknown, ingest: DeclarativeIngestSpec, schema: CollectionSchema): CollectionItem {\n  const record: CollectionItem = {};\n  for (const [targetField, sourcePath] of Object.entries(ingest.map)) {\n    const value = normalizeValue(getByPath(rawItem, sourcePath), schema.fields?.[targetField]?.type);\n    if (value !== undefined) record[targetField] = value;\n  }\n  record[schema.primaryKey] = toSafeId(naturalKey(record, rawItem, ingest, schema));\n  return record;\n}\n","// RSS / Atom retriever. Fetches the feed, parses it, and projects each\n// item's RAW parsed XML element through `ingest.map`. The map's source\n// paths are the item's own tags/attributes (e.g. `title`, `pubDate`,\n// `enclosure.@_url`, `itunes:duration`) — the host hard-codes no field\n// list; the caller inspects the feed and maps what it carries.\n\nimport { fetchText } from \"../fetch/httpClient.js\";\nimport { parseFeed } from \"../fetch/rssParser.js\";\nimport { projectRecord } from \"../projectItem.js\";\nimport { registerRetriever, type RetrieveFn } from \"./index.js\";\n\nconst retrieveRss: RetrieveFn = async (ingest, schema) => {\n  const body = await fetchText(ingest.url);\n  const feed = parseFeed(body);\n  if (!feed) return { items: [], cursor: {} };\n  const items = feed.items.map((item) => projectRecord(item.raw, ingest, schema));\n  return { items, cursor: {} };\n};\n\n// Atom shares the same parser + projection path.\nregisterRetriever(\"rss\", retrieveRss);\nregisterRetriever(\"atom\", retrieveRss);\n\nexport { retrieveRss };\n","// Generic JSON-API retriever. Fetches JSON, walks `ingest.itemsAt` to\n// the array of raw items, and projects each through `ingest.map` (whose\n// source paths are dot/bracket paths into each raw item).\n\nimport { fetchJson } from \"../fetch/httpClient.js\";\nimport { getItemsArray } from \"../pathResolver.js\";\nimport { projectRecord } from \"../projectItem.js\";\nimport { registerRetriever, type RetrieveFn } from \"./index.js\";\n\nconst retrieveHttpJson: RetrieveFn = async (ingest, schema) => {\n  const json = await fetchJson(ingest.url);\n  const rawItems = getItemsArray(json, ingest.itemsAt);\n  const items = rawItems.map((raw) => projectRecord(raw, ingest, schema));\n  return { items, cursor: {} };\n};\n\nregisterRetriever(\"http-json\", retrieveHttpJson);\n\nexport { retrieveHttpJson };\n","// Per-collection retrieval state — when we last fetched/dispatched, the\n// retriever's cursor (for incremental fetches), and a consecutive-failure\n// counter. NOT committed to git. Location depends on the collection's source:\n// feeds keep it alongside the schema at `<workspace>/feeds/<slug>/_state.json`;\n// skill-backed collections with `ingest.kind: \"agent\"` store it at\n// `<workspace>/data/ingest-state/<slug>.json` (a schema-less `feeds/<slug>/`\n// dir would confuse feed discovery, and `_state.json` must never live in a\n// collection's dataDir where `listItems` would read it as a record).\n// Deliberately minimal: the legacy `sources` tree carries richer backoff\n// state, but the engine starts simple and grows on real need.\n\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { CollectionSource } from \"../../collection/index.js\";\nimport { log, requireFeedsHost } from \"./host.js\";\nimport { feedStatePath, ingestStatePath } from \"../paths.js\";\n\n/** Minimal shape needed to locate a collection's state file. `LoadedCollection`\n *  satisfies it. */\nexport interface StateTarget {\n  slug: string;\n  source: CollectionSource;\n}\n\n/** Resolve the on-disk state file for a collection, branching on source. */\nfunction stateFilePath(target: StateTarget, workspaceRoot: string): string {\n  return target.source === \"feed\" ? feedStatePath(target.slug, workspaceRoot) : ingestStatePath(target.slug, workspaceRoot);\n}\n\nexport interface FeedState {\n  slug: string;\n  /** ISO timestamp of the last successful fetch (declarative) or dispatch\n   *  (agent ingest), or null if never. */\n  lastFetchedAt: string | null;\n  /** Free-form retriever cursor (e.g. last-seen id / etag). */\n  cursor: Record<string, string>;\n  /** Consecutive failed fetches/runs; reset to 0 on success. */\n  consecutiveFailures: number;\n  /** Agent ingest only: the notifier entry id of the active \"refresh failed\"\n   *  bell, so a later success can clear exactly that entry. Absent when no\n   *  failure bell is showing. */\n  failureBellId?: string;\n}\n\nexport function defaultFeedState(slug: string): FeedState {\n  return { slug, lastFetchedAt: null, cursor: {}, consecutiveFailures: 0 };\n}\n\n/** The stored cursor, keeping only the string entries its type promises. A\n *  hand-edited state file can hold anything; a non-string entry is unreadable\n *  to every retriever, so carrying it forward would only re-persist junk. */\nfunction readCursor(value: unknown): Record<string, string> {\n  if (!isRecord(value)) return {};\n  return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"));\n}\n\nfunction normalizeState(slug: string, parsed: Record<string, unknown>): FeedState {\n  const base = defaultFeedState(slug);\n  const cursor = parsed.cursor === undefined ? base.cursor : readCursor(parsed.cursor);\n  return {\n    slug,\n    lastFetchedAt: typeof parsed.lastFetchedAt === \"string\" ? parsed.lastFetchedAt : base.lastFetchedAt,\n    cursor,\n    consecutiveFailures: typeof parsed.consecutiveFailures === \"number\" ? parsed.consecutiveFailures : base.consecutiveFailures,\n    ...(typeof parsed.failureBellId === \"string\" ? { failureBellId: parsed.failureBellId } : {}),\n  };\n}\n\n/** Read a collection's retrieval state, tolerating a missing file (first run →\n *  default). The state path branches on `target.source`. */\nexport async function readFeedState(workspaceRoot: string, target: StateTarget): Promise<FeedState> {\n  const { slug } = target;\n  try {\n    const raw = await readFile(stateFilePath(target, workspaceRoot), \"utf-8\");\n    const parsed: unknown = JSON.parse(raw);\n    // A non-object state file has no fields to read, so it normalises to the\n    // same default `normalizeState` would produce from an empty record.\n    return normalizeState(slug, isRecord(parsed) ? parsed : {});\n  } catch (err) {\n    if (!isErrorWithCode(err) || err.code !== \"ENOENT\") {\n      log.warn(\"feeds\", \"failed to read feed state, using default\", { slug, error: String(err) });\n    }\n    return defaultFeedState(slug);\n  }\n}\n\n/** Persist a collection's retrieval state atomically (creating the parent dir\n *  if needed). The state path branches on `target.source`. */\nexport async function writeFeedState(workspaceRoot: string, target: StateTarget, state: FeedState): Promise<void> {\n  const file = stateFilePath(target, workspaceRoot);\n  await mkdir(path.dirname(file), { recursive: true });\n  await requireFeedsHost().writeFileAtomic(file, `${JSON.stringify(state, null, 2)}\\n`);\n}\n","// Agent-performed ingest (`ingest.kind: \"agent\"`). On schedule (or a manual\n// Refresh), the host seeds a hidden background worker — origin `system`, so\n// it never appears in the user's session list — in the collection's declared\n// role with its template + a summary of every record, and the worker edits the\n// records itself via the collections io layer. The host stays domain-free:\n// everything stock-specific (or whatever the collection does) is prose in the\n// template.\n//\n// DI seam: the worker is launched through `FeedsHost.spawnWorker`, injected at\n// boot via `configureFeedsHost`, not imported directly — this module ships in a\n// shared package and must not reach into any host's session/routes layer.\n\nimport { listItems, promptPathsFor, readSkillTemplate, buildCollectionActionSeedPrompt, type LoadedCollection } from \"../../collection/server/index.js\";\nimport { publish as publishNotifier, clear as clearNotifier } from \"../../notifier/index.js\";\nimport { log, requireFeedsHost, type AgentWorkerResult, type AgentWorkerRunner } from \"./host.js\";\nimport { readFeedState, writeFeedState, type FeedState } from \"./state.js\";\nimport { AGENT_INGEST_KIND } from \"../ingestTypes.js\";\nimport type { RefreshResult } from \"./refreshResult.js\";\n\nexport type { AgentWorkerResult, AgentWorkerRunner } from \"./host.js\";\n\n/** The injected worker launcher, or null if the host was never configured.\n *  Read non-throwingly so the failure-isolated contract holds (an unconfigured\n *  host becomes an `errors` entry, not a thrown exception). */\nfunction workerRunnerOrNull(): AgentWorkerRunner | null {\n  try {\n    return requireFeedsHost().spawnWorker;\n  } catch {\n    return null;\n  }\n}\n\nfunction result(slug: string, patch: Partial<RefreshResult>): RefreshResult {\n  return { slug, written: 0, removed: 0, errors: [], ...patch };\n}\n\n/** Dispatch one agent-ingest refresh: build the seed, launch a worker, and (on a\n *  successful launch) stamp `lastFetchedAt` with the DISPATCH time — not\n *  completion. That's what gates the due-loop, so a slow worker can't cause a\n *  double-dispatch. `opts.hidden` (default true) runs an invisible system worker\n *  for SCHEDULED refreshes; a MANUAL Refresh passes `hidden:false` for a visible,\n *  debuggable session. Failure-isolated: never throws; cap-miss / template-miss /\n *  launch error leave state untouched and report via `errors`. */\nexport async function refreshViaAgent(workspaceRoot: string, collection: LoadedCollection, opts?: { hidden?: boolean }): Promise<RefreshResult> {\n  const hidden = opts?.hidden ?? true;\n  const { slug } = collection;\n  const { ingest } = collection.schema;\n  if (ingest?.kind !== AGENT_INGEST_KIND) return result(slug, { errors: [\"collection has no agent ingest config\"] });\n  const workerRunner = workerRunnerOrNull();\n  if (!workerRunner) return result(slug, { errors: [\"agent ingest worker runner not configured\"] });\n\n  const template = await readSkillTemplate(collection.skillDir, ingest.template);\n  if (template === null) return result(slug, { errors: [`ingest template '${ingest.template}' could not be read`] });\n\n  const items = await listItems(collection.dataDir, { workspaceRoot });\n  // Thread the canonical, host-normalized paths through the seed so the\n  // template can pass `--out-dir data/collections/<slug>/items` (etc.) into\n  // any bundled script instead of relying on the script's `argparse` default —\n  // which was written for the author's pre-import layout, NOT the R3-\n  // normalized location the host actually reads records from. Fixes #1891.\n  const message = buildCollectionActionSeedPrompt(items, collection.schema, template, promptPathsFor(collection, workspaceRoot));\n\n  // The runner is injected, so guard its promise here to honour the\n  // failure-isolated contract (a rejection must become an `errors` entry, not\n  // escape into the scheduler loop / route handler).\n  let launch: AgentWorkerResult;\n  try {\n    launch = await workerRunner({\n      message,\n      roleId: ingest.role,\n      hidden,\n      // The seed prompt addresses records root-relatively (`promptPathsFor`), so\n      // the root travels WITH it: a multi-root host spawns the worker there. A\n      // single-workspace host ignores the field — same root either way.\n      workspaceRoot,\n      // A visible manual run is watched directly — only hidden runs get the\n      // completion hook (failure bell + consecutiveFailures); `finalizeRun` only\n      // fires it for system-origin sessions anyway.\n      onComplete: hidden ? (outcome) => recordOutcome(workspaceRoot, collection, outcome.didError) : undefined,\n    });\n  } catch (err) {\n    log.warn(\"feeds\", \"agent ingest worker launch threw\", { slug, error: String(err) });\n    return result(slug, { errors: [String(err)], dispatched: false });\n  }\n  if (!launch.ok) {\n    // Cap-miss or launch error: do NOT touch lastFetchedAt — the next due tick\n    // (or manual Refresh) redials. Surface it so a manual refresh reads honest.\n    log.info(\"feeds\", \"agent ingest dispatch skipped\", { slug, error: launch.error });\n    return result(slug, { errors: [launch.error], dispatched: false });\n  }\n\n  const state = await readFeedState(workspaceRoot, collection);\n  await writeFeedState(workspaceRoot, collection, { ...state, lastFetchedAt: new Date().toISOString() });\n  log.info(\"feeds\", \"agent ingest dispatched\", { slug, role: ingest.role, chatId: launch.chatId, hidden, items: items.length });\n  // Surface the chatId only for a visible run so the client can open it; a hidden\n  // session is excluded from every listing and can't be navigated to anyway.\n  return result(slug, { dispatched: true, ...(hidden ? {} : { chatId: launch.chatId }) });\n}\n\n/** Completion hook: reconcile failure tracking when a dispatched worker\n *  finishes. On error, bump `consecutiveFailures` and raise a single failure\n *  bell (deduped via the persisted `failureBellId`). On success, reset the\n *  counter and clear any standing bell. Best-effort + failure-isolated — it\n *  runs inside the agent run's teardown, so it must never throw. */\nasync function recordOutcome(workspaceRoot: string, collection: LoadedCollection, didError: boolean): Promise<void> {\n  const state = await readFeedState(workspaceRoot, collection);\n  const next: FeedState = didError ? await onWorkerError(state, collection) : await onWorkerSuccess(state, collection);\n  await writeFeedState(workspaceRoot, collection, next);\n}\n\nasync function onWorkerError(state: FeedState, collection: LoadedCollection): Promise<FeedState> {\n  const next: FeedState = { ...state, consecutiveFailures: state.consecutiveFailures + 1 };\n  log.warn(\"feeds\", \"agent ingest worker failed\", { slug: collection.slug, consecutiveFailures: next.consecutiveFailures });\n  // Raise a single bell on the first failure; a standing one isn't piled onto.\n  if (!state.failureBellId) {\n    try {\n      const { id } = await publishNotifier({\n        pluginPkg: \"host\",\n        severity: \"nudge\",\n        lifecycle: \"fyi\",\n        title: \"Collection refresh failed\",\n        body: `“${collection.schema.title}” (${collection.slug}) couldn't refresh. Open it to retry.`,\n        navigateTarget: `/collections/${collection.slug}`,\n      });\n      next.failureBellId = id;\n    } catch (err) {\n      log.warn(\"feeds\", \"failed to publish ingest failure bell\", { slug: collection.slug, error: String(err) });\n    }\n  }\n  return next;\n}\n\nasync function onWorkerSuccess(state: FeedState, collection: LoadedCollection): Promise<FeedState> {\n  log.info(\"feeds\", \"agent ingest worker completed\", { slug: collection.slug });\n  if (state.failureBellId) {\n    await clearNotifier(state.failureBellId).catch((err) =>\n      log.warn(\"feeds\", \"failed to clear ingest failure bell\", { slug: collection.slug, error: String(err) }),\n    );\n  }\n  const next: FeedState = { ...state, consecutiveFailures: 0 };\n  delete next.failureBellId;\n  return next;\n}\n","// Retrieval engine: fetch a feed, upsert its records into the\n// collection's data dir (keyed by primaryKey, so re-fetches replace in\n// place / accumulate by id), and persist per-feed state. Per-feed\n// failures are isolated — `refreshOne` never throws; `refreshDue`\n// processes feeds sequentially to stay gentle on remote hosts (the\n// fetch client does no rate-limiting yet).\n\nimport { discoverCollections, storeFor, type CollectionStore, type LoadedCollection } from \"../../collection/server/index.js\";\nimport type { CollectionItem, CollectionSchema } from \"../../collection/index.js\";\nimport { mergeIntoExisting } from \"../../collection/core/project.js\";\nimport { log, requireFeedsHost } from \"./host.js\";\nimport { getRetriever } from \"./retrievers/index.js\";\nimport \"./retrievers/registerAll.js\";\nimport { readFeedState, writeFeedState, type FeedState } from \"./state.js\";\nimport { DEFAULT_FEED_MAX_ITEMS, AGENT_INGEST_KIND, type FeedSchedule, type IngestSpec } from \"../ingestTypes.js\";\nimport { refreshViaAgent } from \"./agentIngest.js\";\nimport type { RefreshResult } from \"./refreshResult.js\";\n\nexport type { RefreshResult } from \"./refreshResult.js\";\n\n// Refresh cadence anchors (ms). Inlined — the engine needs only these two and\n// must stay free of host-side time-constant modules.\nconst ONE_HOUR_MS = 3_600_000;\nconst ONE_DAY_MS = 86_400_000;\n\n/** A feed's retrieval config, or undefined. `CollectionSchema.ingest` and the\n *  engine's `IngestSpec` are both `IngestZ`'s inferred union, so this is a\n *  rename, not a narrowing — the engine keeps its own vocabulary at the seam. */\nfunction feedIngest(schema: CollectionSchema): IngestSpec | undefined {\n  return schema.ingest;\n}\n\n/** A feed's store. `write`/`delete` are always present (discovery rejects\n *  `ingest` on read-only dataSource schemas); null only as defense in depth. */\nfunction writableFeedStore(\n  workspaceRoot: string,\n  feed: LoadedCollection,\n): (CollectionStore & { write: NonNullable<CollectionStore[\"write\"]>; delete: NonNullable<CollectionStore[\"delete\"]> }) | null {\n  const store = storeFor(feed, { workspaceRoot });\n  const { write, delete: remove } = store;\n  if (!write || !remove) {\n    log.warn(\"feeds\", \"feed store is read-only — refresh skipped\", { slug: feed.slug });\n    return null;\n  }\n  return { ...store, write, delete: remove };\n}\n\n/** Fields the feed itself produces: what it actually returned this run, the\n *  declared map's targets, and the primary key the retriever derives. Anything\n *  else on a record got there some other way — the user.\n *\n *  Both sources, because neither alone is enough. The in-tree retrievers project\n *  through `map`, so it describes them exactly — but `registerRetriever` lets a\n *  host add one that returns whatever it likes, and only the observed keys catch\n *  that. Conversely a run that fetched nothing has no keys to observe, and only\n *  `map` keeps the cap working through it. */\nexport const ingestedFields = (items: readonly CollectionItem[], mappedTargets: readonly string[], primaryKey: string): ReadonlySet<string> =>\n  new Set([primaryKey, ...mappedTargets, ...items.flatMap((item) => Object.keys(item))]);\n\n/** The collection fields a declarative `map` names. Read as `unknown` and\n *  narrowed here rather than through the zod-derived union, whose `map` does not\n *  survive the `kind` narrowing well enough to index safely. Only the keys are\n *  wanted, so the value type never has to be reconstructed. */\nexport function mappedTargetsOf(ingest: IngestSpec | undefined): string[] {\n  const map: unknown = ingest === undefined || ingest.kind === AGENT_INGEST_KIND ? undefined : ingest.map;\n  return typeof map === \"object\" && map !== null ? Object.keys(map) : [];\n}\n\n/** Whether a record carries content the feed did not put there.\n *\n *  Only a NON-EMPTY value counts. A record saved through the UI can carry every\n *  declared field as an empty string, and treating those as local content would\n *  make every touched record un-prunable — the cap would quietly stop working.\n *\n *  An edit to a MAPPED field deliberately does not count: a feed is\n *  one-directional, so the next refresh writes over it by design. The local\n *  COLUMN is the durable half, and the half worth protecting. */\nexport function hasLocalContent(item: CollectionItem, ingested: ReadonlySet<string>): boolean {\n  return Object.entries(item).some(([field, value]) => !ingested.has(field) && value !== undefined && value !== null && value !== \"\");\n}\n\n/** The stored record, or null when it cannot be read.\n *\n *  `readItem` answers null for a MISSING record but THROWS on unparsable JSON\n *  or an IO error. Letting that escape would fail the whole refresh over one bad\n *  file — and write nothing at all, since the loop aborts partway. Before the\n *  merge landed, the ingest simply overwrote such a file and the feed healed\n *  itself; treating \"unreadable\" as \"absent\" keeps that. (Observed during Claude\n *  review; both review bots were rate-limited on this PR.) */\nasync function readableRecord(store: { read: CollectionStore[\"read\"] }, feed: LoadedCollection, itemId: string): Promise<CollectionItem | null> {\n  try {\n    return await store.read(itemId);\n  } catch (error) {\n    log.warn(\"feeds\", \"unreadable feed record — the retrieved item will replace it\", { slug: feed.slug, itemId, error: String(error) });\n    return null;\n  }\n}\n\nasync function upsertItems(workspaceRoot: string, feed: LoadedCollection, items: CollectionItem[]): Promise<number> {\n  const store = writableFeedStore(workspaceRoot, feed);\n  if (!store) return 0;\n  let written = 0;\n  for (const item of items) {\n    const itemId = item[feed.schema.primaryKey];\n    if (typeof itemId !== \"string\" || itemId.length === 0) continue;\n    // Laid OVER the record rather than replacing it. `writeItem` writes the\n    // whole file, so writing the retrieved item alone deletes every column the\n    // ingest does not produce — the note the user put beside the article. The\n    // Google Calendar pull learned this in #2620; the feeds ingest never did\n    // (#2696).\n    const result = await store.write(itemId, mergeIntoExisting(await readableRecord(store, feed, itemId), item));\n    if (result.kind === \"ok\") written += 1;\n    else log.warn(\"feeds\", \"feed item write skipped\", { slug: feed.slug, itemId, kind: result.kind });\n  }\n  return written;\n}\n\n/** The schema's first `date` field, used to order records for the\n *  maxItems cap. Null when the schema declares none. */\nfunction firstDateField(schema: CollectionSchema): string | null {\n  for (const [key, spec] of Object.entries(schema.fields)) {\n    if (spec.type === \"date\") return key;\n  }\n  return null;\n}\n\n// Epoch ms for a record's date value; missing / unparseable sorts oldest.\nfunction recordTime(item: CollectionItem, field: string): number {\n  const value = item[field];\n  const millis = typeof value === \"string\" ? Date.parse(value) : NaN;\n  return Number.isFinite(millis) ? millis : Number.NEGATIVE_INFINITY;\n}\n\n/** Enforce `ingest.maxItems` (default 100): keep the newest N records by\n *  the schema's date field, delete the rest. No-op when the cap is 0/absent\n *  of a date field, or when under the cap. Returns the number deleted. */\nasync function pruneFeed(workspaceRoot: string, feed: LoadedCollection, fetched: readonly CollectionItem[]): Promise<number> {\n  const ingest = feedIngest(feed.schema);\n  // maxItems is a declarative-feed concept; agent ingest manages its own record\n  // set, so it's never pruned here (and `refreshOne` never calls pruneFeed for it).\n  const cap = (ingest && ingest.kind !== AGENT_INGEST_KIND ? ingest.maxItems : undefined) ?? DEFAULT_FEED_MAX_ITEMS;\n  if (cap <= 0) return 0;\n  const dateField = firstDateField(feed.schema);\n  if (!dateField) {\n    log.warn(\"feeds\", \"maxItems prune skipped: schema has no date field to order by\", { slug: feed.slug });\n    return 0;\n  }\n  const store = writableFeedStore(workspaceRoot, feed);\n  if (!store) return 0;\n  const items = await store.list();\n  if (items.length <= cap) return 0;\n  const ingested = ingestedFields(fetched, mappedTargetsOf(ingest), feed.schema.primaryKey);\n  const aged = [...items].sort((left, right) => recordTime(right, dateField) - recordTime(left, dateField)).slice(cap);\n  // Deleting a record the user annotated is the same silent loss the merge above\n  // prevents, one step later: the note survives every refresh and then goes when\n  // the article ages out. Kept records can push a feed past its cap, so say so\n  // rather than letting the number drift unexplained (#2696).\n  const stale = aged.filter((item) => !hasLocalContent(item, ingested));\n  const kept = aged.length - stale.length;\n  if (kept > 0) log.info(\"feeds\", \"keeping aged-out records that carry local content\", { slug: feed.slug, kept, cap });\n  let removed = 0;\n  for (const item of stale) {\n    const itemId = item[feed.schema.primaryKey];\n    if (typeof itemId !== \"string\" || itemId.length === 0) continue;\n    if ((await store.delete(itemId)).kind === \"ok\") removed += 1;\n  }\n  if (removed > 0) log.info(\"feeds\", \"pruned old feed records\", { slug: feed.slug, removed, cap });\n  return removed;\n}\n\n/** Fetch one feed now, upsert its records, then enforce the maxItems cap.\n *  Failure-isolated: returns an errors array rather than throwing. */\nexport async function refreshOne(workspaceRoot: string, feed: LoadedCollection, opts?: { hidden?: boolean }): Promise<RefreshResult> {\n  const { slug } = feed;\n  const ingest = feedIngest(feed.schema);\n  if (!ingest) return { slug, written: 0, removed: 0, errors: [\"collection has no ingest config\"] };\n  // `agent` ingest dispatches a worker instead of fetching: branch off BEFORE\n  // the retriever registry (which only knows declarative kinds). `opts.hidden`\n  // (manual Refresh passes false) only affects the agent path; declarative\n  // feeds ignore it.\n  if (ingest.kind === AGENT_INGEST_KIND) return refreshViaAgent(workspaceRoot, feed, opts);\n  const retriever = getRetriever(ingest.kind);\n  if (!retriever) return { slug, written: 0, removed: 0, errors: [`no retriever registered for kind '${ingest.kind}'`] };\n  const state = await readFeedState(workspaceRoot, feed);\n  try {\n    const result = await retriever(ingest, feed.schema, state);\n    const written = await upsertItems(workspaceRoot, feed, result.items);\n    await writeFeedState(workspaceRoot, feed, { ...state, lastFetchedAt: new Date().toISOString(), cursor: result.cursor, consecutiveFailures: 0 });\n    const removed = await pruneFeed(workspaceRoot, feed, result.items);\n    log.info(\"feeds\", \"feed refreshed\", { slug, written, removed, fetched: result.items.length });\n    return { slug, written, removed, errors: [] };\n  } catch (error) {\n    await writeFeedState(workspaceRoot, feed, { ...state, consecutiveFailures: state.consecutiveFailures + 1 });\n    const message = String(error);\n    log.warn(\"feeds\", \"feed refresh failed\", { slug, error: message });\n    return { slug, written: 0, removed: 0, errors: [message] };\n  }\n}\n\nfunction dueIntervalMs(schedule: FeedSchedule): number {\n  switch (schedule) {\n    case \"daily\":\n      return ONE_DAY_MS;\n    case \"weekly\":\n      return 7 * ONE_DAY_MS;\n    default:\n      return ONE_HOUR_MS;\n  }\n}\n\n/** True iff a `daily` schedule with a UTC `atHour` anchor is due. The system\n *  task ticks hourly, so \"due\" means: we're in the anchor hour AND we haven't\n *  already run in roughly the last day. The 23 h floor (not 24 h) tolerates the\n *  tick landing a few minutes earlier than the previous run, while staying well\n *  above 1 h so a second tick in the same anchor hour can't double-fire. */\nfunction isDailyAtHourDue(now: Date, atHour: number, lastFetchedAt: string | null): boolean {\n  if (now.getUTCHours() !== atHour) return false;\n  if (!lastFetchedAt) return true;\n  const elapsed = now.getTime() - Date.parse(lastFetchedAt);\n  if (!Number.isFinite(elapsed)) return true;\n  return elapsed >= 23 * ONE_HOUR_MS;\n}\n\n/** True iff a collection is due to refresh given its schedule + last run.\n *  `on-demand` is never auto-due; a `daily` schedule with `atHour` anchors to\n *  that UTC hour, otherwise cadence is elapsed-based. */\nfunction isFeedDue(feed: LoadedCollection, state: FeedState): boolean {\n  const ingest = feedIngest(feed.schema);\n  const schedule = ingest?.schedule;\n  if (!schedule || schedule === \"on-demand\") return false;\n  if (schedule === \"daily\" && typeof ingest?.atHour === \"number\") {\n    return isDailyAtHourDue(new Date(), ingest.atHour, state.lastFetchedAt);\n  }\n  if (!state.lastFetchedAt) return true;\n  const elapsed = Date.now() - Date.parse(state.lastFetchedAt);\n  if (!Number.isFinite(elapsed)) return true;\n  return elapsed >= dueIntervalMs(schedule);\n}\n\n/** Refresh every collection whose ingest schedule says it's due — declarative\n *  feeds AND skill-backed collections with `ingest.kind: \"agent\"`. Called by the\n *  hourly system task. Sequential + failure-isolated. */\nexport async function refreshDue(workspaceRoot: string = requireFeedsHost().workspaceRoot): Promise<RefreshResult[]> {\n  const all = await discoverCollections({ workspaceRoot });\n  const withIngest = all.filter((collection) => collection.schema.ingest);\n  const results: RefreshResult[] = [];\n  for (const collection of withIngest) {\n    // Isolate per-collection failures: `readFeedState`/`refreshOne` (esp. the\n    // agent path) can throw, and one bad collection must not abort the whole\n    // due-loop. Capture it as an errors result and move on.\n    try {\n      const state = await readFeedState(workspaceRoot, collection);\n      if (!isFeedDue(collection, state)) continue;\n      results.push(await refreshOne(workspaceRoot, collection));\n    } catch (error) {\n      log.warn(\"feeds\", \"scheduled refresh failed for collection\", { slug: collection.slug, error: String(error) });\n      results.push({ slug: collection.slug, written: 0, removed: 0, errors: [String(error)] });\n    }\n  }\n  return results;\n}\n","// Shareable registration for the hourly \"refresh due collections\" task.\n// Both MulmoClaude (via `initScheduler`, with persistence + catch-up) and a\n// standalone MulmoTerminal/MulmoBooks (via a bare task-manager `registerTask`)\n// register the SAME definition through this factory, so the id/schedule/run\n// can't drift between hosts. See plans/done/feat-shareable-feed-refresh-registration.md.\nimport { SCHEDULE_TYPES, MISSED_RUN_POLICIES } from \"@receptron/task-scheduler\";\nimport type { SystemTaskDef } from \"../../scheduler/adapter.js\";\nimport { canonicalRoot } from \"../../files/root.js\";\nimport { refreshDue } from \"./engine.js\";\n\n// Id kept stable so the scheduler-state row isn't orphaned across hosts/renames.\nexport const FEED_REFRESH_TASK_ID = \"system:feed-refresh\";\n\n/** The task id for a given root. Root-less (the configured workspace) keeps the\n *  bare, historical id, so a single-workspace host's persisted scheduler state\n *  keeps matching. A per-root registration gets its own id — a task id is the\n *  scheduler's primary key, so N roots registered under one id would collide\n *  and only the last would ever run. */\nexport function feedRefreshTaskId(workspaceRoot?: string): string {\n  // Canonical: `/proj` and `/proj/` are one project, so they must resolve to\n  // one task id. Registering both spellings otherwise creates two jobs over one\n  // tree, each with its own persisted state, refreshing the same feeds twice.\n  return workspaceRoot === undefined ? FEED_REFRESH_TASK_ID : `${FEED_REFRESH_TASK_ID}:${canonicalRoot(workspaceRoot)}`;\n}\n// Single source of truth for the default refresh cadence (one hour). Core has no\n// shared time module — `server/utils/time.ts` is host-only — so the named export\n// IS the constant; hosts override via `feedRefreshTaskDef({ intervalMs })`.\nexport const DEFAULT_FEED_REFRESH_INTERVAL_MS = 60 * 60 * 1000;\n\n/**\n * The hourly \"refresh due collections\" task, shared by every host.\n *\n * Drives ALL scheduled ingest: declarative feeds (RSS / JSON) fetch directly,\n * and skill-backed `ingest.kind: \"agent\"` collections dispatch a hidden worker.\n *\n * Returns the rich `SystemTaskDef` rather than registering it — each host keeps\n * its own registration mechanism and applies its own schedule overrides first.\n * A host driving a bare task-manager can still `registerTask(...)` it; the extra\n * `missedRunPolicy` field is simply ignored there.\n *\n * ROOT-PARAMETERISED. Called with no `workspaceRoot` it refreshes the host's\n * configured workspace under the historical task id — unchanged, and what a\n * single-workspace host wants. A multi-root host registers ONE def per root it\n * wants refreshed on a schedule; each gets its own id (see `feedRefreshTaskId`)\n * so the registrations do not overwrite each other. A root that is never\n * registered is never refreshed on a schedule — its feeds update only on an\n * explicit call — so registering per project is the host's decision to make,\n * deliberately, rather than something this module can infer.\n *\n * @param opts.intervalMs   per-host interval override (defaults to one hour)\n * @param opts.workspaceRoot the root to refresh; omitted, the configured FeedsHost workspace\n */\nexport function feedRefreshTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n  // Canonicalise ONCE and use that value for the id, the display name and the\n  // refresh itself. Deriving only the id from it would leave two registrations\n  // for `/proj` and `/proj/` collapsing onto one row whose name and refresh\n  // target still named whichever spelling registered last — the id would look\n  // deduplicated while the job it ran was a coin flip.\n  const root = opts?.workspaceRoot === undefined ? undefined : canonicalRoot(opts.workspaceRoot);\n  return {\n    id: feedRefreshTaskId(root),\n    name: root === undefined ? \"Scheduled collection refresh\" : `Scheduled collection refresh (${root})`,\n    description: \"Refresh due collections — fetch declarative feeds + dispatch agent-ingest workers\",\n    schedule: { type: SCHEDULE_TYPES.interval, intervalMs: opts?.intervalMs ?? DEFAULT_FEED_REFRESH_INTERVAL_MS },\n    missedRunPolicy: MISSED_RUN_POLICIES.runOnce,\n    run: () => refreshDue(root).then(() => {}),\n  };\n}\n"],"mappings":"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"}