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
+78
View File
@@ -0,0 +1,78 @@
import { z } from "zod";
import { Harnesses, type HarnessKind } from "@/src/dict/harness-dict";
import {
HarnessProviderKinds,
type HarnessProviderKind,
} from "@/src/dict/harness-provider-dict";
const ProviderIdSchema = z.coerce
.number()
.int()
.positive("Pick a provider");
export const SaveHarnessProviderRequestSchema = z.object({
provider_id: ProviderIdSchema.optional(),
kind: z.enum(
HarnessProviderKinds.map((kind) => kind.value) as [
HarnessProviderKind,
...HarnessProviderKind[],
],
"Pick a provider type",
),
name: z.string().trim().max(80, "Keep the name under 80 characters").optional(),
base_url: z.string().trim().max(500, "That address is too long").optional(),
api_key: z.string().trim().max(1000, "That key is too long").optional(),
clear_key: z.boolean().optional(),
enabled: z.boolean(),
});
export const HarnessProviderIdRequestSchema = z.object({
provider_id: ProviderIdSchema,
});
export const SaveHarnessSettingsRequestSchema = z.object({
settings: z
.array(
z.object({
harness: z.enum(
Harnesses.map((harness) => harness.value) as [
HarnessKind,
...HarnessKind[],
],
),
model_id: z.coerce.number().int().positive().nullable(),
provider_id: z.coerce.number().int().positive().nullable().optional(),
fallback: z.boolean(),
enabled: z.boolean(),
voice: z.string().trim().max(200).optional(),
}),
)
.min(1, "Nothing to save")
.max(Harnesses.length),
});
/**
* A worker bot's own model settings. `inherit` drops the bot's setting
* for that harness so it uses the default again.
*/
export const SaveBotHarnessSettingsRequestSchema = z.object({
bot_id: z.coerce.number().int().positive(),
settings: z
.array(
SaveHarnessSettingsRequestSchema.shape.settings.element.extend({
inherit: z.boolean().default(false),
}),
)
.min(1, "Nothing to save")
.max(Harnesses.length),
});
export type SaveHarnessProviderRequest = z.infer<
typeof SaveHarnessProviderRequestSchema
>;
export type SaveHarnessSettingsRequest = z.infer<
typeof SaveHarnessSettingsRequestSchema
>;
export type SaveBotHarnessSettingsRequest = z.infer<
typeof SaveBotHarnessSettingsRequestSchema
>;