Share one crew across the install, keep user memory per user
Bots, workspaces and computers now belong to the install. Users reach workers only through bot_users (assigned by a super admin or admin) and workspace_users; the coordinator and main computer are one per install, and only the super admin sees the coordinator. Conversations are per user (bot_sessions.user_id), jobs run for the session's user, and live updates go only to who should see them. A bot's core memory is now general knowledge shared by every user it works for; what it learns about a user lives in bot_user_memories. One-off scripts move existing installs over: move-bot-memory-to-owners (run first), then share-crew. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -7,6 +7,7 @@ import { CpuAi } from "lucide-react";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import type { BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/types/db";
|
||||
import type { BotWithLastMessage, HarnessOverview } from "@/src/types";
|
||||
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
|
||||
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
|
||||
import BotHarnessForm from "./bot-harness-form";
|
||||
import HeaderIconButton from "./header-icon-button";
|
||||
@@ -24,8 +25,9 @@ type Props = {
|
||||
/**
|
||||
* The header's model icon: opens every model the bot uses (text, media
|
||||
* reading, transcription, speech, images) in the Models settings form. On
|
||||
* the coordinator that form is the defaults, for super admins only;
|
||||
* everyone else gets the icon disabled, its title saying why.
|
||||
* the coordinator that form is the defaults, for super admins only; a
|
||||
* worker's (or a workspace's) models are for super admins and admins.
|
||||
* Everyone else gets the icon disabled, its title saying why.
|
||||
*/
|
||||
export default function BotModelsModal({ bot, workspace, harnesses, onSaved }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
@@ -35,9 +37,9 @@ export default function BotModelsModal({ bot, workspace, harnesses, onSaved }: P
|
||||
|
||||
const in_workspace = Boolean(workspace?.id);
|
||||
const is_coordinator = bot.kind == "coordinator" && !in_workspace;
|
||||
const can_edit =
|
||||
!is_coordinator ||
|
||||
isPassThroughUser({ user_types: pageProps?.user_types });
|
||||
const can_edit = is_coordinator
|
||||
? isPassThroughUser({ user_types: pageProps?.user_types })
|
||||
: canManageCrew({ user_types: pageProps?.user_types });
|
||||
const name = bot.name || "This bot";
|
||||
|
||||
const target = (
|
||||
@@ -50,7 +52,9 @@ export default function BotModelsModal({ bot, workspace, harnesses, onSaved }: P
|
||||
: is_coordinator
|
||||
? "Change the default models"
|
||||
: `Change ${name}'s models`
|
||||
: `${name} uses the default models. A super admin can change them in Settings, under Models.`
|
||||
: is_coordinator
|
||||
? `${name} uses the default models. A super admin can change them in Settings, under Models.`
|
||||
: `An admin can change the models ${name} uses.`
|
||||
}
|
||||
disabled={!can_edit}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import Modal from "@moduletrace/twui/elements/Modal";
|
||||
import Button from "@moduletrace/twui/layout/Button";
|
||||
import H2 from "@moduletrace/twui/layout/H2";
|
||||
@@ -8,6 +8,8 @@ import BotAvatar from "@/src/components/general/bot-avatar";
|
||||
import type { BotFace, BotHue } from "@/src/dict/bot-dict";
|
||||
import type { BotWithLastMessage } from "@/src/types";
|
||||
import BotForm from "@/src/layouts/bots/(partials)/bot-form";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
|
||||
|
||||
type Props = {
|
||||
bot: BotWithLastMessage;
|
||||
@@ -15,12 +17,27 @@ type Props = {
|
||||
|
||||
/**
|
||||
* The header's avatar: opens the bot's profile (picture, name, job,
|
||||
* colour, instructions) to edit.
|
||||
* colour, instructions) to edit, for super admins and admins. Everyone
|
||||
* else sees the avatar alone.
|
||||
*/
|
||||
export default function BotProfileModal({ bot }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const name = bot.name || "This bot";
|
||||
|
||||
const avatar = (
|
||||
<BotAvatar
|
||||
image_media_id={bot.image_media_id}
|
||||
hue={(bot.hue || null) as BotHue | null}
|
||||
face={(bot.face || "idle") as BotFace}
|
||||
size={40}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!canManageCrew({ user_types: pageProps?.user_types })) {
|
||||
return <Stack className="shrink-0">{avatar}</Stack>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
target={
|
||||
@@ -32,12 +49,7 @@ export default function BotProfileModal({ bot }: Props) {
|
||||
aria-label={`Edit ${name}`}
|
||||
className="shrink-0 p-0! rounded-default hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ink focus-visible:ring-offset-2 focus-visible:ring-offset-paper"
|
||||
>
|
||||
<BotAvatar
|
||||
image_media_id={bot.image_media_id}
|
||||
hue={(bot.hue || null) as BotHue | null}
|
||||
face={(bot.face || "idle") as BotFace}
|
||||
size={40}
|
||||
/>
|
||||
{avatar}
|
||||
</Button>
|
||||
}
|
||||
targetWrapperProps={{ className: "shrink-0" }}
|
||||
|
||||
@@ -12,18 +12,29 @@ type Props = {
|
||||
* The latest step of the bot's active task.
|
||||
*/
|
||||
progress?: string | null;
|
||||
/**
|
||||
* The thread's last message is the user's, so a reply is due here.
|
||||
*/
|
||||
awaiting_reply?: boolean;
|
||||
/**
|
||||
* This session has an active task.
|
||||
*/
|
||||
has_task?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Under the last message while the bot is working on a reply or a task
|
||||
* (or waiting for a model to come back), so slow work doesn't look like
|
||||
* none. A task shows its latest step, else its title.
|
||||
* none. A task shows its latest step. The bot's face is shared by
|
||||
* everyone who sees it, so the row only shows for work in this
|
||||
* conversation: a reply while the user has the last word, or its own
|
||||
* task.
|
||||
*/
|
||||
export default function BotThinkingRow({ bot, progress }: Props) {
|
||||
export default function BotThinkingRow({ bot, progress, awaiting_reply, has_task }: Props) {
|
||||
const name = bot.name || "The bot";
|
||||
const thinking = bot.face == "working" && bot.status_line == BotReplyJobTitle;
|
||||
const working = bot.face == "working" && !thinking;
|
||||
const waiting = bot.face == "asleep" && bot.status_line == BotWaitingStatusLine;
|
||||
const thinking = bot.face == "working" && bot.status_line == BotReplyJobTitle && Boolean(awaiting_reply);
|
||||
const working = bot.face == "working" && bot.status_line != BotReplyJobTitle && Boolean(has_task);
|
||||
const waiting = bot.face == "asleep" && bot.status_line == BotWaitingStatusLine && Boolean(awaiting_reply || has_task);
|
||||
|
||||
if (!thinking && !working && !waiting) return null;
|
||||
|
||||
@@ -32,7 +43,7 @@ export default function BotThinkingRow({ bot, progress }: Props) {
|
||||
: working
|
||||
? `${name} is working…`
|
||||
: `${BotWaitingStatusLine}…`;
|
||||
const detail = working ? progress || bot.status_line : null;
|
||||
const detail = working ? progress : null;
|
||||
|
||||
return (
|
||||
<Row role="status" className="w-full gap-3! flex-nowrap items-center">
|
||||
|
||||
@@ -14,6 +14,10 @@ type Props = {
|
||||
* The latest step of the bot's active task, shown while it works.
|
||||
*/
|
||||
progress?: string | null;
|
||||
/**
|
||||
* This session has an active task.
|
||||
*/
|
||||
has_task?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -22,7 +26,7 @@ type Props = {
|
||||
* While the bot works on a reply or a task, a status row sits under the
|
||||
* last one.
|
||||
*/
|
||||
export default function MessageThread({ bot, messages, progress }: Props) {
|
||||
export default function MessageThread({ bot, messages, progress, has_task }: Props) {
|
||||
const groups = groupMessages({ messages });
|
||||
|
||||
return (
|
||||
@@ -40,7 +44,12 @@ export default function MessageThread({ bot, messages, progress }: Props) {
|
||||
group={group}
|
||||
/>
|
||||
))}
|
||||
<BotThinkingRow bot={bot} progress={progress} />
|
||||
<BotThinkingRow
|
||||
bot={bot}
|
||||
progress={progress}
|
||||
awaiting_reply={messages[messages.length - 1]?.author == "user"}
|
||||
has_task={has_task}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user