From e5b2f5d11291ea2215c5023aa6283111c57764dc Mon Sep 17 00:00:00 2001 From: Sfgangloff Date: Wed, 10 Jun 2026 11:40:30 +0900 Subject: [PATCH] Add /pending endpoint so open comments are never lost The agent only learned about new comments through a Monitor on the inbox. If no Monitor was running when a comment was sent (a fresh session, a restarted server, the page served from another app), the comment sat in the inbox unseen and the user thought it was lost. This adds a GET /pending endpoint that returns the inbox comments no history change has answered yet, so an agent can pull the open feedback at any time instead of relying on catching it live. SKILL.md now points the agent at /pending on startup. Co-Authored-By: Claude Opus 4.8 --- SKILL.md | 16 +++++++++++----- lib/server.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/SKILL.md b/SKILL.md index d0ea024..ef96b12 100644 --- a/SKILL.md +++ b/SKILL.md @@ -42,7 +42,14 @@ User says any of: ``` Monitor on path: /feedback/inbox.jsonl ``` - Do NOT poll — let the Monitor notification arrive. + Do NOT poll — let the Monitor notification arrive. The Monitor is a + convenience, not the source of truth: it only catches comments that arrive + while it is running. To see every open comment at any time (including ones + sent while no session was watching), ask the server: + ``` + curl -s http://localhost:/pending + ``` + It returns the inbox comments that no history change has answered yet. ## Responding to a feedback batch @@ -71,10 +78,9 @@ When a new batch arrives in `inbox.jsonl`: ## On startup in a directory that already has feedback If you find `/feedback/inbox.jsonl` and `/feedback/history.json` and the skill has been invoked in this session: -1. Scan inbox for comment ids. -2. Scan history's `changes[*].in_response_to` union — those are already processed. -3. If unprocessed comments exist, tell the user the count and ask whether to process now. -4. Either way, set up the Monitor on the inbox. +1. Ask the server for the open comments: `curl -s http://localhost:/pending`. It lists every inbox comment that no history change has answered yet. (Equivalent by hand: inbox comment ids minus the union of history's `changes[*].in_response_to`.) +2. If there are open comments, tell the user the count and ask whether to process them now. +3. Either way, set up the Monitor on the inbox for new ones. ## Stop flow (user wants to kill the server) diff --git a/lib/server.py b/lib/server.py index b3a67bb..81786bd 100644 --- a/lib/server.py +++ b/lib/server.py @@ -104,11 +104,53 @@ def do_GET(self): } self._json(200, info) return + if parsed.path == "/pending": + # Comments in the inbox that no history change has answered yet. + # Lets an agent pull the open feedback at any time, even when no + # live Monitor saw it arrive (new session, server restarted, etc.). + self._json(200, {"pending": self._pending_comments()}) + return if parsed.path.startswith("/lib/"): self._serve_from_lib(parsed.path[len("/lib/"):]) return super().do_GET() + def _pending_comments(self): + """Inbox comments whose id is not in any history in_response_to.""" + inbox = self.feedback_dir / "inbox.jsonl" + history = self.feedback_dir / "history.json" + + answered = set() + if history.exists(): + try: + for batch in json.loads(history.read_text() or "[]"): + for ch in batch.get("changes", []): + for cid in ch.get("in_response_to", []): + answered.add(cid) + except (json.JSONDecodeError, AttributeError): + pass + + pending = [] + if inbox.exists(): + for line in inbox.read_text().splitlines(): + line = line.strip() + if not line: + continue + try: + batch = json.loads(line) + except json.JSONDecodeError: + continue + for c in batch.get("comments", []): + if c.get("id") and c["id"] not in answered: + pending.append({ + "id": c["id"], + "comment": c.get("comment", ""), + "type": c.get("type"), + "page_url": batch.get("page_url"), + "submitted_at": batch.get("submitted_at"), + }) + return pending + def _serve_from_lib(self, rel: str): # Path-traversal-safe lookup inside LIB_DIR try: