任务打断机制

v1.0.1

管理和中断长时间运行任务,支持用户命令终止、资源清理和任务状态保存,防止任务卡死或无限执行。

0· 201·0 current·0 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for guyxlouspg/task-interrupt.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "任务打断机制" (guyxlouspg/task-interrupt) from ClawHub.
Skill page: https://clawhub.ai/guyxlouspg/task-interrupt
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Bare skill slug

openclaw skills install task-interrupt

ClawHub CLI

Package manager switcher

npx clawhub@latest install task-interrupt
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description match the provided artifacts: scripts create/check/clear a /tmp stop-flag, SKILL.md and instructions.md describe polling, checkpointing and optional SIGINT/process.kill behavior. The declared filesystem permission in claw.json fits the functionality.
Instruction Scope
Runtime instructions stay within the task-interrupt domain (write/read/remove flag files, poll, save checkpoints, send SIGINT). Note: docs/examples call process.kill (SIGINT) and exec the create script — expected for this purpose but these actions require correct mapping of sessionId→PID to avoid accidental kills.
Install Mechanism
No install spec; this is instruction-only plus three small shell scripts. Nothing is downloaded or written by an installer, reducing installation risk.
Credentials
The skill requests no environment variables or external credentials. Files and paths referenced are confined to /tmp/agent-stop-{sessionId}.flag, which is consistent with the stated design.
Persistence & Privilege
always is false and the skill does not request persistent or system-wide configuration changes. It needs filesystem access (declared) but does not try to modify other skills or agent configs.
Assessment
This skill appears coherent and implements a simple flag-file + polling interrupt pattern. Before installing, verify: (1) how sessionId is mapped to processId in your environment — ensure only the intended child process can be signalled; (2) file permissions and /tmp isolation — flag files created in /tmp are world-visible by default, consider using a per-agent temp directory or restrictive permissions; (3) that checkpoint/save logic is implemented in your long-running tasks so interruptions are safe; (4) scripts are placed where the agent expects and marked executable; (5) test in a staging environment to confirm no accidental PID collisions or privilege escalations. If you need stronger guarantees, consider authenticated/IPC-based signaling instead of plain /tmp files and process.kill.

Like a lobster shell, security has layers — review code before you run it.

agent-controlvk97b3xrh2ww2xhe5414qrxf9jd83609xinterruptvk97b3xrh2ww2xhe5414qrxf9jd83609xlatestvk97b3xrh2ww2xhe5414qrxf9jd83609xtask-managementvk97b3xrh2ww2xhe5414qrxf9jd83609x
201downloads
0stars
2versions
Updated 1mo ago
v1.0.1
MIT-0

Task Interrupt Skill - 任务打断机制

概述

任务打断机制是一个用于控制和管理OpenClaw Agent任务执行的技能。它解决了Agent执行任务时卡住、运行时间过长或用户想中途终止的问题。

问题场景

  • Agent执行长时间任务时用户无法中断
  • 任务卡住或陷入死循环时无停止机制
  • 用户需要中途放弃当前任务
  • 需要优雅的资源清理和状态保存

核心架构

架构图

┌─────────────────────────────────────────────────────────────────────────┐
│                              飞书群聊                                    │
│                   用户发送: /stop  或  "中断"                            │
└────────────────────────────────┬────────────────────────────────────────┘
                                 │ Webhook/消息事件
                                 ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                        主 Agent (maojingli)                             │
│  ┌──────────────┐    ┌──────────────────┐    ┌───────────────────────┐ │
│  │  消息监听器  │───▶│  中断指令识别器   │───▶│  Subagent 管理器      │ │
│  │              │    │  /stop, 中断      │    │  sessions_*           │ │
│  │              │    │  cancel, abort    │    │                       │ │
│  └──────────────┘    └──────────────────┘    └───────────┬───────────┘ │
│                                                            │             │
│                          ┌─────────────────────────────────┘             │
│                          │                                                │
│                          ▼                                                │
│              ┌───────────────────────┐                                   │
│              │  中断信号发射器        │                                   │
│              │  1. 写入中断标志文件  │                                   │
│              │  2. process.kill()    │                                   │
│              └───────────────────────┘                                   │
└────────────────────────────────┬────────────────────────────────────────┘
                                 │ 中断信号 (信号量/标志文件)
                                 ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    Subagent (maoxiami 等)                               │
│  ┌──────────────┐    ┌──────────────────┐    ┌───────────────────────┐ │
│  │ 心跳/轮询器  │───▶│  中断检测器       │───▶│  任务执行器 (可中断)   │ │
│  │ (每5秒检查)  │    │  检查标志文件    │    │  exec/tool calls      │ │
│  └──────────────┘    └──────────────────┘    └───────────┬───────────┘ │
│        ▲                                               │               │
│        │                                               ▼               │
│        │                                    ┌───────────────────────┐   │
│        │                                    │  安全停止处理器        │   │
│        │                                    │  1. 保存检查点        │   │
│        │                                    │  2. 清理临时文件      │   │
│        │                                    │  3. 释放资源          │   │
│        └────────────────────────────────────┴───────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘

组件说明

1. 中断指令识别器(主Agent)

识别来自用户的打断指令:

  • /stop/中断/cancel/abort
  • 停止中断等关键词

2. 中断信号发射器(主Agent)

将中断信号传递给运行中的subagent:

  • 写入中断标志文件到 /tmp/agent-stop-{sessionId}.flag
  • 可选:发送 SIGINT 信号到子进程
  • 调用 sessions_kill 作为最终手段

3. 中断检测器(Subagent)

轮询检查中断标志:

  • 默认每5秒检查一次
  • 发现标志后触发安全停止流程
  • 自动清理过期标志(60秒)

4. 安全停止处理器(Subagent)

优雅停止并清理资源:

  • 保存当前检查点状态
  • 清理临时文件和连接
  • 取消定时任务
  • 清除中断标志
  • 通知主Agent

使用说明

1. 在主Agent中集成中断功能

在主Agent的消息处理流程中添加中断指令识别和发射逻辑。参考设计文档中的代码示例。

2. 在Subagent中启用中断检测

Subagent需要在启动时调用 startInterruptPolling(),并在任务关键点保存检查点 saveCheckpoint()

基本用法:

const interruptible = new InterruptibleAgent(sessionId);

// 启动中断轮询
await interruptible.startInterruptPolling();

try {
  // 保存检查点
  await interruptible.saveCheckpoint('task_start', { step: 1 });

  // 执行任务...
  await doLongRunningTask();

  // 保存检查点
  await interruptible.saveCheckpoint('task_complete', { success: true });

} catch (error) {
  if (error.message === 'INTERRUPTED') {
    // 处理被中断的情况
    console.log('任务被用户中断');
  }
} finally {
  // 停止轮询
  await interruptible.stopInterruptPolling();
}

3. 命令行工具

技能提供了三个Shell脚本用于手动测试和管理:

create-stop-flag.sh - 创建停止标志

./scripts/create-stop-flag.sh <sessionId> [reason]

示例:

./scripts/create-stop-flag.sh abc123 "User requested stop"

check-stop-flag.sh - 检查停止标志

./scripts/check-stop-flag.sh <sessionId> [maxAgeSeconds]

返回码:

  • 0: 标志存在且在有效期内
  • 1: 标志不存在或已过期

示例:

if ./scripts/check-stop-flag.sh abc123 60; then
  echo "检测到中断请求"
fi

clear-stop-flag.sh - 清除停止标志

./scripts/clear-stop-flag.sh <sessionId>

示例:

./scripts/clear-stop-flag.sh abc123

API 参考

InterruptibleAgent 类

属性

属性类型描述
sessionIdstring当前会话ID
interruptCheckIntervalnumber轮询间隔(毫秒,默认5000)
isInterruptedboolean是否已中断
checkpointsArray已保存的检查点列表

方法

startInterruptPolling()

启动中断检测轮询。

await agent.startInterruptPolling();
stopInterruptPolling()

停止中断检测轮询。

await agent.stopInterruptPolling();
checkForInterrupt()

手动检查中断标志(通常自动调用)。

const flag = await agent.checkForInterrupt();
// 返回 null 或 { sessionId, reason, timestamp, signal }
saveCheckpoint(name, data)

保存任务检查点。

await agent.saveCheckpoint('step1', { processed: 100, total: 200 });
getLastCheckpoint()

获取最后一个检查点。

const checkpoint = agent.getLastCheckpoint();
handleInterrupt(flag)

处理中断(内部调用)。

await agent.handleInterrupt(flag);

SafeStopHandler 类

提供安全停止和资源清理功能。

方法

stop(reason)

执行完整的安全停止流程。

const handler = new SafeStopHandler(agent);
const result = await handler.stop('user_stop');
// 返回 { success: boolean, checkpoint?: object, reason?: string, error?: string }
saveState()

保存Agent状态到 /tmp/agent-state/{sessionId}.json

const statePath = await handler.saveState();
cleanupTempFiles()

清理临时文件模式:

  • /tmp/agent-work/{sessionId}/*
  • /tmp/agent-downloads/{sessionId}/*
await handler.cleanupTempFiles();
closeConnections()

关闭数据库连接和文件句柄。

await handler.closeConnections();
cancelScheduledTasks()

取消所有定时任务。

await handler.cancelScheduledTasks();

文件结构

task-interrupt/
├── SKILL.md                 # 本文档
├── instructions.md          # Agent指令说明
├── claw.json                # 技能元数据
└── scripts/
    ├── create-stop-flag.sh  # 创建停止标志
    ├── check-stop-flag.sh   # 检查停止标志
    └── clear-stop-flag.sh   # 清除停止标志

文件路径规范

中断标志文件:/tmp/agent-stop-{sessionId}.flag

状态保存文件:/tmp/agent-state/{sessionId}.json

标志文件格式:

{
  "sessionId": "abc123",
  "timestamp": 1742345678901,
  "reason": "user_request",
  "signal": "SIGINT"
}

配置选项

可通过环境变量调整行为:

变量默认值说明
INTERRUPT_CHECK_INTERVAL5000轮询间隔(毫秒)
INTERRUPT_FLAG_DIR/tmp/agent-stop标志文件目录
INTERRUPT_FLAG_MAX_AGE60标志最大有效期(秒)
AGENT_STATE_DIR/tmp/agent-state状态保存目录

安全考虑

  • 中断标志文件应具有适当权限(0600)
  • 只允许删除自己session的标志文件
  • 状态文件可能包含敏感数据,应定期清理
  • 清理资源时应确保连接正确关闭

故障排除

中断没有生效

  1. 检查Subagent是否已启动轮询:startInterruptPolling()
  2. 确认标志文件路径是否正确:/tmp/agent-stop-{sessionId}.flag
  3. 检查轮询间隔是否过短或过长

中断后资源未释放

  1. 检查 safeStopHandler.stop() 是否完整执行
  2. 确认数据库连接和文件句柄是否全部关闭
  3. 查看临时文件是否已清理

标志文件未自动清除

  1. 检查文件权限
  2. 确认 clear-stop-flag.sh 是否执行成功
  3. 标志文件60秒后自动过期

示例:完整集成

// interruptible-agent.js
const path = require('path');
const { exec } = require('child_process');

class InterruptibleAgent {
  constructor(sessionId, options = {}) {
    this.sessionId = sessionId;
    this.interruptCheckInterval = options.interval || 5000;
    this.isInterrupted = false;
    this.checkpoints = [];
    this.pollingTimer = null;
  }

  startInterruptPolling() {
    this.pollingTimer = setInterval(async () => {
      await this.checkForInterrupt();
    }, this.interruptCheckInterval);
  }

  stopInterruptPolling() {
    if (this.pollingTimer) {
      clearInterval(this.pollingTimer);
      this.pollingTimer = null;
    }
  }

  async checkForInterrupt() {
    try {
      const flagPath = `/tmp/agent-stop-${this.sessionId}.flag`;
      if (await fs.pathExists(flagPath)) {
        const content = await fs.readFile(flagPath, 'utf8');
        const flag = JSON.parse(content);

        console.log(`[中断] 检测到中断信号: ${flag.reason}`);
        this.isInterrupted = true;
        await this.handleInterrupt(flag);
      }
    } catch (error) {
      console.error(`[中断] 检查失败: ${error.message}`);
    }
  }

  saveCheckpoint(name, data) {
    const checkpoint = {
      name,
      data,
      timestamp: Date.now()
    };
    this.checkpoints.push(checkpoint);
    console.log(`[检查点] 已保存: ${name}`);
  }

  getLastCheckpoint() {
    return this.checkpoints[this.checkpoints.length - 1] || null;
  }

  async handleInterrupt(flag) {
    console.log('[中断] 开始安全停止流程...');

    this.stopInterruptPolling();
    await this.saveState();
    await this.cleanup();
    await this.clearFlag(flag);

    // 通知主Agent
    await this.notifyMainAgent({
      interrupted: true,
      checkpoint: this.getLastCheckpoint(),
      reason: flag.reason
    });
  }

  async clearFlag(flag) {
    const flagPath = `/tmp/agent-stop-${flag.sessionId}.flag`;
    if (await fs.pathExists(flagPath)) {
      await fs.remove(flagPath);
    }
  }

  async saveState() {
    const state = {
      sessionId: this.sessionId,
      checkpoints: this.checkpoints,
      timestamp: Date.now()
    };
    const stateDir = '/tmp/agent-state';
    await fs.ensureDir(stateDir);
    await fs.writeJson(path.join(stateDir, `${this.sessionId}.json`), state);
    console.log(`[状态] 已保存`);
  }

  async cleanup() {
    // 清理临时文件
    const tempDir = `/tmp/agent-work/${this.sessionId}`;
    if (await fs.pathExists(tempDir)) {
      await fs.remove(tempDir);
    }

    // 关闭连接
    if (this.db) await this.db.end();
    if (this.fileHandles) {
      for (const fh of this.fileHandles) {
        await fh.close();
      }
    }

    // 取消定时任务
    if (this.schedules) {
      for (const s of this.schedules) {
        clearTimeout(s.timer);
        clearInterval(s.interval);
      }
    }

    console.log('[清理] 完成');
  }

  async notifyMainAgent(result) {
    // 实现通知逻辑
    console.log('[通知]', result);
  }
}

module.exports = InterruptibleAgent;

性能影响

  • 轮询间隔建议5-10秒
  • 检查点保存频率根据任务重要性调整
  • 状态保存使用异步IO,避免阻塞任务执行

测试

提供测试脚本验证功能:

# 测试1: 创建标志
./scripts/create-stop-flag.sh test-session "test reason"

# 测试2: 检查标志
./scripts/check-stop-flag.sh test-session 60

# 测试3: 清除标志
./scripts/clear-stop-flag.sh test-session

版本历史

  • 1.0.0 - 初始版本
    • 基础中断标志文件机制
    • 轮询检测(5秒间隔)
    • 安全停止流程
    • Shell命令行工具

相关链接

贡献

欢迎提交 Issue 和 Pull Request!

许可证

MIT

Comments

Loading comments...