CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
loop-wakeup.ts181 linesDownload Raw Back to tools
1/**2 * @license3 * Copyright 2026 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import type { ToolInvocation, ToolResult } from './tools.js';8import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';9import { ToolDisplayNames, ToolNames } from './tool-names.js';10import type { Config } from '../config/config.js';11import type { PermissionDecision } from '../permissions/types.js';12import {13  WAKEUP_MAX_SECONDS,14  WAKEUP_MIN_SECONDS,15  clampWakeupSeconds,16} from '../services/cronScheduler.js';17import { getErrorMessage } from '../utils/errors.js';18 19export interface LoopWakeupParams {20  delaySeconds: number;21  prompt: string;22  reason?: string;23}24 25function formatRequested(delaySeconds: number): string {26  return Number.isFinite(delaySeconds) ? `${delaySeconds}s` : `${delaySeconds}`;27}28 29class LoopWakeupInvocation extends BaseToolInvocation<30  LoopWakeupParams,31  ToolResult32> {33  constructor(34    private readonly config: Config,35    params: LoopWakeupParams,36  ) {37    super(params);38  }39 40  getDescription(): string {41    const clamped = clampWakeupSeconds(this.params.delaySeconds);42    const roundedDelaySeconds = Number.isFinite(this.params.delaySeconds)43      ? Math.round(this.params.delaySeconds)44      : this.params.delaySeconds;45    const prefix =46      clamped === roundedDelaySeconds47        ? `${clamped}s`48        : `${clamped}s (requested ${formatRequested(this.params.delaySeconds)})`;49    return `${prefix}: ${this.params.prompt}`;50  }51 52  /**53   * Scheduling future model input is side-effectful: the continuation runs54   * against the agent with full tool access at fire time. Returning 'ask'55   * (never 'allow') keeps it out of AUTO mode's L4 short-circuit so the56   * classifier still vets it — same reasoning as CronCreate.57   */58  override async getDefaultPermission(): Promise<PermissionDecision> {59    return 'ask';60  }61 62  async execute(): Promise<ToolResult> {63    const prompt = this.params.prompt.trim();64    if (!prompt) {65      const message = 'Loop wakeup prompt must not be empty.';66      return {67        llmContent: message,68        returnDisplay: message,69        error: { message },70      };71    }72 73    try {74      const scheduler = this.config.getCronScheduler();75      if (scheduler.disabled) {76        const message =77          'Loop wakeups are disabled for the rest of this session ' +78          '(token limit reached). Restart the session to re-enable.';79        return {80          llmContent: message,81          returnDisplay: message,82          error: { message },83        };84      }85      const { id, scheduledFor, clampedDelaySeconds, wasClamped, replacedId } =86        scheduler.scheduleWakeup(this.params.delaySeconds, prompt);87      const reason = this.params.reason?.trim();88 89      const llmContent = [90        `Scheduled loop wakeup ${id}.`,91        replacedId ? `Replaced pending wakeup ${replacedId}.` : null,92        `Scheduled for: ${scheduledFor} (in ${clampedDelaySeconds}s).`,93        wasClamped94          ? `Requested ${formatRequested(this.params.delaySeconds)} was clamped to the [${WAKEUP_MIN_SECONDS}, ${WAKEUP_MAX_SECONDS}] s range.`95          : null,96        reason ? `Reason: ${reason}.` : null,97        'Session-only one-shot; not persisted. Call LoopWakeup again before ' +98          'ending the turn to keep the loop alive; omit it to end the loop.',99      ]100        .filter(Boolean)101        .join('\n');102      const returnDisplay = `Loop wakeup ${id} scheduled for ${scheduledFor}${103        reason ? ` — ${reason}` : ''104      }`;105 106      return { llmContent, returnDisplay };107    } catch (error) {108      const message = getErrorMessage(error);109      return {110        llmContent: `Error scheduling loop wakeup: ${message}`,111        returnDisplay: message,112        error: { message },113      };114    }115  }116}117 118export class LoopWakeupTool extends BaseDeclarativeTool<119  LoopWakeupParams,120  ToolResult121> {122  static readonly Name = ToolNames.LOOP_WAKEUP;123 124  constructor(private readonly config: Config) {125    super(126      LoopWakeupTool.Name,127      ToolDisplayNames.LOOP_WAKEUP,128      'Schedule when to resume work in a self-paced loop iteration (always pass the `prompt` arg). Call this before ending the turn to keep the loop alive; omit the call to end the loop. Session-only and one-shot — it does not persist or recur. A self-paced wakeup chain may run for at most 24h. When a background task you started will wake you on its own — a backgrounded agent or a Monitor sends a terminal `<task-notification>` on exit, failure, cancellation, or monitor auto-stop — keep this wakeup as a long fallback heartbeat rather than a poll; see `delaySeconds`.',129      Kind.Other,130      {131        type: 'object',132        properties: {133          delaySeconds: {134            type: 'number',135            description: `Seconds from now to wake up. Clamped to [${WAKEUP_MIN_SECONDS}, ${WAKEUP_MAX_SECONDS}]. Use 60-270s only when actively polling external state that nothing else reports (a CI run, a remote queue) — staying inside the ~5-min prompt-cache window. When a background task you started will wake you via a \`<task-notification>\` once it finishes, that is the real wake signal — use 1200-1800s here as a fallback for when it never arrives (the task hangs, a Monitor auto-stops on idle or max-events, or another agent owns it). With no specific signal to watch, default to 1200s+.`,136          },137          prompt: {138            type: 'string',139            maxLength: 10000,140            description:141              'Continuation prompt to enqueue when the wakeup fires. Prefix with `/loop` so the next firing re-invokes the loop skill, e.g. `/loop check the deploy`.',142          },143          reason: {144            type: 'string',145            description:146              'One short sentence explaining the chosen delay. Shown to the user. Be specific.',147          },148        },149        required: ['delaySeconds', 'prompt'],150        additionalProperties: false,151      },152      true, // isOutputMarkdown153      false, // canUpdateOutput154      true, // shouldDefer — scheduling is infrequent155      false, // alwaysLoad156      'loop wakeup continuation follow-up self-pace',157    );158  }159 160  protected createInvocation(161    params: LoopWakeupParams,162  ): ToolInvocation<LoopWakeupParams, ToolResult> {163    return new LoopWakeupInvocation(this.config, params);164  }165 166  /**167   * Forward the continuation prompt and cadence to the AUTO classifier —168   * it is enqueued and executed against the agent at fire time, so it169   * needs the same scrutiny as a direct command (mirrors CronCreate).170   */171  override toAutoClassifierInput(172    params: LoopWakeupParams,173  ): Record<string, unknown> {174    return {175      delaySeconds: clampWakeupSeconds(params.delaySeconds),176      prompt: params.prompt,177      reason: params.reason ?? '',178    };179  }180}181 
basant307/AI_Governance_Project · CoolFace