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,19 @@
import fetchApi from "@moduletrace/twui/utils/fetch/fetchApi";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import type { SaveBotHarnessSettingsRequest } from "@/src/schemas/harness-schema";
import type { BotHarnessOverviewPayload } from "@/src/types";
/**
* Saves a worker bot's own model settings and returns the fresh page data.
*/
export default async function saveBotHarnessSettings(
body: SaveBotHarnessSettingsRequest,
) {
return await fetchApi<
SaveBotHarnessSettingsRequest,
APIResponseObject<BotHarnessOverviewPayload>
>(`/api/admin/bots/save-harness-settings`, {
method: "POST",
body,
});
}
@@ -0,0 +1,25 @@
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
import SettingsShell from "@/src/pages/admin/settings/(partials)/settings-shell";
import BotHarnessForm from "../../(partials)/bot-harness-form";
/**
* A worker bot's models: per harness, the default or its own pick. The
* same form as the chat header's models modal.
*/
export default function BotModelsSection() {
const { pageProps } = useContext(AppContext);
const bot = pageProps?.bot;
const overview = pageProps?.bot_harness_overview;
if (!bot?.id || !overview) return null;
return (
<SettingsShell
title={`${bot.name}'s models`}
description="Uses the default models unless you pick its own."
>
<BotHarnessForm bot={bot} harnesses={overview.harnesses} />
</SettingsShell>
);
}
@@ -0,0 +1,37 @@
import userAuth from "@/src/functions/backend/auth/user-auth";
import grabBots from "@/src/functions/backend/db/bots/grab-bots";
import grabBotHarnessOverview from "@/src/functions/backend/db/harness/grab-bot-harness-overview";
import type { PagePropsType, PageQueryObject } from "@/src/types";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
/**
* A worker bot's model settings. The coordinator always uses the defaults,
* so its page goes back to its conversation.
*/
export const server: BunextPageServerFn<PagePropsType> = async (params) => {
const query: PageQueryObject = params.query;
const { user } = await userAuth({ req: params.req });
if (!user?.id) {
return { redirect: { destination: "/auth/login" } };
}
const bots = await grabBots({ user_id: user.id });
const bot = bots.find((b) => String(b.id) === String(query.bot_id));
if (!bot?.id) {
return { redirect: { destination: "/admin/bots" } };
}
if (bot.kind == "coordinator") {
return { redirect: { destination: `/admin/bots/${bot.id}` } };
}
return {
props: {
bots,
bot,
bot_harness_overview: await grabBotHarnessOverview({ bot_id: bot.id }),
},
};
};
@@ -0,0 +1,17 @@
import type { PagePropsType } from "@/src/types";
import type { BunextPageModuleMetaFn } from "@moduletrace/bunext/types";
import { SiteData } from "@/src/data/site-data";
import BotModelsSection from "./(sections)/bot-models-section";
export default function BotModelsPage() {
return <BotModelsSection />;
}
export const meta: BunextPageModuleMetaFn = async (params) => {
const bot = (params.serverRes?.props as PagePropsType | undefined)?.bot;
return {
title: `${bot?.name || "Bot"} models | ${SiteData["SiteName"]}`,
description: `Which models ${bot?.name || "this bot"} uses.`,
};
};