[object Object]

← back to Norma

instagram: keep ALL accounts live via cron (not just DW)

8cfcfe2a9cb03bd7a96bb969c327b6b29c16282c · 2026-08-18 14:29:26 -0700 · Steve Abrams

- refreshAllAccounts(): serialized, per-account error-isolated sweep of the whole
  35-account roster (incremental by default; full+onlyIncomplete for backfill).
- live-media-refresh cron now sweeps ALL accounts hourly (was DW-only), so the
  cross-account /calendar stays live for the whole roster.
- new live-media-backfill-sweep cron (daily 04:40): full-backfills up to 5 not-yet-
  complete accounts/run as a safety net; no-ops once all complete.
Verified: one-shot incremental sweep = 35 accounts, +14 new, 0 errors, ~56s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 8cfcfe2a9cb03bd7a96bb969c327b6b29c16282c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 18 14:29:26 2026 -0700

    instagram: keep ALL accounts live via cron (not just DW)
    
    - refreshAllAccounts(): serialized, per-account error-isolated sweep of the whole
      35-account roster (incremental by default; full+onlyIncomplete for backfill).
    - live-media-refresh cron now sweeps ALL accounts hourly (was DW-only), so the
      cross-account /calendar stays live for the whole roster.
    - new live-media-backfill-sweep cron (daily 04:40): full-backfills up to 5 not-yet-
      complete accounts/run as a safety net; no-ops once all complete.
    Verified: one-shot incremental sweep = 35 accounts, +14 new, 0 errors, ~56s.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 agents/instagram-agent/live-media.js | 37 +++++++++++++++++++++++++++++++++++-
 agents/instagram-agent/server.js     | 26 +++++++++++++++++++++----
 2 files changed, 58 insertions(+), 5 deletions(-)

diff --git a/agents/instagram-agent/live-media.js b/agents/instagram-agent/live-media.js
index e127001..cb66ca6 100644
--- a/agents/instagram-agent/live-media.js
+++ b/agents/instagram-agent/live-media.js
@@ -160,6 +160,41 @@ async function refreshLiveMedia({ igUserId, full = false, maxPages = full ? 260
   } finally { REFRESH_LOCKS.delete(id); }
 }
 
+/**
+ * Sweep EVERY account in the roster through refreshLiveMedia, serialized (one at a time, with a
+ * small gap) so we stay gentle on the shared Graph token's rate limit and never trip two refreshes
+ * of the same account at once. Per-account errors are captured, not thrown — one bad account never
+ * aborts the sweep. This is what keeps the cross-account calendar LIVE for all accounts, not just DW.
+ *   - incremental (default): each account stops early when it hits already-known posts → cheap.
+ *   - { full:true }: full history walk (used by the backfill safety-net).
+ *   - { onlyIncomplete:true }: skip accounts already marked complete (for the backfill sweep).
+ *   - { max }: cap how many accounts to touch this run (drains a big backfill over several runs).
+ */
+async function refreshAllAccounts({ full = false, onlyIncomplete = false, max = 0, delayMs = 400 } = {}) {
+  if (!TOKEN()) return { skipped: 'simulation mode', accounts: 0, added: 0, results: [] };
+  const roster = listAccounts();
+  const results = [];
+  let touched = 0;
+  for (const a of roster) {
+    if (onlyIncomplete) {
+      const c = readCache(a.ig_user_id);
+      if (c && c.complete === true) continue; // already fully backfilled — skip
+    }
+    try {
+      const r = await refreshLiveMedia({ igUserId: a.ig_user_id, full });
+      results.push({ handle: a.handle, added: r.added, scanned: r.scanned, complete: r.complete });
+    } catch (e) {
+      results.push({ handle: a.handle, error: e.message });
+    }
+    touched++;
+    if (max && touched >= max) break;
+    await new Promise((r) => setTimeout(r, delayMs));
+  }
+  const added = results.reduce((s, r) => s + (r.added || 0), 0);
+  const errors = results.filter((r) => r.error).length;
+  return { accounts: results.length, added, errors, results };
+}
+
 // ── grouping for the viewer ─────────────────────────────────────────────────
 function dayLabel(iso, today) {
   const d = new Date(iso);
@@ -243,4 +278,4 @@ function registerLiveRoutes(app, agentName = 'instagram-agent') {
   });
 }
 
-module.exports = { registerLiveRoutes, refreshLiveMedia, getLivePosts, listAccounts, readCache, readTombstones };
+module.exports = { registerLiveRoutes, refreshLiveMedia, refreshAllAccounts, getLivePosts, listAccounts, readCache, readTombstones };
diff --git a/agents/instagram-agent/server.js b/agents/instagram-agent/server.js
index 454e9f1..f8ad39d 100644
--- a/agents/instagram-agent/server.js
+++ b/agents/instagram-agent/server.js
@@ -129,18 +129,36 @@ const { app, start, scheduler } = createAgentServer({
     },
     {
       name: 'live-media-refresh',
-      schedule: '7 * * * *', // hourly at :07 — pull new posts from the DW feed into the /live cache
+      schedule: '7 * * * *', // hourly at :07 — pull new posts into the /live + /calendar caches
       fn: async () => {
         if (!liveMode()) return;
         try {
-          const { refreshLiveMedia } = require('./live-media');
-          const r = await refreshLiveMedia({ full: false });
-          console.log(`[${AGENT_NAME}] Cron live-media refresh: +${r.added} new (scanned ${r.scanned})`);
+          // Sweep ALL accounts (not just DW) so the cross-account calendar stays LIVE for the
+          // whole roster. Incremental: each account stops early on already-known posts → cheap.
+          const { refreshAllAccounts } = require('./live-media');
+          const r = await refreshAllAccounts({ full: false });
+          console.log(`[${AGENT_NAME}] Cron live-media refresh: +${r.added} new across ${r.accounts} accounts (errors ${r.errors})`);
         } catch (err) {
           console.error(`[${AGENT_NAME}] Cron live-media refresh error:`, err.message);
         }
       },
     },
+    {
+      name: 'live-media-backfill-sweep',
+      schedule: '40 4 * * *', // daily 04:40 — safety net: fully backfill any account not yet complete
+      fn: async () => {
+        if (!liveMode()) return;
+        try {
+          const { refreshAllAccounts } = require('./live-media');
+          // Only touch accounts still marked incomplete, a few per run, so a big backfill drains
+          // over several nights without hammering the shared Graph token. No-ops once all complete.
+          const r = await refreshAllAccounts({ full: true, onlyIncomplete: true, max: 5, delayMs: 1200 });
+          console.log(`[${AGENT_NAME}] Cron backfill sweep: backfilled ${r.accounts} incomplete account(s), +${r.added} posts (errors ${r.errors})`);
+        } catch (err) {
+          console.error(`[${AGENT_NAME}] Cron backfill sweep error:`, err.message);
+        }
+      },
+    },
     {
       name: 'report-to-pulse',
       schedule: '0 */4 * * *', // every 4 hours

← 835c032 instagram: cross-account post Calendar view (/calendar)  ·  back to Norma  ·  auto-data-snapshot: 2026-08-18T14:32:35 (1 data files) — age f85f258 →