Skip to content
// Save the turn: history, lead fields, opt-out and handoff flags. Dry runs save nothing.
const s = $('Settings (edit me)').first().json;
const db = $getWorkflowStaticData('global');
db.convos ??= {};

return $input.all().map(({ json: x }) => {
  const c = x.convo;
  const now = x.now;
  const addHours = (h) => new Date(new Date(now).getTime() + h * 3600e3).toISOString();
  let handoff = false, newLead = false;

  if (!x.duplicate) {
    c.seen = [...c.seen, x.message_id].slice(-50);
    c.last_inbound_at = now;
    c.window_open_until = addHours(24); // WhatsApp: free-form replies only within 24 h of the customer's last message
    c.history.push({ role: 'user', text: x.text || `[${x.type}]`, at: now });

    if (x.action === 'opt_out') c.opted_out = true;
    if (x.action === 'opt_in') c.opted_out = false;
    if (['handoff', 'emergency'].includes(x.action) || x.ai_handoff) { c.handoff_until = addHours(s.handoff_hours); handoff = true; }
    if (x.action === 'answer') {
      c.lead = { ...c.lead, ...x.lead_patch };
      if (!c.lead.name && c.name) c.lead.name = c.name;
      c.awaiting = x.next_awaiting ?? null;
      const complete = ['service', 'zip', 'name', 'preferred_time'].every((k) => c.lead[k]) && s.service_zips.includes(c.lead.zip);
      if (complete && !c.lead_captured_at) { c.lead_captured_at = now; newLead = true; }
    }
    if (x.reply) c.history.push({ role: 'assistant', text: x.reply, at: now });
    c.history = c.history.slice(-40);
    c.updated_at = now;
    if (!x.dry_run) db.convos[x.wa_id] = c;
  }

  // Keep static data small: forget conversations idle for 90 days.
  if (!x.dry_run) for (const [k, v] of Object.entries(db.convos)) if ((v.updated_at ?? v.created_at) < addHours(-24 * 90)) delete db.convos[k];

  const { convo, rules, history_text, ...rest } = x;
  return {
    json: {
      ...rest, has_reply: Boolean(x.reply) && !x.duplicate, handoff, new_lead: newLead,
      lead: c.lead, lead_complete: Boolean(c.lead_captured_at), awaiting: c.awaiting,
      needs_staff: handoff || x.action === 'silent',
    },
  };
});