Replace the composer textarea with a Tiptap markdown editor
The composer now writes markdown with bold, italic, strike, code, links, quotes and lists, from shortcuts, markdown typing or a bubble over the selection. @ mentions a bot and # a workspace, shown as chips and saved as [bot:ID] / [workspace:ID]. Text selected in a message can be quoted into the composer. User messages render as markdown, and replies tell the bot who and what the user mentioned. Adds an ESM stand-in for use-sync-external-store, since bunext keeps React external in page bundles and the CommonJS original requires it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -133,3 +133,17 @@ describe("botMessagesToHarness with files", () => {
|
||||
).toEqual([{ role: "user", content: `[Attached earlier: "shot.png"]` }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("botMessagesToHarness with mentions", () => {
|
||||
test("names the bots and workspaces a user message mentions", () => {
|
||||
expect(
|
||||
botMessagesToHarness({
|
||||
messages: [message("user", "ask [bot:3]"), message("bot", "[bot:3] is on it")],
|
||||
refs: { bots: new Map([[3, { name: "Ada" }]]), workspaces: new Map() },
|
||||
}),
|
||||
).toEqual([
|
||||
{ role: "user", content: "ask [bot:3]\n\n[Mentions: [bot:3] is Ada]" },
|
||||
{ role: "assistant", content: "[bot:3] is on it" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { HarnessContentPart, HarnessMessage } from "@/src/harness/types";
|
||||
import type { BotMessageWithAttachments } from "@/src/types";
|
||||
import describeMessageRefs, { type MessageRefNames } from "@/src/utils/describe-message-refs";
|
||||
|
||||
type Params = {
|
||||
messages: Pick<
|
||||
@@ -11,6 +12,11 @@ type Params = {
|
||||
* messages are only named.
|
||||
*/
|
||||
parts?: Map<number, HarnessContentPart[]>;
|
||||
/**
|
||||
* Names of the bots and workspaces the user's messages mention
|
||||
* (`grabMessageRefNames`), told to the bot after each one.
|
||||
*/
|
||||
refs?: MessageRefNames;
|
||||
};
|
||||
|
||||
type Turn = { role: "user" | "assistant"; parts: HarnessContentPart[] };
|
||||
@@ -22,12 +28,12 @@ type Turn = { role: "user" | "assistant"; parts: HarnessContentPart[] };
|
||||
* repeated roles. Leading bot messages are dropped so the conversation
|
||||
* starts with the user. A turn that is only text stays a string.
|
||||
*/
|
||||
export default function botMessagesToHarness({ messages, parts }: Params): HarnessMessage[] {
|
||||
export default function botMessagesToHarness({ messages, parts, refs }: Params): HarnessMessage[] {
|
||||
const turns: Turn[] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
const role = message.author == "user" ? "user" : "assistant";
|
||||
const content = messageParts({ message, role, parts });
|
||||
const content = messageParts({ message, role, parts, refs });
|
||||
|
||||
if (!content.length) continue;
|
||||
if (!turns.length && role == "assistant") continue;
|
||||
@@ -59,12 +65,16 @@ function messageParts({
|
||||
message,
|
||||
role,
|
||||
parts,
|
||||
refs,
|
||||
}: {
|
||||
message: Params["messages"][number];
|
||||
role: Turn["role"];
|
||||
parts?: Params["parts"];
|
||||
refs?: Params["refs"];
|
||||
}): HarnessContentPart[] {
|
||||
const text = replyContext({ message, text: message.text?.trim() || "" });
|
||||
const own = message.text?.trim() || "";
|
||||
const mentions = role == "user" && refs && own ? describeMessageRefs({ text: own, names: refs }) : "";
|
||||
const text = replyContext({ message, text: mentions ? `${own}\n\n${mentions}` : own });
|
||||
const content: HarnessContentPart[] = text ? [{ type: "text", text }] : [];
|
||||
|
||||
if (role == "assistant" || !message.attachments?.length) return content;
|
||||
|
||||
@@ -17,6 +17,7 @@ import composeComputersNote from "./compose-computers-note";
|
||||
import grabSessionCarryOver from "./grab-session-carry-over";
|
||||
import jobSessionId from "@/src/functions/backend/db/bots-queue/job-session-id";
|
||||
import loadAttachmentParts from "./load-attachment-parts";
|
||||
import grabMessageRefNames from "@/src/functions/backend/db/bots/grab-message-ref-names";
|
||||
import withTakeOverRefs from "./with-take-over-refs";
|
||||
import type { BotJobContext, BotJobOutcome } from "./types";
|
||||
|
||||
@@ -124,6 +125,10 @@ export default async function runBotReply({
|
||||
messages: botMessagesToHarness({
|
||||
messages: history,
|
||||
parts: await loadAttachmentParts({ messages: history }),
|
||||
refs: await grabMessageRefNames({
|
||||
texts: history.filter((m) => m.author == "user").map((m) => m.text || ""),
|
||||
user_id,
|
||||
}),
|
||||
}),
|
||||
tools: [...tools, ...extensions.tools],
|
||||
max_steps: AppData.BotReplyMaxToolSteps,
|
||||
|
||||
Reference in New Issue
Block a user