Progressive GitHub Subscription with Event Tracking#40
Open
JaredforReal wants to merge 4 commits into
Open
Conversation
Signed-off-by: JaredforReal <w13431838023@gmail.com>
Signed-off-by: JaredforReal <w13431838023@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a “tracked events” concept alongside inbox Envelopes to support progressive GitHub subscriptions (new issues/PRs/releases as inbox items, with ongoing updates accumulated on tracked items), and adds a WebUI view + API endpoints to manage these tracked items.
Changes:
- Added a new
Eventmodel persisted in a neweventstable, plus API endpoints to track/list/resolve/analyze events and mark updates as seen. - Extended the GitHub adaptor to label new issues/PRs, poll releases, and poll comments for tracked items (appending them as event updates).
- Added a new “Tracked” WebUI view and supporting UI changes (status
tracked, event-type badges, “Track” action).
Reviewed changes
Copilot reviewed 20 out of 24 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| loom/webui/frontend/src/pages/tracked/TrackedPanel.tsx | New tracked-items panel (list + detail + updates timeline). |
| loom/webui/frontend/src/pages/config/ConfigFormEditor.tsx | Adds releases to GitHub source “events” placeholder text. |
| loom/webui/frontend/src/lib/types.ts | Adds tracked envelope status and new TrackedEvent/EventUpdate types. |
| loom/webui/frontend/src/lib/api.ts | Adds client API helpers for tracking and event CRUD actions. |
| loom/webui/frontend/src/components/Sidebar.tsx | Adds “Tracked” navigation entry. |
| loom/webui/frontend/src/components/KanbanBoard.tsx | Adds tracked to status grouping. |
| loom/webui/frontend/src/components/EnvelopeDetail.tsx | Hides github:* synthetic labels; shows event-type and tracked badges. |
| loom/webui/frontend/src/components/EnvelopeCard.tsx | Displays event-type badge; hides github:* labels from label list. |
| loom/webui/frontend/src/components/Column.tsx | Adds visual token styling for tracked status. |
| loom/webui/frontend/src/components/ActionsPanel.tsx | Adds “Track” action for GitHub envelopes; treats tracked as terminal for dismiss logic. |
| loom/webui/frontend/src/App.tsx | Adds tracked view routing to render TrackedPanel. |
| loom/state/store.py | Introduces EventRow + event CRUD methods in the SQLite store. |
| loom/prompts/prompt_github_update.md | New prompt template for tracked GitHub item updates. |
| loom/prompts/prompt_github_release.md | New prompt template for GitHub release summaries. |
| loom/policies/github.yaml | Adds a policy rule to auto-summarize GitHub releases. |
| loom/daemon.py | Injects the store into the GitHub adaptor to enable tracked-item update polling. |
| loom/core/event.py | Adds the Event dataclass model. |
| loom/core/envelope.py | Adds TRACKED to EnvelopeStatus. |
| loom/api_server.py | Adds event tracking/listing/resolution/analysis endpoints and event serialization. |
| loom/adaptor/github.py | Adds release polling, event-type labels, and tracked comment polling/appending. |
| docs/setup/gmail-setup.md | Adds a new Gmail setup guide (English). |
| docs/setup/gmail-setup-zh.md | Adds a new Gmail setup guide (Chinese). |
| docs/setup/github-token.md | Adds a GitHub token setup guide (English). |
| docs/setup/github-token-zh.md | Adds a GitHub token setup guide (Chinese). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+288
to
+292
| resp = await self._client.get(f"/repos/{key}/releases", params={"per_page": 30}) | ||
| if resp.status_code == 304: | ||
| return | ||
| resp.raise_for_status() | ||
|
|
Comment on lines
+297
to
+306
| release_cursor_key = f"release_cursor:{key}" | ||
| last_seen_tag = self._cursors.get(release_cursor_key) | ||
|
|
||
| for release in releases: | ||
| tag = release.get("tag_name", "") | ||
| if not tag: | ||
| continue | ||
| # Releases come newest-first; stop at cursor | ||
| if last_seen_tag and tag <= last_seen_tag: | ||
| break |
Comment on lines
30
to
41
| const grouped = useMemo(() => { | ||
| const g: Record<EnvelopeStatus, Envelope[]> = { | ||
| pending: [], | ||
| processing: [], | ||
| in_review: [], | ||
| done: [], | ||
| dismissed: [], | ||
| failed: [], | ||
| tracked: [], | ||
| } | ||
| for (const e of envelopes) g[e.status].push(e) | ||
| return g |
Comment on lines
+329
to
+338
| @app.patch("/api/events/{event_id}/updates/seen") | ||
| async def mark_updates_seen(event_id: str): | ||
| ctx = _ctx() | ||
| event = await ctx.store.get_event(event_id) | ||
| if event is None: | ||
| return JSONResponse(status_code=404, content={"error": "not found"}) | ||
| for update in event.updates: | ||
| update["seen"] = True | ||
| await ctx.store.save_event(event) | ||
| return {"marked_seen": len(event.updates)} |
Comment on lines
+329
to
+338
| @app.patch("/api/events/{event_id}/updates/seen") | ||
| async def mark_updates_seen(event_id: str): | ||
| ctx = _ctx() | ||
| event = await ctx.store.get_event(event_id) | ||
| if event is None: | ||
| return JSONResponse(status_code=404, content={"error": "not found"}) | ||
| for update in event.updates: | ||
| update["seen"] = True | ||
| await ctx.store.save_event(event) | ||
| return {"marked_seen": len(event.updates)} |
| """Upsert an event (insert or update by ID).""" | ||
| async with self._session() as session: | ||
| existing = await session.get(EventRow, event.id) | ||
| row = _event_to_row(event, existing) |
Comment on lines
+341
to
+370
| @app.post("/api/events/{event_id}/analyze") | ||
| async def analyze_event(event_id: str): | ||
| """Trigger agent re-analysis on a tracked event (manual).""" | ||
| ctx = _ctx() | ||
| event = await ctx.store.get_event(event_id) | ||
| if event is None: | ||
| return JSONResponse(status_code=404, content={"error": "not found"}) | ||
| if event.status != "active": | ||
| return JSONResponse(status_code=400, content={"error": "event is not active"}) | ||
|
|
||
| # Build context from event + updates | ||
| update_parts = [] | ||
| for u in event.updates: | ||
| update_parts.append( | ||
| f"[{u.get('created_at', '')}] {u.get('author', 'unknown')}:\n{u.get('body', '')}" | ||
| ) | ||
| context = f"## Item: {event.title}\n\nSource: {event.source_id}\n\n" | ||
| if event.metadata.get("html_url"): | ||
| context += f"URL: {event.metadata['html_url']}\n\n" | ||
| if update_parts: | ||
| context += f"## Recent Updates ({len(update_parts)} new)\n\n" | ||
| context += "\n---\n".join(update_parts) | ||
|
|
||
| # Use a fire-and-forget query_once for the analysis | ||
| try: | ||
| session = await ctx.session_mgr.query_once( | ||
| prompt=f"Analyze the following tracked GitHub item and its updates. \ | ||
| Provide a concise summary of what's happening and whether action is needed.\n{context}", | ||
| envelope_id=event.envelope_id, | ||
| ) |
Comment on lines
+375
to
+377
| except Exception as exc: | ||
| logger.error("Error analyzing event %s: %s", event_id, exc) | ||
| return JSONResponse(status_code=500, content={"error": str(exc)}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The GitHub adaptor treats all issues and PRs equally. Every item fetched via the Issues API creates an envelope in the inbox. For active repositories (e.g., vllm-project/vllm with hundreds of daily updates), this floods the inbox with noise. Key gaps:
seen_idsdedupProposed Solution
Introduce an Event entity separate from Envelope, implementing a progressive subscription model:
Event Type Labels
GitHub envelopes are tagged with event type labels for policy-based routing:
github:releasegithub:new_prgithub:new_issuegithub:updateThese use the existing
labelsfield. The policy engine already matches on labels, so no policy engine changes are needed.Subscription Flow
Data Model
Envelope Changes
Add
TRACKEDstatus toEnvelopeStatus:When an envelope is tracked, its status becomes
TRACKEDand it leaves the inbox.New
eventsTableUpdate Object Shape
{ "id": "comment:4567", "type": "comment", "author": "username", "body": "comment text...", "html_url": "https://github.com/...", "created_at": "2026-05-06T12:00:00Z", "seen": false }API Surface
POST/api/envelopes/{id}/trackGET/api/eventsGET/api/events/{id}POST/api/events/{id}/resolvePOST/api/events/{id}/analyzePATCH/api/events/{id}/updates/seenWebUI
Inbox View (existing, minor changes)
release(green),new_pr/new_issue(blue)Tracked View (new)
Source Config
"releases"optionGitHub Adaptor Changes
Event Type Labels
github:new_issuegithub:new_prgithub:releaseReleases Support
GET /repos/{owner}/{repo}/releasesowner/repo:release:tag_name)Comment Fetching for Tracked Items
GitHubAdaptor.set_store(store)last_comment_idstored in Event metadataRate Limit Budget
Policy Examples
Implementation Phases
Open Questions