feat: 任务书 10,submissions 模块与 materials 修改

- 新增 src/submissions:先程序检查(14 条跨字段规则、不许改的文件、代码存不了、指标编号占用、提案数上限),全过再问 Jev;退回原因列出该写的全部编号;回应/统一拼冻结协议主体只在这一处;核验、消融核验通过没有。
- materials:挑刺单写"现在交什么";退回按阶段、作者、种类、对象找(ledger 退回记录加 target,表结构版本 4);核验材料去掉 Jev 预扫;信念本写合法化;删"本该关着";消融核验不通过可重做,不跑的格从 cellNotRun 事件读原因。
- 用户 2026-10-03 定:冻结协议去掉 replies、replyBody,第二次预测的答复从已公开的回应或统一读;takenMetricIds 加两个保留键;解释一律交 cells;Jev 退回只列判"是"的几问、用英文问题名。
- jev 判重材料带 keySettings;读代码工具删 offset;三处旧说法改正。
- 文档:结构.md 第四、七、八部分,WORK_LOG。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 06:08:34 -04:00
parent a0ee4ef7a2
commit 09c44031a0
34 changed files with 1221 additions and 169 deletions
+10 -15
View File
@@ -134,7 +134,7 @@ const READ_MAX_BYTES = 50 * 1024;
* 读一个文件或列一个目录。路径必须是绝对路径,按真实路径(解析 .. 和符号链接之后)判断在不在可读目录里:
* 模型正常探索时就会去 ls 上一层,看到别人还没公开的东西。不合格就抛错,Pi 把错误回给模型。
*/
export function readSource(roots: readonly string[], path: string, offset: number): string {
export function readSource(roots: readonly string[], path: string): string {
const allowed = roots.map((r) => realpathSync(r));
const listing = allowed.map((r) => ` ${r}`).join("\n");
if (!isAbsolute(path)) throw new Error(`read_source 只接受绝对路径,收到的是 "${path}"。可读的目录:\n${listing}`);
@@ -160,26 +160,23 @@ export function readSource(roots: readonly string[], path: string, offset: numbe
lines = text.split("\n");
what = `文件 ${path},共 ${lines.length} 行、${Buffer.byteLength(text)} 字节`;
}
if (offset > lines.length) throw new Error(`offset=${offset} 超出范围:${what},offset 取 1 到 ${lines.length}。`);
const shown: string[] = [];
let bytes = 0;
for (let i = offset - 1; i < lines.length && shown.length < READ_MAX_LINES; i++) {
const line = lines[i] as string;
for (const line of lines) {
if (shown.length === READ_MAX_LINES) break;
bytes += Buffer.byteLength(line) + 1;
if (bytes > READ_MAX_BYTES) {
// 单独一行就超过上限:只给这一行的开头,并明说。
// 第一行就超过上限:只给这一行的开头,并明说。
if (shown.length === 0) {
const head = Buffer.from(line).subarray(0, READ_MAX_BYTES).toString("utf8");
return `${head}\n\n[截断:第 ${offset} 行超过 ${READ_MAX_BYTES} 字节,只显示了它的开头。${what}。]`;
return `${head}\n\n[截断:第 1 行超过 ${READ_MAX_BYTES} 字节,只显示了它的开头。${what}。]`;
}
break;
}
shown.push(line);
}
const last = offset + shown.length - 1;
if (offset === 1 && last === lines.length) return shown.join("\n");
const next = last < lines.length ? `要读后面,用 offset=${last + 1} 再调一次。` : "已读到结尾。";
return `${shown.join("\n")}\n\n[截断:本次显示第 ${offset}–${last} 行(单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节)。${what}。${next}]`;
if (shown.length === lines.length) return shown.join("\n");
return `${shown.join("\n")}\n\n[截断:只显示了前 ${shown.length} 行(单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节)。${what}。]`;
}
/**
@@ -198,8 +195,6 @@ function text(value: string) {
return { content: [{ type: "text" as const, text: value }], details: {} };
}
const OFFSET = Type.Optional(Type.Integer({ minimum: 1, description: "从第几行开始读,默认 1" }));
/** 这个任务的自定义工具(实验员还有换过环境的 bash)。Pi 自带的其余几个由 Pi 按白名单自己装。 */
export function buildTools(task: SessionTask, state: SubmitState, host: Host): ToolDefinition[] {
const tools: ToolDefinition[] = [];
@@ -312,9 +307,9 @@ export function buildTools(task: SessionTask, state: SubmitState, host: Host): T
label: "读代码与资料",
description:
`读取代码或资料文件,或列出一个目录。path 用绝对路径,必须在这些目录里:${roots.join("、")}。` +
`单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节,超出时会告诉你用 offset 从第几行接着读。`,
parameters: Type.Object({ path: Type.String({ description: "绝对路径" }), offset: OFFSET }, { additionalProperties: false }),
execute: async (_id, params) => text(readSource(roots, params.path, params.offset ?? 1)),
`单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节,超出的部分不显示,结尾会写明截断。`,
parameters: Type.Object({ path: Type.String({ description: "绝对路径" }) }, { additionalProperties: false }),
execute: async (_id, params) => text(readSource(roots, params.path)),
}));
}