dsh-eval-guard 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - 禁止联网、子代理、Agent Team/跨 agent 通信、读取其它工作区。
7
7
  - 工具黑名单可配置;未知工具默认放行,但 DSH 内置与 dsh-ops 的工具会做参数路径审计。
8
8
  - 每个用户消息后的单轮有可配置的最长时长(默认 90 分钟);到点只终止当前轮次,并递归清理子代理与后台任务。
9
- - 设置页可安装“第 6 集 · 坦克大战”任务:空工作区 → 复制素材 → 新会话填入提示词(由用户发送)。
9
+ - 设置页可安装“第 6 集 · 坦克大战”任务:空工作区 → 放入打包好的 `workspace.zip` → 新会话填入提示词(由用户发送)。
10
10
 
11
11
  插件默认关闭;关闭时不做任何行为。所有 run 记录、usage、violations 写入
12
12
  $DSH_HOME/eval-guard/runs/<run-id>/,供独立的 ../tank-eval 评测程序读取。
@@ -20,7 +20,7 @@ $DSH_HOME/eval-guard/runs/<run-id>/,供独立的 ../tank-eval 评测程序读
20
20
  - 使用版本:commit `6bd738b`(2026-10-16)
21
21
  - 素材来源:该仓库的 `tank/workspace/`(MIT License)
22
22
 
23
- 本插件打包的 Linux 版 `workspace.zip` 在原素材基础上做了两处适配:把 `baseline.cpp` 编译为 Linux 可执行文件并移出源码;把 `clock.py` 改为单次 90 分钟版本。规则、引擎、本地裁判、模板等其余文件保持原样。
23
+ 本插件打包的 Linux 版 `workspace.zip` 在原素材基础上做了三处适配:把 `baseline.cpp` 编译为 Linux ELF 可执行文件并移出源码,文件名改为 `baseline`(不带 `.exe`);把 `clock.py` 改为单次 90 分钟版本;`README.md` 中对应的 baseline 示例同步改名。规则、引擎、本地裁判、模板等其余文件保持原样。安装任务时只把 `workspace.zip` 放入工作区,不再散装解压。
24
24
 
25
25
  ## 构建
26
26
 
Binary file
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
6
  import { fileURLToPath } from "node:url";
7
- import { execFileSync, spawn } from "node:child_process";
7
+ import { spawn } from "node:child_process";
8
8
  //#region src/config.ts
9
9
  /**
10
10
  * Plugin configuration. The Settings page writes this document through the
@@ -1435,7 +1435,7 @@ function registerRoutes(ctx, guard, tasks) {
1435
1435
  //#endregion
1436
1436
  //#region src/task.ts
1437
1437
  /**
1438
- * The tank task pack: workspace extraction, prompt construction, and the one
1438
+ * The tank task pack: packaged workspace.zip install, prompt construction, and the one
1439
1439
  * run record the evaluator later reads.
1440
1440
  * @module dsh-eval-guard/task
1441
1441
  */
@@ -1443,7 +1443,7 @@ function registerRoutes(ctx, guard, tasks) {
1443
1443
  const TASKS = [{
1444
1444
  id: TANK_TASK,
1445
1445
  label: TANK_TASK_LABEL,
1446
- description: "1v1 坦克大战 bot:编写 submission/bot.cpp,评测时与基准及其它提交打循环赛。"
1446
+ description: "1v1 坦克大战 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp,评测时与基准及其它提交打循环赛。"
1447
1447
  }];
1448
1448
  /** Format a blacklist for the prompt. */
1449
1449
  function blacklistBlock(blacklist) {
@@ -1469,19 +1469,23 @@ const TANK_DEFINITION = {
1469
1469
  "",
1470
1470
  "## 交付",
1471
1471
  "",
1472
- "- 把最终 bot 保存到工作区内的 submission/bot.cpp(只需要这一个文件)。",
1473
- "- 交付前检查并关闭所有后台任务;最多 " + String(options.minutes ?? 90) + " 分钟,到点会被强制终止。",
1472
+ "- 解压工作区里的 workspace.zip,把最终 bot 保存到解压后的 workspace/submission/bot.cpp(只需要这一个文件)。",
1474
1473
  "- 最后用几句话说明你的策略。",
1475
1474
  "",
1475
+ "## 时间规则",
1476
+ "",
1477
+ "- 本次是**连续 " + String(options.minutes ?? 90) + " 分钟的单次任务**,从对话开始时开始计时。",
1478
+ "- 交付前检查并关闭所有后台任务;到点会被强制终止。",
1479
+ "- clock.py 从解压那一刻开始计算,而实际计时从对话开始计算;它**只能作为参考**,请尽快解压以减小误差。实际时间以评测守护为准。",
1480
+ "",
1476
1481
  "## 本次评测禁止调用的工具",
1477
1482
  "",
1478
1483
  blacklistBlock(blacklist),
1479
1484
  "",
1480
1485
  "## 工作区内容",
1481
1486
  "",
1482
- "工作区根目录就是选手包:先读 README.md 和 RULES.md,规则引擎是 tank.hpp。",
1483
- "本地工具(arena.py、check、play、batch、vs 等)和起步模板都在工作区里,可直接使用。",
1484
- "README.md 里提到的上下半场属于原始赛制;clock.py 只是参考,本次是单次任务,以本提示词的时间规则为准。",
1487
+ "工作区里有一个 workspace.zip;解压后进入 workspace/ 目录,先读 README.md 和 RULES.md,规则引擎是 tank.hpp。",
1488
+ "本地工具(arena.py、check、play、batch、vs 等)和起步模板都在解压后的 workspace/ 里,可直接使用。",
1485
1489
  "",
1486
1490
  "开始吧。"
1487
1491
  ].join("\n");
@@ -1527,20 +1531,21 @@ var TaskService = class {
1527
1531
  entries
1528
1532
  };
1529
1533
  }
1530
- /** Extract the task pack to a material directory the manual channel can reveal. */
1531
- materialDir() {
1534
+ /** Copy the packaged workspace.zip to a stable material path for install/manual use. */
1535
+ materialZip() {
1532
1536
  const dir = path.join(guardRoot(), "material", "tank");
1533
- const marker = path.join(guardRoot(), "material", ".tank-complete");
1534
- if (fs.existsSync(marker) && fs.existsSync(path.join(dir, "RULES.md"))) return dir;
1535
- fs.rmSync(dir, {
1536
- recursive: true,
1537
- force: true
1538
- });
1539
1537
  fs.mkdirSync(dir, { recursive: true });
1540
- fs.mkdirSync(path.dirname(marker), { recursive: true });
1541
- this.extract(TANK_DEFINITION.asset, dir);
1542
- fs.writeFileSync(marker, "ok\n", "utf8");
1543
- return dir;
1538
+ const target = path.join(dir, "workspace.zip");
1539
+ const source = TANK_DEFINITION.asset;
1540
+ if (!fs.existsSync(source)) throw new Error("task asset is missing: " + source);
1541
+ let stale = true;
1542
+ try {
1543
+ stale = !fs.existsSync(target) || fs.statSync(target).size !== fs.statSync(source).size;
1544
+ } catch {
1545
+ stale = true;
1546
+ }
1547
+ if (stale) fs.copyFileSync(source, target);
1548
+ return target;
1544
1549
  }
1545
1550
  /** Install the task pack into an empty workspace. */
1546
1551
  install(workspaceId, promptOverride) {
@@ -1563,7 +1568,7 @@ var TaskService = class {
1563
1568
  error: "请先启用评测守护,再安装评测任务。"
1564
1569
  };
1565
1570
  try {
1566
- this.extract(TANK_DEFINITION.asset, checked.path);
1571
+ fs.copyFileSync(this.materialZip(), path.join(checked.path, "workspace.zip"));
1567
1572
  } catch (error) {
1568
1573
  return {
1569
1574
  ok: false,
@@ -1573,7 +1578,7 @@ var TaskService = class {
1573
1578
  };
1574
1579
  }
1575
1580
  const runId = this.newRunId();
1576
- const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.materialDir());
1581
+ const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.materialZip());
1577
1582
  const run = {
1578
1583
  runId,
1579
1584
  task: TANK_TASK,
@@ -1599,39 +1604,16 @@ var TaskService = class {
1599
1604
  /** The prompt for manual copying, without installing anything. */
1600
1605
  prompt(workspaceId) {
1601
1606
  const workspace = this.registry.get(workspaceId);
1602
- return this.guard.promptFor(workspace?.path ?? "", this.materialDir());
1607
+ return this.guard.promptFor(workspace?.path ?? "", this.materialZip());
1603
1608
  }
1604
1609
  /** Filesystem paths for the manual channel. */
1605
1610
  manualPaths(workspaceId) {
1606
1611
  const workspace = this.registry.get(workspaceId);
1607
1612
  return {
1608
- source: this.materialDir(),
1609
- prompt: this.guard.promptFor(workspace?.path ?? "", this.materialDir())
1613
+ source: this.materialZip(),
1614
+ prompt: this.guard.promptFor(workspace?.path ?? "", this.materialZip())
1610
1615
  };
1611
1616
  }
1612
- /** Extract a workspace.zip into a destination, stripping the workspace/ prefix. */
1613
- extract(zip, destination) {
1614
- if (!fs.existsSync(zip)) throw new Error("task asset is missing: " + zip);
1615
- fs.mkdirSync(path.join(guardRoot(), "tmp"), { recursive: true });
1616
- const temporary = fs.mkdtempSync(path.join(guardRoot(), "tmp", "extract-"));
1617
- try {
1618
- execFileSync("unzip", [
1619
- "-q",
1620
- "-o",
1621
- zip,
1622
- "-d",
1623
- temporary
1624
- ], { stdio: "ignore" });
1625
- const inner = path.join(temporary, "workspace");
1626
- if (!fs.existsSync(inner)) throw new Error("task asset has no workspace/ root");
1627
- fs.cpSync(inner, destination, { recursive: true });
1628
- } finally {
1629
- fs.rmSync(temporary, {
1630
- recursive: true,
1631
- force: true
1632
- });
1633
- }
1634
- }
1635
1617
  /** A unique run id. */
1636
1618
  newRunId() {
1637
1619
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14) + "-" + Math.random().toString(16).slice(2, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-eval-guard",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Evaluation guard for DSH: workspace-only filesystem, no network/subagents, wall-clock limits, and task installation for the meow-ai-arena tank task.",
6
6
  "license": "MIT",