Scope workspace tasks to the bot that has them

A workspace's task now carries the bot that has it there (bot_id with
workspace_id), or none while it's shared. A bot reaches its own list and,
in its workspaces, its own tasks plus the shared ones; a workspace admin
every task there. Bots create workspace tasks for themselves by default,
take a shared one by starting it, and hand-overs record the new bot.
Removing or deleting a bot makes its workspace tasks shared again. The
conversation's Tasks modal shows that bot's tasks in the workspace.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-27 19:19:01 +01:00
co-authored by Claude Opus 5.5
parent 184407d76b
commit 864dcce30a
31 changed files with 316 additions and 153 deletions
@@ -6,19 +6,15 @@ type Params = {
};
/**
* A task belongs to exactly one collection: a bot's own list, or a
* workspace's shared one. Checked in the helpers rather than the schema,
* since SQLite treats NULL in a unique constraint as always distinct (the
* `envs` precedent).
* Where a task lives and who has it. A bot's own list is `bot_id` alone; a
* workspace's task has `workspace_id`, plus `bot_id` when a bot there has
* it (none: shared, not yet anyone's). Checked in the helpers rather than
* the schema.
*/
export default function taskScopeValues({ bot_id, workspace_id }: Params) {
const bot = bot_id ? Number(bot_id) : null;
const workspace = workspace_id ? Number(workspace_id) : null;
if (bot && workspace) {
throw new AppError(`A task belongs to a bot or a workspace, not both.`);
}
if (!bot && !workspace) {
throw new AppError(`Say which bot or workspace the task is for.`);
}