Files
cobble/src/harness/tools/bot/send-message-tool.test.ts
T
tbenandClaude Opus 5.5 b0fe619ae8 Let bots take in messages sent while they reply
A reply read the session once at the start, so a message sent while it
ran was never seen, and the follow-up reply queued for it then finished
silently: the message was saved before the reply, so the thread ended
with the bot. Those messages went unanswered.

The text loop now reads new user messages after every round of tools and
before an answer goes out, and the bot decides from what each one means
whether it changes the work (folded in, or the unsent answer redrafted)
or is a separate request, which it acknowledges at once with the new
send_message tool and does after the current work. Messages sent that
way are marked interim, so the thinking row stays up while the bot
works. A reply job records the newest user message it read, and a queued
reply only stays silent when a finished reply already read everything.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 04:49:10 +01:00

53 lines
1.7 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { AppData } from "@/src/data/app-data";
import sendMessageTool from "./send-message-tool";
const signal = new AbortController().signal;
function tool() {
const posted: string[] = [];
const sent: number[] = [];
const send = sendMessageTool({
bot_id: 2,
session_id: 100,
post: async ({ text }) => {
posted.push(text);
return { id: posted.length };
},
onSend: ({ message_id }) => sent.push(message_id),
});
return { send, posted, sent };
}
describe("sendMessageTool", () => {
test("posts the message now and tells the bot to carry on", async () => {
const { send, posted, sent } = tool();
const result = await send.run({ args: { text: "Noted, I'll do that once this is done." }, signal });
expect(posted).toEqual(["Noted, I'll do that once this is done."]);
expect(sent).toEqual([1]);
expect(result).toContain("Carry on");
});
test("stops at the cap, so a reply can't flood the chat", async () => {
const { send, posted } = tool();
for (let i = 0; i < AppData.BotReplyInterimMessagesMax; i++) {
await send.run({ args: { text: `Heads-up ${i}` }, signal });
}
const error = await Promise.resolve(send.run({ args: { text: "One more" }, signal })).catch(
(e) => e,
);
expect(error).toBeInstanceOf(Error);
expect(posted).toHaveLength(AppData.BotReplyInterimMessagesMax);
});
test("refuses an empty message", () => {
expect(tool().send.schema?.safeParse({ text: " " }).success).toBe(false);
});
});