feat(notify): done-signal trigger — notify when an AFK run opens its PR/change request (#100) #110
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/100"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fires one Web Push notification when the reaper detects an AFK run's done-signal — "the agent finished; review and merge is on you now."
What changed
internal/afk/afk.go,internal/afk/notify.go):afk.Options.Notify func(Notification)— nil-safe, injected, the exactAFKRunEnded/ needs-input-trigger idiom.afk.Notificationmirrorspush.Payloadfield-for-field so afk never imports push (ADR-0038 import boundary).internal/afk/reaper.go): insidereapRun, beside the metrics report, only when the outcome is success — it rides the reaper's idempotent claim, so exactly once per run with no extra debounce state. Deaths, timeouts, neutral Stop, three-strikes pause: no send. Manual instances never reach the reaper.internal/tracker/pulls.go): newDonePull(pulls, head) (PullRef, bool)returns the winning open-or-merged head-matching pull;PRPresentis now its boolean projection, and both project from one sharedbestPullcollision loop so they can never diverge.reapActiveRunsthreads the tracker handle + matched pull intoreapRun.<repo>~<label> opened PR #<n>(forge) /… opened change request #<n>(builtin — the glossary reserves "PR" for the forge object); body = the pull's title via the heavierPull(n)detail call, degrading to the bare<noun> #<n>form on fetch error or empty title (the send still fires, the reap is unaffected); tag = run ID (replaces the same run's needs-input item on the lock screen); route/runs/<id>, always PWA-internal, never the forge URL.cmd/lab/main.go): closure overpushSender.Broadcast, which is async — the reaper never blocks on gateway I/O.Acceptance criteria → proof
TestReap_successNotifiesDoneSignal(fake tracker + capture notifier; also asserts the second sweep does not re-fire).TestReap_forgeBindingSaysPR.TestReap_notifyDetailFetchDegrades.TestReap_failuresDoNotNotify.Notifyand stays green.push.Sender.Broadcast, whose fire-and-forget property is #98's tested contract (TestBroadcastNeverBlocks).Design note
The title's detail fetch (
trk.Pull(n)) is synchronous on the reap path — deliberately, per the issue brief: it happens once per run, only on the success path, tolerates failure without affecting the reap, and is bounded by the tracker clients' existing 30s per-request timeout (the same bound as thePullslisting on the same tick). Making it async would buy little and cost ordering guarantees.go build ./...,go vet ./..., and the fullgo test ./...are green (the tmuxx integration tests too, onceshis on PATH — a sandbox quirk, not a code issue).Closes #100
🤖 Generated with Claude Code
https://claude.ai/code/session_017FnXE9vzzwmj6WVrDkoknZ