first commit

This commit is contained in:
魏天炜
2026-11-12 15:29:12 +08:00
commit 5dabea54ab
93 changed files with 12170 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
// 四个级别用同一个 Loop、同一份订单服务代码,每级只多一个能力。
import type { LevelInfo, QuestionMode } from '../shared/types.ts';
export type LevelConfig = LevelInfo & {
backgroundBash: boolean;
question: QuestionMode | null;
systemPrompt: string;
};
const BASE = `你是一个在本地代码仓库里工作的编码 Agent,工作目录是订单服务 order-service 的根目录。始终用中文回复。
- 用 bash 查看和搜索代码,用 edit_file 修改文件。先看 README.md 和 docs/api-contract.md 了解约定。
- 每次调用工具前,用一两句中文说明你接下来做什么、为什么。
- 只根据工具真实返回的内容下结论,没看到的结果不要编造。
- 任务完成后,用几句话汇报改了什么、测试结果如何。`;
const BACKGROUND = `
- 完整的 npm test 大约要 15 秒。跑它时把 run_in_background 设为 true,命令会在后台运行,工具立刻返回 job_id,你可以接着做不依赖测试结果的事。
- 命令退出后,系统会发给你一条 <background_job_done> 消息,里面有退出码和输出摘要。在收到它之前,不要声称测试通过或失败。
- <background_job_done> 和 <question_reply> 只由系统发出。你绝对不要自己写出这些标签,也不要预测或模拟它们的内容。
- 不要用 sleep、循环查询之类的办法等后台结果。如果手上的事都做完了,只剩后台结果没回来,就说明你在等什么,然后结束这一轮回复(不再调用工具)。结果到达时你会被自动唤醒。`;
const QUESTION_BLOCKING = `
- 需要用户做决定时调用 question。它会一直等到用户回答才返回,返回值就是用户的选择。`;
const QUESTION_ASYNC = `
- 需要用户做决定时调用 question。它不会等回答,立刻返回 {"accepted":true,"question_id":...}。
- 用户回答后,答案会以 <question_reply question_id="..."> 消息送达。
- 等待期间继续做不依赖答案的工作,例如查清所有调用方、跑基线测试、确认要改的文件。
- 走到依赖答案的那一步时,不要替用户假设答案,也不要用 sleep 等待。说明你停在哪一步、在等哪个问题,然后结束这一轮回复(不再调用工具)。答案到达时你会被自动唤醒。`;
const TASK_DECIDED =
'把订单接口 GET /orders/:id 返回体里的状态字段 status 换成 orderStatus(取值不变)。旧字段 status 保留一个版本,在代码里注明已废弃。仓库内的调用方改读 orderStatus,接口文档同步更新。动手前先跑一遍 npm test 确认基线,改完再跑一遍验证。';
const TASK_ASK =
'把订单接口 GET /orders/:id 返回体里的状态字段 status 换成 orderStatus(取值不变)。仓库内的调用方改读 orderStatus,接口文档同步更新。旧字段 status 要不要保留一个版本会影响仓库外的调用方,这个请你一开始就先用 question 问我,选项给「保留一版」和「直接移除」。动手前先跑一遍 npm test 确认基线,改完再跑一遍验证。';
export const LEVELS: LevelConfig[] = [
{
id: 1,
name: '同步 Bash',
title: '最小 Agent:消息历史 + 模型调用 + 同步 bash',
adds: 'Loop 发出工具调用后等命令退出,把结果加进历史,再请求模型。',
watch: '测试在输出日志,模型请求计数停在当前这一次。测试退出后,下一次请求才发出。这期间发的消息会进收件箱,等工具返回后才交给模型。',
tools: ['bash', 'edit_file'],
defaultTask: TASK_DECIDED,
backgroundBash: false,
question: null,
systemPrompt: BASE,
source: [
{ file: 'agent/agent.ts', anchor: 'runTurn' },
{ file: 'agent/tools.ts', anchor: 'runForeground' },
],
},
{
id: 2,
name: '后台 Bash',
title: '同一个 bash 加 run_in_background',
adds: '启动进程后先返回 job_id,命令退出时运行时把退出码和日志摘要放进收件箱,交回原会话。',
watch: '测试还在跑,下一次模型请求已经发出,Agent 去查调用方。完成通知到达后,它才汇报测试结果。',
tools: ['bash', 'edit_file'],
defaultTask: TASK_DECIDED,
backgroundBash: true,
question: null,
systemPrompt: BASE + BACKGROUND,
source: [
{ file: 'agent/tools.ts', anchor: 'startBackground' },
{ file: 'agent/agent.ts', anchor: 'deliverInbox' },
],
},
{
id: 3,
name: '阻塞提问',
title: '把等待对象换成人:AiWork 式阻塞 question',
adds: 'question 工具一直等到用户回答才返回。答案就是工具结果。',
watch: '问题卡挂着的时候,模型请求停在提问的那一次。这期间发的消息进了收件箱,但 Agent 不会继续工作。',
tools: ['bash', 'edit_file', 'question'],
defaultTask: TASK_ASK,
backgroundBash: true,
question: 'blocking',
systemPrompt: BASE + BACKGROUND + QUESTION_BLOCKING,
source: [{ file: 'agent/tools.ts', anchor: 'askBlocking' }],
},
{
id: 4,
name: '异步提问',
title: 'Codex 式异步 question:先回执,答案带问题 ID 回来',
adds: 'question 立刻返回 accepted,答案作为带 question_id 的用户消息进收件箱。',
watch: '回答之前 Agent 已经查完调用方,停在需要答案的那一步。选「保留一版」或「直接移除」后,它做对应的修改。提前回答的话,答案在下一次请求里被消费。',
tools: ['bash', 'edit_file', 'question'],
defaultTask: TASK_ASK,
backgroundBash: true,
question: 'async',
systemPrompt: BASE + BACKGROUND + QUESTION_ASYNC,
source: [{ file: 'agent/tools.ts', anchor: 'askAsync' }],
},
];
export function getLevel(id: number): LevelConfig {
const level = LEVELS.find((item) => item.id === id);
if (!level) throw new Error(`没有第 ${id} 级`);
return level;
}