Class: Collavre::TasksController

Inherits:
ApplicationController show all
Defined in:
app/controllers/collavre/tasks_controller.rb

Instance Method Summary collapse

Instance Method Details

#cancelObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'app/controllers/collavre/tasks_controller.rb', line 5

def cancel
  task = Task.find(params[:id])
  creative = task.creative || Creative.find_by(id: task.trigger_event_payload&.dig("creative", "id"))

  unless creative && creative.has_permission?(Current.user, :feedback)
    return head :forbidden
  end

  unless %w[running pending queued pending_approval delegated].include?(task.status)
    return head :unprocessable_entity
  end

  # These statuses count against the topic slot (occupying_topic_slot) yet no
  # live worker will run AiAgentJob's ensure-block drain for them:
  #   - delegated / pending_approval already returned from the job holding the
  #     slot (should_release = false) — awaiting an MCP reply / approval.
  #   - pending may be a waiter that dequeue_next_for_topic promoted
  #     queued -> pending before its job starts; once cancelled, that job
  #     early-returns at the top of #perform and never reaches the ensure drain.
  # So free the agent slot and drain the topic queue here — otherwise
  # cancelling the blocker leaves agent capacity and the next waiter stuck
  # until stuck recovery. release!/dequeue are idempotent (dequeue is bounded
  # by topic_at_capacity?), so a racing live worker that also drains is harmless.
  held_slot_without_worker = %w[pending delegated pending_approval].include?(task.status)
  task.update!(status: "cancelled")

  if held_slot_without_worker && task.agent
    Collavre::Orchestration::ResourceTracker.for(task.agent).release!(task.id)
    Collavre::Orchestration::AgentOrchestrator.dequeue_next_for_topic(task.topic_id, task.creative_id)
  end

  abort_agent_session(task)

  head :ok
end