diff --git a/AGENTS.md b/AGENTS.md index f7077c3..e0ea81e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,7 +153,7 @@ A secret URL, `POST /api/hooks/` (`src/pages/api/hooks/[token].ts`, publi ### Tasks (the to-do tracker) -`tasks` (a bot's own list is `bot_id` alone; a workspace's task has `workspace_id` plus the `bot_id` that has it there, null while shared; a bot reaches its own list and its own plus shared tasks in its workspaces, a workspace admin all of that workspace's, `botTaskScopesSql`; a shared task becomes the bot's when it starts it; removing or deleting a bot makes its workspace tasks shared again), `task_notes`, `task_media` (deliverable|reference). Not the same as `bot_tasks`/`bots_queue` (those record jobs). Visibility SQL in `src/utils/task-access/`. Helpers in `src/functions/backend/db/tasks/`; tools `list_tasks`/`create_task`/`update_task`; API `api/admin/tasks/` (`userAuth` -> `isReadOnlyUser` -> `assertUserReachesTask` -> `safeErrorMessage`). UI in the workspace dashboard and the header's Tasks modal (shared components in `src/components/general/`); a card's title opens `TaskDetailModal` with everything about the task and its status moves (a modal over the list, never swapped in place). **Hand-over:** `handOverTask` (bot: `delegate_task` with `task_id`; UI: Hand over, crew managers) moves an open task to the new bot (a workspace's stays in the workspace, now the new bot's, and is worked there), sets it doing and queues a job with `params.tracker_task_id`; `noteTrackerTask` notes the job's end on it and blocks it on failure. **Not built yet:** task steps, `server:task-changed` pushes, a `/admin/tasks` page. +`tasks` (**every task belongs to a bot**, the coordinator included, never shared: a bot's own list is `bot_id` alone, a workspace's task has `workspace_id` plus the `bot_id` that has it there, enforced by `taskScopeValues`; a worker reaches only its own, a workspace admin all of that workspace's, the coordinator all, `botTaskScopesSql`; a bot removed from a workspace or deleted passes its tasks there to the workspace's admin bot, else the coordinator (`passOnBotTasks`, also run at boot for rows without a bot)), `task_notes`, `task_media` (deliverable|reference). Not the same as `bot_tasks`/`bots_queue` (those record jobs). Visibility SQL in `src/utils/task-access/`. Helpers in `src/functions/backend/db/tasks/`; tools `list_tasks`/`create_task`/`update_task`; API `api/admin/tasks/` (`userAuth` -> `isReadOnlyUser` -> `assertUserReachesTask` -> `safeErrorMessage`). UI in the workspace dashboard and the header's Tasks modal (shared components in `src/components/general/`); a card's title opens `TaskDetailModal` with everything about the task and its status moves (a modal over the list, never swapped in place). **Hand-over:** `handOverTask` (bot: `delegate_task` with `task_id`; UI: Hand over, crew managers) moves an open task to the new bot (a workspace's stays in the workspace, now the new bot's, and is worked there), sets it doing and queues a job with `params.tracker_task_id`; `noteTrackerTask` notes the job's end on it and blocks it on failure. **Not built yet:** task steps, `server:task-changed` pushes, a `/admin/tasks` page. ### Envs diff --git a/src/components/general/task-detail-panel.tsx b/src/components/general/task-detail-panel.tsx index 5a88913..9c4fd33 100644 --- a/src/components/general/task-detail-panel.tsx +++ b/src/components/general/task-detail-panel.tsx @@ -231,7 +231,7 @@ export default function TaskDetailPanel({ task, can_manage, onChanged, onDeleted {task.bot_name || `Bot ${task.bot_id}`} ) : ( - Shared, nobody has taken it up yet + No bot )} diff --git a/src/components/general/task-form.tsx b/src/components/general/task-form.tsx index a6bb414..58a6352 100644 --- a/src/components/general/task-form.tsx +++ b/src/components/general/task-form.tsx @@ -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( + bots[0]?.id ? Number(bots[0].id) : undefined, + ); const [loading, setLoading] = useState(false); const [error, setError] = useState(); @@ -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 ? ( +