Files
Genarrative/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs
T
kdletters e1d031b86f 增加外部API的MCP与异步生成模式
新增托管式Streamable HTTP MCP端点并复用外部API Key鉴权
统一图片视频音频等生成请求为幂等异步提交和状态查询
提供可发现的使用说明、OpenAPI资源及完整Skill下载包
同步更新OpenAPI契约并规定接口变更必须连带维护
适配AI游戏创作Shell的异步提交轮询与安全重试
补齐MCP方法映射、异步Worker和客户端回归测试
2026-07-31 19:48:28 +08:00

2695 lines
95 KiB
JavaScript
Raw 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.
import http from 'node:http';
import { deflateSync } from 'node:zlib';
const LOOPBACK_HOST = '127.0.0.1';
const DEFAULT_FALLBACK_PORTS = Object.freeze(
Array.from({ length: 128 }, (_, index) => 62_128 + index),
);
const CHAT_COMPLETIONS_PATH = '/v1/chat/completions';
const MAX_REQUEST_BYTES = 16 * 1024 * 1024;
const PNG_SIGNATURE = Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]);
const PNG_CRC_TABLE = Object.freeze(
Array.from({ length: 256 }, (_, value) => {
let crc = value;
for (let bit = 0; bit < 8; bit += 1) {
crc = (crc & 1) !== 0 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
}
return crc >>> 0;
}),
);
export const deterministicLaneDefenseModel =
'deterministic-lane-defense-provider-v1';
export const deterministicManifestReadyAgentIds = Object.freeze([
'design-director',
'design-foundation',
'balance-director',
'balance-seed',
'art-director',
'art-asset-plan',
'art-polish',
'audio-director',
'audio-asset-plan',
'code-director',
'code-prototype',
'quality-review',
'preview-readiness',
'preview-playtest',
'publish-strategy',
'publish-package',
]);
const deterministicReadOnlyReadyAgentIds = new Set([
'design-director',
'balance-director',
'art-director',
'art-polish',
'audio-director',
'code-director',
'quality-review',
'preview-readiness',
'preview-playtest',
'publish-strategy',
]);
const deterministicProjectMutationTools = new Set([
'file.write',
'file.patch',
'file.delete',
'project.patchset',
'project.restore',
'command.exec',
'command.start',
'canvas.asset_generate',
]);
export const hiddenCanvasCss = '#game{display:none;';
export const visibleCanvasCss = '#game{display:block;';
function pngCrc32(bytes) {
let crc = 0xffffffff;
for (const byte of bytes) {
crc = PNG_CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}
function pngChunk(type, data) {
const typeBytes = Buffer.from(type, 'ascii');
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length);
const crc = Buffer.alloc(4);
crc.writeUInt32BE(pngCrc32(Buffer.concat([typeBytes, data])));
return Buffer.concat([length, typeBytes, data, crc]);
}
function deterministicPng(width, height) {
const stride = width * 4 + 1;
const pixels = Buffer.alloc(stride * height);
for (let y = 0; y < height; y += 1) {
const row = y * stride;
pixels[row] = 0;
for (let x = 0; x < width; x += 1) {
const offset = row + 1 + x * 4;
const tile = (Math.floor(x / 48) + Math.floor(y / 48)) % 6;
pixels[offset] = (42 + x + tile * 29) % 256;
pixels[offset + 1] = (86 + y * 2 + tile * 17) % 256;
pixels[offset + 2] = (118 + x + y + tile * 31) % 256;
pixels[offset + 3] = 255;
}
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(width, 0);
ihdr.writeUInt32BE(height, 4);
ihdr[8] = 8;
ihdr[9] = 6;
return Buffer.concat([
PNG_SIGNATURE,
pngChunk('IHDR', ihdr),
pngChunk('IDAT', deflateSync(pixels, { level: 6 })),
pngChunk('IEND', Buffer.alloc(0)),
]);
}
export function deterministicLaneDefenseInitialHtml() {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>灵露花园</title>
<style>
*{box-sizing:border-box}body{margin:0;min-height:100vh;background:#f4f8ee;color:#18351f;font:16px system-ui,sans-serif}main{width:min(960px,100%);margin:auto;padding:18px}h1{margin:0 0 4px;font-size:clamp(28px,7vw,46px)}p{margin:4px 0 14px}.toolbar,.plants{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}button{min-height:44px;border:1px solid #315d35;background:#fff;color:#18351f;padding:9px 14px;font:inherit;font-weight:700;cursor:pointer}button:hover{background:#e6f3dc}.board{display:grid;gap:10px;background:#d9edc8;border:2px solid #315d35;padding:10px}.lane{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}.cell{min-height:54px;background:#eef8e7}.status{font-weight:700;min-height:24px}#game{display:none;width:100%;height:auto;aspect-ratio:20/9;background:#18351f;border:2px solid #315d35}@media(max-width:520px){main{padding:12px}button{flex:1 1 44%}.cell{min-height:44px}}
</style>
</head>
<body><main><img src="assets/art-spritesheet.png" alt="Garden defenders" width="96" height="96">
<h1>灵露花园</h1><p>GENARRATIVE_REAL_E2E_VISIBLE</p><p>Goal: defend the garden and win every wave.</p>
<div class="toolbar"><button data-playtest-id="start">Start Game</button><button data-playtest-id="speed-up">Speed Up</button><button data-playtest-id="next-level">Next Level</button><button data-playtest-id="restart">Restart</button></div>
<div class="plants"><button data-playtest-id="defender-option">露华花</button><button id="thorn">棘刺芽</button></div>
<div class="board"><div class="lane"><button class="cell" data-playtest-id="lane-cell" aria-label="Place defender"></button><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div><div class="status" id="status">Level 1 ready</div><canvas id="game" width="800" height="360"></canvas></div>
<script id="playable-web-game-state" type="application/json">{"schemaVersion":"playable-web-game-state.v1","sequence":1,"phase":"ready","level":1,"selectedDefenderId":null,"defenders":[],"enemies":[]}</script>
<script>
const surface=document.getElementById('playable-web-game-state'),statusEl=document.getElementById('status'),canvas=document.getElementById('game'),ctx=canvas.getContext('2d');
let state={schemaVersion:'playable-web-game-state.v1',sequence:1,phase:'ready',level:1,selectedDefenderId:null,defenders:[],enemies:[]},timers=[];
function publish(label){state.sequence+=1;surface.textContent=JSON.stringify(state);statusEl.textContent=label||('Level '+state.level+' '+state.phase);}
function stopTimers(){timers.forEach(clearTimeout);timers=[];}
function reset(keepLevel=true){stopTimers();const level=keepLevel?state.level:1;state={schemaVersion:'playable-web-game-state.v1',sequence:state.sequence,phase:'ready',level,selectedDefenderId:null,defenders:[],enemies:[]};publish('Level '+level+' ready');}
document.querySelector('[data-playtest-id="start"]').onclick=()=>{stopTimers();state.phase='playing';state.enemies=[{id:'gloomling-1',lane:0,position:92,health:100,maxHealth:100}];publish('The first wave is moving');};
document.querySelector('[data-playtest-id="defender-option"]').onclick=()=>{state.selectedDefenderId='nectar-bloom';publish('Nectar Bloom selected');};
document.getElementById('thorn').onclick=()=>{state.selectedDefenderId='thorn-sentry';publish('Thorn Sentry selected');};
document.querySelector('[data-playtest-id="lane-cell"]').onclick=()=>{if(!state.selectedDefenderId)return;state.defenders.push({id:state.selectedDefenderId+'-'+(state.defenders.length+1),lane:0});publish('Defender placed');};
document.querySelector('[data-playtest-id="speed-up"]').onclick=()=>{if(state.phase!=='playing')return;stopTimers();timers.push(setTimeout(()=>{state.enemies[0].position=72;state.enemies[0].health=65;publish('Battle in progress');},120));timers.push(setTimeout(()=>{state.enemies[0].position=48;state.enemies[0].health=20;publish('Gloomling nearly defeated');},300));timers.push(setTimeout(()=>{state.enemies=[];state.phase='won';publish('Level '+state.level+' complete');},560));};
document.querySelector('[data-playtest-id="next-level"]').onclick=()=>{stopTimers();state.level+=1;state.phase='ready';state.selectedDefenderId=null;state.defenders=[];state.enemies=[];publish('Level '+state.level+' ready');};
document.querySelector('[data-playtest-id="restart"]').onclick=()=>reset(true);
function draw(time){ctx.fillStyle='#173b23';ctx.fillRect(0,0,800,360);for(let i=0;i<5;i++){ctx.fillStyle=i%2?'#75ad52':'#86bf5f';ctx.fillRect(i*160,0,158,360)}ctx.fillStyle='#ffe66d';ctx.beginPath();ctx.arc(90,80,30,0,Math.PI*2);ctx.fill();ctx.fillStyle='#fafafa';ctx.font='bold 28px system-ui';ctx.fillText('灵露花园',230,60);ctx.fillStyle='#dbeafe';ctx.fillRect(230+Math.sin(time/350)*30,130,90,100);requestAnimationFrame(draw)}requestAnimationFrame(draw);
</script></main></body></html>
`;
}
const deterministicLaneDefenseBalance = Object.freeze({
schemaVersion: 'lane-defense-balance.v1',
startingSun: 150,
defenderCosts: { 'nectar-bloom': 50, 'thorn-sentry': 100 },
enemyHealth: 100,
enemySpeed: 20,
waveTimingsMs: [120, 300, 560],
scorePerEnemy: 100,
levelDifficultyMultiplier: 1.18,
});
export function deterministicLaneDefenseCanonicalHtml() {
const upstreamContract = JSON.stringify({
schemaVersion: 'lane-defense-upstream-contract.v1',
design: 'game/game_design.md',
balance: 'game/balance.json',
art: 'assets/manifest.art.json',
audio: 'assets/manifest.audio.json',
});
const balance = JSON.stringify(deterministicLaneDefenseBalance);
return deterministicLaneDefenseInitialHtml()
.replace(hiddenCanvasCss, visibleCanvasCss)
.replace(
'<body><main>',
'<body><main data-project-contract="game-design+balance+art+audio">',
)
.replace(
'<script id="playable-web-game-state"',
`<script id="game-project-contract" type="application/json">${upstreamContract}</script>\n<script id="playable-web-game-state"`,
)
.replace(
"let state={schemaVersion:'playable-web-game-state.v1'",
`const BALANCE=${balance};\nlet state={schemaVersion:'playable-web-game-state.v1'`,
)
.replace(
'health:100,maxHealth:100',
'health:BALANCE.enemyHealth,maxHealth:BALANCE.enemyHealth',
)
.replace('},120));', '},BALANCE.waveTimingsMs[0]));')
.replace('},300));', '},BALANCE.waveTimingsMs[1]));')
.replace('},560));', '},BALANCE.waveTimingsMs[2]));');
}
const deterministicProjectMemory = `# 灵露花园项目记忆
## 项目目标
制作一个移动端优先、桌面端同样可玩的单页植物塔防游戏。玩家通过选择并放置植物阻挡敌人,完成波次后进入下一关。
## 长期约束
- 保持 lane-defense-v1 状态面和固定试玩控件稳定。
- 游戏入口是自包含的 \`game/index.html\`,通过本地 HTTP server 运行。
- 数值来自 \`game/balance.json\`,美术和声音范围分别由对应 manifest 描述。
- 每次改动都必须保持开始、选择、放置、战斗、胜利、下一关和重开链路可用。
`;
const deterministicGameDesign = `# 灵露花园游戏设计
## 核心循环
选择原创守卫,消耗灵露放入横向战场,观察敌人推进与受伤,在敌人抵达花园前清空当前波次。
## 胜负条件
- 当前波次敌人全部被击败时获胜并解锁下一关。
- 敌人越过最左侧防线时失败;重开保留当前关卡并重置局内状态。
## 第一版关卡
第一关用于教学选择、放置和加速战斗。后续关卡提高敌人生命和推进节奏,但继续使用相同状态机与输入边界。
## 操作与界面
横屏界面包含资源区、植物选择区、五格战场、波次状态和开始、加速、下一关、重开按钮。移动端按钮保持至少 44px 的点击高度。
`;
const deterministicAudioManifest = Object.freeze({
schemaVersion: 'game-audio-manifest.v1',
bgm: [
{
id: 'garden-day-loop',
usage: '关卡循环背景音乐',
mood: '轻快、明亮、逐步紧张',
loop: true,
status: 'planned',
},
],
sfx: [
{ id: 'plant-select', usage: '选择植物', status: 'planned' },
{ id: 'plant-place', usage: '放置植物', status: 'planned' },
{ id: 'enemy-hit', usage: '敌人受伤', status: 'planned' },
{ id: 'wave-win', usage: '波次胜利', status: 'planned' },
],
});
const deterministicPublishReadme = `# 灵露花园
一款轻量、可快速上手的横向植物塔防网页游戏。选择植物、布置防线并击退逐步增强的敌人波次。
## 操作
1. 点击 Start Game 开始当前关卡。
2. 选择露华花或棘刺芽,再点击战场格放置。
3. 使用 Speed Up 推进战斗,胜利后点击 Next Level。
4. 点击 Restart 重置当前关卡。
## 标签
塔防、策略、休闲、网页游戏、移动端
## 导出检查
- \`game/index.html\` 可通过本地 HTTP server 打开。
- 核心循环、固定控件和双视口试玩已纳入验收。
- 数值、美术和声音 manifest 均随项目交付。
`;
function deterministicArtManifest(
hasVisualAsset,
lifecycle = 'initial-delegation',
) {
return {
schemaVersion: 'game-art-manifest.v1',
lifecycle,
visualDirection: '明亮花园、清晰轮廓、原创植物守卫与敌人',
assets: hasVisualAsset
? [
{
id: 'garden-guardians-spritesheet',
path: 'assets/art-spritesheet.png',
kind: 'art-spritesheet',
usage: ['defenders', 'enemies', 'battlefield-ui'],
source: 'canvas',
status: 'ready',
},
]
: [],
pending: hasVisualAsset
? []
: [
{
id: 'garden-guardians-spritesheet',
path: 'assets/art-spritesheet.png',
reason:
'当前运行未提供 canvas.asset_generate,保留为后续画布生成项',
},
],
};
}
function providerError(code) {
const error = new Error(code);
error.code = code;
return error;
}
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function messageText(message) {
if (typeof message?.content === 'string') return message.content;
if (!Array.isArray(message?.content)) return '';
return message.content
.map((part) => (typeof part?.text === 'string' ? part.text : ''))
.join('\n');
}
function requestContext(payload) {
return Array.isArray(payload.messages)
? payload.messages.map(messageText).join('\n')
: '';
}
function extractIdentity(context) {
const agentId =
context.match(/^- templateAgentId:\s*([a-z0-9-]+)\s*$/imu)?.[1] ??
context.match(/template taskId=([a-z0-9-]+)/iu)?.[1] ??
null;
const runId = context.match(/^- runId:\s*([^\r\n]+?)\s*$/imu)?.[1] ?? null;
if (!agentId || !runId) throw providerError('provider-identity-missing');
return { agentId, runId };
}
function currentPlanSteps(context) {
const marker = '计划进度:';
const markerIndex = context.lastIndexOf(marker);
if (markerIndex < 0) return [];
const steps = [];
for (const line of context
.slice(markerIndex + marker.length)
.split(/\r?\n/u)) {
const match = line.match(/^- #\d+ \[[^\]]+\] (.+)$/u);
if (!match) {
if (steps.length > 0) break;
continue;
}
const title = match[1].split('', 1)[0].trim();
if (title) steps.push(title);
}
return steps;
}
function advertisedToolNames(payload) {
return new Set(
Array.isArray(payload.tools)
? payload.tools
.map((tool) => tool?.function?.name)
.filter((name) => typeof name === 'string' && name.length > 0)
: [],
);
}
function isUiPrototypeInspectionRequest(payload, context) {
const hasInputImage = Array.isArray(payload.messages)
? payload.messages.some((message) =>
Array.isArray(message?.content)
? message.content.some(
(part) =>
part?.type === 'image_url' ||
part?.type === 'input_image' ||
typeof part?.image_url === 'string' ||
typeof part?.image_url?.url === 'string',
)
: false,
)
: false;
return (
hasInputImage &&
context.includes('resourceBar') &&
context.includes('unitCardTray') &&
context.includes('battlefieldGrid') &&
context.includes('enemyEntryDirection') &&
context.includes('waveStatus') &&
context.includes('primaryControls') &&
context.includes('implementationClarity') &&
context.includes('originalTheme')
);
}
function runtimeFunction(tool) {
return `runtime_tool_${tool.replaceAll('.', '_')}`;
}
function nativeAction(tool, reason, input) {
return { name: runtimeFunction(tool), arguments: { reason, input } };
}
function nativeResponse(response) {
return { name: 'respond_to_user', arguments: { response } };
}
function completedPlanCall(explanation, steps) {
return {
name: 'update_agent_plan',
arguments: {
explanation,
steps: steps.map((step) => ({ step, status: 'completed' })),
},
};
}
function completedPlanCallsForContext(context, explanation) {
const steps = currentPlanSteps(context);
return steps.length > 0 ? [completedPlanCall(explanation, steps)] : [];
}
function requireAdvertised(tools, calls) {
for (const call of calls) {
if (!tools.has(call.name)) {
throw providerError(`provider-tool-not-advertised:${call.name}`);
}
}
}
function chatToolResponse(sequence, model, calls) {
return {
id: `chatcmpl-deterministic-${sequence}`,
object: 'chat.completion',
model,
choices: [
{
index: 0,
message: {
role: 'assistant',
content: null,
tool_calls: calls.map((call, index) => ({
id: `call-deterministic-${sequence}-${index + 1}`,
type: 'function',
function: {
name: call.name,
arguments: JSON.stringify(call.arguments),
},
})),
},
finish_reason: 'tool_calls',
},
],
usage: { prompt_tokens: 160, completion_tokens: 48, total_tokens: 208 },
};
}
function chatTextResponse(sequence, model, content) {
return {
id: `chatcmpl-deterministic-${sequence}`,
object: 'chat.completion',
model,
choices: [
{
index: 0,
message: { role: 'assistant', content },
finish_reason: 'stop',
},
],
usage: { prompt_tokens: 120, completion_tokens: 32, total_tokens: 152 },
};
}
function delegateCall(agentId, task, acceptanceCriteria, expectedArtifacts) {
return nativeAction('agent.delegate', `把独立工作交给 ${agentId}`, {
agentId,
task,
acceptanceCriteria,
expectedArtifacts,
repairOfDelegationId: null,
runId: null,
});
}
const projectMutationFunctionNames = new Set(
[
'blackboard.write',
'canvas.asset_generate',
'command.exec',
'command.start',
'file.delete',
'file.patch',
'file.write',
'memory.write',
'project.git_commit',
'project.patchset',
'project.restore',
'task.create',
'task.update',
].map(runtimeFunction),
);
function isGameIndexArtifactContract(expectedArtifacts) {
return (
Array.isArray(expectedArtifacts) &&
expectedArtifacts.length === 1 &&
expectedArtifacts[0] === 'game/index.html'
);
}
function isExplicitReadOnlyQualityTask(task) {
return (
typeof task === 'string' &&
task.includes('只读验收') &&
task.includes('不要修改任何文件')
);
}
function staticSmokeCall(reason) {
return nativeAction('command.run_limited', reason, {
commandId: 'game.static_smoke',
});
}
function previewCall(reason) {
return nativeAction('preview.validate', reason, {
viewports: ['desktop', 'mobile'],
expectedText: [
'灵露花园',
'Start Game',
'露华花',
'棘刺芽',
'Speed Up',
'Next Level',
'Restart',
],
settleMs: 300,
failOnConsoleError: true,
playtestScenario: null,
});
}
function runStatusCall(reason) {
return nativeAction('agent.run_status', reason, {
agentId: null,
scope: 'all',
delegationId: null,
});
}
function repairPatchCall(reason) {
return nativeAction('file.patch', reason, {
path: 'game/index.html',
oldText: hiddenCanvasCss,
newText: visibleCanvasCss,
expectedReplacements: 1,
});
}
function fileReadCall(path, reason) {
return nativeAction('file.read', reason, {
path,
startLine: 1,
maxLines: 120,
});
}
function fileWriteCall(path, content, reason) {
return nativeAction('file.write', reason, { path, content });
}
function assetListCall(reason) {
return nativeAction('asset.list', reason, {});
}
function canvasAssetCall(agentId) {
if (agentId === 'design-foundation') {
return nativeAction('canvas.asset_generate', '生成横屏游戏界面原型', {
prompt:
'原创明亮花园植物塔防游戏的 16:9 完整 UI 原型。画面必须清楚展示资源数值栏、带费用的植物卡槽、五列战场网格、敌人来袭方向、波次状态、开始、加速、下一关和重开控件;适合直接指导 HTML/CSS 实现,不使用任何现有游戏角色、Logo 或受保护视觉语言。',
outputPath: 'assets/ui-prototype.png',
aspectRatio: '16:9',
imageSize: '2K',
assetKind: 'ui-prototype',
assetLabel: '游戏横屏界面原型图',
replaceExisting: false,
});
}
return nativeAction('canvas.asset_generate', '生成首版核心美术素材', {
prompt:
'原创明亮花园塔防游戏的方形 spritesheet。包含露华花守卫、棘刺芽守卫、雾影兽入侵者、晶苔战场地块、投射物和灵露资源图标;透明或纯色分区背景,轮廓清楚、比例一致,可直接切分用于网页游戏,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。',
outputPath: 'assets/art-spritesheet.png',
aspectRatio: '1:1',
imageSize: '1K',
assetKind: 'art-spritesheet',
assetLabel: '游戏首版核心美术素材',
replaceExisting: false,
});
}
function imageInspectCall() {
return nativeAction('image.inspect', '检查 UI 原型是否满足实现合同', {
paths: ['assets/ui-prototype.png'],
question:
'核对资源栏、植物卡槽、战场网格、敌人方向、波次状态、主要控件、实现清晰度和原创主题。',
});
}
function readyTaskContext(context, agentId) {
return (
context.includes('处理 manifest ready 任务:') &&
context.includes(`任务 ID${agentId}`)
);
}
function observationContext(context) {
const start = context.lastIndexOf('已有工具观察:');
if (start < 0) return '';
const tail = context.slice(start + '已有工具观察:'.length);
const end = tail.indexOf('\n\n计划更新约定:');
return end < 0 ? tail : tail.slice(0, end);
}
function latestToolObservation(context) {
const raw = observationContext(context).trim();
if (!raw.startsWith('[')) return null;
try {
const observations = JSON.parse(raw);
return Array.isArray(observations) ? (observations.at(-1) ?? null) : null;
} catch {
return null;
}
}
function revisionBlockedObservation(context) {
const observation = latestToolObservation(context);
return (
observation?.tool === 'runtime.verification' &&
observation?.status === 'blocked' &&
typeof observation?.detail === 'string' &&
observation.detail.includes('responseRevision=') &&
observation.detail.includes('currentRevision=')
);
}
function specialistVerificationRepairContext(context) {
return (
context.includes(
'当前是 autonomous-game-build 的非只读专业任务,且本人 run 已有 mutation',
) &&
context.includes(
'本次修复的原生工具目录只保留 project.verify 与 command.run_limited',
)
);
}
function transientCommandObservation(context) {
const observation = latestToolObservation(context);
const detail = observation?.detail;
return (
observation?.tool === 'command.run_limited' &&
((observation?.status === 'blocked' &&
(detail?.includes('repositoryContextDrift=true') ||
(detail?.includes('projectRevisionDrift=true') &&
detail.includes('replanRequired=true')))) ||
(observation?.status === 'failed' &&
detail?.includes('.agent/project.lock')))
);
}
function successfulProjectMutationObservation(context) {
const observation = latestToolObservation(context);
return (
observation?.status === 'ok' &&
deterministicProjectMutationTools.has(observation?.tool)
);
}
function successfulCommandObservation(context) {
const observation = latestToolObservation(context);
return (
observation?.tool === 'command.run_limited' && observation?.status === 'ok'
);
}
function observationsIncludeAsset(context, path) {
const observations = observationContext(context);
return observations.includes('# 本地项目资产') && observations.includes(path);
}
function completedReadyPlanCalls(context, agentId) {
const existing = completedPlanCallsForContext(
context,
`${agentId} 已完成 manifest ready 任务`,
);
if (existing.length > 0) return existing;
return [
completedPlanCall(`${agentId} 已完成 manifest ready 任务`, [
`完成 ${agentId} 正式任务`,
]),
];
}
function readyTaskFinalizationCalls(context, agentId, tools) {
const calls = [];
if (tools.has('respond_to_user')) {
if (tools.has('update_agent_plan')) {
calls.push(...completedReadyPlanCalls(context, agentId));
}
calls.push(nativeResponse(`${agentId} 的 manifest ready 任务已经完成。`));
}
return calls;
}
export function createDeterministicLaneDefenseRouter({
apiKey,
model = deterministicLaneDefenseModel,
} = {}) {
if (typeof apiKey !== 'string' || apiKey.length < 16) {
throw providerError('provider-api-key-invalid');
}
const runKinds = new Map();
const runPhases = new Map();
const runData = new Map();
const finalReplyRuns = new Set();
const readyTaskRunIdsByAgent = new Map();
const completedReadyTaskRuns = new Set();
const readyTaskCompletionRetryArmedRuns = new Set();
const readyTaskCompletionVerificationTools = new Map();
const readyTaskCompletionRetryCounts = new Map();
const readyTaskPreCompletionRetryCounts = new Map();
const readyTaskReplayAvailableRuns = new Set();
const terminalRetryCounts = new Map();
const terminalVerificationPendingRuns = new Set();
const terminalReplayAvailableRuns = new Set();
const terminalObservationFingerprints = new Map();
const agentCounts = new Map();
const stats = {
requestCount: 0,
contextCompactionRequestCount: 0,
planningRequestCount: 0,
finalReplyRequestCount: 0,
initialDelegationCount: 0,
followupDelegationCount: 0,
runStatusCount: 0,
sourceWriteCount: 0,
sourcePatchCount: 0,
staticSmokeCount: 0,
previewValidationCount: 0,
supervisorDirectMutationAttemptCount: 0,
delegationContractCount: 0,
delegationContractViolationCount: 0,
codePrototypeDelegationCount: 0,
codePrototypeExpectedArtifactCount: 0,
codePrototypeGameIndexArtifactDelegationCount: 0,
qualityReviewDelegationCount: 0,
qualityReviewReadOnlyDelegationCount: 0,
qualityReviewExpectedArtifactCount: 0,
artAssetPlanDelegationCount: 0,
artAssetPlanExpectedArtifactCount: 0,
artAssetPlanArtifactContractCount: 0,
qualityRevisionReplanCount: 0,
manifestReadyTaskRunCount: 0,
manifestReadyTaskCompletionCount: 0,
manifestReadyTaskFileReadCount: 0,
manifestReadyTaskFileWriteCount: 0,
manifestReadyTaskStaticSmokeCount: 0,
manifestReadyTaskPreviewValidationCount: 0,
manifestReadyTaskCanvasGenerationCount: 0,
canonicalCodeRunCount: 0,
imageInspectionRequestCount: 0,
interactionExecuteCount: 0,
readyTaskRunsByAgent: {},
readyTaskCompletionsByAgent: {},
readyTaskCountsByAgent: {},
unexpectedRequestCount: 0,
rejectionCodes: {},
rejections: [],
byAgent: {},
};
let responseSequence = 0;
let parentStage = 'initial-delegation';
let initialBuilderRunId = null;
let repairBuilderRunId = null;
function updateReadyTaskCounts(agentId, kind) {
const current = stats.readyTaskCountsByAgent[agentId] ?? {
run: 0,
completion: 0,
};
current[kind] += 1;
stats.readyTaskCountsByAgent[agentId] = current;
}
function consumeTerminalRetry(agentId, runId) {
const retryCount = terminalRetryCounts.get(runId) ?? 0;
if (retryCount >= 16) {
throw providerError(`provider-terminal-retry-exhausted:${agentId}`);
}
terminalRetryCounts.set(runId, retryCount + 1);
}
function retryTerminalStaticVerification(agentId, runId, tools, reason) {
if (!tools.has(runtimeFunction('command.run_limited'))) {
throw providerError(
`provider-terminal-retry-verification-tool-missing:${agentId}`,
);
}
consumeTerminalRetry(agentId, runId);
terminalReplayAvailableRuns.delete(runId);
terminalVerificationPendingRuns.add(runId);
stats.staticSmokeCount += 1;
return callsResponse(agentId, tools, [staticSmokeCall(reason)]);
}
function consumeTerminalReplayAuthorization(agentId, runId) {
if (terminalVerificationPendingRuns.delete(runId)) return;
if (terminalReplayAvailableRuns.delete(runId)) return;
throw providerError(`provider-terminal-replay-unarmed:${agentId}`);
}
function consumeUniqueTerminalObservation(agentId, runId, context) {
const observation = latestToolObservation(context);
const repairFingerprint = context.includes('当前专业合同明确要求只读交付')
? 'read-only-delivery-repair'
: null;
if (!observation && !repairFingerprint) {
throw providerError(`provider-terminal-observation-missing:${agentId}`);
}
const fingerprint = observation
? JSON.stringify(observation)
: repairFingerprint;
const seen = terminalObservationFingerprints.get(runId) ?? new Set();
if (seen.has(fingerprint)) {
throw providerError(`provider-terminal-observation-duplicate:${agentId}`);
}
consumeTerminalRetry(agentId, runId);
seen.add(fingerprint);
terminalObservationFingerprints.set(runId, seen);
}
function recordReadyTaskRun(agentId, runId) {
if (!deterministicManifestReadyAgentIds.includes(agentId)) {
throw providerError(`provider-ready-agent-unsupported:${agentId}`);
}
const existingRunId = readyTaskRunIdsByAgent.get(agentId);
if (existingRunId && existingRunId !== runId) {
throw providerError(`provider-ready-agent-run-duplicate:${agentId}`);
}
readyTaskRunIdsByAgent.set(agentId, runId);
stats.manifestReadyTaskRunCount += 1;
stats.readyTaskRunsByAgent[agentId] =
(stats.readyTaskRunsByAgent[agentId] ?? 0) + 1;
updateReadyTaskCounts(agentId, 'run');
}
function recordReadyTaskCompletion(agentId, runId) {
if (readyTaskRunIdsByAgent.get(agentId) !== runId) {
throw providerError(`provider-ready-run-identity-invalid:${agentId}`);
}
if (completedReadyTaskRuns.has(runId)) {
throw providerError(`provider-ready-run-terminal-duplicate:${agentId}`);
}
completedReadyTaskRuns.add(runId);
readyTaskReplayAvailableRuns.add(runId);
stats.manifestReadyTaskCompletionCount += 1;
stats.readyTaskCompletionsByAgent[agentId] =
(stats.readyTaskCompletionsByAgent[agentId] ?? 0) + 1;
updateReadyTaskCounts(agentId, 'completion');
}
function ensureReadyTaskRun(runId, agentId) {
const expectedKind = `manifest-ready:${agentId}`;
const existingKind = runKinds.get(runId);
if (!existingKind) {
recordReadyTaskRun(agentId, runId);
runKinds.set(runId, expectedKind);
runPhases.set(runId, 0);
} else if (existingKind !== expectedKind) {
throw providerError(
`provider-ready-run-kind-invalid:${existingKind}:${agentId}`,
);
}
return runPhases.get(runId) ?? 0;
}
function advanceReadyTaskRun(runId, phase) {
runPhases.set(runId, phase + 1);
}
function readyCallsResponse(agentId, runId, tools, calls) {
advanceReadyTaskRun(runId, runPhases.get(runId) ?? 0);
return callsResponse(agentId, tools, calls);
}
function readyTaskCompleteResponse(agentId, runId, tools, context) {
if (completedReadyTaskRuns.has(runId)) {
const retryCount = readyTaskCompletionRetryCounts.get(runId) ?? 0;
const expectedVerificationTool =
readyTaskCompletionVerificationTools.get(runId) ??
(agentId === 'preview-playtest'
? 'preview.validate'
: 'command.run_limited');
if (readyTaskCompletionRetryArmedRuns.has(runId)) {
const observation = latestToolObservation(context);
if (
observation?.tool === expectedVerificationTool &&
observation?.status === 'ok'
) {
if (!tools.has('respond_to_user')) {
throw providerError(
`provider-ready-retry-finalization-tool-missing:${agentId}`,
);
}
readyTaskCompletionRetryArmedRuns.delete(runId);
readyTaskCompletionVerificationTools.delete(runId);
readyTaskReplayAvailableRuns.delete(runId);
return readyCallsResponse(
agentId,
runId,
tools,
readyTaskFinalizationCalls(context, agentId, tools),
);
}
const transientCommandFailure = transientCommandObservation(context);
if (
transientCommandFailure &&
tools.has(runtimeFunction('command.run_limited'))
) {
if (retryCount >= 16) {
throw providerError(
`provider-ready-retry-verification-exhausted:${agentId}`,
);
}
readyTaskCompletionRetryCounts.set(runId, retryCount + 1);
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall(`重试验证 ${agentId} 的当前项目 revision`),
]);
}
throw providerError(
`provider-ready-retry-verification-invalid:${agentId}`,
);
}
const latestObservation = latestToolObservation(context);
const successfulProjectMutation =
successfulProjectMutationObservation(context);
const standaloneSuccessfulVerification =
latestObservation?.tool === expectedVerificationTool &&
latestObservation?.status === 'ok' &&
(agentId === 'preview-readiness' ||
agentId === 'preview-playtest' ||
!deterministicReadOnlyReadyAgentIds.has(agentId));
if (standaloneSuccessfulVerification) {
if (!readyTaskReplayAvailableRuns.delete(runId)) {
throw providerError(
`provider-ready-run-terminal-duplicate:${agentId}`,
);
}
if (!tools.has('respond_to_user')) {
throw providerError(
`provider-ready-retry-finalization-tool-missing:${agentId}`,
);
}
return readyCallsResponse(
agentId,
runId,
tools,
readyTaskFinalizationCalls(context, agentId, tools),
);
}
if (
!revisionBlockedObservation(context) &&
!specialistVerificationRepairContext(context) &&
!transientCommandObservation(context) &&
!successfulProjectMutation
) {
throw providerError(`provider-ready-run-terminal-duplicate:${agentId}`);
}
if (retryCount >= 16) {
throw providerError(`provider-ready-run-terminal-duplicate:${agentId}`);
}
readyTaskCompletionRetryCounts.set(runId, retryCount + 1);
readyTaskReplayAvailableRuns.delete(runId);
let verificationCall = null;
let verificationTool = null;
if (
successfulProjectMutation &&
tools.has(runtimeFunction('command.run_limited')) &&
!deterministicReadOnlyReadyAgentIds.has(agentId)
) {
verificationCall = staticSmokeCall(
`重新验证 ${agentId} 并发修改后的当前项目 revision`,
);
verificationTool = 'command.run_limited';
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
} else if (
agentId === 'preview-playtest' &&
tools.has(runtimeFunction('preview.validate'))
) {
verificationCall = previewCall(
`重新验证 ${agentId} 的当前项目 revision`,
);
verificationTool = 'preview.validate';
stats.previewValidationCount += 1;
stats.manifestReadyTaskPreviewValidationCount += 1;
} else if (
tools.has(runtimeFunction('command.run_limited')) &&
(agentId === 'preview-readiness' ||
!deterministicReadOnlyReadyAgentIds.has(agentId))
) {
verificationCall = staticSmokeCall(
`重新验证 ${agentId} 的当前项目 revision`,
);
verificationTool = 'command.run_limited';
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
}
if (verificationCall) {
readyTaskCompletionRetryArmedRuns.add(runId);
readyTaskCompletionVerificationTools.set(runId, verificationTool);
return readyCallsResponse(agentId, runId, tools, [verificationCall]);
}
if (successfulProjectMutation) {
throw providerError(
`provider-ready-mutation-verification-tool-missing:${agentId}`,
);
}
if (!tools.has('respond_to_user')) {
throw providerError(
`provider-ready-retry-finalization-tool-missing:${agentId}`,
);
}
readyTaskReplayAvailableRuns.delete(runId);
return readyCallsResponse(
agentId,
runId,
tools,
readyTaskFinalizationCalls(context, agentId, tools),
);
}
const preCompletionRetryCount =
readyTaskPreCompletionRetryCounts.get(runId) ?? 0;
const mayRunStaticVerification =
agentId === 'preview-readiness' ||
!deterministicReadOnlyReadyAgentIds.has(agentId);
if (
mayRunStaticVerification &&
tools.has(runtimeFunction('command.run_limited')) &&
(specialistVerificationRepairContext(context) ||
transientCommandObservation(context))
) {
if (preCompletionRetryCount >= 16) {
throw providerError(
`provider-ready-precompletion-verification-exhausted:${agentId}`,
);
}
readyTaskPreCompletionRetryCounts.set(runId, preCompletionRetryCount + 1);
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall(`重试验证 ${agentId} 的当前项目 revision`),
]);
}
const calls = readyTaskFinalizationCalls(context, agentId, tools);
if (calls.length === 0) {
throw providerError(
`provider-ready-finalization-tools-invalid:${agentId}:${[...tools].sort().join(',')}`,
);
}
if (calls.some((call) => call.name === 'respond_to_user')) {
recordReadyTaskCompletion(agentId, runId);
}
return readyCallsResponse(agentId, runId, tools, calls);
}
function manifestTasksCompleted(context) {
const matches = [
...observationContext(context).matchAll(
/seedTaskCounts: completed=(\d+) running=(\d+) pending=(\d+) waiting=(\d+) failed=(\d+) total=(\d+)/g,
),
];
const match = matches.at(-1);
return (
match !== undefined &&
Number(match[1]) === 16 &&
Number(match[2]) === 0 &&
Number(match[3]) === 0 &&
Number(match[4]) === 0 &&
Number(match[5]) === 0 &&
Number(match[6]) === 16
);
}
function publishAgentCounts() {
stats.byAgent = Object.fromEntries(
[...agentCounts.entries()].sort(([left], [right]) =>
left.localeCompare(right),
),
);
}
function getAgentCounts(agentId) {
return (
agentCounts.get(agentId) ?? {
planning: 0,
finalReply: 0,
projectMutation: 0,
}
);
}
function recordAgent(agentId, kind) {
const current = getAgentCounts(agentId);
current[kind] += 1;
agentCounts.set(agentId, current);
publishAgentCounts();
}
function recordEmittedCalls(agentId, calls) {
const current = getAgentCounts(agentId);
current.projectMutation += calls.filter((call) =>
projectMutationFunctionNames.has(call.name),
).length;
agentCounts.set(agentId, current);
publishAgentCounts();
for (const call of calls) {
if (call.name !== runtimeFunction('agent.delegate')) continue;
stats.delegationContractCount += 1;
const input = call.arguments?.input;
const hasExplicitExpectedArtifacts = Array.isArray(
input?.expectedArtifacts,
);
const expectedArtifacts = hasExplicitExpectedArtifacts
? input.expectedArtifacts
: [];
if (input?.agentId === 'code-prototype') {
stats.codePrototypeDelegationCount += 1;
stats.codePrototypeExpectedArtifactCount += expectedArtifacts.length;
if (isGameIndexArtifactContract(expectedArtifacts)) {
stats.codePrototypeGameIndexArtifactDelegationCount += 1;
} else {
stats.delegationContractViolationCount += 1;
}
} else if (input?.agentId === 'quality-review') {
stats.qualityReviewDelegationCount += 1;
stats.qualityReviewExpectedArtifactCount += expectedArtifacts.length;
if (
hasExplicitExpectedArtifacts &&
expectedArtifacts.length === 0 &&
isExplicitReadOnlyQualityTask(input.task)
) {
stats.qualityReviewReadOnlyDelegationCount += 1;
} else {
stats.delegationContractViolationCount += 1;
}
} else if (input?.agentId === 'art-asset-plan') {
stats.artAssetPlanDelegationCount += 1;
stats.artAssetPlanExpectedArtifactCount += expectedArtifacts.length;
if (
hasExplicitExpectedArtifacts &&
expectedArtifacts.length === 2 &&
expectedArtifacts.includes('assets/manifest.art.json') &&
expectedArtifacts.includes('assets/art-spritesheet.png') &&
typeof input?.task === 'string' &&
input.task.includes('生成')
) {
stats.artAssetPlanArtifactContractCount += 1;
} else {
stats.delegationContractViolationCount += 1;
}
} else {
stats.delegationContractViolationCount += 1;
}
}
}
function callsResponse(agentId, tools, calls) {
requireAdvertised(tools, calls);
recordEmittedCalls(agentId, calls);
responseSequence += 1;
return chatToolResponse(responseSequence, model, calls);
}
function parentCalls(context, tools) {
const manifestDagWaitPrompt =
context.includes('当前正式 manifest DAG 仍有专业 task 在运行') &&
tools.has(runtimeFunction('task.list')) &&
tools.has(runtimeFunction('agent.run_status'));
if (manifestDagWaitPrompt) {
parentStage = 'await-manifest';
stats.runStatusCount += 1;
return callsResponse('project-supervisor', tools, [
nativeAction('task.list', '检查正式产物任务图是否已经收敛', {}),
runStatusCall('等待并读取并行专业任务的最新状态'),
]);
}
const delegateRepairPrompt =
(context.includes('当前父 run 已进入只编排模式') &&
context.includes('只保留 agent.delegate')) ||
(parentStage === 'await-delegation-repair' &&
tools.has(runtimeFunction('agent.delegate')));
if (delegateRepairPrompt) {
if (parentStage !== 'await-delegation-repair') {
throw providerError('provider-repair-prompt-out-of-order');
}
parentStage = 'verify-repair';
stats.followupDelegationCount += 1;
return callsResponse('project-supervisor', tools, [
delegateCall(
'code-prototype',
'修复最近一次真实浏览器验证证明的 canvas 不可见问题;保持现有 lane-defense-v1 交互、状态面、关卡推进和重开行为不变,并在修改后通过静态自检。',
[
'game/index.html 的 canvas 在桌面与移动视口可见且有非空像素',
'lane-defense-v1 全部固定试玩断言继续通过',
'修改后的当前 revision 通过 game.static_smoke',
],
['game/index.html'],
),
]);
}
switch (parentStage) {
case 'initial-delegation': {
parentStage = 'claim-initial';
stats.initialDelegationCount += 3;
return callsResponse('project-supervisor', tools, [
delegateCall(
'code-prototype',
'生成一个紧凑但完整的植物塔防网页游戏:支持开始、选择植物、放置防御单位、敌人移动和受伤、胜利、下一关与重开;写入 game/index.html,并在修改后通过静态自检。',
[
'game/index.html 提供完整 lane-defense-v1 状态面和六个可点击控件',
'敌人会移动并受伤,关卡可胜利、进入下一关并重开',
'修改后的当前 revision 通过 game.static_smoke',
],
['game/index.html'],
),
delegateCall(
'quality-review',
'执行只读验收:独立核对塔防交付合同是否覆盖植物选择、战斗、胜利、下一关、重开、桌面和移动视口;只返回合同验收重点,不要修改任何文件,也不要读取或依赖并行 code-prototype 尚未完成的项目产物。',
[
'验收结论覆盖完整可玩循环和双视口',
'只读返回合同重点且项目 mutation 为零',
],
[],
),
delegateCall(
'art-asset-plan',
'生成原创首版核心美术素材并写入正式资产清单;必须调用 canvas.asset_generate 生成 assets/art-spritesheet.png,并写入可解析的 assets/manifest.art.json,不得用占位文件代替。',
[
'assets/art-spritesheet.png 是可解码的原创 1:1 PNG',
'assets/manifest.art.json 可解析并登记该图片',
'生成结果已通过资产列表核对',
],
['assets/manifest.art.json', 'assets/art-spritesheet.png'],
),
]);
}
case 'claim-initial':
parentStage = 'static-initial';
stats.runStatusCount += 1;
return callsResponse('project-supervisor', tools, [
runStatusCall('认领两份首轮专业回执'),
]);
case 'static-initial':
parentStage = 'preview-initial';
stats.staticSmokeCount += 1;
return callsResponse('project-supervisor', tools, [
staticSmokeCall('验证初版当前 revision'),
]);
case 'preview-initial':
parentStage = 'attempt-forbidden-repair';
stats.previewValidationCount += 1;
return callsResponse('project-supervisor', tools, [
previewCall('真实试玩并检查桌面与移动视口'),
]);
case 'attempt-forbidden-repair':
if (observationContext(context).includes('浏览器验证已通过')) {
parentStage = 'respond';
return parentCalls(context, tools);
}
parentStage = 'await-delegation-repair';
stats.supervisorDirectMutationAttemptCount += 1;
return callsResponse('project-supervisor', tools, [
repairPatchCall('根据浏览器失败诊断直接修复不可见 canvas'),
]);
case 'verify-repair':
parentStage = 'await-manifest';
stats.runStatusCount += 1;
return callsResponse('project-supervisor', tools, [
runStatusCall('先认领返工后的专业交付,再验证最新 revision'),
]);
case 'await-manifest':
if (!tools.has(runtimeFunction('task.list'))) {
if (tools.has(runtimeFunction('command.run_limited'))) {
stats.staticSmokeCount += 1;
return callsResponse('project-supervisor', tools, [
staticSmokeCall('刷新并行产物修改后的当前 revision 验证凭证'),
]);
}
if (tools.has(runtimeFunction('preview.validate'))) {
parentStage = 'respond';
stats.previewValidationCount += 1;
return callsResponse('project-supervisor', tools, [
previewCall('对最终 current revision 完成双视口交互试玩'),
]);
}
}
if (!manifestTasksCompleted(context)) {
stats.runStatusCount += 1;
return callsResponse('project-supervisor', tools, [
nativeAction('task.list', '检查正式产物任务图是否已经收敛', {}),
runStatusCall('等待并读取并行专业任务的最新状态'),
]);
}
parentStage = 'verify-final';
return parentCalls(context, tools);
case 'verify-final':
parentStage = 'respond';
stats.staticSmokeCount += 1;
stats.previewValidationCount += 1;
return callsResponse('project-supervisor', tools, [
staticSmokeCall('验证全部正式产物整合后的当前 revision'),
previewCall('对最终 current revision 重跑完整真实试玩和双视口检查'),
]);
case 'respond':
parentStage = 'done';
return callsResponse('project-supervisor', tools, [
...completedPlanCallsForContext(
context,
'可试玩项目和全部验证已经完成',
),
nativeResponse(
'可试玩塔防版本已经完成,并通过静态检查、完整交互试玩以及桌面和移动视口验证。',
),
]);
case 'done':
if (tools.has(runtimeFunction('task.list'))) {
parentStage = 'await-manifest';
return parentCalls(context, tools);
}
if (
tools.has(runtimeFunction('command.run_limited')) &&
tools.has(runtimeFunction('preview.validate'))
) {
parentStage = 'verify-final';
return parentCalls(context, tools);
}
if (tools.has('respond_to_user')) {
return callsResponse('project-supervisor', tools, [
nativeResponse(
'可试玩塔防版本已经完成,并通过正式产物检查、静态检查和双视口交互试玩。',
),
]);
}
throw providerError('provider-parent-done-tools-invalid');
default:
throw providerError(`provider-parent-stage-invalid:${parentStage}`);
}
}
function readOnlyReadyCalls(agentId, runId, tools, context) {
const phase = ensureReadyTaskRun(runId, agentId);
const taskListCall = nativeAction(
'task.list',
'读取 manifest 任务图并核对专业边界',
{},
);
const batches = {
'design-director': [[taskListCall]],
'balance-director': [
[
fileReadCall('game/game_design.md', '读取玩法规格以确定数值口径'),
fileReadCall('memory/project.md', '读取长期项目约束'),
],
],
'art-director': [
[
fileReadCall('game/game_design.md', '读取玩法规格以确定视觉方向'),
assetListCall('核对当前已有美术资产'),
],
],
'art-polish': [
[
fileReadCall(
'assets/manifest.art.json',
'检查美术清单是否覆盖首版范围',
),
assetListCall('核对清单与真实项目资产'),
],
],
'audio-director': [
[fileReadCall('game/game_design.md', '读取玩法规格以确定声音方向')],
],
'code-director': [
[
fileReadCall('game/game_design.md', '读取玩法与界面规格'),
fileReadCall('game/balance.json', '读取程序必须消费的数值'),
fileReadCall('assets/manifest.art.json', '读取美术资产范围'),
],
[
fileReadCall('assets/manifest.audio.json', '读取声音事件范围'),
fileReadCall('game/index.html', '核对当前程序入口与整合边界'),
],
],
'publish-strategy': [
[
fileReadCall('game/game_design.md', '读取玩法卖点与操作说明'),
fileReadCall('game/index.html', '核对最终可玩入口'),
fileReadCall('assets/manifest.art.json', '核对发布素材范围'),
],
],
};
const agentBatches = batches[agentId];
if (!agentBatches) {
throw providerError(`provider-read-only-agent-unsupported:${agentId}`);
}
if (phase < agentBatches.length) {
const calls = agentBatches[phase];
stats.manifestReadyTaskFileReadCount += calls.filter(
(call) => call.name === runtimeFunction('file.read'),
).length;
return readyCallsResponse(agentId, runId, tools, calls);
}
if (phase === agentBatches.length) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= agentBatches.length + 1) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(
`provider-ready-read-only-stage-invalid:${agentId}:${phase}`,
);
}
function designFoundationReadyCalls(runId, tools, context) {
const agentId = 'design-foundation';
const phase = ensureReadyTaskRun(runId, agentId);
const hasCanvas = tools.has(runtimeFunction('canvas.asset_generate'));
if (phase === 0) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
'memory/project.md',
deterministicProjectMemory,
'写入稳定项目目标与长期约束',
),
]);
}
if (phase === 1) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
'game/game_design.md',
deterministicGameDesign,
'写入核心循环、胜负条件、关卡与界面规格',
),
]);
}
if (phase === 2) {
if (hasCanvas) {
stats.manifestReadyTaskCanvasGenerationCount += 1;
runData.set(runId, { hasCanvas: true });
return readyCallsResponse(agentId, runId, tools, [
canvasAssetCall(agentId),
]);
}
const calls = [
fileReadCall('memory/project.md', '回读项目记忆产物'),
fileReadCall('game/game_design.md', '回读玩法规格产物'),
staticSmokeCall('验证设计产物写入后的当前 revision'),
];
stats.manifestReadyTaskFileReadCount += 2;
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
runData.set(runId, { hasCanvas: false });
return readyCallsResponse(agentId, runId, tools, calls);
}
if (phase === 3) {
const data = runData.get(runId) ?? { hasCanvas: false };
if (data.hasCanvas) {
stats.manifestReadyTaskFileReadCount += 2;
return readyCallsResponse(agentId, runId, tools, [
imageInspectCall(),
fileReadCall('memory/project.md', '回读项目记忆产物'),
fileReadCall('game/game_design.md', '回读玩法规格产物'),
]);
}
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase === 4) {
const data = runData.get(runId) ?? { hasCanvas: false };
if (data.hasCanvas) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 5) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(`provider-ready-design-stage-invalid:${phase}`);
}
function verifiedFileWriterReadyCalls(
agentId,
runId,
tools,
context,
{ path, content, writeReason },
) {
const phase = ensureReadyTaskRun(runId, agentId);
const recovery = runData.get(runId)?.writerRecovery ?? null;
const observations = observationContext(context);
if (recovery === 'after-write') {
const writeOkIndex = observations.lastIndexOf(
`file.writeok · 已写入 ${path}`,
);
const writeBlockedIndex = Math.max(
observations.lastIndexOf('file.writeblocked'),
observations.lastIndexOf('file.writefailed'),
);
if (writeBlockedIndex > writeOkIndex) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(path, content, `再次修复并写入 ${path}`),
]);
}
runData.set(runId, { writerRecovery: 'after-smoke' });
if (completedReadyTaskRuns.has(runId)) {
readyTaskCompletionRetryArmedRuns.add(runId);
}
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall(`验证修复写入 ${path} 后的当前 revision`),
]);
}
if (recovery === 'after-smoke') {
const smokeOkIndex = observations.lastIndexOf(
'command.run_limitedok · game.static_smoke 已完成',
);
const smokeBlockedIndex = Math.max(
observations.lastIndexOf('command.run_limitedblocked'),
observations.lastIndexOf('command.run_limitedfailed'),
);
if (smokeBlockedIndex > smokeOkIndex) {
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall(`重新验证修复写入 ${path} 后的当前 revision`),
]);
}
runData.delete(runId);
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase === 0) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(path, content, writeReason),
]);
}
if (phase === 1) {
stats.manifestReadyTaskFileReadCount += 1;
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall(path, `回读并核对 ${path}`),
staticSmokeCall(`验证 ${path} 写入后的当前 revision`),
]);
}
if (phase === 2) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 3) {
if (
!tools.has('respond_to_user') &&
!tools.has(runtimeFunction('command.run_limited')) &&
tools.has(runtimeFunction('file.write'))
) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
runData.set(runId, { writerRecovery: 'after-write' });
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(path, content, `修复并重新写入 ${path}`),
]);
}
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(
`provider-ready-writer-stage-invalid:${agentId}:${phase}`,
);
}
function initialArtDelegationCalls(runId, tools, context) {
const agentId = 'art-asset-plan';
const existingKind = runKinds.get(runId);
if (!existingKind) {
runKinds.set(runId, 'initial-art-delegation');
runPhases.set(runId, 0);
} else if (existingKind !== 'initial-art-delegation') {
throw providerError(`provider-initial-art-run-kind-invalid:${runId}`);
}
const phase = runPhases.get(runId) ?? 0;
const respond = (calls) => {
runPhases.set(runId, phase + 1);
return callsResponse(agentId, tools, calls);
};
if (phase === 0) {
return respond([assetListCall('核对首版核心美术是否已经生成')]);
}
if (phase === 1) {
stats.sourceWriteCount += 1;
return respond([
fileWriteCall(
'assets/manifest.art.json',
`${JSON.stringify(deterministicArtManifest(true), null, 2)}\n`,
'写入登记首版核心图片的正式美术资产清单',
),
]);
}
if (phase === 2) {
if (!tools.has(runtimeFunction('canvas.asset_generate'))) {
throw providerError('provider-initial-art-canvas-tool-missing');
}
return respond([canvasAssetCall(agentId)]);
}
if (phase === 3) {
return respond([
fileReadCall('assets/manifest.art.json', '回读并核对正式美术清单'),
assetListCall('确认核心美术图片已生成并登记'),
]);
}
if (phase === 4) {
terminalReplayAvailableRuns.add(runId);
return respond([
...completedPlanCallsForContext(
context,
'首版核心美术和正式资产清单已经完成',
),
nativeResponse('首版核心美术图片与正式资产清单已经生成并核对。'),
]);
}
if (successfulCommandObservation(context) && tools.has('respond_to_user')) {
consumeTerminalReplayAuthorization(agentId, runId);
return respond([
...completedPlanCallsForContext(
context,
'首版核心美术和正式资产清单已经完成并通过验证',
),
nativeResponse('首版核心美术图片与正式资产清单已经生成并验证。'),
]);
}
if (
specialistVerificationRepairContext(context) ||
revisionBlockedObservation(context) ||
transientCommandObservation(context)
) {
return retryTerminalStaticVerification(
agentId,
runId,
tools,
'补充验证首版核心美术和正式资产清单的当前 revision',
);
}
if (
tools.has('respond_to_user') &&
context.includes('当前 revision 已通过验证')
) {
consumeTerminalReplayAuthorization(agentId, runId);
return respond([
...completedPlanCallsForContext(
context,
'首版核心美术和正式资产清单已经完成并通过验证',
),
nativeResponse('首版核心美术图片与正式资产清单已经生成并验证。'),
]);
}
throw providerError('provider-initial-art-terminal-duplicate');
}
function artAssetReadyCalls(runId, tools, context) {
const agentId = 'art-asset-plan';
const phase = ensureReadyTaskRun(runId, agentId);
if (phase === 0) {
return readyCallsResponse(agentId, runId, tools, [
assetListCall('先核对首版核心素材是否已经生成和登记'),
]);
}
if (phase === 1) {
const existing = observationsIncludeAsset(
context,
'assets/art-spritesheet.png',
);
const hasCanvas = tools.has(runtimeFunction('canvas.asset_generate'));
const willGenerate = hasCanvas && !existing;
const hasVisualAsset = existing || willGenerate;
const calls = [
fileWriteCall(
'assets/manifest.art.json',
`${JSON.stringify(deterministicArtManifest(hasVisualAsset, 'manifest-ready'), null, 2)}\n`,
'写入可解析的首版美术资产清单',
),
];
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
if (willGenerate) {
calls.push(canvasAssetCall(agentId));
stats.manifestReadyTaskCanvasGenerationCount += 1;
}
runData.set(runId, { existing, hasCanvas, willGenerate });
return readyCallsResponse(agentId, runId, tools, calls);
}
if (phase === 2) {
const data = runData.get(runId) ?? {
existing: false,
hasCanvas: false,
willGenerate: false,
};
const calls = [
fileReadCall('assets/manifest.art.json', '回读并核对美术清单 JSON'),
];
stats.manifestReadyTaskFileReadCount += 1;
if (data.willGenerate) {
calls.push(assetListCall('核对新图片已登记到项目资产清单'));
} else {
calls.push(staticSmokeCall('验证美术清单写入后的当前 revision'));
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
}
return readyCallsResponse(agentId, runId, tools, calls);
}
if (phase === 3) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 4) {
if (
!tools.has('respond_to_user') &&
!tools.has(runtimeFunction('command.run_limited')) &&
tools.has(runtimeFunction('file.write'))
) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
'assets/manifest.art.json',
`${JSON.stringify(deterministicArtManifest(true, 'manifest-ready-recovery'), null, 2)}\n`,
'补写可验证的正式美术任务产物',
),
]);
}
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(`provider-ready-art-stage-invalid:${phase}`);
}
function canonicalCodeReadyCalls(runId, tools, context) {
const agentId = 'code-prototype';
const phase = ensureReadyTaskRun(runId, agentId);
if (phase === 0) {
stats.canonicalCodeRunCount += 1;
stats.manifestReadyTaskFileReadCount += 3;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('memory/project.md', '读取项目长期约束'),
fileReadCall('game/game_design.md', '读取玩法与界面规格'),
fileReadCall('game/balance.json', '读取可执行数值参数'),
]);
}
if (phase === 1) {
stats.manifestReadyTaskFileReadCount += 3;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('assets/manifest.art.json', '读取美术资产清单'),
fileReadCall('assets/manifest.audio.json', '读取声音事件清单'),
fileReadCall('game/index.html', '读取当前入口以保留既有试玩行为'),
]);
}
if (phase === 2) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
'game/index.html',
deterministicLaneDefenseCanonicalHtml(),
'整合设计、数值、美术和声音合同并重写 canonical 游戏入口',
),
]);
}
if (phase === 3) {
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall('验证整合上游后的 canonical 游戏入口'),
]);
}
if (phase === 4) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 5) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(`provider-ready-canonical-code-stage-invalid:${phase}`);
}
function qualityReadyCalls(runId, tools, context) {
const agentId = 'quality-review';
const phase = ensureReadyTaskRun(runId, agentId);
if (phase === 0) {
stats.manifestReadyTaskFileReadCount += 3;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('game/index.html', '只读检查最终可玩入口'),
fileReadCall('game/balance.json', '只读检查数值合同'),
fileReadCall('assets/manifest.art.json', '只读检查美术合同'),
]);
}
if (phase === 1) {
stats.manifestReadyTaskFileReadCount += 2;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('assets/manifest.audio.json', '只读检查声音合同'),
fileReadCall('game/game_design.md', '只读检查玩法规格覆盖'),
]);
}
if (phase === 2) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 3) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(`provider-ready-quality-stage-invalid:${phase}`);
}
function previewReadyCalls(agentId, runId, tools, context) {
const phase = ensureReadyTaskRun(runId, agentId);
if (phase === 0) {
if (agentId === 'preview-readiness') {
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
return readyCallsResponse(agentId, runId, tools, [
staticSmokeCall('执行 canonical 游戏入口静态自检'),
]);
}
stats.previewValidationCount += 1;
stats.manifestReadyTaskPreviewValidationCount += 1;
return readyCallsResponse(agentId, runId, tools, [
previewCall('执行 canonical 游戏入口真实双视口试玩'),
]);
}
if (phase === 1) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 2) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(
`provider-ready-preview-stage-invalid:${agentId}:${phase}`,
);
}
function manifestReadyCalls(identity, tools, context) {
const { agentId, runId } = identity;
if (
[
'design-director',
'balance-director',
'art-director',
'art-polish',
'audio-director',
'code-director',
'publish-strategy',
].includes(agentId)
) {
return readOnlyReadyCalls(agentId, runId, tools, context);
}
if (agentId === 'design-foundation') {
return designFoundationReadyCalls(runId, tools, context);
}
if (agentId === 'balance-seed') {
return verifiedFileWriterReadyCalls(agentId, runId, tools, context, {
path: 'game/balance.json',
content: `${JSON.stringify(deterministicLaneDefenseBalance, null, 2)}\n`,
writeReason: '写入可被程序读取的初版数值参数',
});
}
if (agentId === 'art-asset-plan') {
return artAssetReadyCalls(runId, tools, context);
}
if (agentId === 'audio-asset-plan') {
return verifiedFileWriterReadyCalls(agentId, runId, tools, context, {
path: 'assets/manifest.audio.json',
content: `${JSON.stringify(deterministicAudioManifest, null, 2)}\n`,
writeReason: '写入 BGM 与关键交互音效需求清单',
});
}
if (agentId === 'code-prototype') {
return canonicalCodeReadyCalls(runId, tools, context);
}
if (agentId === 'quality-review') {
return qualityReadyCalls(runId, tools, context);
}
if (agentId === 'preview-readiness' || agentId === 'preview-playtest') {
return previewReadyCalls(agentId, runId, tools, context);
}
if (agentId === 'publish-package') {
return verifiedFileWriterReadyCalls(agentId, runId, tools, context, {
path: 'exports/README.md',
content: deterministicPublishReadme,
writeReason: '写入发布标题、简介、操作、标签与导出检查',
});
}
throw providerError(`provider-ready-agent-unsupported:${agentId}`);
}
function codeCalls(runId, tools, context) {
if (!runKinds.has(runId)) {
if (!initialBuilderRunId) {
initialBuilderRunId = runId;
runKinds.set(runId, 'initial-builder');
} else if (!repairBuilderRunId) {
repairBuilderRunId = runId;
runKinds.set(runId, 'repair-builder');
} else {
throw providerError('provider-extra-builder-run');
}
runPhases.set(runId, 0);
}
const kind = runKinds.get(runId);
const phase = runPhases.get(runId);
if (kind === 'initial-builder') {
if (phase === 0) {
runPhases.set(runId, 1);
stats.sourceWriteCount += 1;
return callsResponse('code-prototype', tools, [
nativeAction('file.write', '写入完整可玩的塔防初版', {
path: 'game/index.html',
content: deterministicLaneDefenseInitialHtml(),
}),
]);
}
if (phase === 1) {
runPhases.set(runId, 2);
stats.staticSmokeCount += 1;
return callsResponse('code-prototype', tools, [
staticSmokeCall('验证初版源码与状态面'),
]);
}
if (phase === 2) {
runPhases.set(runId, 3);
terminalReplayAvailableRuns.add(runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'初版实现和静态验证已经完成',
),
nativeResponse('初版塔防玩法与交互状态面已经生成,并通过静态自检。'),
]);
}
if (phase >= 3) {
if (
successfulCommandObservation(context) &&
tools.has('respond_to_user')
) {
consumeTerminalReplayAuthorization('code-prototype', runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'初版实现和静态验证已经完成',
),
nativeResponse(
'初版塔防玩法与交互状态面已经生成,并通过静态自检。',
),
]);
}
if (
specialistVerificationRepairContext(context) ||
revisionBlockedObservation(context) ||
transientCommandObservation(context)
) {
return retryTerminalStaticVerification(
'code-prototype',
runId,
tools,
'重试验证初版源码与状态面',
);
}
if (
tools.has('respond_to_user') &&
context.includes('当前 revision 已通过验证')
) {
consumeTerminalReplayAuthorization('code-prototype', runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'初版实现和静态验证已经完成',
),
nativeResponse(
'初版塔防玩法与交互状态面已经生成,并通过静态自检。',
),
]);
}
}
}
if (kind === 'repair-builder') {
if (phase === 0) {
runPhases.set(runId, 1);
stats.sourcePatchCount += 1;
return callsResponse('code-prototype', tools, [
repairPatchCall('修复真实浏览器发现的 canvas 可见性'),
]);
}
if (phase === 1) {
runPhases.set(runId, 2);
stats.staticSmokeCount += 1;
return callsResponse('code-prototype', tools, [
staticSmokeCall('验证返工后的源码'),
]);
}
if (phase === 2) {
runPhases.set(runId, 3);
terminalReplayAvailableRuns.add(runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'画布可见性返工和静态验证已经完成',
),
nativeResponse(
'画布可见性已修复,原有完整试玩行为保持不变并通过静态自检。',
),
]);
}
if (phase >= 3) {
if (
successfulCommandObservation(context) &&
tools.has('respond_to_user')
) {
consumeTerminalReplayAuthorization('code-prototype', runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'画布可见性返工和静态验证已经完成',
),
nativeResponse(
'画布可见性已修复,原有完整试玩行为保持不变并通过静态自检。',
),
]);
}
if (
specialistVerificationRepairContext(context) ||
revisionBlockedObservation(context) ||
transientCommandObservation(context)
) {
return retryTerminalStaticVerification(
'code-prototype',
runId,
tools,
'重试验证返工后的源码',
);
}
if (
tools.has('respond_to_user') &&
context.includes('当前 revision 已通过验证')
) {
consumeTerminalReplayAuthorization('code-prototype', runId);
return callsResponse('code-prototype', tools, [
...completedPlanCallsForContext(
context,
'画布可见性返工和静态验证已经完成',
),
nativeResponse(
'画布可见性已修复,原有完整试玩行为保持不变并通过静态自检。',
),
]);
}
}
}
throw providerError(`provider-builder-stage-invalid:${kind}:${phase}`);
}
function qualityCalls(runId, tools, context) {
const phase = runPhases.get(runId) ?? 0;
if (phase === 0) {
runKinds.set(runId, 'quality-review');
runPhases.set(runId, 1);
return callsResponse('quality-review', tools, [
...completedPlanCallsForContext(context, '独立验收重点已经核对'),
nativeResponse(
'验收必须以真实植物选择、放置、战斗推进、胜利、下一关、重开和双视口可见性为准。',
),
]);
}
if (phase === 1) {
if (
!revisionBlockedObservation(context) &&
!context.includes('当前专业合同明确要求只读交付')
) {
throw providerError('provider-quality-terminal-duplicate');
}
consumeUniqueTerminalObservation('quality-review', runId, context);
runPhases.set(runId, 2);
stats.qualityRevisionReplanCount += 1;
return callsResponse('quality-review', tools, [
...completedPlanCallsForContext(
context,
'项目 revision 更新后已重新核对独立验收重点',
),
nativeResponse(
'已基于最新项目 revision 复核验收重点,仍需以真实植物选择、放置、战斗推进、胜利、下一关、重开和双视口可见性为准。',
),
]);
}
if (
phase >= 2 &&
tools.has('respond_to_user') &&
(revisionBlockedObservation(context) ||
context.includes('当前专业合同明确要求只读交付'))
) {
consumeUniqueTerminalObservation('quality-review', runId, context);
return callsResponse('quality-review', tools, [
...completedPlanCallsForContext(
context,
'项目 revision 更新后已重新核对独立验收重点',
),
nativeResponse(
'已基于最新项目 revision 复核验收重点,仍需以真实植物选择、放置、战斗推进、胜利、下一关、重开和双视口可见性为准。',
),
]);
}
throw providerError(`provider-quality-stage-invalid:${phase}`);
}
function finalReply(identity, context) {
const key = `${identity.agentId}\0${identity.runId}`;
if (finalReplyRuns.has(key)) {
throw providerError('provider-duplicate-final-reply-request');
}
if (
!context.includes('给用户一个正常中文回复') &&
!context.includes('给开发者一个正常中文回复')
) {
throw providerError('provider-unexpected-text-request');
}
finalReplyRuns.add(key);
const content =
identity.agentId === 'project-supervisor'
? '项目已经生成并完成真实试玩验证:植物可以选择和放置,敌人会移动并受伤,关卡可以胜利、进入下一关并重新开始。'
: identity.agentId === 'quality-review'
? '已完成独立验收重点核对,后续以真实交互和双视口浏览器结果作为最终依据。'
: '专业实现任务已经完成,修改和静态验证结果已交回总控。';
responseSequence += 1;
return chatTextResponse(responseSequence, model, content);
}
function route({ authorization, payload }) {
stats.requestCount += 1;
let diagnostic = {
agentId: 'unknown',
tools: [],
delegatedPlaytestRepair: false,
responsePlanRepair: false,
verifiedDeliveryRepair: false,
readOnlyDeliveryRepair: false,
genericFormatRepair: false,
};
try {
if (authorization !== `Bearer ${apiKey}`) {
throw providerError('provider-authorization-invalid');
}
if (
!isPlainObject(payload) ||
payload.model !== model ||
payload.stream === true
) {
throw providerError('provider-request-shape-invalid');
}
const context = requestContext(payload);
const tools = advertisedToolNames(payload);
if (
context.includes('统一的 Agent interaction loop') &&
context.includes('用户这轮输入:') &&
tools.has('runtime_execute')
) {
stats.planningRequestCount += 1;
stats.interactionExecuteCount += 1;
recordAgent('project-supervisor', 'planning');
return callsResponse('project-supervisor', tools, [
{ name: 'runtime_execute', arguments: {} },
]);
}
if (isUiPrototypeInspectionRequest(payload, context)) {
stats.imageInspectionRequestCount += 1;
responseSequence += 1;
return chatTextResponse(
responseSequence,
model,
JSON.stringify({
checks: {
resourceBar: true,
unitCardTray: true,
battlefieldGrid: true,
enemyEntryDirection: true,
waveStatus: true,
primaryControls: true,
implementationClarity: true,
originalTheme: true,
},
issues: [],
summary: '确定性 UI 原型覆盖完整横屏塔防实现合同。',
}),
);
}
if (context.includes('你负责压缩 Agent 的旧历史')) {
stats.contextCompactionRequestCount += 1;
responseSequence += 1;
return chatTextResponse(
responseSequence,
model,
'用户要求生成可直接试玩的植物塔防游戏。静态专业委派已经完成;正式 manifest 任务图正在按依赖执行,完成后仍需运行最终静态检查和双视口交互试玩。',
);
}
const identity = extractIdentity(context);
const latestObservation = latestToolObservation(context);
diagnostic = {
agentId: identity.agentId,
tools: [...tools].sort(),
latestObservationTool: latestObservation?.tool ?? null,
latestObservationStatus: latestObservation?.status ?? null,
revisionBlockedObservation: revisionBlockedObservation(context),
revisionBlockedContext:
context.includes('responseRevision=') &&
context.includes('currentRevision='),
specialistVerificationRepair:
specialistVerificationRepairContext(context),
delegatedPlaytestRepair:
context.includes('当前父 run 已进入只编排模式') ||
context.includes('后续修复委派'),
responsePlanRepair:
context.includes('只保留 update_agent_plan 与 respond_to_user') ||
context.includes('结构化计划仍未完成'),
verifiedDeliveryRepair:
context.includes('当前 revision 已通过验证') &&
context.includes('只保留 respond_to_user'),
readOnlyDeliveryRepair:
context.includes('当前专业合同明确要求只读交付') ||
context.includes('不允许修改项目'),
genericFormatRepair: context.includes('请修复格式'),
};
if (tools.size === 0) {
stats.finalReplyRequestCount += 1;
recordAgent(identity.agentId, 'finalReply');
return finalReply(identity, context);
}
stats.planningRequestCount += 1;
recordAgent(identity.agentId, 'planning');
if (readyTaskContext(context, identity.agentId)) {
return manifestReadyCalls(identity, tools, context);
}
if (identity.agentId === 'project-supervisor') {
return parentCalls(context, tools);
}
if (identity.agentId === 'code-prototype') {
return codeCalls(identity.runId, tools, context);
}
if (identity.agentId === 'quality-review') {
return qualityCalls(identity.runId, tools, context);
}
if (identity.agentId === 'art-asset-plan') {
return initialArtDelegationCalls(identity.runId, tools, context);
}
throw providerError(`provider-agent-unsupported:${identity.agentId}`);
} catch (error) {
stats.unexpectedRequestCount += 1;
const code =
typeof error?.code === 'string'
? error.code
: 'provider-request-rejected';
stats.rejectionCodes[code] = (stats.rejectionCodes[code] ?? 0) + 1;
if (stats.rejections.length < 8) {
stats.rejections.push({ ...diagnostic, code });
}
throw error;
}
}
return Object.freeze({
route,
getStats() {
return structuredClone(stats);
},
});
}
function listen(server, host, port) {
return new Promise((resolve, reject) => {
const onError = (error) => {
cleanup();
reject(error);
};
const onListening = () => {
cleanup();
resolve();
};
const cleanup = () => {
server.off('error', onError);
server.off('listening', onListening);
};
server.once('error', onError);
server.once('listening', onListening);
server.listen({ host, port, exclusive: true });
});
}
async function bindLoopback(server, fallbackPorts) {
try {
await listen(server, LOOPBACK_HOST, 0);
return;
} catch (error) {
if (error?.code !== 'EADDRINUSE') throw error;
}
for (const port of fallbackPorts) {
try {
await listen(server, LOOPBACK_HOST, port);
return;
} catch (error) {
if (error?.code !== 'EADDRINUSE') throw error;
}
}
throw providerError('provider-loopback-port-exhausted');
}
function readJsonBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
let bytes = 0;
request.on('data', (chunk) => {
bytes += chunk.length;
if (bytes > MAX_REQUEST_BYTES) {
reject(providerError('provider-request-too-large'));
request.destroy();
return;
}
chunks.push(chunk);
});
request.once('end', () => {
try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
} catch {
reject(providerError('provider-request-json-invalid'));
}
});
request.once('error', () =>
reject(providerError('provider-request-read-failed')),
);
});
}
function sendJson(response, statusCode, body) {
if (response.destroyed) return;
const bytes = Buffer.from(JSON.stringify(body));
sendBytes(response, statusCode, 'application/json; charset=utf-8', bytes);
}
function sendBytes(response, statusCode, contentType, bytes) {
if (response.destroyed) return;
response.writeHead(statusCode, {
connection: 'close',
'content-length': bytes.length,
'content-type': contentType,
'x-request-id': `deterministic-${statusCode}`,
});
response.end(bytes);
}
function createDeterministicCanvasFixture(apiKey) {
const projectId = 'deterministic-canvas-project';
const folderId = 'deterministic-canvas-folder';
const images = new Map();
const generationOperations = new Map();
const imageCache = new Map();
const stats = {
canvasApiRequestCount: 0,
canvasGenerationRequestCount: 0,
canvasDownloadRequestCount: 0,
canvasGeneratedAspectRatios: {},
};
let projectTitle = null;
let folderLabel = null;
let generationSequence = 0;
function json(statusCode, body) {
return {
statusCode,
contentType: 'application/json; charset=utf-8',
bytes: Buffer.from(JSON.stringify(body)),
};
}
function imageForAspectRatio(aspectRatio) {
const normalized = aspectRatio === '16:9' ? '16:9' : '1:1';
if (!imageCache.has(normalized)) {
imageCache.set(
normalized,
normalized === '16:9'
? {
width: 1280,
height: 720,
bytes: deterministicPng(1280, 720),
}
: {
width: 1024,
height: 1024,
bytes: deterministicPng(1024, 1024),
},
);
}
return { aspectRatio: normalized, ...imageCache.get(normalized) };
}
async function handle(request) {
const parsed = new URL(request.url ?? '/', 'http://127.0.0.1');
const isSignedImage = parsed.pathname.startsWith('/signed/');
const isCanvasApi = parsed.pathname.startsWith('/api/external/v1/');
if (!isSignedImage && !isCanvasApi) return null;
stats.canvasApiRequestCount += 1;
if (
!isSignedImage &&
request.headers.authorization !== `Bearer ${apiKey}`
) {
request.resume();
return json(401, { error: { message: 'unauthorized' } });
}
if (
request.method === 'GET' &&
parsed.pathname === '/api/external/v1/editor/projects'
) {
request.resume();
return json(200, {
data: {
projects: projectTitle ? [{ projectId, title: projectTitle }] : [],
},
});
}
if (
request.method === 'POST' &&
parsed.pathname === '/api/external/v1/editor/projects'
) {
const body = await readJsonBody(request);
projectTitle =
typeof body?.title === 'string' && body.title.trim()
? body.title.trim()
: '未命名游戏原型';
return json(200, {
data: { project: { projectId, title: projectTitle } },
});
}
if (
request.method === 'GET' &&
parsed.pathname === '/api/external/v1/editor/assets/library'
) {
request.resume();
return json(200, {
data: {
library: {
folders: folderLabel ? [{ folderId, label: folderLabel }] : [],
},
},
});
}
if (
request.method === 'POST' &&
parsed.pathname === '/api/external/v1/editor/assets/folders'
) {
const body = await readJsonBody(request);
folderLabel =
typeof body?.label === 'string' && body.label.trim()
? body.label.trim()
: '未命名游戏原型';
return json(200, { data: { folder: { folderId, label: folderLabel } } });
}
if (
request.method === 'POST' &&
parsed.pathname === '/api/external/v1/editor/images/generations'
) {
const idempotencyKey = request.headers['idempotency-key'];
if (
typeof idempotencyKey !== 'string' ||
!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
idempotencyKey,
)
) {
request.resume();
return json(400, { error: { message: 'invalid idempotency key' } });
}
const body = await readJsonBody(request);
const image = imageForAspectRatio(body?.aspectRatio);
generationSequence += 1;
stats.canvasGenerationRequestCount += 1;
stats.canvasGeneratedAspectRatios[image.aspectRatio] =
(stats.canvasGeneratedAspectRatios[image.aspectRatio] ?? 0) + 1;
const imageId = `deterministic-${generationSequence}`;
const objectKey = `generated/deterministic/${imageId}.png`;
const assetObjectId = `asset-object-${imageId}`;
const resourceId = `resource-${imageId}`;
const assetKind =
typeof body?.assetKind === 'string' ? body.assetKind : 'game-art';
images.set(imageId, { ...image, objectKey });
const operationId = `task-${imageId}`;
generationOperations.set(operationId, {
imageSrc: `/${objectKey}`,
objectKey,
assetObjectId,
width: image.width,
height: image.height,
sourceType: 'generated',
prompt: body?.prompt ?? 'deterministic canvas fixture',
actualPrompt: body?.prompt ?? 'deterministic canvas fixture',
model: 'deterministic-canvas-v1',
provider: 'deterministic-loopback',
taskId: `task-${imageId}`,
resource: {
resourceId,
projectId,
imageSrc: `/${objectKey}`,
objectKey,
assetObjectId,
width: image.width,
height: image.height,
sourceType: 'generated',
prompt: body?.prompt ?? 'deterministic canvas fixture',
actualPrompt: body?.prompt ?? 'deterministic canvas fixture',
model: 'deterministic-canvas-v1',
provider: 'deterministic-loopback',
taskId: `task-${imageId}`,
assetKind,
},
asset: {
assetId: `asset-${imageId}`,
assetObjectId,
assetKind,
},
});
return json(202, {
data: {
operationId,
kind: 'editor_image_generation',
status: 'queued',
statusUrl: `/api/external/v1/generations/${operationId}`,
pollAfterMs: 1,
updatedAtMicros: generationSequence,
},
});
}
if (
request.method === 'GET' &&
parsed.pathname.startsWith('/api/external/v1/generations/')
) {
request.resume();
const operationId = parsed.pathname.slice(
'/api/external/v1/generations/'.length,
);
const result = generationOperations.get(operationId);
if (!result) return json(404, { error: { message: 'operation not found' } });
return json(200, {
data: {
operationId,
kind: 'editor_image_generation',
status: 'completed',
phaseLabel: '图片画布生成图片',
phaseDetail: '生成已完成。',
progress: 100,
result,
updatedAtMicros: generationSequence,
},
});
}
if (
request.method === 'GET' &&
parsed.pathname === '/api/external/v1/assets/read-url'
) {
request.resume();
const objectKey = parsed.searchParams.get('objectKey');
const entry = [...images.entries()].find(
([, image]) => image.objectKey === objectKey,
);
if (!entry) return json(404, { error: { message: 'image not found' } });
const [imageId] = entry;
const host = request.headers.host ?? LOOPBACK_HOST;
return json(200, {
data: {
read: {
provider: 'deterministic-loopback',
bucket: 'deterministic',
endpoint: LOOPBACK_HOST,
host: LOOPBACK_HOST,
objectKey,
expiresAt: '2099-01-01T00:00:00Z',
signedUrl: `http://${host}/signed/${imageId}.png`,
},
},
});
}
if (request.method === 'GET' && isSignedImage) {
request.resume();
const imageId = parsed.pathname.slice('/signed/'.length, -'.png'.length);
const image = images.get(imageId);
if (!image) return json(404, { error: { message: 'image not found' } });
stats.canvasDownloadRequestCount += 1;
return {
statusCode: 200,
contentType: 'image/png',
bytes: image.bytes,
};
}
request.resume();
return json(404, { error: { message: 'not found' } });
}
return Object.freeze({
handle,
getStats: () => structuredClone(stats),
});
}
export async function startDeterministicLaneDefenseProvider({
apiKey,
model = deterministicLaneDefenseModel,
fallbackPorts = DEFAULT_FALLBACK_PORTS,
} = {}) {
const router = createDeterministicLaneDefenseRouter({ apiKey, model });
const canvasFixture = createDeterministicCanvasFixture(apiKey);
const sockets = new Set();
let stopped = false;
let stopPromise = null;
const server = http.createServer(async (request, response) => {
try {
const canvasResponse = await canvasFixture.handle(request);
if (canvasResponse) {
sendBytes(
response,
canvasResponse.statusCode,
canvasResponse.contentType,
canvasResponse.bytes,
);
return;
}
if (request.method !== 'POST' || request.url !== CHAT_COMPLETIONS_PATH) {
request.resume();
sendJson(response, 404, { error: { message: 'not found' } });
return;
}
const payload = await readJsonBody(request);
const result = router.route({
authorization: request.headers.authorization,
payload,
});
sendJson(response, 200, result);
} catch (error) {
sendJson(response, 422, {
error: { message: error?.code ?? 'provider-request-rejected' },
});
}
});
server.on('connection', (socket) => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket));
socket.on('error', () => {});
});
server.on('clientError', (_error, socket) => socket.destroy());
await bindLoopback(server, fallbackPorts);
const address = server.address();
if (
!address ||
typeof address === 'string' ||
address.address !== LOOPBACK_HOST
) {
throw providerError('provider-loopback-bind-invalid');
}
return Object.freeze({
baseUrl: `http://${LOOPBACK_HOST}:${address.port}/v1`,
editorBaseUrl: `http://${LOOPBACK_HOST}:${address.port}`,
getStats() {
return { ...router.getStats(), ...canvasFixture.getStats(), stopped };
},
stop() {
if (stopPromise) return stopPromise;
stopPromise = new Promise((resolve) => {
server.close(() => {
stopped = true;
resolve();
});
for (const socket of sockets) socket.destroy();
server.closeAllConnections?.();
});
return stopPromise;
},
});
}