CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
task-stop.ts264 linesDownload Raw Back to tools
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7/**8 * @fileoverview TaskStop tool — lets the model stop a background task.9 */10 11import type { Config } from '../config/config.js';12import { ToolErrorType } from './tool-error.js';13import { ToolNames, ToolDisplayNames } from './tool-names.js';14import {15  BaseDeclarativeTool,16  BaseToolInvocation,17  Kind,18  type ToolInvocation,19  type ToolResult,20} from './tools.js';21 22export interface TaskStopParams {23  /** The ID of the background task to stop. */24  task_id: string;25}26 27class TaskStopInvocation extends BaseToolInvocation<28  TaskStopParams,29  ToolResult30> {31  constructor(32    private readonly config: Config,33    params: TaskStopParams,34  ) {35    super(params);36  }37 38  getDescription(): string {39    return `Stop background task ${this.params.task_id}`;40  }41 42  async execute(_signal: AbortSignal): Promise<ToolResult> {43    const taskId = this.params.task_id;44 45    // Subagent registry first (Phase A control plane). Agent IDs follow the46    // pattern `<subagentName>-<suffix>`, so they cannot collide with shell47    // IDs (which are `bg_<8 hex chars>` from the background shell pool).48    const agentRegistry = this.config.getBackgroundTaskRegistry();49    const agentEntry = agentRegistry.get(taskId);50    if (agentEntry) {51      if (agentEntry.status === 'paused') {52        const abandoned = this.config.abandonBackgroundAgent(taskId);53        if (!abandoned) {54          return {55            llmContent:56              `Error: Background agent "${taskId}" could not be cancelled ` +57              `from paused state.`,58            returnDisplay: 'Task could not be cancelled.',59            error: {60              message: `Task could not be cancelled: ${taskId}`,61              type: ToolErrorType.TASK_STOP_NOT_RUNNING,62            },63          };64        }65 66        const desc = agentEntry.description;67        return {68          llmContent:69            `Cancelled paused background agent "${taskId}".\n` +70            `Description: ${desc}`,71          returnDisplay: `Cancelled: ${desc}`,72        };73      }74      if (agentEntry.status !== 'running') {75        return notRunningError('agent', taskId, agentEntry.status);76      }77      agentRegistry.cancel(taskId);78      // The terminal task-notification is emitted by the agent's own handler79      // (via registry.complete/fail) rather than cancel(), so the parent80      // model still receives the agent's real partial/final result — not just81      // a bare "cancelled" message — once the reasoning loop unwinds.82      const desc = agentEntry.description;83      return {84        llmContent:85          `Cancellation requested for background agent "${taskId}". ` +86          `A final task-notification carrying the agent's last result will ` +87          `follow.\nDescription: ${desc}`,88        returnDisplay: `Cancelled: ${desc}`,89      };90    }91 92    // Background shell registry (Phase B). Settles asynchronously when the93    // child process exits in response to the AbortController; the registry94    // entry's terminal state (`cancelled`) and final exit code/output stay95    // observable via `/tasks` (text), the interactive Background tasks96    // dialog (focus the footer Background tasks pill, then Enter), and97    // the on-disk output file.98    const shellRegistry = this.config.getBackgroundShellRegistry();99    const shellEntry = shellRegistry.get(taskId);100    if (shellEntry) {101      if (shellEntry.status !== 'running') {102        return notRunningError('shell', taskId, shellEntry.status);103      }104      // requestCancel triggers the AbortController only — the registry's105      // settle path records the real terminal status + endTime once the106      // process actually drains. Calling cancel(id, Date.now()) here would107      // mark the entry terminal immediately and lose the real exit info.108      shellRegistry.requestCancel(taskId);109      return {110        llmContent:111          `Cancellation requested for background shell "${taskId}". ` +112          `Final status will be visible via /tasks (text) or the interactive Background tasks dialog (focus the footer Background tasks pill, then Enter) once the process drains; ` +113          `captured output remains at ${shellEntry.outputPath}.\n` +114          `Command: ${shellEntry.command}`,115        returnDisplay: `Cancelled shell: ${shellEntry.command}`,116      };117    }118 119    const monitorRegistry = this.config.getMonitorRegistry();120    const monitorEntry = monitorRegistry.get(taskId);121    if (monitorEntry) {122      if (monitorEntry.status !== 'running') {123        return notRunningError('monitor', taskId, monitorEntry.status);124      }125      monitorRegistry.cancel(taskId);126      return {127        llmContent:128          // Unlike background shells (which settle asynchronously when the129          // child process exits), `monitorRegistry.cancel()` settles the130          // entry synchronously — the cancelled state is observable right131          // now, no drain phrasing.132          `Monitor "${taskId}" cancelled. ` +133          `Status is visible via /tasks (text) or the interactive Background tasks dialog (focus the footer Background tasks pill, then Enter).\n` +134          `Command: ${monitorEntry.command}`,135        returnDisplay: `Cancelled monitor: ${monitorEntry.description}`,136      };137    }138 139    // MemoryManager memory tasks (dream + extract). Memory tasks live140    // outside the registry trio (MemoryManager owns its own task map).141    // Only `dream` is cancellable — extract is short-lived and runs on142    // the request loop, so cancelling it would interfere with the143    // user's own turn. Surface a distinct error for known-but-not-144    // cancellable records so the model doesn't conclude the id was145    // never valid (which would happen if we fell through to NOT_FOUND).146    const memoryManager = this.config.getMemoryManager();147    const memoryRecord = memoryManager.getTask(taskId);148    if (memoryRecord) {149      if (memoryRecord.taskType !== 'dream') {150        return {151          llmContent:152            `Error: Memory task "${taskId}" (${memoryRecord.taskType}) is ` +153            `not cancellable. Only dream consolidation tasks support ` +154            `cancellation; extract tasks run on the request loop and ` +155            `complete in milliseconds.`,156          returnDisplay: `Task not cancellable (${memoryRecord.taskType}).`,157          error: {158            message: `task is not cancellable: ${taskId} (${memoryRecord.taskType})`,159            type: ToolErrorType.TASK_STOP_NOT_CANCELLABLE,160          },161        };162      }163      if (memoryRecord.status !== 'running') {164        return notRunningError('dream', taskId, memoryRecord.status);165      }166      // cancelTask returns false if the AbortController is missing for167      // a running record (logic-level invariant violation; see168      // MemoryManager.cancelTask). Surface that explicitly so the model169      // sees the cancel didn't take and doesn't claim success.170      const cancelled = memoryManager.cancelTask(taskId);171      if (!cancelled) {172        // Distinct from TASK_STOP_NOT_RUNNING (the task IS running)173        // and TASK_STOP_NOT_CANCELLABLE (the kind supports cancel,174        // we just couldn't deliver it). INTERNAL_ERROR signals that175        // this is unexpected and worth filing — the abort controller176        // should have been registered alongside status='running' in177        // scheduleDream.178        return {179          llmContent:180            `Error: Dream task "${taskId}" could not be cancelled ` +181            `(internal state inconsistency — abort controller missing).`,182          returnDisplay: 'Dream cancellation failed (internal state).',183          error: {184            message: `dream cancel failed: ${taskId}`,185            type: ToolErrorType.TASK_STOP_INTERNAL_ERROR,186          },187        };188      }189      return {190        llmContent:191          `Cancellation requested for dream task "${taskId}". ` +192          `The fork agent is being aborted; the consolidation lock will ` +193          `be released as the agent unwinds. Status is visible via the ` +194          `interactive Background tasks dialog (focus the footer Background ` +195          `tasks pill, then Enter).`,196        returnDisplay: `Cancelled dream: ${taskId}`,197      };198    }199 200    return {201      llmContent: `Error: No background task found with ID "${taskId}".`,202      returnDisplay: 'Task not found.',203      error: {204        message: `Task not found: ${taskId}`,205        type: ToolErrorType.TASK_STOP_NOT_FOUND,206      },207    };208  }209}210 211function notRunningError(212  kind: 'agent' | 'shell' | 'monitor' | 'dream',213  taskId: string,214  status: string,215): ToolResult {216  return {217    llmContent: `Error: Background ${kind} "${taskId}" is not running (status: ${status}).`,218    returnDisplay: `Task not running (${status}).`,219    error: {220      message: `${kind} is ${status}: ${taskId}`,221      type: ToolErrorType.TASK_STOP_NOT_RUNNING,222    },223  };224}225 226export class TaskStopTool extends BaseDeclarativeTool<227  TaskStopParams,228  ToolResult229> {230  static readonly Name = ToolNames.TASK_STOP;231 232  constructor(private readonly config: Config) {233    super(234      TaskStopTool.Name,235      ToolDisplayNames.TASK_STOP,236      'Stop a background task by its ID. Running agents and shells are cancelled; paused recovered agents are abandoned without resuming them.',237      Kind.Other,238      {239        type: 'object',240        properties: {241          task_id: {242            type: 'string',243            description:244              'The ID of the background task to stop (from the launch response or notification).',245          },246        },247        required: ['task_id'],248        additionalProperties: false,249      },250      true, // isOutputMarkdown251      false, // canUpdateOutput252      true, // shouldDefer — stopping tasks is infrequent253      false, // alwaysLoad254      'task stop cancel kill background',255    );256  }257 258  protected createInvocation(259    params: TaskStopParams,260  ): ToolInvocation<TaskStopParams, ToolResult> {261    return new TaskStopInvocation(this.config, params);262  }263}264 
basant307/AI_Governance_Project · CoolFace