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 +2 -2
- package/assets/tank/workspace.zip +0 -0
- package/lib/index.js +30 -48
- package/package.json +1 -1
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`
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
"-
|
|
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
|
-
"
|
|
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
|
-
/**
|
|
1531
|
-
|
|
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
|
-
|
|
1541
|
-
|
|
1542
|
-
fs.
|
|
1543
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1609
|
-
prompt: this.guard.promptFor(workspace?.path ?? "", this.
|
|
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
|
+
"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",
|