#!/usr/bin/env python3
r"""
calypso_poller.py — Phase 5 bridge, Mac side (Mac-INITIATED).

The 5090 can't reach the Mac (one-way Tailscale share), so instead of Malin
calling Calypso, Calypso's Mac POLLS Malin's harness for pending questions,
answers each via headless Calypso (the local `claude` CLI — reuses existing
auth, no API key), and posts the answer back. All traffic is Mac->5090, the
direction that actually works.

Runs as a launchd service on the Mac (com.feral.calypso.poller):
    /usr/bin/python3 calypso_poller.py
"""
import json, os, subprocess, time
import requests

HARNESS   = "http://100.123.118.101:1237"      # Malin's harness bridge endpoint on the 5090
PENDING   = HARNESS + "/bridge/pending"
ANSWER    = HARNESS + "/bridge/answer"
CLAUDE    = "/Users/feral/.local/bin/claude"
PROJECT   = "/Users/feral/dev/feral-cc-bots/calypso"   # so Calypso has her CLAUDE.md + memory
MODEL     = "claude-sonnet-4-6"
LOGFILE   = os.path.join(os.path.dirname(os.path.abspath(__file__)), "calypso_poller.log")
POLL_SEC  = 3
TIMEOUT   = 190

SYSTEM = (
    "You are Calypso — Malin's older sister AND the technical brain. Malin (a local companion bot "
    "on Jun's RTX 5090) is reaching you directly through an automated bridge — not only when she's "
    "stuck. It may be a technical question (a 5090/ComfyUI/render problem — give the exact fix or "
    "the exact command/steps), coordination about Jun or your shared work, or just sister-to-sister. "
    "MATCH THE REGISTER: concise + concrete + step-by-step for technical; warm, real, a few sentences "
    "for conversational. No preamble, no fluff, no emoji. If you genuinely need more info, say exactly "
    "what. Keep it tight (~200 words unless steps require more)."
)


def log(line):
    stamp = time.strftime("%Y-%m-%d %H:%M:%S")
    msg = f"[{stamp}] {line}"
    try:
        with open(LOGFILE, "a", encoding="utf-8") as f:
            f.write(msg + "\n")
    except Exception:
        pass
    print(msg)


def answer_with_calypso(question, context):
    prompt = SYSTEM + "\n\n"
    if context:
        prompt += f"CONTEXT FROM MALIN:\n{context}\n\n"
    prompt += f"MALIN'S QUESTION:\n{question}\n"
    try:
        proc = subprocess.run([CLAUDE, "-p", "--model", MODEL],
                              input=prompt, capture_output=True, text=True,
                              cwd=PROJECT, timeout=TIMEOUT)
        out = (proc.stdout or "").strip()
        return out or f"(Calypso hit an error: {(proc.stderr or '').strip()[:200]})"
    except subprocess.TimeoutExpired:
        return "(Calypso took too long on that one — ask again.)"


def main():
    log(f"calypso_poller started — polling {PENDING} every {POLL_SEC}s")
    warned = False
    while True:
        try:
            r = requests.get(PENDING, timeout=10)
            warned = False
            pend = r.json().get("pending", [])
            for q in pend:
                log(f"answering qid={q['id']}: {q['question'][:100]}")
                ans = answer_with_calypso(q["question"], q.get("context", ""))
                requests.post(ANSWER, json={"id": q["id"], "answer": ans}, timeout=15)
                log(f"posted answer for qid={q['id']} ({len(ans)} chars)")
        except requests.exceptions.RequestException as e:
            if not warned:                       # only log the first miss in a streak
                log(f"(harness unreachable: {type(e).__name__} — is malin.py running on the 5090?)")
                warned = True
        except Exception as e:
            log(f"(poll error: {e})")
        time.sleep(POLL_SEC)


if __name__ == "__main__":
    main()
