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
+35 -7
View File
@@ -1,14 +1,42 @@
import type { BotJobRunner } from "./types";
import grabBotById from "@/src/functions/backend/db/bots/grab-bot-by-id";
import HarnessError from "@/src/harness/harness-error";
import BotJobFatalError from "./bot-job-fatal-error";
import runBotReply from "./run-bot-reply";
import runBotTask from "./run-bot-task";
import type { BotJobRunner } from "./types";
/**
* Does a job's actual work: the bot follows `job.instructions` with its
* full prompt (`grabBotPrompt`), on `job.computer_id` if set, and returns
* the message for the user. This is where the model layer plugs in; until
* it exists every job fails at once and the user is told.
* Does a job's actual work on the harness: a `reply` answers the user in
* chat (`runBotReply`), a `task` works through its brief (`runBotTask`).
*
* Harness failures that waiting can fix (rate limits, outages) are thrown
* as they are, so the queue retries with backoff; the rest are fatal.
*/
const runBotJob: BotJobRunner = async () => {
throw new BotJobFatalError(`Bots can't run queued work yet.`);
const runBotJob: BotJobRunner = async (context) => {
const { job } = context;
const bot = job.bot_id ? await grabBotById({ bot_id: job.bot_id }) : null;
if (!bot?.id || !bot.user_id) {
throw new BotJobFatalError(`The bot for this job no longer exists.`);
}
const owned = { ...bot, id: bot.id, user_id: bot.user_id };
try {
return job.kind == "reply"
? await runBotReply({ ...context, bot: owned })
: await runBotTask({ ...context, bot: owned });
} catch (error: unknown) {
if (
error instanceof HarnessError &&
error.code != "aborted" &&
!error.retryable
) {
throw new BotJobFatalError(error.message);
}
throw error;
}
};
export default runBotJob;