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
+380
View File
@@ -13,8 +13,12 @@ import {
ContainerPurposes,
ContainerStatuses,
} from "@/src/dict/computer-dict";
import { Harnesses } from "@/src/dict/harness-dict";
import { HarnessProviderKinds } from "@/src/dict/harness-provider-dict";
import { BotJobKinds } from "@/src/dict/bot-queue-dict";
import { AppData } from "@/src/data/app-data";
import type { BUN_SQLITE_DatabaseSchemaType } from "@moduletrace/bun-sqlite/types";
import { EnvParadigms } from "@/src/dict/env-dict";
const schema: BUN_SQLITE_DatabaseSchemaType = {
tables: [
@@ -396,6 +400,13 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
backoff: AppData.BotQueueBackoffMs,
},
fields: [
{
fieldName: "kind",
fieldDescription: `task (long-running work, shown in the working-on panel) or reply (answers the user in chat)`,
dataType: "TEXT",
options: BotJobKinds.map((k) => k.value),
defaultValue: "task",
},
{
fieldName: "bot_id",
fieldDescription: `The bot doing the work`,
@@ -572,6 +583,50 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
],
},
{
tableName: "bot_message_media",
tableDescription: `Files attached to a chat message (\`media\` rows, private to the message's owner)`,
fields: [
{
fieldName: "message_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "bot_messages",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_message_media_message_fk",
},
},
{
fieldName: "media_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "media",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_message_media_media_fk",
},
},
],
uniqueConstraints: [
{
alias: "one_link_per_message_media",
constraintName: "A file is attached to a message once",
constraintTableFields: [
{ value: "message_id" },
{ value: "media_id" },
],
},
],
indexes: [
{
indexName: "idx_bot_message_media_message_id",
indexTableFields: ["message_id"],
},
],
},
{
tableName: "containers",
fields: [
@@ -773,6 +828,260 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
],
},
{
tableName: "harness_providers",
fields: [
{
fieldName: "kind",
fieldDescription: `Which provider preset this is`,
dataType: "TEXT",
options: HarnessProviderKinds.map((hpk) => hpk.value),
},
{
fieldName: "name",
dataType: "TEXT",
},
{
fieldName: "base_url",
fieldDescription: `API base URL, e.g. https://openrouter.ai/api/v1`,
dataType: "TEXT",
},
{
fieldName: "api_key",
fieldDescription: `API key, encrypted with ENCRYPTION_KEY. Never sent to the client`,
dataType: "TEXT",
},
{
fieldName: "enabled",
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "last_synced_at",
fieldDescription: `Unix timestamp of the last model list sync`,
dataType: "INTEGER",
},
{
fieldName: "sync_error",
fieldDescription: `Why the last model list sync failed, if it did`,
dataType: "TEXT",
},
],
},
{
tableName: "harness_models",
fields: [
{
fieldName: "provider_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "harness_providers",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "harness_model_provider_fk",
},
},
{
fieldName: "slug",
fieldDescription: `The provider's model id, e.g. deepseek/deepseek-v4.1-flash:free`,
dataType: "TEXT",
},
{
fieldName: "name",
dataType: "TEXT",
},
{
fieldName: "free",
dataType: "INTEGER",
options: [0, 1],
defaultValue: 0,
},
{
fieldName: "available",
fieldDescription: `0 once the provider stops listing it`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "input_modalities",
fieldDescription: `JSON array, e.g. ["text","image"]`,
dataType: "TEXT",
json: true,
},
{
fieldName: "output_modalities",
fieldDescription: `JSON array, e.g. ["text"], ["speech"], ["image"]`,
dataType: "TEXT",
json: true,
},
{
fieldName: "supports_tools",
dataType: "INTEGER",
options: [0, 1],
defaultValue: 0,
},
{
fieldName: "context_length",
dataType: "INTEGER",
},
{
fieldName: "max_output_tokens",
dataType: "INTEGER",
},
{
fieldName: "voices",
fieldDescription: `JSON array of voice names, for speech models`,
dataType: "TEXT",
json: true,
},
{
fieldName: "intelligence_score",
fieldDescription: `Artificial Analysis intelligence index`,
dataType: "REAL",
},
{
fieldName: "agentic_score",
fieldDescription: `Artificial Analysis agentic index`,
dataType: "REAL",
},
{
fieldName: "released_at",
dataType: "INTEGER",
},
{
fieldName: "expires_at",
fieldDescription: `When the provider retires the model`,
dataType: "INTEGER",
},
{
fieldName: "last_seen_at",
fieldDescription: `Unix timestamp of the last sync that listed it`,
dataType: "INTEGER",
},
],
uniqueConstraints: [
{
alias: "one_model_slug_per_provider",
constraintName: "A provider lists a model once",
constraintTableFields: [
{ value: "provider_id" },
{ value: "slug" },
],
},
],
indexes: [
{
indexName: "idx_harness_models_free_available",
indexTableFields: ["free", "available"],
},
],
},
{
tableName: "harness_settings",
fields: [
{
fieldName: "harness",
dataType: "TEXT",
options: Harnesses.map((h) => h.value),
unique: true,
},
{
fieldName: "model_id",
fieldDescription: `harness_models row picked by a super admin. Null picks automatically. Not a foreign key, so removing a provider leaves the setting`,
dataType: "INTEGER",
},
{
fieldName: "provider_id",
fieldDescription: `harness_providers row this harness is limited to (its pick and automatic picks). Null: any provider. Not a foreign key; removing the provider clears it`,
dataType: "INTEGER",
},
{
fieldName: "fallback",
fieldDescription: `When the picked model fails, try the automatic picks`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "enabled",
fieldDescription: `0 turns the harness off. Text is always on`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "options",
fieldDescription: `Harness options, e.g. {"voice":"george"} for speech`,
dataType: "TEXT",
json: true,
},
],
},
{
tableName: "bot_harness_settings",
fields: [
{
fieldName: "bot_id",
fieldDescription: `The worker bot that overrides the default. The coordinator always uses the defaults`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "bots",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_harness_settings_bot_fk",
},
},
{
fieldName: "harness",
dataType: "TEXT",
options: Harnesses.map((h) => h.value),
},
{
fieldName: "model_id",
fieldDescription: `harness_models row picked for this bot. Null picks automatically. Not a foreign key, like harness_settings`,
dataType: "INTEGER",
},
{
fieldName: "provider_id",
fieldDescription: `harness_providers row this bot's harness is limited to. Null: any provider. Not a foreign key, like harness_settings`,
dataType: "INTEGER",
},
{
fieldName: "fallback",
fieldDescription: `When the picked model fails, try the automatic picks`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "enabled",
fieldDescription: `0 turns the harness off for this bot. Text is always on`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "options",
fieldDescription: `Harness options, e.g. {"voice":"george"} for speech`,
dataType: "TEXT",
json: true,
},
],
uniqueConstraints: [
{
alias: "one_setting_per_bot_harness",
constraintName: "A bot overrides a harness once",
constraintTableFields: [
{ value: "bot_id" },
{ value: "harness" },
],
},
],
},
{
tableName: "logs",
fields: [
@@ -836,6 +1145,77 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
],
},
{
tableName: "envs",
fields: [
{
fieldName: "key",
fieldDescription: `Variable name. One per key and scope (enforced in saveEnv: SQLite unique constraints treat NULL ids as distinct)`,
dataType: "TEXT",
},
{
fieldName: "value",
fieldDescription: `Encrypted env value.`,
dataType: "TEXT",
},
{
fieldName: "description",
dataType: "TEXT",
fieldDescription: `Env description. Can be searched. Bots see it beside the key, never the value`,
},
{
fieldName: "paradigm",
fieldDescription: `Where it applies: general, a workspace, or a computer. Computer beats workspace beats general`,
dataType: "TEXT",
options: EnvParadigms.map((p) => p.value),
defaultValue: "general",
},
{
fieldName: "workspace_id",
fieldDescription: `The workspace, for paradigm workspace`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "workspaces",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "envs_workspace_fk",
},
},
{
fieldName: "computer_id",
fieldDescription: `The computer, for paradigm computer`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "computers",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "envs_computer_fk",
},
},
{
fieldName: "secret",
fieldDescription: `1 hides the value once saved (only replaced, never shown) and redacts it from everything bots read`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
{
fieldName: "expose_to_computers",
fieldDescription: `1 puts it in the shell of every computer it applies to. App settings (EnvDict) never are`,
dataType: "INTEGER",
options: [0, 1],
defaultValue: 1,
},
],
indexes: [
{
indexName: "idx_envs_paradigm_key",
indexTableFields: ["paradigm", "key"],
},
],
},
],
};
+248 -3
View File
@@ -12,11 +12,17 @@ export const BunSQLiteTables = [
"bots_queue",
"workspaces",
"workspace_bots",
"bot_message_media",
"containers",
"computers",
"bot_computers",
"workspace_computers",
"harness_providers",
"harness_models",
"harness_settings",
"bot_harness_settings",
"logs",
"envs",
] as const
export type BUN_SQLITE_COBBLE_USERS = {
@@ -108,7 +114,7 @@ export type BUN_SQLITE_COBBLE_MEDIA = {
title?: string;
description?: string;
summary?: string;
media_type?: "image" | "video" | "document" | "";
media_type?: "image" | "video" | "document" | "audio" | "";
media_write_path?: string;
media_thumbnail_write_path?: string;
mime_type?: string;
@@ -133,7 +139,7 @@ export type BUN_SQLITE_COBBLE_MEDIA_PARADIGMS = {
*/
updated_at?: number | "";
media_id?: number | "";
media_paradigm?: "user-profile-image" | "generic" | "event" | "";
media_paradigm?: "user-profile-image" | "generic" | "event" | "bot-attachment" | "";
}
export type BUN_SQLITE_COBBLE_BOTS = {
@@ -265,6 +271,10 @@ export type BUN_SQLITE_COBBLE_BOTS_QUEUE = {
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* task (long-running work, shown in the working-on panel) or reply (answers the user in chat)
*/
kind?: "task" | "reply" | "";
/**
* The bot doing the work
*/
@@ -382,6 +392,23 @@ export type BUN_SQLITE_COBBLE_WORKSPACE_BOTS = {
bot_id?: number | "";
}
export type BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
message_id?: number | "";
media_id?: number | "";
}
export type BUN_SQLITE_COBBLE_CONTAINERS = {
/**
* The unique identifier of the record.
@@ -500,6 +527,177 @@ export type BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS = {
computer_id?: number | "";
}
export type BUN_SQLITE_COBBLE_HARNESS_PROVIDERS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* Which provider preset this is
*/
kind?: "openrouter" | "openai" | "anthropic" | "google" | "deepseek" | "groq" | "mistral" | "xai" | "ollama" | "custom" | "";
name?: string;
/**
* API base URL, e.g. https://openrouter.ai/api/v1
*/
base_url?: string;
/**
* API key, encrypted with ENCRYPTION_KEY. Never sent to the client
*/
api_key?: string;
enabled?: 0 | 1 | "";
/**
* Unix timestamp of the last model list sync
*/
last_synced_at?: number | "";
/**
* Why the last model list sync failed, if it did
*/
sync_error?: string;
}
export type BUN_SQLITE_COBBLE_HARNESS_MODELS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
provider_id?: number | "";
/**
* The provider's model id, e.g. deepseek/deepseek-v4.1-flash:free
*/
slug?: string;
name?: string;
free?: 0 | 1 | "";
/**
* 0 once the provider stops listing it
*/
available?: 0 | 1 | "";
/**
* JSON array, e.g. ["text","image"]
*/
input_modalities?: string;
/**
* JSON array, e.g. ["text"], ["speech"], ["image"]
*/
output_modalities?: string;
supports_tools?: 0 | 1 | "";
context_length?: number | "";
max_output_tokens?: number | "";
/**
* JSON array of voice names, for speech models
*/
voices?: string;
/**
* Artificial Analysis intelligence index
*/
intelligence_score?: number | "";
/**
* Artificial Analysis agentic index
*/
agentic_score?: number | "";
released_at?: number | "";
/**
* When the provider retires the model
*/
expires_at?: number | "";
/**
* Unix timestamp of the last sync that listed it
*/
last_seen_at?: number | "";
}
export type BUN_SQLITE_COBBLE_HARNESS_SETTINGS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
harness?: "text" | "vision" | "transcription" | "speech" | "image" | "";
/**
* harness_models row picked by a super admin. Null picks automatically. Not a foreign key, so removing a provider leaves the setting
*/
model_id?: number | "";
/**
* harness_providers row this harness is limited to (its pick and automatic picks). Null: any provider. Not a foreign key; removing the provider clears it
*/
provider_id?: number | "";
/**
* When the picked model fails, try the automatic picks
*/
fallback?: 0 | 1 | "";
/**
* 0 turns the harness off. Text is always on
*/
enabled?: 0 | 1 | "";
/**
* Harness options, e.g. {"voice":"george"} for speech
*/
options?: string;
}
export type BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* The worker bot that overrides the default. The coordinator always uses the defaults
*/
bot_id?: number | "";
harness?: "text" | "vision" | "transcription" | "speech" | "image" | "";
/**
* harness_models row picked for this bot. Null picks automatically. Not a foreign key, like harness_settings
*/
model_id?: number | "";
/**
* harness_providers row this bot's harness is limited to. Null: any provider. Not a foreign key, like harness_settings
*/
provider_id?: number | "";
/**
* When the picked model fails, try the automatic picks
*/
fallback?: 0 | 1 | "";
/**
* 0 turns the harness off for this bot. Text is always on
*/
enabled?: 0 | 1 | "";
/**
* Harness options, e.g. {"voice":"george"} for speech
*/
options?: string;
}
export type BUN_SQLITE_COBBLE_LOGS = {
/**
* The unique identifier of the record.
@@ -547,4 +745,51 @@ export type BUN_SQLITE_COBBLE_LOGS = {
ip_address?: string;
}
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_LOGS
export type BUN_SQLITE_COBBLE_ENVS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* Variable name. One per key and scope (enforced in saveEnv: SQLite unique constraints treat NULL ids as distinct)
*/
key?: string;
/**
* Encrypted env value.
*/
value?: string;
/**
* Env description. Can be searched. Bots see it beside the key, never the value
*/
description?: string;
/**
* Where it applies: general, a workspace, or a computer. Computer beats workspace beats general
*/
paradigm?: "general" | "workspace" | "computer" | "";
/**
* The workspace, for paradigm workspace
*/
workspace_id?: number | "";
/**
* The computer, for paradigm computer
*/
computer_id?: number | "";
/**
* 1 hides the value once saved (only replaced, never shown) and redacts it from everything bots read
*/
secret?: 0 | 1 | "";
/**
* 1 puts it in the shell of every computer it applies to. App settings (EnvDict) never are
*/
expose_to_computers?: 0 | 1 | "";
}
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_HARNESS_PROVIDERS & BUN_SQLITE_COBBLE_HARNESS_MODELS & BUN_SQLITE_COBBLE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_LOGS & BUN_SQLITE_COBBLE_ENVS