Compare commits

..

10 Commits

Author SHA1 Message Date
78003c0e33 fix(gh-monitor): notify main session immediately on new PR activity (#3)
* fix(gh-monitor): notify main session immediately on new PR activity

Previously, dispatch_agent only fired an isolated agent to handle the event.
The main session (Hans) was never directly notified, so new PR comments
only surfaced when Andrew messaged directly.

Now calls notify() immediately before dispatching the agent, so the main
session gets a system event about the new activity in real-time.

* refactor(gh-monitor): route PR activity through main session instead of isolated agents

Replaced dispatch_agent() (which spawned isolated cron jobs) with
dispatch_to_main() (which sends a system event to the main session).

This way Hans handles PR comments directly in the main session —
no sync issues between isolated and main sessions, no lost context.
2026-03-16 11:22:35 -04:00
01c34b7e4c fix(gh-monitor): poll every 5 minutes 2026-03-16 01:07:06 -04:00
68d00058f0 fix(gh-monitor): set PATH + HOME in plist so launchd can find gh and openclaw 2026-03-16 00:59:08 -04:00
9c34c0401b fix(gh-monitor): use absolute paths for gh and openclaw (launchd has no PATH) 2026-03-16 00:46:42 -04:00
505fe09ed3 fix(gh-monitor): use system event for Signal notification instead of broken announce delivery 2026-03-16 00:32:51 -04:00
1909389c33 fix(gh-monitor): poll every 1 minute instead of 5 2026-03-16 00:22:40 -04:00
9abd075121 fix(gh-monitor): add --to signal uuid for announce delivery 2026-03-16 00:17:59 -04:00
95380ec3b1 fix(gh-monitor): instruct agent to post acknowledgment reply before doing any work 2026-03-15 23:47:12 -04:00
91a2f0fb62 fix(gh-monitor): filter own comments to prevent loops; switch delivery to announce 2026-03-15 22:58:50 -04:00
c25fb2e10e feat(gh-monitor): dispatch isolated agentTurn on PR events via openclaw cron add 2026-03-15 21:37:37 -04:00
3 changed files with 42 additions and 3 deletions

View File

@@ -16,6 +16,13 @@
<string>/Users/hansheinemann/Projects/hans-tools/tools/gh-monitor/state/stdout.log</string> <string>/Users/hansheinemann/Projects/hans-tools/tools/gh-monitor/state/stdout.log</string>
<key>StandardErrorPath</key> <key>StandardErrorPath</key>
<string>/Users/hansheinemann/Projects/hans-tools/tools/gh-monitor/state/stderr.log</string> <string>/Users/hansheinemann/Projects/hans-tools/tools/gh-monitor/state/stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/Users/hansheinemann/.nvm/versions/node/v22.22.1/bin</string>
<key>HOME</key>
<string>/Users/hansheinemann</string>
</dict>
<key>RunAtLoad</key> <key>RunAtLoad</key>
<false/> <false/>
</dict> </dict>

View File

@@ -6,3 +6,4 @@ repos:
- review_comment - review_comment
- issue_comment - issue_comment
- pr_closed - pr_closed
ignore_actor: hansheinemann # skip my own comments to prevent feedback loops

View File

@@ -153,6 +153,31 @@ def format_notification(repo_slug: str, pr: dict, event: dict) -> str:
return f'[gh-monitor] PR #{number} "{title}"{actor} {action}:\n"{body_preview}"\n{url}' return f'[gh-monitor] PR #{number} "{title}"{actor} {action}:\n"{body_preview}"\n{url}'
def dispatch_to_main(repo_slug: str, pr: dict, event: dict) -> None:
"""
Send a system event to the main session with full context so Hans handles
the PR comment directly — no isolated agent needed.
"""
number = pr["number"]
title = pr["title"]
actor = event["actor"]
action = event["action"]
body = (event["body"] or "")[:500]
url = event["url"]
text = (
f"[gh-monitor] New PR activity on {repo_slug}#{number} \"{title}\":\n"
f"{actor} {action}:\n"
f'"{body}"\n'
f"{url}\n\n"
f"Read the full comment, address it if needed (code change + GitHub reply), "
f"and notify Andrew on Signal when done."
)
notify(text)
log.info("Notified main session for PR #%d %s by %s", number, action, actor)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# STEP 8 — error tracking # STEP 8 — error tracking
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -208,6 +233,8 @@ def poll_repo(repo_cfg: dict, state: dict) -> dict:
all_new_events: list[tuple[dict, dict]] = [] # (pr, event) all_new_events: list[tuple[dict, dict]] = [] # (pr, event)
my_login = repo_cfg.get("ignore_actor", "hansheinemann")
for pr in open_prs: for pr in open_prs:
pr_number = pr["number"] pr_number = pr["number"]
fetchers = [] fetchers = []
@@ -223,6 +250,11 @@ def poll_repo(repo_cfg: dict, state: dict) -> dict:
events = fetcher(owner, repo, pr_number) events = fetcher(owner, repo, pr_number)
new = new_events_since(events, cursor, seen_ids) new = new_events_since(events, cursor, seen_ids)
for event in new: for event in new:
if event.get("actor") == my_login:
log.debug("Skipping own event from %s", my_login)
if event.get("id"):
seen_ids.add(event["id"])
continue
all_new_events.append((pr, event)) all_new_events.append((pr, event))
except GHAPIError as e: except GHAPIError as e:
log.error("[%s] PR #%d fetch error: %s", repo_slug, pr_number, e) log.error("[%s] PR #%d fetch error: %s", repo_slug, pr_number, e)
@@ -259,9 +291,8 @@ def poll_repo(repo_cfg: dict, state: dict) -> dict:
all_new_events.sort(key=lambda x: x[1]["created_at"]) all_new_events.sort(key=lambda x: x[1]["created_at"])
for pr, event in all_new_events: for pr, event in all_new_events:
text = format_notification(repo_slug, pr, event) log.info("[%s] New event: PR #%d %s by %s", repo_slug, pr["number"], event["action"], event["actor"])
notify(text) dispatch_to_main(repo_slug, pr, event)
log.info("[%s] Notified: PR #%d %s by %s", repo_slug, pr["number"], event["action"], event["actor"])
if event.get("id"): if event.get("id"):
seen_ids.add(event["id"]) seen_ids.add(event["id"])