src/bot/startupCatchup.ts

import { getMemorySettings, getReplyPermissions, getStartupSettings } from "../settings/agentStack.ts";

export async function runStartupCatchup(runtime, settings) { const startup = getStartupSettings(settings); const replyPermissions = getReplyPermissions(settings); const memory = getMemorySettings(settings); if (!startup.catchupEnabled) return; if (!replyPermissions.allowReplies) return;

const logCatchup = (content, metadata = {}) => { runtime.store?.logAction?.({ kind: "bot_lifecycle", userId: runtime.botUserId || null, content, metadata }); };

const channels = runtime.getStartupScanChannels(settings); const lookbackMs = startup.catchupLookbackHours * 60 * 60_000; const maxMessages = startup.catchupMaxMessagesPerChannel; const maxRepliesPerChannel = startup.maxCatchupRepliesPerChannel; const now = Date.now(); let scannedChannelCount = 0; let hydratedMessageCount = 0; let queuedReplyCount = 0; let channelsWithQueuedReplies = 0;

logCatchup("startup_catchup_begin", { channelCount: channels.length, lookbackHours: startup.catchupLookbackHours, maxMessagesPerChannel: maxMessages, maxRepliesPerChannel });

for (const channel of channels) { scannedChannelCount += 1; let repliesSent = 0;

const messages = await runtime.hydrateRecentMessages(channel, maxMessages);
hydratedMessageCount += messages.length;
for (let index = messages.length - 1; index >= 0; index -= 1) {
  const message = messages[index];
  if (repliesSent >= maxRepliesPerChannel) break;
  if (
    !message?.author ||
    String(message.author.id || "") === String(runtime.botUserId || "")
  ) continue;
  if (!message.guild || !message.channel) continue;
  if (!runtime.isChannelAllowed(settings, message.channelId)) continue;
  if (runtime.isUserBlocked(settings, message.author.id)) continue;

  const recentMessages = runtime.store.getRecentMessages(
    message.channelId,
    memory.promptSlice.maxRecentMessages
  );
  const addressSignal = await runtime.getReplyAddressSignal(settings, message, recentMessages);
  if (!addressSignal.triggered) continue;
  if (now - message.createdTimestamp > lookbackMs) continue;
  if (runtime.store.hasTriggeredResponse(message.id)) continue;
  if (
    runtime.hasStartupFollowupAfterMessage({
      messages,
      messageIndex: index,
      triggerMessageId: message.id
    })
  ) {
    continue;
  }
  const queued = runtime.enqueueReplyJob({
    message,
    source: "startup_catchup",
    addressSignal
  });
  if (queued) {
    repliesSent += 1;
    queuedReplyCount += 1;
  }
}

if (repliesSent > 0) {
  channelsWithQueuedReplies += 1;
  logCatchup("startup_catchup_channel_scanned", {
    guildId: String(channel.guildId || channel.guild?.id || "").trim() || null,
    channelId: String(channel.id || "").trim() || null,
    hydratedMessageCount: messages.length,
    queuedReplyCount: repliesSent
  });
}

}

logCatchup("startup_catchup_complete", { scannedChannelCount, hydratedMessageCount, queuedReplyCount, channelsWithQueuedReplies }); }