cancelSubagentWithoutJob function

Future<String> cancelSubagentWithoutJob({
  1. required String id,
  2. required SubagentManager? manager,
  3. TaskExecutor? executor,
  4. String source = 'task_cancel',
})

Cancels the subagent named id when its id names no live background TaskJob (issue #332) — the ONE helper both cancel surfaces (the task_cancel tool and /tasks cancel) route through, so their wording can never diverge. Three honest outcomes:

  • a child running inline on executor (a blocking-batch spawn or an in-flight resume — these have NO TaskJob by design) is aborted through its in-flight cancel source; its registry row settles through the normal run path. Tombstoning here would lie 'aborted' over a LIVE child and lock steering/task_send out until self-heal.
  • a registry row whose runner died with a previous host process (no live runner ANYWHERE) is tombstoned SubagentStatus.aborted — cancel must always be able to clear a 'running' row.
  • anything else reports honestly (terminal state / unknown id).

Implementation

Future<String> cancelSubagentWithoutJob({
  required String id,
  required SubagentManager? manager,
  TaskExecutor? executor,
  String source = 'task_cancel',
}) async {
  if (executor != null && executor.isInFlight(id)) {
    executor.cancelInFlight(id);
    return 'cancel requested for subagent $id — it is running inline '
        '(blocking batch or resume) with no background job; its registry '
        'row settles when the child stops';
  }
  final handle = manager?[id];
  if (handle == null) {
    return 'no background job with id "$id"';
  }
  if (handle.isTerminal) {
    return 'subagent $id already ${handle.status.name}';
  }
  await manager!.update(
    id,
    status: SubagentStatus.aborted,
    error:
        'cancelled by $source: no live runner '
        '(the host session restarted before this child settled)',
  );
  return 'tombstoned subagent $id as aborted — no live runner existed '
      '(interrupted before start), registry row cleared';
}