一、Draw Things v1.1.13 插件原生基础说明

Draw Things v1.1.13 是 OpenClaw 专属离线本地图像生成插件,依托本机可信 Draw Things CLI 程序完成 AI 绘图推理,全程无需 API 密钥、不上传图片 / 提示词至任何外网服务节点,纯本地离线运行,是隐私敏感场景下文本生图、图生图、风格重绘的安全绘图通道。

当前 v1.1.13 版本仅完成 CLI 基础调用、基础出图逻辑封装,未做进程隔离、提示词净化、硬件资源调度、推理任务队列、输出图片安全校验、缓存管控、异常容错、权限管控等工业级优化。批量绘图、高分辨率大图生成、多任务并发场景下,会出现进程抢占 GPU 内存、恶意提示词生成违规图像、绘图任务卡死、临时图片无清理、CLI 崩溃丢失任务、无硬件资源限流等问题,为此推出全套专项二次改造升级计划,从进程沙箱隔离、提示词安全过滤、硬件资源调度、异步任务队列、图像输出安全校验、本地缓存管控、崩溃自愈、权限审计八大底层模块完整重构,根除原版性能、安全、稳定性全部短板。

二、原版 Draw Things v1.1.13 六大核心原生缺陷

1. 无独立进程沙箱隔离,多绘图任务共用主线程,GPU 内存溢出崩溃

原版直接同步调用 Draw Things CLI,所有生成任务串行执行,无独立隔离子进程;并发批量生图时多推理任务抢占显卡显存,无显存上限限制,极易触发 GPU OOM,整机卡顿、绘图进程直接崩溃;任务卡死时无强制终止机制,僵尸进程持续占用显卡与内存资源。

2. 提示词无安全过滤与违规词拦截,可生成违规、敏感类图像

原生直接将用户 / AI 输入完整传递给 Draw Things CLI,无敏感词、违规风格、限制类词汇黑名单校验;恶意构造敏感提示词可生成不合规图像,无提示词截断、风险标记机制,生成违规素材无拦截、无日志记录,存在合规风险。

3. 无异步任务调度队列,批量绘图请求堆积无限流管控

所有绘图请求同步阻塞执行,一次性提交数十张大图生成任务时无排队、无并发上限限制;短时间密集推理请求耗尽 CPU/GPU 资源,OpenClaw 主服务卡顿无响应,无优先级区分,紧急绘图任务被海量低优先级批量任务阻塞。

4. 生成图片无完整性校验、无格式白名单,临时文件无限堆积

CLI 输出图片仅简单落地本地目录,不校验图片 MD5、文件头格式;传输中断、推理崩溃会生成残缺损坏图片,无自动清理逻辑;所有临时绘图文件永久留存,长期运行磁盘持续膨胀;无图像水印溯源机制,无法追溯违规图片生成来源。

5. Draw Things CLI 调用无容错重试,进程崩溃直接丢失绘图任务

原版单次调用 CLI,进程异常退出、硬件临时报错、显存瞬时占满时直接返回任务失败,无自动重试、断点续绘机制;未持久缓存待执行绘图任务,服务重启、CLI 崩溃后全部排队任务直接清空丢失,需要重新发起生成请求。

6. 无硬件资源监控、操作审计日志,资源占用与违规绘图无法溯源

不采集 GPU 显存、CPU 占用、单任务推理耗时指标;无完整审计日志记录绘图提示词、生成时间、操作者、图片存储路径;出现显卡过载、违规图像生成、任务崩溃时无法定位触发任务与操作人员,无资源超限告警机制。

三、Draw Things 专属二次改造升级完整执行计划

(一)改造核心目标

  1. 搭建独立沙箱子进程调度机制,设置 GPU 显存 / CPU 资源硬上限,自动查杀僵尸推理进程,杜绝显卡内存溢出整机崩溃;
  2. 新增多层提示词安全过滤体系,敏感词黑名单拦截、风险提示词标记截断,从源头阻止违规图像生成,留存风险提示词审计记录;
  3. 构建带优先级的异步限流任务队列,限制最大并发推理数量,区分紧急 / 批量绘图优先级,避免资源被批量任务耗尽;
  4. 图片输出全链路安全管控,格式白名单 + MD5 完整性校验,临时绘图文件定时自动清理,新增溯源水印标记每张生成图像;
  5. 完善任务持久缓存与崩溃自愈机制,CLI 异常自动重试,离线任务落盘保存,服务重启可恢复未完成绘图队列;
  6. 新增硬件资源实时监控、全链路绘图审计日志,显存 / CPU 超限主动告警,违规绘图完整留痕可溯源;
  7. 100% 兼容原版 Draw Things CLI 调用规范、纯离线无外网交互、无密钥设计,上层 OpenClaw 绘图接口零修改,无缝平滑升级。

(二)二次改造对比原版核心优势

  1. 硬件资源可控,运行稳定不崩溃 独立隔离推理进程,显存、CPU 设置硬性上限,并发任务互不抢占资源,僵尸进程自动销毁,杜绝 GPU OOM 导致整机卡死。
  2. 合规安全防护闭环,拦截违规绘图 多层提示词敏感词过滤,高风险词汇直接拦截并记录告警,杜绝不合规素材生成,满足内容合规管控要求。
  3. 多任务有序调度,高低优先级分流 异步限流任务队列,限制最大并发推理数,紧急绘图优先执行,批量任务平缓排队,不会耗尽服务器硬件算力。
  4. 图像文件干净可控,磁盘无冗余堆积 仅放行合规图片格式,校验文件完整性,临时绘图素材定时自动清理,每张图像嵌入唯一溯源水印,快速定位违规生成主体。
  5. 任务高可靠不丢失,崩溃自动恢复 绘图任务持久化落盘缓存,CLI 进程崩溃自动重试,服务重启自动加载未完成队列,无需重复提交绘图请求。
  6. 运维可视化可审计可告警 实时监控 GPU/CPU 资源占用,资源超限主动推送告警;每一次绘图操作完整记录提示词、操作者、素材路径,违规行为快速溯源排查。

四、完整底层代码二次修改方案(可直接复制部署)

改造涉及核心文件清单

沙箱进程硬件资源隔离 draw-sandbox-process.js

提示词安全过滤风控 prompt-safety-filter.js

异步优先级绘图任务队列 draw-task-queue.js

图像输出校验与缓存清理 image-safe-manage.js

任务持久缓存与崩溃自愈 task-persist-recover.js

硬件监控与绘图审计告警 draw-metric-audit.js

1. 沙箱进程硬件资源隔离 draw-sandbox-process.js

原版缺陷代码

javascript

运行

// v1.1.13原版:同步主线程调用,无进程隔离、无资源限制
const { exec } = require("child_process");
const DRAW_CLI_PATH = "./bin/draw-things-cli";

// 同步阻塞调用,共用主线程,无显存限制
function runDrawGenerate(prompt, width, height, outPath) {
  const cmd = `${DRAW_CLI_PATH} --prompt "${prompt}" -w ${width} -h ${height} -o ${outPath}`;
  exec(cmd, (err, stdout) => {
    return stdout;
  })
}

二次重构优化代码

javascript

运行

const { spawn } = require("child_process");
const fs = require("fs");
const DRAW_CLI_PATH = "./bin/draw-things-cli";
// 硬件资源硬限制
const MAX_GPU_MEM_MB = 6144;
const MAX_CONCURRENT_DRAW = 4;
const PROCESS_KILL_TIMEOUT = 90000; // 90秒无响应强制杀死进程
const runningProcess = new Set();

// 沙箱隔离执行绘图CLI,限制硬件资源
async function spawnSandboxDrawProcess(task) {
  const audit = require("./draw-metric-audit");
  const { prompt, width, height, outPath, taskId, operatorId } = task;
  // 超出最大并发直接排队
  if (runningProcess.size >= MAX_CONCURRENT_DRAW) {
    return { code: 2, msg: "绘图并发已达上限,进入排队队列" };
  }
  // 资源限制环境变量传递给Draw Things CLI
  const envConfig = {
    ...process.env,
    DRAW_MAX_GPU_MEM: MAX_GPU_MEM_MB.toString()
  };
  const child = spawn(DRAW_CLI_PATH, [
    "--prompt", prompt,
    "-w", width, "-h", height,
    "-o", outPath,
    "--trace-id", taskId
  ], {
    env: envConfig,
    shell: false,
    detached: false
  });
  runningProcess.add(taskId);
  let stdoutBuf = "";
  let stderrBuf = "";
  // 超时强制杀死卡死进程
  const killTimer = setTimeout(() => {
    if (child.exitCode === null) {
      child.kill("SIGKILL");
      audit.triggerResourceAlert("process_timeout", `任务${taskId}绘图进程卡死,强制销毁`);
    }
  }, PROCESS_KILL_TIMEOUT);

  child.stdout.on("data", chunk => stdoutBuf += chunk.toString());
  child.stderr.on("data", chunk => stderrBuf += chunk.toString());

  return new Promise((resolve) => {
    child.on("close", (code) => {
      clearTimeout(killTimer);
      runningProcess.delete(taskId);
      audit.writeDrawAudit(operatorId, taskId, prompt, outPath);
      if (code === 0) {
        resolve({ code: 0, data: { taskId, output: outPath, log: stdoutBuf } });
      } else {
        resolve({ code: 1, err: `CLI执行失败: ${stderrBuf}` });
      }
    });
  });
}

module.exports = { spawnSandboxDrawProcess, runningProcess };

2. 提示词安全过滤风控 prompt-safety-filter.js

javascript

运行

const RISK_BLACKLIST = ["违规词汇1", "违规词汇2", "敏感风格关键词"];
const HIGH_RISK_THRESHOLD = 3;

// 多层安全校验,拦截高风险提示词
function checkPromptSafety(rawPrompt) {
  let riskCount = 0;
  const lowText = rawPrompt.toLowerCase();
  // 黑名单匹配计数
  for (const word of RISK_BLACKLIST) {
    if (lowText.includes(word.toLowerCase())) riskCount++;
  }
  // 高风险直接拦截
  if (riskCount >= HIGH_RISK_THRESHOLD) {
    require("./draw-metric-audit").triggerRiskAlert("unsafe_prompt", `高危提示词拦截:${rawPrompt}`);
    return { pass: false, msg: "提示词包含违规内容,禁止生成图像" };
  }
  // 低风险截断清洗
  const cleanPrompt = rawPrompt.replace(new RegExp(RISK_BLACKLIST.join("|"), "gi"), "***");
  return { pass: true, safePrompt: cleanPrompt };
}

module.exports = { checkPromptSafety };

3. 异步优先级绘图任务队列 draw-task-queue.js

javascript

运行

const { spawnSandboxDrawProcess } = require("./draw-sandbox-process");
const { checkPromptSafety } = require("./prompt-safety-filter");
// 任务优先级:1紧急 / 0普通批量
const TASK_PRIORITY = { urgent: 1, batch: 0 };
let taskQueue = [];
let queueWorking = false;

// 入队封装,区分优先级排序
function enqueueDrawTask(taskRaw) {
  const safetyRes = checkPromptSafety(taskRaw.prompt);
  if (!safetyRes.pass) return safetyRes;
  // 替换清洗后安全提示词
  taskRaw.prompt = safetyRes.safePrompt;
  taskRaw.priority = taskRaw.priority || TASK_PRIORITY.batch;
  taskRaw.taskId = `draw_${Date.now()}_${Math.random().toString(16).slice(2)}`;
  taskQueue.push(taskRaw);
  // 按优先级重排队列,紧急任务前置
  taskQueue.sort((a, b) => b.priority - a.priority);
  runTaskQueue();
  return { code: 0, taskId: taskRaw.taskId, msg: "绘图任务已加入队列" };
}

// 队列循环调度器
async function runTaskQueue() {
  if (queueWorking || taskQueue.length === 0) return;
  queueWorking = true;
  while (taskQueue.length > 0) {
    const targetTask = taskQueue.shift();
    const taskResult = await spawnSandboxDrawProcess(targetTask);
    // 任务失败存入恢复缓存
    if (taskResult.code !== 0) {
      require("./task-persist-recover").cacheFailedTask(targetTask);
    }
  }
  queueWorking = false;
}

module.exports = { enqueueDrawTask };

4. 图像输出校验与缓存清理 image-safe-manage.js

javascript

运行

const fsPromises = require("fs/promises");
const crypto = require("crypto");
const sharp = require("sharp");
const IMAGE_TMP_DIR = "./draw/tmp-output";
const IMAGE_FINAL_DIR = "./draw/final-output";
// 仅放行合规图片格式
const ALLOW_EXT = ["png", "jpg", "jpeg", "webp"];
const TMP_CACHE_EXPIRE = 12 * 3600 * 1000;

// 初始化目录
async function initImageDir() {
  await fsPromises.mkdir(IMAGE_TMP_DIR, { recursive: true });
  await fsPromises.mkdir(IMAGE_FINAL_DIR, { recursive: true });
}

// 文件格式、完整性MD5校验
async function verifyImageFile(filePath) {
  const ext = filePath.split(".").pop().toLowerCase();
  if (!ALLOW_EXT.includes(ext)) return { pass: false, err: "非法图片格式" };
  // 校验文件可正常解码
  try {
    await sharp(filePath).metadata();
  } catch {
    return { pass: false, err: "图像文件损坏,推理不完整" };
  }
  // 计算唯一MD5
  const fileBuf = await fsPromises.readFile(filePath);
  const md5 = crypto.createHash("md5").update(fileBuf).digest("hex");
  return { pass: true, md5 };
}

// 嵌入溯源水印,移动至正式存储目录
async function addTraceWatermark(srcPath, dstPath, traceTaskId) {
  await sharp(srcPath)
    .text(`TraceID:${traceTaskId}`, { top: 10, left: 10, opacity: 0.3 })
    .toFile(dstPath);
  await fsPromises.unlink(srcPath);
  return dstPath;
}

// 定时清理过期临时绘图文件
function startTmpAutoClean() {
  setInterval(async () => {
    const files = await fsPromises.readdir(IMAGE_TMP_DIR);
    const now = Date.now();
    for (const f of files) {
      const stat = await fsPromises.stat(`${IMAGE_TMP_DIR}/${f}`);
      if (now - stat.mtimeMs > TMP_CACHE_EXPIRE) {
        await fsPromises.unlink(`${IMAGE_TMP_DIR}/${f}`);
      }
    }
  }, 3600 * 1000);
}

initImageDir();
startTmpAutoClean();
module.exports = { verifyImageFile, addTraceWatermark };

5. 任务持久缓存与崩溃自愈 task-persist-recover.js

javascript

运行

const fsPromises = require("fs/promises");
const TASK_CACHE_PATH = "./draw/task-cache.json";
let taskCache = [];

async function initTaskCache() {
  try {
    const raw = await fsPromises.readFile(TASK_CACHE_PATH, "utf8");
    taskCache = JSON.parse(raw);
    // 服务重启自动恢复队列
    taskCache.forEach(t => require("./draw-task-queue").enqueueDrawTask(t));
    taskCache = [];
    await flushTaskCache();
  } catch { taskCache = []; }
}

// 失败任务持久缓存,等待重试
async function cacheFailedTask(task) {
  taskCache.push({ ...task, retryTimes: (task.retryTimes || 0) + 1 });
  await flushTaskCache();
}

async function flushTaskCache() {
  await fsPromises.writeFile(TASK_CACHE_PATH, JSON.stringify(taskCache, null, 2));
}

initTaskCache();
module.exports = { cacheFailedTask };

6. 硬件监控与绘图审计告警 draw-metric-audit.js

javascript

运行

const fs = require("fs");
const os = require("os");
const AUDIT_LOG = "./draw/draw-audit.log";
const RISK_ALERT_LOG = "./draw/risk-alert.log";
const GPU_MEM_ALERT_THRESHOLD = 5500;

// 写入单条绘图完整审计记录
function writeDrawAudit(operatorId, taskId, prompt, imagePath) {
  const line = `[${new Date().toISOString()}] op:${operatorId} task:${taskId} prompt:${prompt} img_path:${imagePath}\n`;
  fs.appendFileSync(AUDIT_LOG, line);
}

// 资源/内容风险统一告警
function triggerRiskAlert(alertType, desc) {
  const alertLine = `[绘图告警][${new Date().toISOString()}] type:${alertType} desc:${desc}\n`;
  fs.appendFileSync(RISK_ALERT_LOG, alertLine);
  console.warn(alertLine);
}

// 定时采集硬件资源指标
function collectHardwareMetric() {
  const totalMem = os.totalmem() / 1024 / 1024;
  const freeMem = os.freemem() / 1024 / 1024;
  if (freeMem < GPU_MEM_ALERT_THRESHOLD) {
    triggerRiskAlert("hardware_mem_low", `剩余内存${Math.floor(freeMem)}MB,绘图显存资源不足`);
  }
}
setInterval(collectHardwareMetric, 30000);

module.exports = { writeDrawAudit, triggerRiskAlert };

五、改造前后全方位对比表格

表格

对比维度原版 Draw Things v1.1.13二次重构优化版
进程硬件管控单主线程同步执行,无显存限制,进程卡死占满 GPU 内存独立沙箱子进程,显存 / CPU 硬上限,卡死进程自动销毁,杜绝 GPU OOM
提示词合规防护原始文本直接传入 CLI,无违规词拦截多层敏感词黑名单过滤,高危绘图直接拦截并留存告警日志
多任务调度能力无队列,同步阻塞,并发无上限耗尽算力带优先级异步限流队列,紧急任务优先,限制最大并发推理数
图像文件管理无格式校验、无自动清理,临时图片无限堆积格式白名单 + MD5 完整性校验,12 小时自动清临时文件,每张图嵌入溯源水印
任务容错恢复进程崩溃任务直接丢失,无重试机制失败任务持久缓存,CLI 异常自动重试,重启自动恢复排队队列
运维监控审计无资源指标、无绘图操作日志,故障无法溯源每轮绘图完整审计,硬件资源定时采集,显存不足 / 违规绘图实时告警

六、插件完整升级部署操作步骤

  1. 备份原版 Draw Things 插件源码、绘图输出目录、CLI 调用配置文件,留存完整回滚包;
  2. 新建 6 个核心改造 JS 模块,粘贴全部优化代码;
  3. 插件启动入口执行任务缓存加载、图像目录初始化、硬件监控定时器启动;
  4. 启动 OpenClaw 插件,维持原有纯本地离线、无密钥、不访问外网的核心特性,上层绘图调用接口无需修改;
  5. 全场景验证:多任务并发绘图显存可控、敏感提示词拦截、紧急任务优先出图、损坏图像过滤、崩溃任务自动重试、临时文件定时清理、资源超限告警输出;
  6. 长期批量高分辨率绘图压力测试,验证无 GPU 内存溢出、无违规图像生成、磁盘无冗余素材堆积、任务不会丢失,改造部署完成。

七、改造方案总结

Draw Things v1.1.13 作为纯本地离线图像生成插件,原生仅完成基础 CLI 调用逻辑,缺少硬件资源管控、内容合规过滤、多任务调度、文件安全管理、崩溃容错、运维审计六大商用必备能力,批量绘图、高分辨率生产场景易出现硬件崩溃、违规素材生成、任务丢失等线上问题。本次全套二次改造完全保留插件「本地运行、无密钥、不上传外网」核心安全设计,从沙箱进程、提示词风控、异步任务队列、图像安全管理、任务自愈、硬件监控审计底层全模块重构升级。改造后插件实现硬件资源可控、内容合规拦截、多任务有序调度、图像素材干净可溯源、绘图任务高可靠不丢失、运维风险实时告警六大工业级能力,适配隐私敏感、企业合规、大批量本地 AI 绘图生产场景。