]> git.giacomolli.uk - onechat/blob - probe_log_helper_test.go
Define child agent areas and reporting rules
[onechat] / probe_log_helper_test.go
1 package main
2
3 import (
4         "encoding/json"
5         "fmt"
6         "math/rand/v2"
7         "strings"
8 )
9
10 // probeMessages is the length of the generated log for the live probe.
11 const probeMessages = 1000
12
13 // ProbeWriter writes readable made-up messages.
14 type ProbeWriter struct {
15         random *rand.Rand
16 }
17
18 func (g *ProbeWriter) message() (string, string) {
19         roll := g.random.IntN(100)
20         switch {
21         case roll < 5:
22                 return "user", g.fill(g.userLine, 10+g.random.IntN(200))
23         case roll < 10:
24                 return "reply", g.fill(g.replyLine, 100+g.random.IntN(1500))
25         case roll < 12:
26                 return "note", g.fill(g.noteLine, 50+g.random.IntN(600))
27         case roll < 13:
28                 return "work", "[" + g.pick(probePeople) + "] report: " + g.fill(g.replyLine, 300+g.random.IntN(3000))
29         case roll < 56:
30                 return "tool", g.toolCall(60 + g.random.IntN(400))
31         case roll < 57:
32                 return "echo", g.fill(g.outputLine, 30000+g.random.IntN(30000))
33         default:
34                 return "echo", g.fill(g.outputLine, 20+g.random.IntN(4000))
35         }
36 }
37
38 // fill joins lines of write until the text has about size bytes.
39 func (g *ProbeWriter) fill(write func() string, size int) string {
40         var text strings.Builder
41
42         for text.Len() < size {
43                 if text.Len() > 0 {
44                         text.WriteByte(' ')
45                 }
46
47                 text.WriteString(write())
48         }
49
50         return text.String()
51 }
52
53 func (g *ProbeWriter) userLine() string {
54         return g.pick([]string{
55                 fmt.Sprintf("Please check why the %s %s.", g.pick(probeThings), g.pick(probeProblems)),
56                 fmt.Sprintf("Move the %s to %s before %s.", g.pick(probeThings), g.pick(probePlaces), g.pick(probeDays)),
57                 fmt.Sprintf("Thanks, that works. Next, %s.", g.pick(probeTasks)),
58                 fmt.Sprintf("No, keep the old %s. %s asked for it.", g.pick(probeThings), g.pick(probePeople)),
59                 fmt.Sprintf("How many %s orders came in on %s?", g.pick(probeBreads), g.pick(probeDays)),
60         })
61 }
62
63 func (g *ProbeWriter) replyLine() string {
64         return g.pick([]string{
65                 fmt.Sprintf("I looked at %s. The %s %s because %s.", g.pick(probeFiles), g.pick(probeThings), g.pick(probeProblems), g.pick(probeCauses)),
66                 fmt.Sprintf("I changed %s, and the tests pass now.", g.pick(probeChanges)),
67                 fmt.Sprintf("There were %d %s orders on %s, %d of them late.", 10+g.random.IntN(90), g.pick(probeBreads), g.pick(probeDays), g.random.IntN(10)),
68                 fmt.Sprintf("Open question: should %s get %s?", g.pick(probePeople), g.pick(probeTasks)),
69         })
70 }
71
72 func (g *ProbeWriter) noteLine() string {
73         return g.pick([]string{
74                 fmt.Sprintf("%s prefers %s.", g.pick(probePeople), g.pick(probePreferences)),
75                 fmt.Sprintf("The bakery opens at %d and closes at %d on %s.", 5+g.random.IntN(3), 17+g.random.IntN(4), g.pick(probeDays)),
76         })
77 }
78
79 func (g *ProbeWriter) pick(words []string) string {
80         return words[g.random.IntN(len(words))]
81 }
82
83 // toolCall returns a tool name and its JSON input of about size bytes.
84 func (g *ProbeWriter) toolCall(size int) string {
85         name := g.pick([]string{"shell", "read", "write", "edit"})
86
87         var input map[string]string
88
89         switch name {
90         case "shell":
91                 input = map[string]string{"command": "grep -n '" + g.pick(probeThings) + "' " + g.pick(probeFiles)}
92         case "read":
93                 input = map[string]string{"path": g.pick(probeFiles)}
94         default:
95                 input = map[string]string{
96                         "path": g.pick(probeFiles),
97                         "text": g.fill(g.replyLine, max(0, size-80)),
98                 }
99         }
100
101         data, err := json.Marshal(input)
102         if err != nil {
103                 panic(err)
104         }
105
106         return name + " " + string(data)
107 }
108
109 func (g *ProbeWriter) outputLine() string {
110         return g.pick([]string{
111                 fmt.Sprintf("%02d:%02d order %d for %d %s, packed by %s.", 6+g.random.IntN(12), g.random.IntN(60), 1000+g.random.IntN(9000), 1+g.random.IntN(24), g.pick(probeBreads), g.pick(probePeople)),
112                 fmt.Sprintf("%s:%d: %s %s.", g.pick(probeFiles), 1+g.random.IntN(400), g.pick(probeThings), g.pick(probeProblems)),
113                 fmt.Sprintf("ok   bakery/%s %d.%03ds", g.pick(probeModules), g.random.IntN(3), g.random.IntN(1000)),
114         })
115 }
116
117 // probeLog returns count messages of made-up work on a small bakery order
118 // system, with the mix of kinds and sizes of replayMessage. The same count
119 // always gives the same log.
120 func probeLog(count int) []DatedMessage {
121         g := ProbeWriter{
122                 random: rand.New(rand.NewPCG(7, 11)),
123         }
124
125         messages := make([]DatedMessage, count)
126         for id := range messages {
127                 kind, text := g.message()
128                 messages[id] = DatedMessage{
129                         Kind: kind,
130                         Text: text,
131                 }
132         }
133
134         return messages
135 }
136
137 var (
138         probePeople      = []string{"Mara", "Joel", "Ines", "Tomas", "Priya", "Lena"}
139         probeBreads      = []string{"rye", "sourdough", "baguette", "brioche", "spelt", "seed loaf"}
140         probeDays        = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
141         probeThings      = []string{"order form", "price list", "delivery route", "stock count", "invoice export", "oven schedule"}
142         probeProblems    = []string{"shows the wrong total", "skips the last row", "is slow at noon", "loses the customer note", "rounds the tax down"}
143         probePlaces      = []string{"the front page", "the weekly report", "the shared folder", "the admin menu"}
144         probeTasks       = []string{"add a field for allergies", "send the invoices on Friday", "split the route in two", "print labels for the shelf"}
145         probeCauses      = []string{"the date has no time zone", "the cache keeps old prices", "the loop stops one row early", "the form trims the note"}
146         probeChanges     = []string{"the total to use cents", "the route to sort by street", "the export to add the tax line", "the form to keep the note"}
147         probePreferences = []string{"short answers", "a call before a change to prices", "invoices as PDF", "reports in the morning"}
148         probeFiles       = []string{"orders/form.go", "prices/list.go", "routes/plan.go", "stock/count.go", "export/invoice.go", "ovens/schedule.go"}
149         probeModules     = []string{"orders", "prices", "routes", "stock", "export", "ovens"}
150 )