summary |
shortlog |
log |
commit | commitdiff |
tree
raw |
inline | side by side (from parent 1:
80e3487)
Multiline literals and calls have a blank separator before the next
logical block, except for their immediate error check. This includes
test case literals before loops. Event fields and empty callbacks use
multiline bodies. Whitespace and literal layout do not change behavior.
Issue:
3cdc678
48 files changed:
responsibility: "Keep the garden plans.",
},
}
responsibility: "Keep the garden plans.",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
stop := context.AfterFunc(ctx, func() {
_ = conn.Close()
})
stop := context.AfterFunc(ctx, func() {
_ = conn.Close()
})
defer stop()
err = json.NewEncoder(conn).Encode(SocketRequest{
defer stop()
err = json.NewEncoder(conn).Encode(SocketRequest{
err = errors.Join(err, archive.Close(), compressed.Close())
if err != nil {
return 0, fmt.Errorf("write archive: %w", err)
err = errors.Join(err, archive.Close(), compressed.Close())
if err != nil {
return 0, fmt.Errorf("write archive: %w", err)
ctx: ctx,
reader: compressed,
})
ctx: ctx,
reader: compressed,
})
seen := make(map[string]bool)
var directories []*tar.Header
seen := make(map[string]bool)
var directories []*tar.Header
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
archive := backupTestArchive(t, tc.header)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
archive := backupTestArchive(t, tc.header)
"agent/instructions.md": "Keep the instructions.\n",
"agent/telegram-offset.json": "23\n",
}
"agent/instructions.md": "Keep the instructions.\n",
"agent/telegram-offset.json": "23\n",
}
for name, data := range files {
path := filepath.Join(dir, name)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
for name, data := range files {
path := filepath.Join(dir, name)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
dir := t.TempDir()
err := backupRestore(t.Context(), archive, dir)
require.ErrorContains(t, err, "unsafe archive path")
dir := t.TempDir()
err := backupRestore(t.Context(), archive, dir)
require.ErrorContains(t, err, "unsafe archive path")
require.Never(t, func() bool {
return len(backedUp) != 0
}, 50*time.Millisecond, time.Millisecond)
require.Never(t, func() bool {
return len(backedUp) != 0
}, 50*time.Millisecond, time.Millisecond)
release.Do(func() {
close(disconnect.release)
})
release.Do(func() {
close(disconnect.release)
})
<input>
%s
</input>`, task.Index, nodeBytes, compactorRuler, task.Input[0])
<input>
%s
</input>`, task.Index, nodeBytes, compactorRuler, task.Input[0])
if len(task.Images) > 0 {
text += "\nKeep each [image FILE] name and describe its kind, visible text, amounts, names, and context."
}
if len(task.Images) > 0 {
text += "\nKeep each [image FILE] name and describe its kind, visible text, amounts, names, and context."
}
root: l,
messages: messages,
}
root: l,
messages: messages,
}
l.tools.children = runtime
var workers sync.WaitGroup
l.tools.children = runtime
var workers sync.WaitGroup
t.Cleanup(func() {
require.NoError(t, agents.Close())
})
t.Cleanup(func() {
require.NoError(t, agents.Close())
})
require.Len(t, agents, 3)
require.Equal(t, []string{"Garden", "Library"}, agents.ChildNames())
require.Len(t, agents, 3)
require.Equal(t, []string{"Garden", "Library"}, agents.ChildNames())
cause: "must have its own name and ID",
},
}
cause: "must have its own name and ID",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
modelWriteEvents(w, modelCompletedEvent(items, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent(items, 100, 0, 5))
}
whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
require.NoError(t, err)
whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
require.NoError(t, err)
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
child: "A" + strings.Repeat("b", 31),
},
}
child: "A" + strings.Repeat("b", 31),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
child: "Hous\u00e9",
},
}
child: "Hous\u00e9",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
test := &DaemonChildrenTest{
server: newModelFake(t),
}
test := &DaemonChildrenTest{
server: newModelFake(t),
}
agents, err := openDaemonAgents(t.Context(), t.TempDir(), test.server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, time.Now, discardLog())
require.NoError(t, err)
agents, err := openDaemonAgents(t.Context(), t.TempDir(), test.server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, time.Now, discardLog())
require.NoError(t, err)
root: test.root,
messages: messages,
}
root: test.root,
messages: messages,
}
test.root.tools.children = test.runtime
t.Cleanup(func() {
cancel()
test.root.tools.children = test.runtime
t.Cleanup(func() {
cancel()
ordered: append([]*TurnLoop(nil), loops...),
loops: make(map[string]*TurnLoop),
}
ordered: append([]*TurnLoop(nil), loops...),
loops: make(map[string]*TurnLoop),
}
for _, loop := range loops {
m.loops[loop.agent.config.Name] = loop
loop.messages = m
for _, loop := range loops {
m.loops[loop.agent.config.Name] = loop
loop.messages = m
Source: source,
Status: status,
})
Source: source,
Status: status,
})
loop.Wake(logged)
if message.WhatsAppBatch > 0 {
loop.Wake(logged)
if message.WhatsAppBatch > 0 {
m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
return !held[number]
})
m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
return !held[number]
})
if len(m.state.WhatsAppBatches) == before {
return nil
}
if len(m.state.WhatsAppBatches) == before {
return nil
}
test := &DaemonMessageTest{
logs: &SyncBuffer{},
}
test := &DaemonMessageTest{
logs: &SyncBuffer{},
}
test.agents, err = openDaemonAgents(t.Context(), dir, server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, newTestClock().Now, daemonTestLogger(test.logs))
require.NoError(t, err)
t.Cleanup(func() {
test.agents, err = openDaemonAgents(t.Context(), dir, server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, newTestClock().Now, daemonTestLogger(test.logs))
require.NoError(t, err)
t.Cleanup(func() {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the gate size."))
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the gate size."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
telegram := newTelegramFake(t)
bot := newTelegramBot(newTelegramAPI(telegram.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), discardLog())
test := newDaemonMessageTest(t, t.TempDir(), server)
telegram := newTelegramFake(t)
bot := newTelegramBot(newTelegramAPI(telegram.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), discardLog())
require.Eventually(t, func() bool {
return len(telegram.Sent()) >= 2
}, 10*time.Second, time.Millisecond)
require.Eventually(t, func() bool {
return len(telegram.Sent()) >= 2
}, 10*time.Second, time.Millisecond)
require.Equal(t, []string{"[Garden] finished", "I will ask the user."}, telegram.Sent())
require.Equal(t, subagentFinished, test.events[0].LastEvent("work").Status)
require.Equal(t, "[Garden] Approve the gate size.", test.events[0].Last("work"))
require.Equal(t, []string{"[Garden] finished", "I will ask the user."}, telegram.Sent())
require.Equal(t, subagentFinished, test.events[0].LastEvent("work").Status)
require.Equal(t, "[Garden] Approve the gate size.", test.events[0].Last("work"))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(tc.text)}, 100, 0, 5))
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(tc.text)}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Keep the plan."))
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Keep the plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the first plan."))
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the first plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan needs approval.")}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan needs approval.")}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the plan."))
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the plan."))
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
first := newDaemonMessageTest(t, dir, server)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Remember the garden plan."))
require.Equal(t, 1, first.agents[1].memory.Len())
first := newDaemonMessageTest(t, dir, server)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Remember the garden plan."))
require.Equal(t, 1, first.agents[1].memory.Len())
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Finish the garden plan."))
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Finish the garden plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan is ready.")}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan is ready.")}, 100, 0, 5))
}
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 0, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Review the plan."))
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 0, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Review the plan."))
require.Never(t, func() bool {
return len(sent) > 0
}, 50*time.Millisecond, time.Millisecond)
require.Never(t, func() bool {
return len(sent) > 0
}, 50*time.Millisecond, time.Millisecond)
once.Do(func() {
close(release)
})
once.Do(func() {
close(release)
})
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.ErrorContains(t, test.messages.Tell(tc.source, tc.target, "Do not deliver."), "not a child")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.ErrorContains(t, test.messages.Tell(tc.source, tc.target, "Do not deliver."), "not a child")
file: `{"next":0,"pending":[{"id":1,"source":"OneChat","target":"Garden","kind":"user"}]}`,
},
}
file: `{"next":0,"pending":[{"id":1,"source":"OneChat","target":"Garden","kind":"user"}]}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
test := newDaemonMessageTest(t, t.TempDir(), server)
child := test.agents[1]
at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
test := newDaemonMessageTest(t, t.TempDir(), server)
child := test.agents[1]
at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
dir := t.TempDir()
test := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
dir := t.TempDir()
test := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
ID: whatsappTestAna.String(),
Name: "Ana",
}
ID: whatsappTestAna.String(),
Name: "Ana",
}
source := "whatsapp:" + chat.ID
require.NoError(t, test.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Keep the gate plan.")
source := "whatsapp:" + chat.ID
require.NoError(t, test.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Keep the gate plan.")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
first := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
chat := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
}
first := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
chat := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
}
source := "whatsapp:" + chat.ID
require.NoError(t, first.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Remember the gate plan.")
source := "whatsapp:" + chat.ID
require.NoError(t, first.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Remember the gate plan.")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
data, err := json.Marshal(file)
require.NoError(t, err)
require.NoError(t, os.WriteFile(path, data, 0o600))
data, err := json.Marshal(file)
require.NoError(t, err)
require.NoError(t, os.WriteFile(path, data, 0o600))
ID: whatsappTestAna.String(),
Name: "Ana",
}
ID: whatsappTestAna.String(),
Name: "Ana",
}
second := WhatsAppChat{
ID: whatsappTestFamily.String(),
Name: "Garden plans",
second := WhatsAppChat{
ID: whatsappTestFamily.String(),
Name: "Garden plans",
Name: "Garden plans",
Group: true,
}
Name: "Garden plans",
Group: true,
}
library := WhatsAppChat{
ID: whatsappTestBruno.String(),
Name: "Bruno",
}
library := WhatsAppChat{
ID: whatsappTestBruno.String(),
Name: "Bruno",
}
root := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
root := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
Type: "input_text",
Text: text,
}}
Type: "input_text",
Text: text,
}}
for _, name := range names {
if store == nil {
return nil, errors.New("image store is not available")
for _, name := range names {
if store == nil {
return nil, errors.New("image store is not available")
Input: []string{"user: " + imageText([]string{name}, "Read the receipt.")},
Images: []string{name},
}
Input: []string{"user: " + imageText([]string{name}, "Read the receipt.")},
Images: []string{name},
}
text, err := compactor.Summarize(t.Context(), task)
require.NoError(t, err)
require.Equal(t, "receipt, Ada, $12, red square", text)
text, err := compactor.Summarize(t.Context(), task)
require.NoError(t, err)
require.Equal(t, "receipt, Ada, $12, red square", text)
store: store,
bot: bot,
}}
store: store,
bot: bot,
}}
args, err := json.Marshal(ToolImageShowArguments{
Image: name,
Caption: "A red point.",
args, err := json.Marshal(ToolImageShowArguments{
Image: name,
Caption: "A red point.",
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
telegram := newTelegramFake(t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
telegram := newTelegramFake(t)
store := &ImageStore{dir: t.TempDir()}
picture := imageTestPNG(t, 3, 3)
name, err := store.Save(picture)
store := &ImageStore{dir: t.TempDir()}
picture := imageTestPNG(t, 3, 3)
name, err := store.Save(picture)
store: store,
bot: bot,
}
store: store,
bot: bot,
}
_, err = show.Show(t.Context(), name, "Caption")
if tc.failure {
require.Error(t, err)
_, err = show.Show(t.Context(), name, "Caption")
if tc.failure {
require.Error(t, err)
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5))
}
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5))
}
socket := daemonStartTestWithBot(t, server, bot)
terminal := terminalStartTestClient(t, socket)
terminal.WaitFor(t, "[connected to OneChat]")
socket := daemonStartTestWithBot(t, server, bot)
terminal := terminalStartTestClient(t, socket)
terminal.WaitFor(t, "[connected to OneChat]")
telegram.Add(TelegramUpdate{
UpdateID: 1,
Message: message,
})
telegram.Add(TelegramUpdate{
UpdateID: 1,
Message: message,
})
terminal.WaitFor(t, "Shown.\n[done]")
require.Contains(t, terminal.Output(), "[result] [image "+name+"]")
require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name))
terminal.WaitFor(t, "Shown.\n[done]")
require.Contains(t, terminal.Output(), "[result] [image "+name+"]")
require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name))
dir: filepath.Join(root.dir, "children", "House"),
config: AgentConfig{Parent: root.config.Name},
}
dir: filepath.Join(root.dir, "children", "House"),
config: AgentConfig{Parent: root.config.Name},
}
require.Equal(t, root.Images().dir, child.Images().dir)
data := imageTestPNG(t, 3, 2)
require.Equal(t, root.Images().dir, child.Images().dir)
data := imageTestPNG(t, 3, 2)
offset: j.size,
length: uint32(len(line)),
}
offset: j.size,
length: uint32(len(line)),
}
j.size += uint64(len(line))
return place, nil
j.size += uint64(len(line))
return place, nil
Text: text,
Size: len(text),
})
Text: text,
Size: len(text),
})
slices.SortFunc(running, func(a, b NodeKey) int {
return a.index - b.index
})
slices.SortFunc(running, func(a, b NodeKey) int {
return a.index - b.index
})
require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
require.Eventually(t, func() bool {
require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
require.Eventually(t, func() bool {
cause: "read queued message",
},
}
cause: "read queued message",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error {
map[string]any{"type": "input_text", "text": "hello"},
},
}}, body["input"])
map[string]any{"type": "input_text", "text": "hello"},
},
}}, body["input"])
require.Len(t, body["tools"], 1)
}
require.Len(t, body["tools"], 1)
}
End your turn with an empty reply when nothing needs the parent or the user.
Send nothing to WhatsApp. Draft replies in your report instead, so the parent
can ask the user before sending.`, 1)
End your turn with an empty reply when nothing needs the parent or the user.
Send nothing to WhatsApp. Draft replies in your report instead, so the parent
can ask the user before sending.`, 1)
text = strings.Replace(text, promptChildrenText, "", 1)
return promptWithInstructions(text, "Do the tasks of your area yourself, with your tools.", promptInstructionsClause, instructions)
text = strings.Replace(text, promptChildrenText, "", 1)
return promptWithInstructions(text, "Do the tasks of your area yourself, with your tools.", promptInstructionsClause, instructions)
require.Contains(t, text, `Do the user's tasks yourself, with your tools, following the user's instructions
at the end of this prompt: who they are, how their files are organized and how
they want work done.`)
require.Contains(t, text, `Do the user's tasks yourself, with your tools, following the user's instructions
at the end of this prompt: who they are, how their files are organized and how
they want work done.`)
require.True(t, strings.HasSuffix(text, "\n\n# The user's instructions\n\nI am Lena. Keep replies short."))
}
require.True(t, strings.HasSuffix(text, "\n\n# The user's instructions\n\nI am Lena. Keep replies short."))
}
logger: logger,
targets: make(map[string]string),
}
logger: logger,
targets: make(map[string]string),
}
data, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return r, nil
data, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return r, nil
cause: "must be a child",
},
}
cause: "must be a child",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
whatsapp.Command("status", func(text string) {
answer = text
})
whatsapp.Command("status", func(text string) {
answer = text
})
require.Contains(t, answer, "WhatsApp is not linked.")
require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
}
require.Contains(t, answer, "WhatsApp is not linked.")
require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
}
text: `{"whatsapp:example@lid":""}`,
},
}
text: `{"whatsapp:example@lid":""}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "routes.json")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "routes.json")
"[Schedule 1] Sunday 2026-11-08 09:00 -03:00, once\nRemind the user to call.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
"[Schedule 2] Sunday 2026-11-08 10:00 -03:00, repeats 0 * * * *\nTell the user the time.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
}, texts[len(texts)-2:])
"[Schedule 1] Sunday 2026-11-08 09:00 -03:00, once\nRemind the user to call.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
"[Schedule 2] Sunday 2026-11-08 10:00 -03:00, repeats 0 * * * *\nTell the user the time.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
}, texts[len(texts)-2:])
require.Equal(t, "2 | Sunday 2026-11-08 21:00 -03:00 | 0 * * * * | Tell the user the time.", schedules.List())
require.Equal(t, "<6>schedule ran id=1 late=11h30m0s\n<6>schedule ran id=2 late=10h30m0s\n", logs.String())
}
require.Equal(t, "2 | Sunday 2026-11-08 21:00 -03:00 | 0 * * * * | Tell the user the time.", schedules.List())
require.Equal(t, "<6>schedule ran id=1 late=11h30m0s\n<6>schedule ran id=2 late=10h30m0s\n", logs.String())
}
log: log,
syncer: syncer,
}
log: log,
syncer: syncer,
}
s.open[name] = subagent
return subagent, nil
s.open[name] = subagent
return subagent, nil
"work: " + report,
"reply: Noted.",
}, test.Log(t))
"work: " + report,
"reply: Noted.",
}, test.Log(t))
require.Equal(t, Event{
Kind: "work",
ID: 4,
require.Equal(t, Event{
Kind: "work",
ID: 4,
Source: name,
Status: subagentFinished,
}, test.events.LastEvent("work"))
Source: name,
Status: subagentFinished,
}, test.events.LastEvent("work"))
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent done name="+name+" after=0s\n", test.logs.String())
var first, second *ModelFakeCall
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent done name="+name+" after=0s\n", test.logs.String())
var first, second *ModelFakeCall
"work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
"reply: It failed.",
}, test.Log(t))
"work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
"reply: It failed.",
}, test.Log(t))
require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
}
require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
}
"note: Stopped by the user: " + name + ".",
"work: " + subagentReport(name, []string{"stopped"}),
}, test.Log(t))
"note: Stopped by the user: " + name + ".",
"work: " + subagentReport(name, []string{"stopped"}),
}, test.Log(t))
require.Equal(t, subagentStopped, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent stopped name="+name+" after=0s\n", test.logs.String())
require.Equal(t, subagentStopped, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent stopped name="+name+" after=0s\n", test.logs.String())
"work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
"reply: a line",
}, subagentTestChat(t, memory))
"work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
"reply: a line",
}, subagentTestChat(t, memory))
require.Equal(t, subagentFailed, events.LastEvent("work").Status)
require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
require.Equal(t, subagentFailed, events.LastEvent("work").Status)
require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
s := newSyncer(func() error {
return unix.Syncfs(int(file.Fd()))
})
s := newSyncer(func() error {
return unix.Syncfs(int(file.Fd()))
})
s.file = file
return s, nil
s.file = file
return s, nil
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
tc.reply(w)
}))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
tc.reply(w)
}))
t.Cleanup(server.Close)
err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil)
t.Cleanup(server.Close)
err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil)
added: make(chan struct{}, 1),
stopped: make(chan struct{}),
}
added: make(chan struct{}, 1),
stopped: make(chan struct{}),
}
t.Cleanup(func() {
close(f.stopped)
})
t.Cleanup(func() {
close(f.stopped)
})
UpdateID: 1,
Message: message,
}))
UpdateID: 1,
Message: message,
}))
test.events.WaitFor(t, "idle", 1)
files, err := os.ReadDir(test.agent.Images().dir)
test.events.WaitFor(t, "idle", 1)
files, err := os.ReadDir(test.agent.Images().dir)
failure: "exceeds 20 MB",
},
}
failure: "exceeds 20 MB",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document})
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document})
bot, logs := telegramStartTestBot(t, telegram)
server := newModelFake(t)
socket := daemonStartTestWithBot(t, server, bot)
bot, logs := telegramStartTestBot(t, telegram)
server := newModelFake(t)
socket := daemonStartTestWithBot(t, server, bot)
telegram.Add(TelegramUpdate{
UpdateID: 7,
Message: message,
})
telegram.Add(TelegramUpdate{
UpdateID: 7,
Message: message,
})
terminal.WaitFor(t, "[done]")
require.Eventually(t, func() bool {
offset, err := telegramLoadOffset(bot.offsetPath)
return err == nil && offset == 8
}, 10*time.Second, time.Millisecond)
terminal.WaitFor(t, "[done]")
require.Eventually(t, func() bool {
offset, err := telegramLoadOffset(bot.offsetPath)
return err == nil && offset == 8
}, 10*time.Second, time.Millisecond)
require.Contains(t, logs.String(), "receive failed")
require.Contains(t, logs.String(), "receive back")
require.NotContains(t, logs.String(), telegramTestToken)
require.Contains(t, logs.String(), "receive failed")
require.Contains(t, logs.String(), "receive back")
require.NotContains(t, logs.String(), telegramTestToken)
err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{
UpdateID: 1,
Message: message,
err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{
UpdateID: 1,
Message: message,
Source: whatsappSource,
Text: "[WhatsApp] Ana Souza",
})
Source: whatsappSource,
Text: "[WhatsApp] Ana Souza",
})
bot.Notify(Event{
Kind: "work",
Source: scheduleSource,
Text: "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
})
bot.Notify(Event{
Kind: "work",
Source: scheduleSource,
Text: "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
})
require.Empty(t, bot.queue)
}
require.Empty(t, bot.queue)
}
require.Eventually(t, func() bool {
return strings.Contains(logs.String(), "<4>receive failed retry=1s: no answer: ")
}, 10*time.Second, time.Millisecond)
require.Eventually(t, func() bool {
return strings.Contains(logs.String(), "<4>receive failed retry=1s: no answer: ")
}, 10*time.Second, time.Millisecond)
require.NotContains(t, logs.String(), telegramTestToken)
}
require.NotContains(t, logs.String(), telegramTestToken)
}
"<4>receive failed retry=1s: 502 Bad Gateway",
"<6>receive back after=31s",
}, lines)
"<4>receive failed retry=1s: 502 Bad Gateway",
"<6>receive back after=31s",
}, lines)
require.NotContains(t, logs.String(), "secret word")
require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
cancel()
require.NotContains(t, logs.String(), "secret word")
require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
cancel()
marker: "transcriber or model is not available",
},
}
marker: "transcriber or model is not available",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newTestTurnLoop(t, newModelFake(t))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newTestTurnLoop(t, newModelFake(t))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
request, command := terminalImageRequest(tc.line)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
request, command := terminalImageRequest(tc.line)
require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600))
require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) {
}), "JPEG, PNG, or WebP")
require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600))
require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) {
}), "JPEG, PNG, or WebP")
require.Equal(t, 0, test.memory.Len())
require.Equal(t, 0, server.Calls())
require.Equal(t, 0, test.memory.Len())
require.Equal(t, 0, server.Calls())
want: []string{"safe[31m end"},
},
}
want: []string{"safe[31m end"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
want: []string{"| a", "| b"},
},
}
want: []string{"| a", "| b"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
Kind: "work",
Text: "[BlueBird]\n" + text,
})
Kind: "work",
Text: "[BlueBird]\n" + text,
})
require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
}
require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
}
Kind: "reply",
Text: text,
})
Kind: "reply",
Text: text,
})
require.Zero(t, terminalColumns(out))
contents, err := os.ReadFile(out.Name())
require.Zero(t, terminalColumns(out))
contents, err := os.ReadFile(out.Name())
Event{Kind: "busy"},
Event{Kind: "idle"},
)
Event{Kind: "busy"},
Event{Kind: "idle"},
)
require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
"[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
"[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
"[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
"[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
Kind: "reply",
Text: strings.Repeat("a", 81),
})
Kind: "reply",
Text: strings.Repeat("a", 81),
})
require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
require.Equal(t, 40, terminalColumns(&out))
require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
require.Equal(t, 40, terminalColumns(&out))
require.Equal(t, "[connected to Example]\n> "+
"\r\x1b[2KIdle notice\n> "+
"\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
require.Equal(t, "[connected to Example]\n> "+
"\r\x1b[2KIdle notice\n> "+
"\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
}
require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmd.Cancel = func() error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
cmd.Cancel = func() error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
cmd.WaitDelay = toolShellOutputTimeout
output := &ToolHeadTail{}
cmd.WaitDelay = toolShellOutputTimeout
output := &ToolHeadTail{}
"flush, marker absent",
"echo: done\n",
}, log.Events())
"flush, marker absent",
"echo: done\n",
}, log.Events())
require.FileExists(t, filepath.Join(dir, "ran"))
}
require.FileExists(t, filepath.Join(dir, "ran"))
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
"echo: hi\n",
"reply: It said hi.",
}, test.Log(t))
"echo: hi\n",
"reply: It said hi.",
}, test.Log(t))
require.Equal(t, []string{"user", "busy", "tool", "echo", "reply", "idle"}, test.events.Kinds())
var turns []string
require.Equal(t, []string{"user", "busy", "tool", "echo", "reply", "idle"}, test.events.Kinds())
var turns []string
output := filepath.Join(dir, "text")
command := exec.CommandContext(ctx, program, "--model", model, "--file", path,
"--language", "auto", "--max-context", "0", "--output-txt", "--output-file", output)
output := filepath.Join(dir, "text")
command := exec.CommandContext(ctx, program, "--model", model, "--file", path,
"--language", "auto", "--max-context", "0", "--output-txt", "--output-file", output)
command.Stdout = io.Discard
command.Stderr = io.Discard
command.WaitDelay = voiceProcessExitTimeout
command.Stdout = io.Discard
command.Stderr = io.Discard
command.WaitDelay = voiceProcessExitTimeout
printf 'private speech in process output'
printf 'private speech in process errors' >&2
`)
printf 'private speech in process output'
printf 'private speech in process errors' >&2
`)
data := []byte("made-up audio")
text := transcriber.Text(t.Context(), store, data)
require.Contains(t, text, "[voice ")
data := []byte("made-up audio")
text := transcriber.Text(t.Context(), store, data)
require.Contains(t, text, "[voice ")
script: "while [ \"$1\" != --output-file ]; do shift; done; shift; touch \"$1.txt\"",
},
}
script: "while [ \"$1\" != --output-file ]; do shift; done; shift; touch \"$1.txt\"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
store, transcriber := voiceTestInput(t, tc.script)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
store, transcriber := voiceTestInput(t, tc.script)
program: valid.Transcriber,
},
}
program: valid.Transcriber,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
program, model, err := voiceLookup(tc.config, tc.program, tc.model)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
program, model, err := voiceLookup(tc.config, tc.program, tc.model)
var pairing sync.WaitGroup
var pairing sync.WaitGroup
defer func() {
cancel()
pairing.Wait()
defer func() {
cancel()
pairing.Wait()
switch {
case request.unlink:
switch {
case request.unlink:
Date: "2025-01-03T10:20:00Z",
},
})
Date: "2025-01-03T10:20:00Z",
},
})
rebuildTestLog(t, childDir, []DatedMessage{
{
Kind: "work",
rebuildTestLog(t, childDir, []DatedMessage{
{
Kind: "work",
Text: "Keep the root history.",
Date: "2025-02-01T00:00:00Z",
}})
Text: "Keep the root history.",
Date: "2025-02-01T00:00:00Z",
}})
rebuildTestLog(t, childDir, []DatedMessage{{
Kind: "user",
Text: "Keep the child history.",
rebuildTestLog(t, childDir, []DatedMessage{{
Kind: "user",
Text: "Keep the child history.",
},
{name: "send failure stores a safe cause"},
}
},
{name: "send failure stores a safe cause"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
whatsapp, fake := whatsappStartTestLinked(t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
whatsapp, fake := whatsappStartTestLinked(t)
require.NotContains(t, box.run(t.Context(), "image_show", `{"image":"`+names[0]+`","caption":"The plan."}`), "error:")
require.Contains(t, box.run(t.Context(), "whatsapp_send", `{"id":"forward","chat":"15550002222@s.whatsapp.net","text":"The plan.","image":"`+names[0]+`"}`), "Sent to")
require.Equal(t, [][]byte{picture}, fake.Uploads())
require.NotContains(t, box.run(t.Context(), "image_show", `{"image":"`+names[0]+`","caption":"The plan."}`), "error:")
require.Contains(t, box.run(t.Context(), "whatsapp_send", `{"id":"forward","chat":"15550002222@s.whatsapp.net","text":"The plan.","image":"`+names[0]+`"}`), "Sent to")
require.Equal(t, [][]byte{picture}, fake.Uploads())
Chat: chat,
First: arrival,
})
Chat: chat,
First: arrival,
})
index = len(s.file.Batches) - 1
}
index = len(s.file.Batches) - 1
}
test.whatsapp.Command("pair 15550001111", func(text string) {
answers <- text
})
test.whatsapp.Command("pair 15550001111", func(text string) {
answers <- text
})
require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
synctest.Wait()
test.fake.mutex.Lock()
require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
synctest.Wait()
test.fake.mutex.Lock()
synctest.Wait()
test.whatsapp.Command("unlink", func(_ string) {
})
synctest.Wait()
test.whatsapp.Command("unlink", func(_ string) {
})
synctest.Wait()
require.False(t, test.whatsapp.isLinked())
synctest.Wait()
require.False(t, test.whatsapp.isLinked())
"<4>disconnected, retrying",
"<6>connected again after=1m25s",
}, strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n"))
"<4>disconnected, retrying",
"<6>connected again after=1m25s",
}, strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n"))
require.NotContains(t, logs.String(), whatsappTestNumber)
require.NotContains(t, logs.String(), whatsappTestLID)
}
require.NotContains(t, logs.String(), whatsappTestNumber)
require.NotContains(t, logs.String(), whatsappTestLID)
}
require.Eventually(t, func() bool {
return logs.String() != ""
}, 10*time.Second, time.Millisecond)
require.Eventually(t, func() bool {
return logs.String() != ""
}, 10*time.Second, time.Millisecond)
require.Equal(t, "<4>first connect failed, retrying: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
}
require.Equal(t, "<4>first connect failed, retrying: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
first := newDaemonMessageTest(t, dir, server)
whatsappTestSession(t, dir, true)
first := newDaemonMessageTest(t, dir, server)
whatsappTestSession(t, dir, true)