* Claims a task with an atomic check for agent busy status. * Uses a task-list-level lock to ensure the busy check and claim are atomic.
( taskListId: string, taskId: string, claimantAgentId: string, )
| 616 | * Uses a task-list-level lock to ensure the busy check and claim are atomic. |
| 617 | */ |
| 618 | async function claimTaskWithBusyCheck( |
| 619 | taskListId: string, |
| 620 | taskId: string, |
| 621 | claimantAgentId: string, |
| 622 | ): Promise<ClaimTaskResult> { |
| 623 | const lockPath = await ensureTaskListLockFile(taskListId) |
| 624 | |
| 625 | let release: (() => Promise<void>) | undefined |
| 626 | try { |
| 627 | // Acquire exclusive lock on the task list |
| 628 | release = await lockfile.lock(lockPath, LOCK_OPTIONS) |
| 629 | |
| 630 | // Read all tasks to check agent status and task state atomically |
| 631 | const allTasks = await listTasks(taskListId) |
| 632 | |
| 633 | // Find the task we want to claim |
| 634 | const task = allTasks.find(t => t.id === taskId) |
| 635 | if (!task) { |
| 636 | return { success: false, reason: 'task_not_found' } |
| 637 | } |
| 638 | |
| 639 | // Check if already claimed by another agent |
| 640 | if (task.owner && task.owner !== claimantAgentId) { |
| 641 | return { success: false, reason: 'already_claimed', task } |
| 642 | } |
| 643 | |
| 644 | // Check if already resolved |
| 645 | if (task.status === 'completed') { |
| 646 | return { success: false, reason: 'already_resolved', task } |
| 647 | } |
| 648 | |
| 649 | // Check for unresolved blockers (open or in_progress tasks block) |
| 650 | const unresolvedTaskIds = new Set( |
| 651 | allTasks.filter(t => t.status !== 'completed').map(t => t.id), |
| 652 | ) |
| 653 | const blockedByTasks = task.blockedBy.filter(id => |
| 654 | unresolvedTaskIds.has(id), |
| 655 | ) |
| 656 | if (blockedByTasks.length > 0) { |
| 657 | return { success: false, reason: 'blocked', task, blockedByTasks } |
| 658 | } |
| 659 | |
| 660 | // Check if agent is busy with other unresolved tasks |
| 661 | const agentOpenTasks = allTasks.filter( |
| 662 | t => |
| 663 | t.status !== 'completed' && |
| 664 | t.owner === claimantAgentId && |
| 665 | t.id !== taskId, |
| 666 | ) |
| 667 | if (agentOpenTasks.length > 0) { |
| 668 | return { |
| 669 | success: false, |
| 670 | reason: 'agent_busy', |
| 671 | task, |
| 672 | busyWithTasks: agentOpenTasks.map(t => t.id), |
| 673 | } |
| 674 | } |
| 675 |
no test coverage detected