Add bot types with a permission matrix, and workspace admin bots

A bot's powers came from bots.kind alone, so only the coordinator could
hand work to other bots. bot_types now works like user_types: a row with
no workspace holds everywhere (the coordinator), one with a workspace
only there (a workspace admin), and no row is a worker. bots.kind stays
the legacy marker of the one coordinator for who sees it.

The matrices in src/dict/bot-types-dict.ts say what each type may do,
whom it may direct and who grants it, read by one generic interpreter
(rbac/type-assignment.ts) that userTypeAssignment now wraps too. Every
run reads its powers once (grabBotRunPowers) and the chat tools, task
runs and task tools gate on them instead of kind; a run's powers are its
type's and its user's together.

In the workspace it leads, an admin hands tasks to the workers there
(results relayed back to it), sets their instructions, adds tasks to
their lists and keeps the workspace's context when its user may. Super
admins and admins grant it from the workspace's settings or the chat
header's menu, and the coordinator with set_bot_type. Admins are marked
with a filled green shield beside their name across the workspace, kept
live by pushBotUpdated's admin_workspace_ids.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 09:04:14 +01:00
co-authored by Claude Opus 5.5
parent 4161863587
commit ea62dd11f0
65 changed files with 1970 additions and 239 deletions
+40
View File
@@ -18,6 +18,7 @@ import type {
BunSQLiteTables,
} from "@/db/types/db";
import type { UserTypes } from "../dict/user-types-dict";
import type { BotPermissions, BotTypes } from "../dict/bot-types-dict";
import type { StorageVolume } from "../utils/storage-volumes";
import type { RunningVersion } from "../utils/running-version";
import type { WebsocketRooms } from "../dict/websocket-rooms";
@@ -72,6 +73,11 @@ export type BotWithFace = BUN_SQLITE_COBBLE_BOTS & {
* unread, latest activity and latest message, for the rail.
*/
export type BotWithLastMessage = BotWithFace & {
/**
* In a workspace's crew: the bot is that workspace's admin
* (`bot_types`), shown with `AdminShield` beside its name.
*/
is_admin?: boolean | null;
unread?: number | null;
last_activity_at?: number | null;
last_message_text?: string | null;
@@ -198,6 +204,10 @@ export type WorkspaceBot = BotWithLastMessage & {
export type BotWorkspace = WorkspaceWithActivity & {
workspace_system_prompt?: string | null;
workspace_memory?: string | null;
/**
* The bot is this workspace's admin (`bot_types`).
*/
is_admin?: boolean;
};
/**
@@ -307,6 +317,11 @@ export type BotUpdatedData = {
* it has none.
*/
image_media_id?: number | null;
/**
* The workspaces it's admin of, when the push read them: a workspace's
* crew shows or drops its shield without a reload.
*/
admin_workspace_ids?: number[];
};
/**
@@ -1676,6 +1691,31 @@ export type UserTypeAssignmentMatrixType = Record<
UserTypeAssignmentEntry
>;
/**
* The type-matrix shapes, for any set of types (`BotTypeAssignmentMatrix`,
* `BotTypeTargetMatrix`). Actors may be user types or bot types.
*/
export type TypeAssignmentRules<A extends string = string> = {
all?: boolean;
same_as?: A;
allowed_targets?: string[];
excluded_targets?: string[];
};
export type TypeAssignmentEntry<A extends string = string> = {
allowed_actors?: A[] | Partial<Record<A, TypeAssignmentRules<A>>>;
excluded_targets?: string[];
};
export type TypeAssignmentMatrixType<
T extends string,
A extends string = string,
> = Record<T, TypeAssignmentEntry<A>>;
export type BotType = (typeof BotTypes)[number]["value"];
export type BotPermission = (typeof BotPermissions)[number]["value"];
export type LoginFormObject = {
username_or_email?: string;
password?: string;