dsh-eval-guard 0.1.2 → 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/LICENSE +1 -1
- package/README.md +3 -3
- package/assets/tank/workspace.zip +0 -0
- package/lib/index.js +59 -57
- package/package.json +1 -1
package/LICENSE
CHANGED
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
|
|
|
@@ -34,7 +34,7 @@ node scripts/build-native.mjs
|
|
|
34
34
|
~~~bash
|
|
35
35
|
scripts/install.sh web
|
|
36
36
|
# 如果脚本找不到 dsh 命令:
|
|
37
|
-
DSH_CHECKOUT=/
|
|
37
|
+
DSH_CHECKOUT=/path/to/deepseek-harness scripts/install.sh web
|
|
38
38
|
# 然后重启 dsh web
|
|
39
39
|
~~~
|
|
40
40
|
|
|
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
|
|
@@ -337,10 +337,29 @@ function packageRoot() {
|
|
|
337
337
|
function supervisorPath() {
|
|
338
338
|
return path.join(packageRoot(), "native", "dsh-eval-supervisor");
|
|
339
339
|
}
|
|
340
|
-
/**
|
|
340
|
+
/** Launcher-provided facts; nothing here is a fixed install path. */
|
|
341
|
+
let runtimeHome;
|
|
342
|
+
let runtimeProfileDir;
|
|
343
|
+
/** Record the launcher's profile/home paths. */
|
|
344
|
+
function configureRuntimeContext(next) {
|
|
345
|
+
if (next.home !== void 0 && next.home !== "") runtimeHome = next.home;
|
|
346
|
+
if (next.profileDir !== void 0 && next.profileDir !== "") runtimeProfileDir = next.profileDir;
|
|
347
|
+
}
|
|
348
|
+
/** The DSH home: launcher profile context, then $DSH_HOME, then ~/.dsh. */
|
|
341
349
|
function dshHome() {
|
|
350
|
+
if (runtimeHome !== void 0) return runtimeHome;
|
|
342
351
|
return process.env.DSH_HOME && process.env.DSH_HOME !== "" ? process.env.DSH_HOME : path.join(os.homedir(), ".dsh");
|
|
343
352
|
}
|
|
353
|
+
/** Candidate node_modules directories, launcher profile first and then profiles/*. */
|
|
354
|
+
function profileNodeModules() {
|
|
355
|
+
const out = [];
|
|
356
|
+
if (runtimeProfileDir !== void 0) out.push(path.join(runtimeProfileDir, "node_modules"));
|
|
357
|
+
const profiles = path.join(dshHome(), "profiles");
|
|
358
|
+
try {
|
|
359
|
+
for (const entry of fs.readdirSync(profiles, { withFileTypes: true })) if (entry.isDirectory()) out.push(path.join(profiles, entry.name, "node_modules"));
|
|
360
|
+
} catch {}
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
344
363
|
/** Root for run records, audits, and sandbox policy files. */
|
|
345
364
|
function guardRoot() {
|
|
346
365
|
return path.join(dshHome(), "eval-guard");
|
|
@@ -381,14 +400,9 @@ function packageDir(name) {
|
|
|
381
400
|
const require = createRequire(path.join(process.cwd(), "noop.js"));
|
|
382
401
|
candidates.push(path.dirname(require.resolve(name + "/package.json")));
|
|
383
402
|
} catch {}
|
|
384
|
-
if (name === "@deepseek-ai/dsh-ptc-runtime-node")
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
if (name === "@deepseek-ai/dsh-subprocess") {
|
|
389
|
-
candidates.push(path.join(process.cwd(), "packages", "subprocess", "subprocess"));
|
|
390
|
-
candidates.push(path.join(dshHome(), "profiles", "web", "node_modules", name));
|
|
391
|
-
}
|
|
403
|
+
if (name === "@deepseek-ai/dsh-ptc-runtime-node") candidates.push(path.join(process.cwd(), "packages", "ptc-runtime", "ptc-runtime-node"));
|
|
404
|
+
if (name === "@deepseek-ai/dsh-subprocess") candidates.push(path.join(process.cwd(), "packages", "subprocess", "subprocess"));
|
|
405
|
+
for (const modules of profileNodeModules()) candidates.push(path.join(modules, name));
|
|
392
406
|
for (const candidate of candidates) try {
|
|
393
407
|
if (fs.statSync(path.join(candidate, "package.json")).isFile()) return fs.realpathSync.native(candidate);
|
|
394
408
|
} catch {}
|
|
@@ -1421,7 +1435,7 @@ function registerRoutes(ctx, guard, tasks) {
|
|
|
1421
1435
|
//#endregion
|
|
1422
1436
|
//#region src/task.ts
|
|
1423
1437
|
/**
|
|
1424
|
-
* The tank task pack: workspace
|
|
1438
|
+
* The tank task pack: packaged workspace.zip install, prompt construction, and the one
|
|
1425
1439
|
* run record the evaluator later reads.
|
|
1426
1440
|
* @module dsh-eval-guard/task
|
|
1427
1441
|
*/
|
|
@@ -1429,7 +1443,7 @@ function registerRoutes(ctx, guard, tasks) {
|
|
|
1429
1443
|
const TASKS = [{
|
|
1430
1444
|
id: TANK_TASK,
|
|
1431
1445
|
label: TANK_TASK_LABEL,
|
|
1432
|
-
description: "1v1 坦克大战 bot
|
|
1446
|
+
description: "1v1 坦克大战 bot:解压 workspace.zip 后编写 workspace/submission/bot.cpp,评测时与基准及其它提交打循环赛。"
|
|
1433
1447
|
}];
|
|
1434
1448
|
/** Format a blacklist for the prompt. */
|
|
1435
1449
|
function blacklistBlock(blacklist) {
|
|
@@ -1455,19 +1469,23 @@ const TANK_DEFINITION = {
|
|
|
1455
1469
|
"",
|
|
1456
1470
|
"## 交付",
|
|
1457
1471
|
"",
|
|
1458
|
-
"-
|
|
1459
|
-
"- 交付前检查并关闭所有后台任务;最多 " + String(options.minutes ?? 90) + " 分钟,到点会被强制终止。",
|
|
1472
|
+
"- 解压工作区里的 workspace.zip,把最终 bot 保存到解压后的 workspace/submission/bot.cpp(只需要这一个文件)。",
|
|
1460
1473
|
"- 最后用几句话说明你的策略。",
|
|
1461
1474
|
"",
|
|
1475
|
+
"## 时间规则",
|
|
1476
|
+
"",
|
|
1477
|
+
"- 本次是**连续 " + String(options.minutes ?? 90) + " 分钟的单次任务**,从对话开始时开始计时。",
|
|
1478
|
+
"- 交付前检查并关闭所有后台任务;到点会被强制终止。",
|
|
1479
|
+
"- clock.py 从解压那一刻开始计算,而实际计时从对话开始计算;它**只能作为参考**,请尽快解压以减小误差。实际时间以评测守护为准。",
|
|
1480
|
+
"",
|
|
1462
1481
|
"## 本次评测禁止调用的工具",
|
|
1463
1482
|
"",
|
|
1464
1483
|
blacklistBlock(blacklist),
|
|
1465
1484
|
"",
|
|
1466
1485
|
"## 工作区内容",
|
|
1467
1486
|
"",
|
|
1468
|
-
"
|
|
1469
|
-
"本地工具(arena.py、check、play、batch、vs
|
|
1470
|
-
"README.md 里提到的上下半场属于原始赛制;clock.py 只是参考,本次是单次任务,以本提示词的时间规则为准。",
|
|
1487
|
+
"工作区里有一个 workspace.zip;解压后进入 workspace/ 目录,先读 README.md 和 RULES.md,规则引擎是 tank.hpp。",
|
|
1488
|
+
"本地工具(arena.py、check、play、batch、vs 等)和起步模板都在解压后的 workspace/ 里,可直接使用。",
|
|
1471
1489
|
"",
|
|
1472
1490
|
"开始吧。"
|
|
1473
1491
|
].join("\n");
|
|
@@ -1513,20 +1531,21 @@ var TaskService = class {
|
|
|
1513
1531
|
entries
|
|
1514
1532
|
};
|
|
1515
1533
|
}
|
|
1516
|
-
/**
|
|
1517
|
-
|
|
1534
|
+
/** Copy the packaged workspace.zip to a stable material path for install/manual use. */
|
|
1535
|
+
materialZip() {
|
|
1518
1536
|
const dir = path.join(guardRoot(), "material", "tank");
|
|
1519
|
-
const marker = path.join(guardRoot(), "material", ".tank-complete");
|
|
1520
|
-
if (fs.existsSync(marker) && fs.existsSync(path.join(dir, "RULES.md"))) return dir;
|
|
1521
|
-
fs.rmSync(dir, {
|
|
1522
|
-
recursive: true,
|
|
1523
|
-
force: true
|
|
1524
|
-
});
|
|
1525
1537
|
fs.mkdirSync(dir, { recursive: true });
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
fs.
|
|
1529
|
-
|
|
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;
|
|
1530
1549
|
}
|
|
1531
1550
|
/** Install the task pack into an empty workspace. */
|
|
1532
1551
|
install(workspaceId, promptOverride) {
|
|
@@ -1549,7 +1568,7 @@ var TaskService = class {
|
|
|
1549
1568
|
error: "请先启用评测守护,再安装评测任务。"
|
|
1550
1569
|
};
|
|
1551
1570
|
try {
|
|
1552
|
-
this.
|
|
1571
|
+
fs.copyFileSync(this.materialZip(), path.join(checked.path, "workspace.zip"));
|
|
1553
1572
|
} catch (error) {
|
|
1554
1573
|
return {
|
|
1555
1574
|
ok: false,
|
|
@@ -1559,7 +1578,7 @@ var TaskService = class {
|
|
|
1559
1578
|
};
|
|
1560
1579
|
}
|
|
1561
1580
|
const runId = this.newRunId();
|
|
1562
|
-
const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.
|
|
1581
|
+
const prompt = promptOverride ?? this.guard.promptFor(checked.path, this.materialZip());
|
|
1563
1582
|
const run = {
|
|
1564
1583
|
runId,
|
|
1565
1584
|
task: TANK_TASK,
|
|
@@ -1585,39 +1604,16 @@ var TaskService = class {
|
|
|
1585
1604
|
/** The prompt for manual copying, without installing anything. */
|
|
1586
1605
|
prompt(workspaceId) {
|
|
1587
1606
|
const workspace = this.registry.get(workspaceId);
|
|
1588
|
-
return this.guard.promptFor(workspace?.path ?? "", this.
|
|
1607
|
+
return this.guard.promptFor(workspace?.path ?? "", this.materialZip());
|
|
1589
1608
|
}
|
|
1590
1609
|
/** Filesystem paths for the manual channel. */
|
|
1591
1610
|
manualPaths(workspaceId) {
|
|
1592
1611
|
const workspace = this.registry.get(workspaceId);
|
|
1593
1612
|
return {
|
|
1594
|
-
source: this.
|
|
1595
|
-
prompt: this.guard.promptFor(workspace?.path ?? "", this.
|
|
1613
|
+
source: this.materialZip(),
|
|
1614
|
+
prompt: this.guard.promptFor(workspace?.path ?? "", this.materialZip())
|
|
1596
1615
|
};
|
|
1597
1616
|
}
|
|
1598
|
-
/** Extract a workspace.zip into a destination, stripping the workspace/ prefix. */
|
|
1599
|
-
extract(zip, destination) {
|
|
1600
|
-
if (!fs.existsSync(zip)) throw new Error("task asset is missing: " + zip);
|
|
1601
|
-
fs.mkdirSync(path.join(guardRoot(), "tmp"), { recursive: true });
|
|
1602
|
-
const temporary = fs.mkdtempSync(path.join(guardRoot(), "tmp", "extract-"));
|
|
1603
|
-
try {
|
|
1604
|
-
execFileSync("unzip", [
|
|
1605
|
-
"-q",
|
|
1606
|
-
"-o",
|
|
1607
|
-
zip,
|
|
1608
|
-
"-d",
|
|
1609
|
-
temporary
|
|
1610
|
-
], { stdio: "ignore" });
|
|
1611
|
-
const inner = path.join(temporary, "workspace");
|
|
1612
|
-
if (!fs.existsSync(inner)) throw new Error("task asset has no workspace/ root");
|
|
1613
|
-
fs.cpSync(inner, destination, { recursive: true });
|
|
1614
|
-
} finally {
|
|
1615
|
-
fs.rmSync(temporary, {
|
|
1616
|
-
recursive: true,
|
|
1617
|
-
force: true
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
1617
|
/** A unique run id. */
|
|
1622
1618
|
newRunId() {
|
|
1623
1619
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14) + "-" + Math.random().toString(16).slice(2, 8);
|
|
@@ -1650,6 +1646,12 @@ function apply(ctx, config) {
|
|
|
1650
1646
|
"sandboxPolicy",
|
|
1651
1647
|
"workspaceRegistry"
|
|
1652
1648
|
], (child) => {
|
|
1649
|
+
const getService = ctx.get;
|
|
1650
|
+
const profile = typeof getService === "function" ? getService.call(ctx, "profileContext") : void 0;
|
|
1651
|
+
configureRuntimeContext({
|
|
1652
|
+
home: profile?.home,
|
|
1653
|
+
profileDir: profile?.dir
|
|
1654
|
+
});
|
|
1653
1655
|
const services = {
|
|
1654
1656
|
sessions: child.get("sessions"),
|
|
1655
1657
|
agents: child.get("agents"),
|
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",
|