Let the coordinator set a workspace's picture
CI / check (push) Canceled after 0s

update_workspace now takes an image media id (or null to remove the
picture). updateWorkspace writes image_media_id through
resolveWorkspacePicture, which only accepts the caller's own image and
deletes the picture it replaced, so a bot can never show someone else's
file or drop a picture it is still using.
This commit is contained in:
2026-10-22 21:10:06 +01:00
parent 34667581b6
commit a5eb2b6a95
6 changed files with 221 additions and 10 deletions
+40 -7
View File
@@ -22,39 +22,72 @@ type Args = {
name?: string;
description?: string;
context?: string;
/**
* A picture for the workspace: the media id the user uploaded, or null
* to remove it.
*/
picture_media_id?: number | null;
};
/**
* Lets the coordinator change a workspace's record: its name, its
* one-line description and the context every bot working there gets.
* one-line description and the context every bot working there gets, and
* its picture.
*/
export default function updateWorkspaceTool({ user_id, workspaces, workspace_id }: Params): HarnessTool<Args> {
export default function updateWorkspaceTool({
user_id,
workspaces,
workspace_id,
}: Params): HarnessTool<Args> {
const list = workspaces.map((w) => `${w.id}: ${w.name}`).join("; ");
return {
name: "update_workspace",
description: `Change a workspace: its name, its one-line description, or its context (markdown every bot working there gets in its prompt). Only the fields you give change; \`context\` replaces the whole text, so keep what still applies. Use it when the user asks, or to record something every bot in that workspace must know.${workspace_id ? " Leave out workspace_id for this workspace." : ""} Workspaces: ${list}.`,
description: `Change a workspace: its name, its one-line description, its context (markdown every bot working there gets in its prompt), or its picture. Only the fields you give change; \`context\` replaces the whole text, so keep what still applies. Use it when the user asks, or to record something every bot in that workspace must know.${workspace_id ? " Leave out workspace_id for this workspace." : ""} Workspaces: ${list}.`,
schema: z.object({
workspace_id: z.number().int().optional(),
name: z.string().trim().min(1).max(60).optional(),
description: z.string().trim().max(240).optional(),
context: z.string().trim().max(AppData.PromptMaxLength).optional(),
picture_media_id: z
.number()
.int()
.positive()
.nullable()
.optional()
.describe(
`An image the user uploaded, by media id; null removes the picture. Leave it out to keep the one it has.`,
),
}),
run: async ({ args }) => {
const target = workspaces.find((w) => w.id == (args.workspace_id || workspace_id));
if (!target?.id) {
throw new Error(`No workspace has id ${args.workspace_id ?? "(none given)"}. Workspaces: ${list}.`);
throw new Error(
`No workspace has id ${args.workspace_id ?? "(none given)"}. Workspaces: ${list}.`,
);
}
if (args.name === undefined && args.description === undefined && args.context === undefined) {
throw new Error(`Give a name, a description or a context to change.`);
if (
args.name === undefined &&
args.description === undefined &&
args.context === undefined &&
args.picture_media_id === undefined
) {
throw new Error(
`Give a name, a description, a context or a picture to change.`,
);
}
await updateWorkspace({
user_id,
workspace_id: target.id,
data: { name: args.name, description: args.description, system_prompt: args.context },
data: {
name: args.name,
description: args.description,
system_prompt: args.context,
image_media_id: args.picture_media_id,
},
});
return `${args.name || target.name} is saved. Bots there get the change from their next run.`;