#!/usr/bin/env node // Export a self-contained bench any agent can run in its own sandbox: four JS // files, a level pack and a protocol. No repo, no npm, no network. // node scripts/bench-pack.mjs [--set=levels/prototypes/d5.json] [--levels=1-18|all|1,4] [--out=bench/pack] [--peek] // Hand the folder (or a zip of it) to Grok / DeepSeek / any harness; they run // `node play.mjs …` and send back runs/*.json, which bench-import.mjs reads. import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; const flags = Object.fromEntries(process.argv.slice(2).map(a => { const [k, ...v] = a.replace(/^--/, '').split('='); return [k, v.length ? v.join('=') : true]; })); const setPath = flags.set || 'levels/prototypes/d5.json'; const out = resolve(flags.out || 'bench/pack'); const raw = JSON.parse(readFileSync(resolve(setPath), 'utf8')); const all = Array.isArray(raw) ? raw : Array.isArray(raw.levels) ? raw.levels : Object.values(raw).filter(l => l && l.cells); const spec = String(flags.levels || 'all'); const picked = spec === 'all' ? all : spec.split(',').flatMap(part => { const m = part.match(/^(\d+)-(\d+)$/); return m ? Array.from({ length: Number(m[2]) - Number(m[1]) + 1 }, (_, i) => Number(m[1]) + i) : [Number(part)]; }) .filter(n => n >= 1 && n <= all.length).map(n => all[n - 1]); if (!picked.length) { console.error('没有选中任何关卡'); process.exit(1); } // Rewrite the repo CLI into a standalone one. Every replacement is asserted, so // a rename in bench.mjs breaks the pack loudly instead of shipping it broken. let cli = readFileSync(resolve('scripts/bench.mjs'), 'utf8'); const swap = (from, to) => { if (!cli.includes(from)) { console.error(`bench-pack 过时了:scripts/bench.mjs 里找不到 ${JSON.stringify(from)}`); process.exit(1); } cli = cli.split(from).join(to); }; swap("from '../src/bench.js'", "from './bench.js'"); swap("resolve('bench/runs')", "resolve('runs')"); swap("flags.set || 'levels/prototypes/d5.json'", "flags.set || 'levels.json'"); swap('node scripts/bench.mjs', 'node play.mjs'); swap('labs/bench/AGENT.md', 'AGENT.md'); rmSync(out, { recursive: true, force: true }); mkdirSync(resolve(out, 'runs'), { recursive: true }); for (const f of ['path-math.js', 'core.js', 'bench.js']) cpSync(resolve('src', f), resolve(out, f)); writeFileSync(resolve(out, 'play.mjs'), cli); writeFileSync(resolve(out, 'levels.json'), JSON.stringify({ version: 1, source: setPath, levels: picked })); // The pack ships the same single file agents read in the repo, with the // command name and paths rewritten for a standalone folder. const guide = readFileSync(resolve('labs/bench/AGENT.md'), 'utf8') .split('node scripts/bench.mjs').join('node play.mjs') .split('labs/bench/AGENT.md').join('AGENT.md') .split('`src/`').join('`core.js` / `bench.js`') .split('--set=levels/v0.5.json').join('--set=levels.json'); writeFileSync(resolve(out, 'AGENT.md'), guide); writeFileSync(resolve(out, 'README.md'), `# HEX TRAILS 关卡实验台 · 便携包 给任意 Agent 用的独立实验台:**只要 Node 20+,不需要联网、不需要 npm install、不需要原仓库。** 包含 ${picked.length} 关(来自 \`${setPath}\`)。 ## 给 Agent 的任务提示(整段复制给它) > 读这个文件夹里的 \`AGENT.md\`,然后按它说的玩第 1 关。你的模型标识写 \`<换成它的名字>\`。 \`AGENT.md\` 里已经包含规则、读盘方法、全部命令和示例,Agent 读完就能直接开始,不需要别的说明。 ## 命令 | 命令 | 作用 | |---|---| | \`node play.mjs list\` | 列出本包里的关卡 | | \`node play.mjs start --model=X --level=N --persona=… --no-rules\` | 开一局(局名自动生成)| | \`node play.mjs observe --run=X\` | 再看一次盘面 | | \`node play.mjs act --run=X tap t4, tap t5 --note="…"\` | 提交一个决定(可连做几步) | | \`node play.mjs rate --run=X --json='{…}'\` | 评分(必填) | | \`node play.mjs status --run=X\` / \`metrics --run=X\` | 回看步骤 / 指标 | 人设:\`reader\`(读盘型)\`tapper\`(试错型)\`turner\`(转盘型)\`novice\`(新手)。 ## 交回结果 每局写在 \`runs/<局名>.json\`,含每一步的动作、理由、结果和时间戳。把整个 \`runs/\` 交回即可,原仓库用 \`node scripts/bench-import.mjs --from=<路径>/runs --tag=<模型名>\` 导入,然后在观察页回放、在汇总表里比较。 生成时间:${new Date().toISOString()} `); console.log(`便携包已生成:${out}`); console.log(` ${picked.length} 关 · 文件:play.mjs bench.js core.js path-math.js levels.json AGENT.md README.md runs/`); console.log(` 打包发给别的 agent:cd ${resolve(out, '..')} && zip -r bench-pack.zip ${out.split('/').pop()}`); console.log(` 自测:cd ${out} && node play.mjs list && node play.mjs start --run=t --level=1 --model=me`);