Give every task a bot: drop shared workspace tasks

A workspace task with no bot was seen by every bot working there. Now a
task can't be saved without a bot, a worker reaches only its own (the
workspace admin and the coordinator still reach all), the workspace page
picks who has a new task, and a bot leaving a workspace or deleted passes
its tasks there to the workspace's admin bot, else the coordinator (also
at boot for rows without a bot).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 06:26:44 +01:00
co-authored by Claude Opus 5.5
parent 3e78d51b56
commit 623112fd19
30 changed files with 239 additions and 106 deletions
+1 -1
View File
@@ -231,7 +231,7 @@ export default function TaskDetailPanel({ task, can_manage, onChanged, onDeleted
<Span className="min-w-0 truncate">{task.bot_name || `Bot ${task.bot_id}`}</Span>
</>
) : (
<Span className="text-ink-secondary">Shared, nobody has taken it up yet</Span>
<Span className="text-ink-secondary">No bot</Span>
)}
</TaskFactRow>
<TaskFactRow label="Where">
+29 -1
View File
@@ -18,6 +18,11 @@ type Props = {
*/
bot_id?: number | null;
workspace_id?: number | null;
/**
* Adding to a workspace without a `bot_id`: the bots there to pick who
* has it, since every task belongs to a bot.
*/
bots?: { id?: number | string | null; name?: string | null }[];
/**
* Edits this task; adds one when not given.
*/
@@ -34,6 +39,7 @@ type Props = {
export default function TaskForm({
bot_id,
workspace_id,
bots = [],
task,
onSaved,
onCancel,
@@ -46,6 +52,10 @@ export default function TaskForm({
const [blocked_reason, setBlockedReason] = useState(
task?.blocked_reason || "",
);
const pick_bot = !task && !bot_id && Boolean(workspace_id);
const [picked_bot_id, setPickedBotId] = useState<number | undefined>(
bots[0]?.id ? Number(bots[0].id) : undefined,
);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
@@ -57,6 +67,11 @@ export default function TaskForm({
return;
}
if (pick_bot && !picked_bot_id) {
setError("Pick the bot that has it.");
return;
}
setLoading(true);
setError(undefined);
@@ -67,7 +82,7 @@ export default function TaskForm({
method: "POST",
body: {
task_id: task?.id ? Number(task.id) : undefined,
bot_id: task ? undefined : bot_id || undefined,
bot_id: task ? undefined : bot_id || picked_bot_id || undefined,
workspace_id: task ? undefined : workspace_id || undefined,
title,
description,
@@ -100,6 +115,19 @@ export default function TaskForm({
maxLength={AppData.TaskTitleMaxLength}
disabled={loading}
/>
{pick_bot ? (
<Select
title="Who has it"
label="Who has it"
showLabel
options={bots.map((bot) => ({
title: bot.name || `Bot ${bot.id}`,
value: String(bot.id),
default: Number(bot.id) == picked_bot_id,
}))}
changeHandler={(value) => setPickedBotId(Number(value) || undefined)}
/>
) : null}
<CodeEditor
mode="markdown"
className="h-[500px]"