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
+66
View File
@@ -0,0 +1,66 @@
import { z } from "zod";
import type { BUN_SQLITE_COBBLE_COMPUTERS } from "@/db/types/db";
import enqueueBotJob from "@/src/functions/backend/db/bots-queue/enqueue-bot-job";
import type { HarnessTool } from "../../types";
type Params = {
user_id: number | string;
bot_id: number | string;
/**
* The bot's computers, its main one first (`grabBotComputers`).
*/
computers: BUN_SQLITE_COBBLE_COMPUTERS[];
/**
* The user message that asked for the work.
*/
message_id?: number | string;
};
type Args = {
title: string;
instructions: string;
computer_id?: number;
};
/**
* Lets a bot queue long-running work for itself. The task runs in the
* background with the computer's tools and posts its result to the chat.
*/
export default function startTaskTool({
user_id,
bot_id,
computers,
message_id,
}: Params): HarnessTool<Args> {
const listed = computers
.map((computer) => `${computer.id} (${computer.name || computer.kind})`)
.join(", ");
return {
name: "start_task",
description: `Queue work that takes a while or needs a computer. It runs in the background and its result is posted in this chat. Write the instructions as a full brief for yourself: what to do, where, and how you'll know it's done. ${
computers.length
? `Computers you can use: ${listed}. Defaults to ${computers[0]!.id}.`
: `You have no computer, so the task can't run commands or browse.`
}`,
schema: z.object({
title: z.string().min(1).max(80).describe("A short label, e.g. \"Check the backup logs\""),
instructions: z.string().min(1).max(8000),
computer_id: z.number().int().optional(),
}),
run: async ({ args }) => {
const computer_id = args.computer_id ?? computers[0]?.id ?? undefined;
const job_id = await enqueueBotJob({
user_id,
bot_id,
title: args.title,
instructions: args.instructions,
message_id,
computer_id: computer_id || undefined,
});
return `Task #${job_id} queued. Tell the user you're on it; the result will be posted here when it's done.`;
},
};
}