A worker asks for another bot's help with ask_crew. The coordinator reads the request, asks the right bot (in a workspace, only its members), and sends the answer back into the asking bot's conversation, waking it. Each exchange is kept as a read-only chatroom: listed in the main rail after the workspaces, or in the workspace's rail for requests made there, with live status rows for each bot while it's open and a Stop button. Room sessions are hidden from every conversation list, other bots' words reach the model fenced as data, and turns, nesting and open rooms are capped. Closed rooms are pruned after a retention period and past a cap per place (Settings > General). Co-Authored-By: Claude Opus 5.5 <[email protected]>
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import { Database } from "bun:sqlite";
|
|
import { describe, expect, test } from "bun:test";
|
|
import searchPastSessionsSql, { type SearchPastSessionsParams } from "./past-session-messages-sql";
|
|
|
|
/**
|
|
* A bot's search of its earlier sessions reads only that user's
|
|
* conversation with it (in the same place), never the session it's
|
|
* answering, nor its part of a chatroom. Run against real SQLite.
|
|
*/
|
|
const db = new Database(":memory:");
|
|
|
|
db.run(`CREATE TABLE bot_sessions (id INTEGER PRIMARY KEY, user_id INTEGER, bot_id INTEGER, workspace_id INTEGER, title TEXT)`);
|
|
db.run(`CREATE TABLE bot_messages (id INTEGER PRIMARY KEY, session_id INTEGER, author TEXT, text TEXT, created_at INTEGER)`);
|
|
db.run(`INSERT INTO bot_sessions VALUES
|
|
(1, 1, 10, NULL, 'Invoices'),
|
|
(2, 1, 10, NULL, 'Current'),
|
|
(3, 2, 10, NULL, 'Another user'),
|
|
(4, 1, 11, NULL, 'Another bot'),
|
|
(5, 1, 10, 7, 'In a workspace')`);
|
|
db.run(`INSERT INTO bot_messages (id, session_id, author, text) VALUES
|
|
(1, 1, 'user', 'Send the Acme invoice to [email protected]'),
|
|
(2, 1, 'bot', 'Done: invoice INV-42 sent to Acme.'),
|
|
(3, 2, 'user', 'What happened with the Acme invoice?'),
|
|
(4, 3, 'user', 'Acme invoice for someone else'),
|
|
(5, 4, 'user', 'Acme invoice to another bot'),
|
|
(6, 5, 'user', 'Acme invoice in the workspace'),
|
|
(7, 1, 'user', 'Discount of 100% off'),
|
|
(8, 1, 'user', 'Discount of 10 off')`);
|
|
/**
|
|
* The bot's part of a chatroom: never one of its past sessions.
|
|
*/
|
|
db.run(`ALTER TABLE bot_sessions ADD COLUMN chatroom_id INTEGER`);
|
|
db.run(`INSERT INTO bot_sessions (id, user_id, bot_id, workspace_id, title, chatroom_id) VALUES (6, 1, 10, NULL, 'A chatroom', 3)`);
|
|
db.run(`INSERT INTO bot_messages (id, session_id, author, text) VALUES (9, 6, 'bot', 'Acme invoice asked in a chatroom')`);
|
|
|
|
function ids(params: Partial<SearchPastSessionsParams>) {
|
|
const { sql, values } = searchPastSessionsSql({
|
|
user_id: 1,
|
|
bot_id: 10,
|
|
current_session_id: 2,
|
|
query: "",
|
|
...params,
|
|
});
|
|
|
|
return (db.query(sql).all(...values) as { id: number }[]).map((row) => row.id);
|
|
}
|
|
|
|
describe("searchPastSessionsSql", () => {
|
|
test("finds only this conversation's earlier sessions, newest first", () => {
|
|
expect(ids({ query: "acme invoice" })).toEqual([2, 1]);
|
|
});
|
|
|
|
test("keeps the workspace's conversation apart from the bot's own chat", () => {
|
|
expect(ids({ query: "acme", workspace_id: 7, current_session_id: 99 })).toEqual([6]);
|
|
});
|
|
|
|
test("every word must match", () => {
|
|
expect(ids({ query: "invoice INV-42" })).toEqual([2]);
|
|
});
|
|
|
|
test("% matches only itself", () => {
|
|
expect(ids({ query: "100%" })).toEqual([7]);
|
|
});
|
|
|
|
test("pages with before_id", () => {
|
|
expect(ids({ query: "acme", before_id: 2 })).toEqual([1]);
|
|
});
|
|
|
|
test("cuts the text around the match", () => {
|
|
const { sql, values } = searchPastSessionsSql({
|
|
user_id: 1,
|
|
bot_id: 10,
|
|
current_session_id: 2,
|
|
query: "billing",
|
|
snippet_chars: 12,
|
|
});
|
|
const row = db.query(sql).get(...values) as { snippet: string; session_title: string };
|
|
|
|
expect(row.snippet).toContain("billing");
|
|
expect(row.snippet.length).toBeLessThanOrEqual(12);
|
|
expect(row.session_title).toBe("Invoices");
|
|
});
|
|
});
|