tinyrelaycommit

Commit d2f0451b3282

browse files at this commit | history

1diff --git a/internal/daemon/jobs_test.go b/internal/daemon/jobs_test.go
2index b5b51b4..3b16d5c 100644
3--- a/internal/daemon/jobs_test.go
4+++ b/internal/daemon/jobs_test.go
5@@ -169,6 +169,38 @@ func TestMCPJobToolsBuildValidateAndPublish(t *testing.T) {
6 }
7 }
8
9+func TestJobAnswersWakeTheRequesterAsMentions(t *testing.T) {
10+ _, tenant, member, agent := jobTenant(t)
11+ ctx := context.Background()
12+ now := time.Now().Unix()
13+ request := wikiPublish(t, tenant, testMemberSecret, 5001, now-100, "", []string{"i", "hello", "text"})
14+ result := event.Event{Kind: 6001, PubKey: agent, CreatedAt: now, Tags: [][]string{{"e", request.ID}, {"p", member}}, Content: "hola"}
15+ notices := tenant.pushNotices(ctx, result)
16+ if len(notices) != 1 || notices[0].category != pushMentions || notices[0].recipient != member || notices[0].body != "job 5001 result" || !strings.HasSuffix(notices[0].url, "/e/"+result.ID) || len(notices[0].actions) != 0 {
17+ t.Fatalf("result notices %+v", notices)
18+ }
19+ for status, want := range map[string]int{"error": 1, "payment-required": 1, "processing": 0, "success": 0, "partial": 0} {
20+ feedback := event.Event{Kind: 7000, PubKey: agent, CreatedAt: now, Tags: [][]string{{"status", status}, {"e", request.ID}, {"p", member}}}
21+ notices := tenant.pushNotices(ctx, feedback)
22+ if len(notices) != want {
23+ t.Fatalf("%s feedback notices %+v", status, notices)
24+ }
25+ if want == 1 && (notices[0].category != pushMentions || notices[0].body != "job 5001 "+status) {
26+ t.Fatalf("%s feedback notice %+v", status, notices[0])
27+ }
28+ }
29+ // Feedback for a request the relay does not hold names its own kind.
30+ elsewhere := event.Event{Kind: 7000, PubKey: agent, CreatedAt: now, Tags: [][]string{{"status", "error"}, {"e", strings.Repeat("e", 64)}, {"p", member}}}
31+ if notices := tenant.pushNotices(ctx, elsewhere); len(notices) != 1 || notices[0].body != "job 7000 error" {
32+ t.Fatalf("elsewhere notices %+v", notices)
33+ }
34+ // The requester's own feedback wakes nobody.
35+ own := event.Event{Kind: 7000, PubKey: member, CreatedAt: now, Tags: [][]string{{"status", "error"}, {"e", request.ID}, {"p", member}}}
36+ if notices := tenant.pushNotices(ctx, own); len(notices) != 0 {
37+ t.Fatalf("own notices %+v", notices)
38+ }
39+}
40+
41 func TestBrowseJobsListsRequestsWithStatusAndResults(t *testing.T) {
42 _, tenant, member, agent := jobTenant(t)
43 ctx := context.Background()
44diff --git a/internal/daemon/webpush_triggers.go b/internal/daemon/webpush_triggers.go
45index db8dec2..f8c8e16 100644
46--- a/internal/daemon/webpush_triggers.go
47+++ b/internal/daemon/webpush_triggers.go
48@@ -91,6 +91,15 @@ func (t *Tenant) pushNotices(ctx context.Context, e event.Event) []pushNotice {
49 if kind, ok := approvalRequest(e); ok {
50 return t.approvalNotices(e, kind)
51 }
52+ // A job result, or feedback that needs the requester's attention,
53+ // reaches the requester as a mention.
54+ if status, ok := jobNotice(e); ok {
55+ body := "job " + t.jobRequestKind(ctx, e) + " " + status
56+ for _, recipient := range pushRecipients(e) {
57+ notices = append(notices, pushNotice{recipient: recipient, category: pushMentions, body: body, url: base + "/e/" + e.ID})
58+ }
59+ return notices
60+ }
61 switch e.Kind {
62 case event.KIND_WRAP, 4:
63 for _, recipient := range pushRecipients(e) {
64@@ -146,6 +155,38 @@ func (t *Tenant) pushNotices(ctx context.Context, e event.Event) []pushNotice {
65 return notices
66 }
67
68+// jobNotice reports whether a long task event wakes its requester and with
69+// which status: every result does, and feedback does when it asks for
70+// payment or reports an error. Progress and success feedback stay quiet,
71+// since the result follows.
72+func jobNotice(e event.Event) (string, bool) {
73+ if event.IsJobResult(e.Kind) {
74+ return "result", true
75+ }
76+ if e.Kind != event.KIND_JOB_FEEDBACK {
77+ return "", false
78+ }
79+ status := event.Tag(e, "status")
80+ return status, status == "error" || status == "payment-required"
81+}
82+
83+// jobRequestKind names the request kind an answer belongs to: a result
84+// says it by its own kind, and feedback by the request the relay holds.
85+func (t *Tenant) jobRequestKind(ctx context.Context, e event.Event) string {
86+ if event.IsJobResult(e.Kind) {
87+ return strconv.Itoa(e.Kind - 1000)
88+ }
89+ id := event.Tag(e, "e")
90+ if len(id) != 64 {
91+ return strconv.Itoa(e.Kind)
92+ }
93+ rows, err := t.store.Query(ctx, event.Filter{IDs: []string{id}, Tags: map[string][]string{}}, storage.QueryOptions{Now: time.Now().Unix(), Access: storage.Access{All: true}, Limit: 1})
94+ if err != nil || len(rows.Events) == 0 || !event.IsJobRequest(rows.Events[0].Kind) {
95+ return strconv.Itoa(e.Kind)
96+ }
97+ return strconv.Itoa(rows.Events[0].Kind)
98+}
99+
100 // pushRecipients lists the addressed keys other than the author, bounded so
101 // a broadcast to a large list cannot fan out to every device.
102 func pushRecipients(e event.Event) []string {

About

ownerc01503e8035f
visibilitypublic
headd2f0451b3282

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs