- 打通 Worker → WorkerManager → Provider 的 tools 传递链路,ProviderManager/adapter 返回结构化 tool_calls 给 WorkerRuntime - OpenAI-compatible/Anthropic adapter 发送工具 schema,并解析 provider 返回的 tool_calls/tool_use;OpenAI 工具名使用 fs.write ↔ fs__write 双向映射 - 修复独立复审发现的 OpenAI 协议隐患:assistant tool_use blocks 必须转换为 assistant.tool_calls,后续 role=tool 消息的 tool_call_id 必须匹配前一轮 tool_calls[].id;不再把 tool_use JSON 字符串化为普通文本 - ExecutorRole 优先消费原生 tool_calls,回灌 canonical tool_result block;移除 fs.write(...)/shell.run(...) 函数调用正则解析,只保留严格 JSON tool_call fallback 与 filename code block 兼容 - DONE 前执行 verification-before-completion:任务要求 build/compile/run/test/编译/ 运行/测试时必须实际 shell.run 验证,失败不 checkpoint、不返回 completed - fs.write 覆盖已有文件也强制 read-before-write,补齐 Claude Code 文件状态纪律 - 新增 packages/workers/test/executor-role.test.ts 行为测试:原生 tool_calls 执行、 verification 失败不得 completed 真实验收: - TSC=0 - bun test packages/workers/test/executor-role.test.ts: 2 pass / 0 fail - OpenAI converter 探针确认 assistant.tool_calls 与 role=tool 的 tool_call_id 匹配 - 真实 GLM Worker C++ 编译运行任务通过,worker verification 记录实际命令: c++ hello.cpp -o /tmp/aircoding-verify && /tmp/aircoding-verify Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
2.4 KiB
TypeScript
Executable File
71 lines
2.4 KiB
TypeScript
Executable File
import { describe, expect, test } from 'bun:test'
|
|
import { ExecutorRole } from '../src/roles/ExecutorRole.js'
|
|
|
|
class NativeToolRuntime {
|
|
turns = 0
|
|
calls: Array<{ name: string; args: Record<string, unknown> }> = []
|
|
emit() {}
|
|
heartbeat() {}
|
|
checkpoint() {}
|
|
async call_llm() {
|
|
this.turns++
|
|
if (this.turns === 1) {
|
|
return { content: '', tool_calls: [{ id: 'tu_1', name: 'fs.write', arguments: { path: 'a.txt', content: 'X' } }] }
|
|
}
|
|
return { content: 'DONE' }
|
|
}
|
|
async call_tool(name: string, args: Record<string, unknown>) {
|
|
this.calls.push({ name, args })
|
|
return { call_id: 'c', type: 'text' as const, content: { ok: true } }
|
|
}
|
|
}
|
|
|
|
class VerificationFailRuntime {
|
|
turns = 0
|
|
checkpointed = false
|
|
emit() {}
|
|
heartbeat() {}
|
|
checkpoint() { this.checkpointed = true }
|
|
async call_llm() {
|
|
this.turns++
|
|
if (this.turns === 1) {
|
|
return { content: '', tool_calls: [{ id: 'tu_main', name: 'fs.write', arguments: { path: 'main.cpp', content: 'int main(){return 0;}' } }] }
|
|
}
|
|
return { content: 'DONE' }
|
|
}
|
|
async call_tool(name: string, _args: Record<string, unknown>) {
|
|
if (name === 'fs.write') return { call_id: 'w', type: 'text' as const, content: { ok: true } }
|
|
if (name === 'shell.run') return { call_id: 's', type: 'error' as const, content: { exit_code: 1, stderr: 'compile failed' } }
|
|
return { call_id: 'x', type: 'error' as const, content: { message: 'unexpected tool' } }
|
|
}
|
|
}
|
|
|
|
describe('ExecutorRole structured tool execution', () => {
|
|
test('executes native tool_calls instead of regex text parsing', async () => {
|
|
const runtime = new NativeToolRuntime()
|
|
const result = await new ExecutorRole(runtime as any).run({
|
|
id: 't1',
|
|
title: 'create file',
|
|
description: 'create a file',
|
|
acceptance_criteria: ['file exists'],
|
|
})
|
|
|
|
expect(result.status).toBe('completed')
|
|
expect(runtime.calls).toEqual([{ name: 'fs.write', args: { path: 'a.txt', content: 'X' } }])
|
|
})
|
|
|
|
test('does not complete executable tasks when verification fails', async () => {
|
|
const runtime = new VerificationFailRuntime()
|
|
const result = await new ExecutorRole(runtime as any).run({
|
|
id: 't2',
|
|
title: 'compile cpp',
|
|
description: 'write and compile C++',
|
|
acceptance_criteria: ['must compile'],
|
|
})
|
|
|
|
expect(result.status).not.toBe('completed')
|
|
expect(runtime.checkpointed).toBe(false)
|
|
expect(result.changes).toEqual([{ file: 'main.cpp', type: 'create' }])
|
|
})
|
|
})
|