Class: Rubino::Tools::TaskStopTool

Inherits:
Base
  • Object
show all
Defined in:
lib/rubino/tools/task_stop_tool.rb

Overview

Cancels a running background subagent started by ‘task`. The KillShell analogue: flips the child Runner’s CancelToken (the exact mechanism Run::Executor’s stop-watcher uses for top-level runs), which unwinds the child loop cooperatively at its next cancel checkpoint.

Constant Summary collapse

STOPPABLE =

The live statuses a stop applies to. A child parked on a human approval or an ask_parent gate still holds its thread + concurrency slot (BackgroundTasks#live_status?), so it MUST be stoppable — refusing left a blocked child as a zombie holding its slot until the ask-gate timeout (#197). :stopping is excluded: a second stop is honestly “already stopping — nothing to stop”.

%i[running needs_approval blocked_on_human blocked_on_parent].freeze

Instance Attribute Summary

Attributes inherited from Base

#cancel_token, #read_tracker, #stream_chunk, #stream_kind

Instance Method Summary collapse

Methods inherited from Base

#cancellation_requested?, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots

Instance Method Details

#call(arguments) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rubino/tools/task_stop_tool.rb', line 49

def call(arguments)
  task_id = (arguments["task_id"] || arguments[:task_id]).to_s.strip
  return "Error: task_id is required" if task_id.empty?

  registry = BackgroundTasks.instance
  entry    = registry.find(task_id)
  return "Error: no background subagent with task_id=#{task_id}" unless entry

  return "[#{task_id}] already #{entry.status} — nothing to stop." unless STOPPABLE.include?(entry.status)

  # The shared per-entry stop body (the SAME one the human /agents <id>
  # --stop path and the parent-teardown #cancel_all use): mark the stop so
  # the list/cards show ◌ stopping and the unwind records as :stopped, not
  # failed (#108/#13); cancel the child's OWN approval/ask gates so a parked
  # wait wakes (Interrupted → deny/cancel) and unwinds NOW instead of holding
  # its thread + slot until the bound elapses (#197); run the stop-cascade so
  # any descendant parked on a blocking ask_parent unwinds too (S5a — no
  # orphaned blocked grandchild); and flip the runner's CancelToken so a
  # child between checkpoints observes it at its next one.
  registry.stop_entry(entry)
  "[#{task_id}] stop requested (subagent '#{entry.subagent}'). " \
    "It will unwind at its next checkpoint; check task_result(\"#{task_id}\")."
end

#config_keyObject



14
15
16
# File 'lib/rubino/tools/task_stop_tool.rb', line 14

def config_key
  "task"
end

#descriptionObject



26
27
28
29
30
# File 'lib/rubino/tools/task_stop_tool.rb', line 26

def description
  "Stop a running background subagent started by `task` — including one " \
    "parked on an approval or an ask_parent question. Cancels the " \
    "subagent's nested run; its task_result will then report failed/cancelled."
end

#input_schemaObject



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubino/tools/task_stop_tool.rb', line 32

def input_schema
  {
    type: "object",
    properties: {
      task_id: {
        type: "string",
        description: "The task id (sa_…) returned by `task`."
      }
    },
    required: %w[task_id]
  }
end

#nameObject



10
11
12
# File 'lib/rubino/tools/task_stop_tool.rb', line 10

def name
  "task_stop"
end

#risk_levelObject



45
46
47
# File 'lib/rubino/tools/task_stop_tool.rb', line 45

def risk_level
  :medium
end