Let crew managers edit a bot's workspace instructions where they work with it

A bot's instructions and memory for one workspace were only reachable
from the workspace's settings. The editor is now shared and also opens
from the conversation menu in that workspace ("Instructions here",
loaded on open through api/admin/workspaces/workspace-bot) and from the
workspace rows on the bot's settings page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 13:17:32 +01:00
co-authored by Claude Opus 5.5
parent 84f8c0cac8
commit 2d85177c59
14 changed files with 431 additions and 87 deletions
@@ -7,16 +7,22 @@ import SettingsRowAction from "../../../(partials)/settings-row-action";
type Props = {
workspace: BotWorkspace;
busy?: boolean;
/**
* Opens what the bot has for this workspace alone.
*/
onEdit?: () => void;
onRemove?: () => void;
};
/**
* One workspace the bot works in. Its title opens the workspace's
* settings; a worker can be taken out from here.
* settings; its instructions and memory there are edited from here, and a
* worker can be taken out.
*/
export default function BotWorkspaceRow({
workspace,
busy,
onEdit,
onRemove,
}: Props) {
const name = workspace.name || `Workspace ${workspace.id}`;
@@ -41,6 +47,14 @@ export default function BotWorkspaceRow({
: workspace.description || "Uses the workspace's context"
}
>
{onEdit ? (
<SettingsRowAction
title={`Instructions and memory in ${name}`}
label="Instructions"
disabled={busy}
onClick={onEdit}
/>
) : null}
{onRemove ? (
<SettingsRowAction
title={`Take ${name} out of this workspace`}
@@ -1,6 +1,7 @@
import { useState } from "react";
import type { BotWorkspace, BotWithLastMessage, WorkspaceWithActivity } from "@/src/types";
import type { BotWorkspace, BotWithLastMessage, WorkspaceBot, WorkspaceWithActivity } from "@/src/types";
import InlineAddSelect from "@/src/components/general/inline-add-select";
import WorkspaceBotModal from "@/src/components/general/workspace-bot-modal";
import postAdminApi from "@/src/layouts/bots/(functions)/post-admin-api";
import SettingsGroup from "../../../(partials)/settings-group";
import SettingsList from "../../../(partials)/settings-list";
@@ -33,6 +34,7 @@ export default function BotWorkspacesGroup({
const [bot_workspaces, setBotWorkspaces] = useState(initial);
const [busy_id, setBusyId] = useState<number | "add">();
const [status, setStatus] = useState<Status>();
const [editing, setEditing] = useState<BotWorkspace>();
const is_coordinator = bot.kind == "coordinator";
const addable = workspaces.filter(
@@ -86,6 +88,27 @@ export default function BotWorkspacesGroup({
setStatus({ success: true, msg: res.msg || "Saved." });
};
/**
* The save answers with the workspace's crew: this bot's row there
* carries what it has for the workspace now.
*/
const saved = ({ workspace_id, bots }: { workspace_id: number; bots: WorkspaceBot[] }) => {
const member = bots.find((item) => item.id == bot.id);
setBotWorkspaces((prev) =>
prev.map((workspace) =>
Number(workspace.id) == workspace_id
? {
...workspace,
workspace_system_prompt: member?.workspace_system_prompt || null,
workspace_memory: member?.workspace_memory || null,
}
: workspace,
),
);
setStatus({ success: true, msg: "Saved." });
};
return (
<SettingsGroup
id="workspaces"
@@ -109,6 +132,7 @@ export default function BotWorkspacesGroup({
key={workspace.id}
workspace={workspace}
busy={busy_id == workspace.id}
onEdit={() => setEditing(workspace)}
onRemove={
is_coordinator
? undefined
@@ -141,6 +165,18 @@ export default function BotWorkspacesGroup({
/>
)}
<SettingsStatus status={status} />
{editing ? (
<WorkspaceBotModal
workspace_id={Number(editing.id)}
workspace_name={editing.name}
bot_id={Number(bot.id)}
bot_name={bot.name}
system_prompt={editing.workspace_system_prompt}
memory={editing.workspace_memory}
onClose={() => setEditing(undefined)}
onSaved={(bots) => saved({ workspace_id: Number(editing.id), bots })}
/>
) : null}
</SettingsGroup>
);
}
@@ -1,80 +0,0 @@
import { useState } from "react";
import Form from "@moduletrace/twui/form/Form";
import CodeEditor from "@/src/components/general/code-editor";
import type { WorkspaceBot } from "@/src/types";
import postAdminApi from "@/src/layouts/bots/(functions)/post-admin-api";
import AddFormActions from "@/src/layouts/bots/(partials)/add-form-actions";
import AddModalShell from "@/src/layouts/bots/(partials)/add-modal-shell";
type Props = {
workspace_id: number;
workspace_name: string;
bot: WorkspaceBot;
onClose: () => void;
onSaved: (bots: WorkspaceBot[]) => void;
};
/**
* What one bot has for this workspace alone: instructions on top of its
* own, and its memory here. The bot rewrites both as it learns; the
* coordinator can set the instructions too.
*/
export default function WorkspaceBotModal({ workspace_id, workspace_name, bot, onClose, onSaved }: Props) {
const [system_prompt, setSystemPrompt] = useState(bot.workspace_system_prompt || "");
const [memory, setMemory] = useState(bot.workspace_memory || "");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const name = bot.name || "This bot";
const submit = async () => {
if (loading) return;
if (!window.confirm(`Save ${bot.name || "this bot"}'s instructions and memory here?`)) return;
setLoading(true);
setError(undefined);
const res = await postAdminApi<WorkspaceBot>({
path: "workspaces/update-bot",
body: { workspace_id, bot_id: bot.id, system_prompt, memory },
});
setLoading(false);
if (!res.success) {
setError(res.msg || "Couldn't save.");
return;
}
onSaved(res.payload || []);
onClose();
};
return (
<AddModalShell
open
setOpen={(open) => !open && onClose()}
title={`${name} in ${workspace_name}`}
description={`${name} keeps its own instructions and memory everywhere. These apply only here, and it updates them itself as it learns how this workspace works.`}
>
<Form className="w-full gap-4! items-stretch pt-4" onSubmit={submit}>
<CodeEditor
mode="markdown"
className="h-44"
title="Instructions here"
content={system_prompt}
placeholder="In this workspace, write for the marketing team: short, plain, no jargon."
onChange={(value) => setSystemPrompt(value)}
/>
<CodeEditor
mode="markdown"
className="h-32"
title="Memory here"
content={memory}
info="What it must remember in this workspace."
onChange={(value) => setMemory(value)}
/>
<AddFormActions submit_label="Save" loading={loading} error={error} onCancel={onClose} />
</Form>
</AddModalShell>
);
}
@@ -9,7 +9,7 @@ import postAdminApi from "@/src/layouts/bots/(functions)/post-admin-api";
import SettingsGroup from "@/src/pages/admin/settings/(partials)/settings-group";
import SettingsStatus from "@/src/pages/admin/settings/(partials)/settings-status";
import InlineAddSelect from "@/src/components/general/inline-add-select";
import WorkspaceBotModal from "./workspace-bot-modal";
import WorkspaceBotModal from "@/src/components/general/workspace-bot-modal";
import WorkspaceBotRow from "./workspace-bot-row";
type Props = {
@@ -148,7 +148,10 @@ export default function WorkspaceBotsGroup({ workspace_id, workspace_name, bots:
<WorkspaceBotModal
workspace_id={workspace_id}
workspace_name={workspace_name}
bot={editing}
bot_id={Number(editing.id)}
bot_name={editing.name}
system_prompt={editing.workspace_system_prompt}
memory={editing.workspace_memory}
onClose={() => setEditing(undefined)}
onSaved={setBots}
/>