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]>
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
import { AppData } from "@/src/data/app-data";
|
|
import type { ConversationHistory } from "@/src/types";
|
|
import grabActiveBotTask from "../bots/grab-active-bot-task";
|
|
import grabBotMessages from "../bots/grab-bot-messages";
|
|
import ensureBotSession from "./ensure-bot-session";
|
|
import grabCarriedToSessionId from "./grab-carried-to-session-id";
|
|
import reopenBotSession from "./reopen-bot-session";
|
|
import grabUserBotSession from "./grab-user-bot-session";
|
|
import grabUserConversation from "./grab-user-conversation";
|
|
import markBotSessionRead from "./mark-bot-session-read";
|
|
|
|
type Params = {
|
|
user_id: number;
|
|
bot_id: number | string;
|
|
/**
|
|
* The workspace the conversation is in; none for the bot's own chat.
|
|
*/
|
|
workspace_id?: number | string | null;
|
|
/**
|
|
* A session of the conversation to show instead of the active one
|
|
* (the page's `?session_id=`). One that isn't the user's, or is in
|
|
* another conversation, is ignored.
|
|
*/
|
|
session_id?: number | string | null;
|
|
};
|
|
|
|
/**
|
|
* A conversation's history, which the conversation pages load on the
|
|
* client once they render (`api/admin/bots/conversation-history`): the
|
|
* bot's active session there, or the one asked for (marked read; a closed
|
|
* one is reopened, unless it was carried over, which only shows, with
|
|
* where the conversation went on), its latest
|
|
* `AppData.ConversationFirstMessages` messages (the thread loads older
|
|
* ones as it scrolls) and the bot's active task in it. Everything else on
|
|
* the page comes with it from the server (`grabConversationProps`).
|
|
* Null when the user can't chat with the bot there.
|
|
*/
|
|
export default async function grabConversationHistory({
|
|
user_id,
|
|
bot_id,
|
|
workspace_id,
|
|
session_id,
|
|
}: Params): Promise<ConversationHistory | null> {
|
|
const conversation = await grabUserConversation({ user_id, bot_id, workspace_id }).catch(
|
|
() => null,
|
|
);
|
|
|
|
if (!conversation?.bot?.id) return null;
|
|
|
|
const open_bot_id = conversation.bot.id;
|
|
const conversation_workspace_id = conversation.workspace_id || null;
|
|
|
|
const asked = session_id
|
|
? await grabUserBotSession({ session_id, user_id })
|
|
: null;
|
|
|
|
const in_conversation =
|
|
asked?.id &&
|
|
!asked.chatroom_id &&
|
|
Number(asked.bot_id) == Number(open_bot_id) &&
|
|
(asked.workspace_id ? Number(asked.workspace_id) : null) ==
|
|
(conversation_workspace_id ? Number(conversation_workspace_id) : null);
|
|
|
|
/**
|
|
* A closed session asked for is reopened, as clicking it in a list
|
|
* does, unless it was carried over into a newer one: that one only
|
|
* shows, with the way to where the conversation went on.
|
|
*/
|
|
const carried_to =
|
|
in_conversation && asked.status != "active"
|
|
? await grabCarriedToSessionId({ session_id: asked.id!, user_id })
|
|
: null;
|
|
|
|
const session = !in_conversation
|
|
? await ensureBotSession({
|
|
user_id,
|
|
bot_id: open_bot_id,
|
|
workspace_id: conversation.workspace_id,
|
|
})
|
|
: asked.status == "active" || carried_to
|
|
? asked
|
|
: await reopenBotSession({ session_id: asked.id!, user_id });
|
|
|
|
if (session.unread) {
|
|
await markBotSessionRead({ session_id: session.id!, user_id });
|
|
session.unread = 0;
|
|
}
|
|
|
|
const [messages, task] = await Promise.all([
|
|
grabBotMessages({
|
|
session_id: session.id!,
|
|
user_id,
|
|
limit: AppData.ConversationFirstMessages + 1,
|
|
}),
|
|
grabActiveBotTask({ bot_id: open_bot_id, session_id: session.id }),
|
|
]);
|
|
|
|
const has_more = messages.length > AppData.ConversationFirstMessages;
|
|
|
|
return {
|
|
session,
|
|
messages: has_more ? messages.slice(1) : messages,
|
|
has_more,
|
|
task,
|
|
moved_to_session_id: carried_to,
|
|
};
|
|
}
|