- commit
- d2f0451b3282236f6c81a4c748f39a8687318c3f
- parent
- 51a4c905ad75f7693542b26289ca654ac8926bcc
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 32 | +++++++++++++++++++++++ |
| M | internal/ | 41 | ++++++++++++++++++++++++++++++ |
@@ -169,6 +169,38 @@ func TestMCPJobToolsBuildValidateAndPublish(t *testing.T) {
169169 }170170 }171171 172+func TestJobAnswersWakeTheRequesterAsMentions(t *testing.T) {173+ _, tenant, member, agent := jobTenant(t)174+ ctx := context.Background()175+ now := time.Now().Unix()176+ request := wikiPublish(t, tenant, testMemberSecret, 5001, now-100, "", []string{"i", "hello", "text"})177+ result := event.Event{Kind: 6001, PubKey: agent, CreatedAt: now, Tags: [][]string{{"e", request.ID}, {"p", member}}, Content: "hola"}178+ notices := tenant.pushNotices(ctx, result)179+ 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 {180+ t.Fatalf("result notices %+v", notices)181+ }182+ for status, want := range map[string]int{"error": 1, "payment-required": 1, "processing": 0, "success": 0, "partial": 0} {183+ feedback := event.Event{Kind: 7000, PubKey: agent, CreatedAt: now, Tags: [][]string{{"status", status}, {"e", request.ID}, {"p", member}}}184+ notices := tenant.pushNotices(ctx, feedback)185+ if len(notices) != want {186+ t.Fatalf("%s feedback notices %+v", status, notices)187+ }188+ if want == 1 && (notices[0].category != pushMentions || notices[0].body != "job 5001 "+status) {189+ t.Fatalf("%s feedback notice %+v", status, notices[0])190+ }191+ }192+ // Feedback for a request the relay does not hold names its own kind.193+ elsewhere := event.Event{Kind: 7000, PubKey: agent, CreatedAt: now, Tags: [][]string{{"status", "error"}, {"e", strings.Repeat("e", 64)}, {"p", member}}}194+ if notices := tenant.pushNotices(ctx, elsewhere); len(notices) != 1 || notices[0].body != "job 7000 error" {195+ t.Fatalf("elsewhere notices %+v", notices)196+ }197+ // The requester's own feedback wakes nobody.198+ own := event.Event{Kind: 7000, PubKey: member, CreatedAt: now, Tags: [][]string{{"status", "error"}, {"e", request.ID}, {"p", member}}}199+ if notices := tenant.pushNotices(ctx, own); len(notices) != 0 {200+ t.Fatalf("own notices %+v", notices)201+ }202+}203+172204 func TestBrowseJobsListsRequestsWithStatusAndResults(t *testing.T) {173205 _, tenant, member, agent := jobTenant(t)174206 ctx := context.Background()@@ -91,6 +91,15 @@ func (t *Tenant) pushNotices(ctx context.Context, e event.Event) []pushNotice {
9191 if kind, ok := approvalRequest(e); ok {9292 return t.approvalNotices(e, kind)9393 }94+ // A job result, or feedback that needs the requester's attention,95+ // reaches the requester as a mention.96+ if status, ok := jobNotice(e); ok {97+ body := "job " + t.jobRequestKind(ctx, e) + " " + status98+ for _, recipient := range pushRecipients(e) {99+ notices = append(notices, pushNotice{recipient: recipient, category: pushMentions, body: body, url: base + "/e/" + e.ID})100+ }101+ return notices102+ }94103 switch e.Kind {95104 case event.KIND_WRAP, 4:96105 for _, recipient := range pushRecipients(e) {@@ -146,6 +155,38 @@ func (t *Tenant) pushNotices(ctx context.Context, e event.Event) []pushNotice {
146155 return notices147156 }148157 158+// jobNotice reports whether a long task event wakes its requester and with159+// which status: every result does, and feedback does when it asks for160+// payment or reports an error. Progress and success feedback stay quiet,161+// since the result follows.162+func jobNotice(e event.Event) (string, bool) {163+ if event.IsJobResult(e.Kind) {164+ return "result", true165+ }166+ if e.Kind != event.KIND_JOB_FEEDBACK {167+ return "", false168+ }169+ status := event.Tag(e, "status")170+ return status, status == "error" || status == "payment-required"171+}172+173+// jobRequestKind names the request kind an answer belongs to: a result174+// says it by its own kind, and feedback by the request the relay holds.175+func (t *Tenant) jobRequestKind(ctx context.Context, e event.Event) string {176+ if event.IsJobResult(e.Kind) {177+ return strconv.Itoa(e.Kind - 1000)178+ }179+ id := event.Tag(e, "e")180+ if len(id) != 64 {181+ return strconv.Itoa(e.Kind)182+ }183+ 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})184+ if err != nil || len(rows.Events) == 0 || !event.IsJobRequest(rows.Events[0].Kind) {185+ return strconv.Itoa(e.Kind)186+ }187+ return strconv.Itoa(rows.Events[0].Kind)188+}189+149190 // pushRecipients lists the addressed keys other than the author, bounded so150191 // a broadcast to a large list cannot fan out to every device.151192 func pushRecipients(e event.Event) []string {