Files
2026-11-12 15:29:12 +08:00

103 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 四个级别用同一个 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;
}