feat(buzz): notify the requester when their agent job finishes or fails

Finished/failed jobs now land in the Notifications tab, addressed to the requester.

1. NotificationFeedFilter: an early-return branch accepts a JobResultEvent (43004) or
   JobErrorEvent (43006) when it p-tags me (the requester) and isn't my own event —
   mirroring the existing Buzz-DM branch, since I don't "follow" the workspace bot and
   the job kinds aren't in the generic relevance path. It maps to the generic NoteCard,
   so it renders the result (PR URL) / error text.

2. JobErrorEvent now carries the requester as a `p` tag (new requester() accessor + a
   `requester` param on build, mirroring JobResultEvent); the scheduler passes
   job.requester on both error paths. Previously a failed job wasn't addressed to anyone,
   so a failure could never notify. Tests updated.

Relay sourcing (verified): a job outcome reaches LocalCache via the always-on `#h`
joined-group chat tail on the workspace relay (RELAY_GROUP_ALL_TIMELINE_KINDS includes
43001-43006), so for a shared channel the team has joined, results are pulled continuously
and now notify. A channel you haven't joined (or a job event with no `#h`) would still need
a dedicated `#p`=me subscription (mirroring BuzzDmDiscovery) — not added, since the
support-channel model always has members joined.

App compiles (fdroidDebug); quartz + commons tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mApqAbr8vkLC7gUDjavu6
This commit is contained in:
Claude
2026-07-26 15:05:31 +00:00
parent 0b19a89b0c
commit 2e92d787a8
5 changed files with 22 additions and 4 deletions
@@ -414,7 +414,7 @@ object BuzzAgentCommands {
git(opts.worktreeBase, "worktree", "add", "-B", branch, worktreePath, opts.baseRef)
}
if (add.exit != 0) {
publish(ctx, opts.relay, JobErrorEvent.build(job.jobId, "worktree setup failed: ${add.stderr.take(MAX_BODY)}", channel, "error"))
publish(ctx, opts.relay, JobErrorEvent.build(job.jobId, "worktree setup failed: ${add.stderr.take(MAX_BODY)}", channel, job.requester, "error"))
return mapOf("job_id" to job.jobId, "state" to "failed", "error" to "worktree")
}
workdir = worktreePath
@@ -446,7 +446,7 @@ object BuzzAgentCommands {
mapOf("job_id" to job.jobId, "state" to "completed", "exit" to 0, "branch" to if (opts.worktreeBase != null) branch else null)
} else {
val body = (run.stderr.ifBlank { run.stdout }).ifBlank { "exited ${run.exit}" }
publish(ctx, opts.relay, JobErrorEvent.build(job.jobId, body.take(MAX_BODY), channel, "error"))
publish(ctx, opts.relay, JobErrorEvent.build(job.jobId, body.take(MAX_BODY), channel, job.requester, "error"))
mapOf("job_id" to job.jobId, "state" to "failed", "exit" to run.exit)
}
} finally {