Add the harness, envs, per-bot models and chat attachments

- Harness: providers, model catalog synced hourly, automatic free picks,
  cooldowns, tool loop with context trimming, MCP computer tools
- Bots queue runs replies and tasks through the harness; jobs wait
  (attempt refunded) while every model is resting
- Envs: Settings > Environment, scoped general/workspace/computer,
  written to computers and redacted from tool output
- Models: Settings > Models, per-harness provider select defaulting to
  OpenRouter, and a models modal on each bot's chat (the coordinator's
  edits the defaults)
- Chat: thinking row while a reply runs, file attachments (upload,
  paste, drop) that replies read as images, audio and document text
- Fix media uploads failing after a user's first file, and private
  files being served from the public media path

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 03:38:38 +01:00
co-authored by Claude Opus 5.5
parent d49f524fe8
commit 6a2665dae9
223 changed files with 12342 additions and 162 deletions
@@ -0,0 +1,68 @@
import { describe, expect, test } from "bun:test";
import botMessagesToHarness from "./bot-messages-to-harness";
const message = (author: "user" | "bot", text: string) => ({ author, text });
describe("botMessagesToHarness", () => {
test("maps authors to roles", () => {
expect(
botMessagesToHarness({
messages: [message("user", "hi"), message("bot", "hello")],
}),
).toEqual([
{ role: "user", content: "hi" },
{ role: "assistant", content: "hello" },
]);
});
test("joins back-to-back messages from one side", () => {
expect(
botMessagesToHarness({
messages: [message("user", "one"), message("user", "two")],
}),
).toEqual([{ role: "user", content: "one\n\ntwo" }]);
});
test("starts with the user and skips empty messages", () => {
expect(
botMessagesToHarness({
messages: [
message("bot", "welcome"),
message("user", " "),
message("user", "hi"),
],
}),
).toEqual([{ role: "user", content: "hi" }]);
});
});
describe("botMessagesToHarness with files", () => {
const attachments = [{ id: 9, name: "shot.png", media_type: "image" as const, url: "/media/private/9" }];
test("puts a message's loaded files after its text", () => {
const parts = new Map([[1, [{ type: "image" as const, url: "data:image/jpeg;base64,AA", name: "shot.png" }]]]);
expect(
botMessagesToHarness({
messages: [{ id: 1, author: "user", text: "What's this?", attachments }],
parts,
}),
).toEqual([
{
role: "user",
content: [
{ type: "text", text: "What's this?" },
{ type: "image", url: "data:image/jpeg;base64,AA", name: "shot.png" },
],
},
]);
});
test("names files it didn't load, and keeps a file-only message", () => {
expect(
botMessagesToHarness({
messages: [{ id: 2, author: "user", text: "", attachments }],
}),
).toEqual([{ role: "user", content: `[Attached earlier: "shot.png"]` }]);
});
});