Add replies and emoji reactions to chat messages

Every message has a row under it to reply or react. Replying quotes the
message above the composer, and the sent message stores
reply_to_message_id (a plain id, not a foreign key, so deleting the
quoted message only drops the quote). send-message accepts a quote from
any session of the same conversation, so a rollover or a thread that
moved on still sends; the conversation drops a picked quote when its
session changes. The bot reads the quote as "[Replying to ...]" before
the user's turn, file-only replies included, and relayed messages carry
it too.

Reactions are a fixed set (BotMessageReactions) in
bot_message_reactions, one per message, emoji and user or bot. Users
toggle their own through api/admin/bots/react-message, and every bot
gets react_to_message for the session it answers (the user's latest
message by default). Both push the fresh list as server:bot-reaction,
with mine worked out for the session's user.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 21:56:31 +01:00
co-authored by Claude Opus 5.5
parent bc499a7466
commit d1b5b6d68a
44 changed files with 1289 additions and 58 deletions
+36 -1
View File
@@ -26,7 +26,7 @@ import type { HarnessKind } from "../dict/harness-dict";
import type { HarnessProviderKind } from "../dict/harness-provider-dict";
import type { EnvParadigm, KnownEnv } from "../dict/env-dict";
import type { ContainerStatus } from "../dict/computer-dict";
import type { BotFace } from "../dict/bot-dict";
import type { BotFace, BotMessageReactionEmoji } from "../dict/bot-dict";
import type { McpServerStatus, McpTransport } from "../dict/mcp-dict";
import type {
CustomToolHttpMethod,
@@ -587,8 +587,39 @@ export type BotMessageAttachment = {
thumbnail_url?: string;
};
/**
* An emoji reaction on a message, grouped by emoji.
*/
export type BotMessageReaction = {
emoji: BotMessageReactionEmoji;
count: number;
/**
* The session user reacted with it.
*/
mine: boolean;
};
/**
* The message a reply quotes, as the thread shows it.
*/
export type BotMessageReply = {
id: number;
author: "bot" | "user";
text: string;
};
export type BotMessageWithAttachments = BUN_SQLITE_COBBLE_BOT_MESSAGES & {
attachments?: BotMessageAttachment[];
reactions?: BotMessageReaction[];
reply_to?: BotMessageReply | null;
};
/**
* `server:bot-reaction` data: a message's reactions changed.
*/
export type BotMessageReactionData = {
message_id: number;
reactions: BotMessageReaction[];
};
export type BotHarnessOverviewPayload = {
@@ -877,6 +908,10 @@ export const WebSocketEvents = [
* `WorkspaceRemovedData`)
*/
"server:workspace-removed",
/**
* A message's emoji reactions changed (`data`: `BotMessageReactionData`)
*/
"server:bot-reaction",
/**
* A running job took a step (`data`: `BotJobUpdateData`)
*/