103 lines
5.9 KiB
TypeScript
103 lines
5.9 KiB
TypeScript
// 四个级别用同一个 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;
|
||
}
|