Files
Genarrative/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs
kdletters fc14190f58
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
图集切片模式改为必须显式声明并补齐决策要求 (#408)
## 背景

切图新增基于连通域的切分后,LLM 仍倾向显式传 `sliceMode=grid`:参数只存在于部分 LLM 可见面、带默认值、没有任何决策规则,生成结果也不回显生效模式。

## 变更

- 平台:`/api/editor/icon-spritesheets/generations` 与 `/api/external/v1/editor/icon-spritesheets/generations` 把 `sliceMode` 改为必填并移除默认值;缺失、空白或未知取值在引用解析、定价与任何 provider / OSS 副作用之前返回 `400`,错误统一带 `field` 与决策要求。
- 契约:`grid` 必须同时提供 `gridX`/`gridY`,`connected-components` 不接受网格尺寸;`sliceCount` 只约束连通域切分,请求与响应的公开上限统一为 `256`;OpenAPI 去掉默认值并补必填与失败语义。
- AGC:MCP 工具说明去掉默认值并补决策要求,桥接层新增可测试的切分声明校验;原生工具 `canvas.asset_generate` 暴露 `sliceMode/gridX/gridY/sliceCount` 并要求图集显式声明;生成结果回显 `sliceMode/gridX/gridY` 与 `slicePaths`;严格图集在本地提交前校验平台回显与请求声明一致。
- 标准美术包:显式声明 `connected-components` 加 `sliceCount=4`,并在四张 canonical 切片用途映射前校验数量,禁止截断或错位。
- 前端与画板:画板 Agent 工具装配与画板提交计划显式声明连通域切分;前端类型要求显式 `sliceMode` 并在本地校验声明自洽。
- 文档与 Skill:主规范、OpenAPI、AGC Skill、外部编辑器 Skill、里程碑与实施计划、共享决策记录同步更新。
- 测试环境:测试构建对提权 Windows 主机上系统临时目录的所有者偏差做一次性所有者初始化重试,临时目录之外的越权所有者继续失败关闭。

## 兼容性影响

省略 `sliceMode` 的旧调用方(含已发布但未更新的 AGC 客户端与第三方外部 API 调用方)会在图集生成上收到 `400`;这是本次"不允许默认值"的预期结果,仓库内自有调用方已全部改为显式声明。

## 验证

- 平台:`slice_mode_must_be_declared_*` 与 OpenAPI 契约测试通过;全量 `cargo test -p api-server` 1043 通过 / 11 失败(`wallet_refund_outbox` 临时文件 `拒绝访问`,已在改动前基线复现,属本机环境)。
- AGC:`slice` 30、`spritesheet` 21、`direct_tools_mcp` 23、`agent_native_tools` 16、`canvas_generation_tests` 83、提示词上限与桥接门禁各 1 条、`cargo check --tests` 全部通过。
- 前端:182 条定向测试与 `typecheck` 通过。
- 门禁:`cargo fmt --check`(两个 workspace)、`check:encoding`、`check:doc-index`、`git diff --check` 通过。
- 未验证:真实 Provider 与浏览器试玩、确定性 e2e 车道;整机全量 AGC 单进程运行在本机受提权 shell 的所有者与时序问题影响,不作为门禁信号。

---------

Co-authored-by: kdletters <61648117+kdletters@users.noreply.github.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/408
2026-09-17 18:10:34 +08:00

3041 lines
108 KiB
JavaScript
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.
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-polish',
'audio-director',
'code-director',
'quality-review',
'preview-readiness',
'preview-playtest',
'publish-strategy',
]);
// These owner tasks are checked by Runtime's owner-artifact gate. Their
// request-scoped tool catalog deliberately removes command.run_limited, so a
// deterministic response must deliver after a successful fixed-path write
// instead of trying to emit a tool that the runtime did not advertise.
const deterministicOwnerArtifactValidationAgentIds = new Set([
'design-foundation',
'balance-seed',
'art-asset-plan',
'audio-asset-plan',
]);
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,
{ variant = 0, transparent = false } = {},
) {
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 + variant * 13) % 256;
pixels[offset + 1] = (86 + y * 2 + tile * 17 + variant * 19) % 256;
pixels[offset + 2] = (118 + x + y + tile * 31 + variant * 23) % 256;
pixels[offset + 3] =
transparent && (x + y + variant) % 11 === 0 ? 0 : 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');
const atlasArt=new Image(),playerArt=new Image(),targetArt=new Image(),sceneArt=new Image(),feedbackArt=new Image();atlasArt.src='../assets/art-spritesheet.png';playerArt.src='../assets/art-spritesheet-slices/player.png';targetArt.src='../assets/art-spritesheet-slices/blocks-and-targets.png';sceneArt.src='../assets/art-spritesheet-slices/obstacles-and-scene.png';feedbackArt.src='../assets/art-spritesheet-slices/feedback-effects.png';
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);if(atlasArt.complete&&atlasArt.naturalWidth>0)ctx.drawImage(atlasArt,0,0,256,256,18,220,96,96);if(playerArt.complete&&playerArt.naturalWidth>0)ctx.drawImage(playerArt,140,220,64,64);if(targetArt.complete&&targetArt.naturalWidth>0)ctx.drawImage(targetArt,220,220,64,64);if(sceneArt.complete&&sceneArt.naturalWidth>0)ctx.drawImage(sceneArt,300,220,64,64);if(feedbackArt.complete&&feedbackArt.naturalWidth>0)ctx.drawImage(feedbackArt,380,220,64,64);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('informationHud') &&
context.includes('gameplaySurface') &&
context.includes('objectiveEntities') &&
context.includes('primaryControls') &&
context.includes('failureRestartFlow') &&
context.includes('responsiveLayout') &&
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 goalContractCall() {
return nativeAction(
'agent.goal_contract',
'冻结本轮可玩游戏交付目标与验收标准',
{
outcome:
'交付一个可完整游玩的原创植物塔防网页游戏,并让当前最终 revision 通过自动静态检查和桌面、移动双视口真实试玩。',
nonNegotiables: [
'必须形成从开始、放置防御单位、敌人移动与受伤到胜利、下一关和重开的完整闭环',
'必须在无人确认、无人补充输入和无人 steer 的条件下完成',
],
preferences: ['保持单页自包含并提供清晰的桌面与移动端操作反馈'],
forbiddenAssumptions: [
'不得把静态页面、占位素材或未执行的试玩声明为完成',
],
openQuestions: [],
acceptanceNodes: [
{
criterionId: 'static-current-revision',
criterion:
'当前最终 revision 的游戏入口通过可执行 JavaScript、Canvas 与完整玩法合同静态检查',
required: true,
requiredEvidence: ['tool:command.run_limited'],
dependsOn: [],
},
{
criterionId: 'playable-current-revision',
criterion:
'当前最终 revision 的完整塔防循环在桌面与移动视口均通过真实浏览器试玩',
required: true,
requiredEvidence: ['tool:preview.validate'],
dependsOn: ['static-current-revision'],
},
],
},
);
}
function actionHistoryCall() {
return nativeAction(
'agent.action_history',
'读取当前根 Run 的最终静态检查与真实试玩动作回执',
{
runId: null,
actionId: null,
tool: null,
status: 'ok',
limit: 10,
},
);
}
function goalContractFingerprint(context) {
const marker = '[Root Goal Contract]';
const start = context.lastIndexOf(marker);
if (start < 0) return null;
const tail = context.slice(start);
const end = tail.indexOf('\n\n');
const goalContractContext = end < 0 ? tail : tail.slice(0, end);
const matches = new Set(
[
...goalContractContext.matchAll(
/"contractFingerprint"\s*:\s*"([0-9a-f]{64})"/giu,
),
].map((match) => match[1].toLowerCase()),
);
return matches.size === 1 ? [...matches][0] : null;
}
function finalAcceptanceEvidence(context, expectedRunId) {
const observation = latestToolObservation(context);
if (
observation?.tool !== 'agent.action_history' ||
observation?.status !== 'ok' ||
typeof observation.detail !== 'string'
) {
throw providerError('provider-final-action-history-observation-invalid');
}
let detail;
try {
detail = JSON.parse(observation.detail);
} catch {
throw providerError('provider-final-action-history-detail-invalid');
}
if (
detail?.runId !== expectedRunId ||
!Array.isArray(detail?.actions) ||
detail?.count !== detail.actions.length
) {
throw providerError('provider-final-action-history-identity-invalid');
}
const evidenceByTool = new Map();
for (const action of detail.actions) {
if (
action?.agentId !== 'project-supervisor' ||
action?.runId !== expectedRunId ||
action?.status !== 'ok' ||
typeof action?.actionId !== 'string' ||
!/^action-[0-9a-f]{24}$/iu.test(action.actionId) ||
!['command.run_limited', 'preview.validate'].includes(action?.tool)
) {
continue;
}
evidenceByTool.set(action.tool, {
agentId: action.agentId,
runId: action.runId,
actionId: action.actionId,
});
}
const staticSmoke = evidenceByTool.get('command.run_limited');
const preview = evidenceByTool.get('preview.validate');
if (!staticSmoke || !preview) {
throw providerError('provider-final-acceptance-evidence-missing');
}
return { staticSmoke, preview };
}
function acceptanceUpdateCall(context, runId) {
const contractFingerprint = goalContractFingerprint(context);
if (!contractFingerprint) {
throw providerError('provider-goal-contract-fingerprint-missing');
}
const evidence = finalAcceptanceEvidence(context, runId);
return nativeAction(
'agent.acceptance_update',
'提交当前最终 revision 的静态检查与双视口试玩验收证据',
{
contractFingerprint,
evaluations: [
{
criterionId: 'static-current-revision',
status: 'passed',
evidence: [evidence.staticSmoke],
summary: '当前最终 revision 已通过 game.static_smoke。',
},
{
criterionId: 'playable-current-revision',
status: 'passed',
evidence: [evidence.preview],
summary: '当前最终 revision 已通过桌面与移动双视口真实试玩。',
},
],
},
);
}
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 },
};
}
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 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 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 === 'art-director') {
return nativeAction('canvas.asset_generate', '生成统一视觉规范图', {
prompt:
'原创明亮花园植物塔防游戏的方形图标规范图。统一展示露华花守卫、棘刺芽守卫、雾影兽入侵者、晶苔地块、灵露资源和主要 UI 图标的轮廓、配色、材质与尺寸关系;纯色分区背景,适合后续角色、场景和 UI 共用,不使用任何现有游戏角色、Logo 或受保护视觉语言。',
outputPath: 'assets/art-spec.png',
aspectRatio: '1:1',
imageSize: '1K',
assetKind: 'icon-spec',
assetLabel: '游戏统一视觉规范图',
replaceExisting: false,
});
}
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,
sliceMode: 'connected-components',
});
}
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) {
// The standard tool-plan prompt labels this section "已有工具观察:",
// while the relaxed autonomous prompt intentionally uses the shorter
// "已有观察:" label. Keep the parser independent of that presentation
// detail; otherwise a settled task.list observation is invisible to the
// deterministic parent and it will poll forever until the run budget ends.
const markers = ['已有工具观察:', '已有观察:', '工具观察:'];
let start = -1;
let markerLength = 0;
for (const marker of markers) {
const candidate = context.lastIndexOf(marker);
if (candidate > start) {
start = candidate;
markerLength = marker.length;
}
}
if (start < 0) return '';
const tail = context.slice(start + markerLength);
const endMarkers = ['\n\n计划更新约定:', '\n\n工具 input 字段约定:'];
const ends = endMarkers
.map((marker) => tail.indexOf(marker))
.filter((index) => index >= 0);
const end = ends.length > 0 ? Math.min(...ends) : -1;
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 projectRevisionDriftObservation(context, tool) {
const observation = latestToolObservation(context);
const detail =
typeof observation?.detail === 'string' ? observation.detail : '';
const hasStructuredDetail =
detail.includes('projectRevisionDrift=true') &&
detail.includes('expectedRevision=') &&
detail.includes('currentRevision=') &&
detail.includes('replanRequired=true');
const detailUnavailable =
observation?.detail == null || detail === '[redacted sensitive context]';
return (
observation?.tool === tool &&
observation?.status === 'blocked' &&
observation?.summary === '并行项目变更使旧动作过期,旧动作未执行' &&
(hasStructuredDetail || detailUnavailable)
);
}
function visualAssetBlockedObservation(context) {
const observation = latestToolObservation(context);
return (
observation?.tool === 'runtime.visual_asset' &&
observation?.status === 'blocked' &&
typeof observation?.detail === 'string' &&
observation.detail.includes('expectedPath=assets/ui-prototype.png') &&
observation.detail.includes('requiredInspection=image.inspect')
);
}
function missingGeneratedVisualAssetObservation(context, agentId) {
const expected = {
'art-director': {
path: 'assets/art-spec.png',
kind: 'icon-spec',
summary: '统一视觉规范图尚未按正式视觉流程生成并登记,不能完成任务',
},
'design-foundation': {
path: 'assets/ui-prototype.png',
kind: 'ui-prototype',
summary: '策划界面原型图尚未按正式视觉流程生成并登记,不能完成任务',
},
'art-asset-plan': {
path: 'assets/art-spritesheet.png',
kind: 'art-spritesheet',
summary: '首版美术素材图尚未按正式视觉流程生成并登记,不能完成任务',
},
}[agentId];
if (!expected) return false;
const observation = latestToolObservation(context);
return (
observation?.tool === 'runtime.visual_asset' &&
observation?.status === 'blocked' &&
observation?.summary === expected.summary &&
typeof observation?.detail === 'string' &&
(observation.detail === '[redacted sensitive context]' ||
(observation.detail.includes(`expectedPath=${expected.path}`) &&
observation.detail.includes(`expectedKind=${expected.kind}`)))
);
}
function specialistVerificationRepairContext(context) {
return (
context.includes(
'当前是 autonomous-game-build 的非只读专业任务,且本人 run 已有 mutation',
) &&
context.includes(
'本次修复的原生工具目录只保留 project.verify 与 command.run_limited',
)
);
}
function verifiedDeliveryRepairContext(context) {
return (
context.includes('当前 revision 已通过验证') &&
context.includes('本次修复的原生工具目录只保留 respond_to_user')
);
}
function transientCommandObservation(context) {
const observation = latestToolObservation(context);
const detail =
typeof observation?.detail === 'string'
? observation.detail.replaceAll('\\', '/')
: '';
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 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,
relaxed = false,
} = {}) {
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 agentCounts = new Map();
const relaxedAutonomous = relaxed === true;
const stats = {
relaxedAutonomous,
requestCount: 0,
contextCompactionRequestCount: 0,
planningRequestCount: 0,
finalReplyRequestCount: 0,
goalContractCount: 0,
actionHistoryCount: 0,
acceptanceUpdateCount: 0,
delegateActionCount: 0,
runStatusCount: 0,
sourceWriteCount: 0,
staticSmokeCount: 0,
previewValidationCount: 0,
manifestReadyTaskRunCount: 0,
manifestReadyTaskCompletionCount: 0,
manifestReadyTaskFileReadCount: 0,
manifestReadyTaskFileWriteCount: 0,
manifestReadyTaskStaticSmokeCount: 0,
manifestReadyTaskPreviewValidationCount: 0,
manifestReadyTaskCanvasGenerationCount: 0,
canonicalCodeRunCount: 0,
canonicalCodeAssetListCount: 0,
imageInspectionRequestCount: 0,
interactionExecuteCount: 0,
readyTaskRunsByAgent: {},
readyTaskCompletionsByAgent: {},
readyTaskCountsByAgent: {},
unexpectedRequestCount: 0,
rejectionCodes: {},
rejections: [],
byAgent: {},
};
let responseSequence = 0;
let parentStage = relaxedAutonomous ? 'await-manifest' : 'goal-contract';
function updateReadyTaskCounts(agentId, kind) {
const current = stats.readyTaskCountsByAgent[agentId] ?? {
run: 0,
completion: 0,
};
current[kind] += 1;
stats.readyTaskCountsByAgent[agentId] = current;
}
function recordReadyTaskRun(agentId, runId) {
if (
!relaxedAutonomous &&
!deterministicManifestReadyAgentIds.includes(agentId)
) {
throw providerError(`provider-ready-agent-unsupported:${agentId}`);
}
const existingRunId = readyTaskRunIdsByAgent.get(agentId);
if (!relaxedAutonomous && 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 (!relaxedAutonomous && readyTaskRunIdsByAgent.get(agentId) !== runId) {
throw providerError(`provider-ready-run-identity-invalid:${agentId}`);
}
if (!relaxedAutonomous && 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 (relaxedAutonomous) {
if (!tools.has('respond_to_user')) {
throw providerError(
`provider-relaxed-ready-finalization-tool-missing:${agentId}`,
);
}
if (!completedReadyTaskRuns.has(runId)) {
recordReadyTaskCompletion(agentId, runId);
}
return readyCallsResponse(agentId, runId, tools, [
nativeResponse(`${agentId} 的任务已完成。`),
]);
}
if (completedReadyTaskRuns.has(runId)) {
const retryCount = readyTaskCompletionRetryCounts.get(runId) ?? 0;
const expectedVerificationTool =
readyTaskCompletionVerificationTools.get(runId) ??
(agentId === 'preview-playtest'
? 'preview.validate'
: 'command.run_limited');
if (
tools.size === 1 &&
tools.has('respond_to_user') &&
verifiedDeliveryRepairContext(context)
) {
readyTaskCompletionRetryArmedRuns.delete(runId);
readyTaskCompletionVerificationTools.delete(runId);
readyTaskReplayAvailableRuns.delete(runId);
return readyCallsResponse(
agentId,
runId,
tools,
readyTaskFinalizationCalls(context, agentId, tools),
);
}
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),
);
}
if (
expectedVerificationTool === 'image.inspect' &&
observation?.tool === 'image.inspect' &&
['blocked', 'failed'].includes(observation?.status) &&
tools.has(runtimeFunction('image.inspect'))
) {
if (retryCount >= 16) {
throw providerError(
`provider-ready-retry-verification-exhausted:${agentId}`,
);
}
readyTaskCompletionRetryCounts.set(runId, retryCount + 1);
return readyCallsResponse(agentId, runId, tools, [
imageInspectCall(),
]);
}
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);
if (
missingGeneratedVisualAssetObservation(context, agentId) &&
tools.has(runtimeFunction('canvas.asset_generate'))
) {
if (retryCount >= 16) {
throw providerError(
`provider-ready-retry-verification-exhausted:${agentId}`,
);
}
readyTaskCompletionRetryCounts.set(runId, retryCount + 1);
stats.manifestReadyTaskCanvasGenerationCount += 1;
return readyCallsResponse(agentId, runId, tools, [
canvasAssetCall(agentId),
]);
}
if (
agentId === 'design-foundation' &&
visualAssetBlockedObservation(context) &&
tools.has(runtimeFunction('image.inspect'))
) {
if (retryCount >= 16) {
throw providerError(
`provider-ready-retry-verification-exhausted:${agentId}`,
);
}
readyTaskCompletionRetryCounts.set(runId, retryCount + 1);
readyTaskCompletionRetryArmedRuns.add(runId);
readyTaskCompletionVerificationTools.set(runId, 'image.inspect');
return readyCallsResponse(agentId, runId, tools, [imageInspectCall()]);
}
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 manifestTasksSettled(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[2]) === 0 &&
Number(match[3]) === 0 &&
Number(match[4]) === 0 &&
Number(match[1]) + Number(match[5]) === Number(match[6])
);
}
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.goal_contract')) {
stats.goalContractCount += 1;
} else if (call.name === runtimeFunction('agent.action_history')) {
stats.actionHistoryCount += 1;
} else if (call.name === runtimeFunction('agent.acceptance_update')) {
stats.acceptanceUpdateCount += 1;
} else if (call.name === runtimeFunction('agent.delegate')) {
stats.delegateActionCount += 1;
}
if (
agentId === 'code-prototype' &&
call.name === runtimeFunction('asset.list')
) {
stats.canonicalCodeAssetListCount += 1;
}
}
}
function callsResponse(agentId, tools, calls) {
requireAdvertised(tools, calls);
recordEmittedCalls(agentId, calls);
responseSequence += 1;
return chatToolResponse(responseSequence, model, calls);
}
function parentCalls(context, tools) {
if (relaxedAutonomous) {
if (manifestTasksSettled(context)) {
if (!tools.has('respond_to_user')) {
throw providerError('provider-relaxed-parent-respond-tool-missing');
}
parentStage = 'done';
return callsResponse('project-supervisor', tools, [
nativeResponse('项目任务已经完成,已交回总控。'),
]);
}
if (tools.has(runtimeFunction('task.list'))) {
const calls = [nativeAction('task.list', '查看并行任务当前状态', {})];
if (tools.has(runtimeFunction('agent.run_status'))) {
stats.runStatusCount += 1;
calls.push(runStatusCall('读取并行任务的最新运行状态'));
}
return callsResponse('project-supervisor', tools, calls);
}
if (tools.has('respond_to_user')) {
parentStage = 'done';
return callsResponse('project-supervisor', tools, [
nativeResponse('已完成当前自主构建回合。'),
]);
}
throw providerError('provider-relaxed-parent-tools-invalid');
}
const goalContractTool = runtimeFunction('agent.goal_contract');
if (tools.size === 1 && tools.has(goalContractTool)) {
if (parentStage !== 'goal-contract') {
throw providerError('provider-goal-contract-out-of-order');
}
parentStage = 'await-manifest';
return callsResponse('project-supervisor', tools, [goalContractCall()]);
}
const advertisedProjectMutation = [...tools].some((tool) =>
projectMutationFunctionNames.has(tool),
);
const advertisedSafeExecution = [
'agent.goal_contract',
'task.list',
'agent.run_status',
'command.run_limited',
'preview.validate',
'agent.action_history',
'agent.acceptance_update',
].some((tool) => tools.has(runtimeFunction(tool)));
if (advertisedProjectMutation && !advertisedSafeExecution) {
if (
tools.has('respond_to_user') &&
context.includes('当前父 run 已进入只编排模式')
) {
return callsResponse('project-supervisor', tools, [
nativeResponse('专业任务尚未收敛,继续等待 Runtime 完成门复核。'),
]);
}
throw providerError('provider-supervisor-direct-mutation-denied');
}
switch (parentStage) {
case 'await-manifest':
if (!manifestTasksCompleted(context)) {
if (
!tools.has(runtimeFunction('task.list')) ||
!tools.has(runtimeFunction('agent.run_status'))
) {
throw providerError('provider-manifest-wait-tools-invalid');
}
stats.runStatusCount += 1;
return callsResponse('project-supervisor', tools, [
nativeAction('task.list', '检查正式产物任务图是否已经收敛', {}),
runStatusCall('等待并读取并行专业任务的最新状态'),
]);
}
parentStage = 'final-static-smoke';
return parentCalls(context, tools);
case 'final-static-smoke':
if (!tools.has(runtimeFunction('command.run_limited'))) {
throw providerError('provider-final-static-smoke-tool-missing');
}
parentStage = 'final-preview';
stats.staticSmokeCount += 1;
return callsResponse('project-supervisor', tools, [
staticSmokeCall('验证全部正式产物整合后的当前 revision'),
]);
case 'final-preview': {
const observation = latestToolObservation(context);
if (
observation?.tool !== 'command.run_limited' ||
observation?.status !== 'ok'
) {
throw providerError(
'provider-final-static-smoke-observation-invalid',
);
}
if (!tools.has(runtimeFunction('preview.validate'))) {
throw providerError('provider-final-preview-tool-missing');
}
parentStage = 'final-action-history';
stats.previewValidationCount += 1;
return callsResponse('project-supervisor', tools, [
previewCall('对最终 current revision 重跑完整真实试玩和双视口检查'),
]);
}
case 'final-action-history': {
const observation = latestToolObservation(context);
if (
observation?.tool !== 'preview.validate' ||
observation?.status !== 'ok'
) {
throw providerError('provider-final-preview-observation-invalid');
}
if (!tools.has(runtimeFunction('agent.action_history'))) {
throw providerError('provider-final-action-history-tool-missing');
}
parentStage = 'final-acceptance-update';
return callsResponse('project-supervisor', tools, [
actionHistoryCall(),
]);
}
case 'final-acceptance-update':
if (!tools.has(runtimeFunction('agent.acceptance_update'))) {
throw providerError('provider-final-acceptance-update-tool-missing');
}
{
const call = acceptanceUpdateCall(
context,
extractIdentity(context).runId,
);
parentStage = 'final-respond';
return callsResponse('project-supervisor', tools, [call]);
}
case 'final-respond': {
const observation = latestToolObservation(context);
if (
observation?.tool !== 'agent.acceptance_update' ||
observation?.status !== 'ok'
) {
throw providerError('provider-final-acceptance-observation-invalid');
}
if (!tools.has('respond_to_user')) {
throw providerError('provider-final-respond-tool-missing');
}
parentStage = 'done';
return callsResponse('project-supervisor', tools, [
nativeResponse(
'可试玩塔防版本已经完成,并通过当前 revision 静态检查、完整交互试玩以及桌面和移动视口验证。',
),
]);
}
case 'done':
throw providerError('provider-parent-terminal-duplicate');
case 'goal-contract':
throw providerError('provider-goal-contract-required-first');
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-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 artDirectorReadyCalls(runId, tools, context) {
const agentId = 'art-director';
const phase = ensureReadyTaskRun(runId, agentId);
if (phase === 0) {
return readyCallsResponse(agentId, runId, tools, [
assetListCall('核对当前已有美术资产'),
]);
}
if (phase === 1) {
if (!tools.has(runtimeFunction('canvas.asset_generate'))) {
throw providerError('provider-ready-art-director-canvas-tool-missing');
}
stats.manifestReadyTaskCanvasGenerationCount += 1;
return readyCallsResponse(agentId, runId, tools, [
canvasAssetCall(agentId),
]);
}
if (phase === 2) {
if (
tools.has('respond_to_user') &&
!tools.has(runtimeFunction('asset.list'))
) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
return readyCallsResponse(agentId, runId, tools, [
assetListCall('核对统一视觉规范图已经生成并登记'),
]);
}
if (phase === 3) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
if (phase >= 4) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
throw providerError(`provider-ready-art-director-stage-invalid:${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 hasManualVerification = tools.has(
runtimeFunction('command.run_limited'),
);
const latestObservation = latestToolObservation(context);
const latestWriteBlocked =
latestObservation?.tool === 'file.write' &&
['blocked', 'failed'].includes(latestObservation?.status);
if (latestWriteBlocked && tools.has(runtimeFunction('file.write'))) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
path,
content,
`重试写入 ${path} 并交给 Runtime 收束门验证`,
),
]);
}
// Runtime validates fixed owner artifacts after the owner responds. Once
// the write itself is accepted, finish the task directly when the manual
// verification tool is absent. This branch is intentionally limited to
// the four owner-artifact agents above; code-prototype and publish-package
// still require their explicit smoke contracts.
if (
deterministicOwnerArtifactValidationAgentIds.has(agentId) &&
!hasManualVerification &&
latestObservation?.tool === 'file.write' &&
latestObservation.status === 'ok'
) {
const calls = readyTaskFinalizationCalls(context, agentId, tools);
if (calls.some((call) => call.name === 'respond_to_user')) {
recordReadyTaskCompletion(agentId, runId);
}
return readyCallsResponse(agentId, runId, tools, calls);
}
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);
}
if (!hasManualVerification) {
runData.delete(runId);
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
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;
const calls = [fileReadCall(path, `回读并核对 ${path}`)];
if (hasManualVerification) {
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
calls.push(staticSmokeCall(`验证 ${path} 写入后的当前 revision`));
}
return readyCallsResponse(agentId, runId, tools, calls);
}
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 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;
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
runData.set(runId, { existing, hasCanvas, willGenerate });
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(
'assets/manifest.art.json',
`${JSON.stringify(deterministicArtManifest(hasVisualAsset, 'manifest-ready'), null, 2)}\n`,
'写入可解析的首版美术资产清单',
),
]);
}
if (phase === 2) {
const data = runData.get(runId) ?? {
existing: false,
hasCanvas: false,
willGenerate: false,
};
if (data.willGenerate) {
stats.manifestReadyTaskCanvasGenerationCount += 1;
return readyCallsResponse(agentId, runId, tools, [
canvasAssetCall(agentId),
]);
}
const calls = [
fileReadCall('assets/manifest.art.json', '回读并核对美术清单 JSON'),
];
stats.manifestReadyTaskFileReadCount += 1;
if (data.existing) {
calls.push(assetListCall('核对新图片已登记到项目资产清单'));
} else {
calls.push(staticSmokeCall('验证美术清单写入后的当前 revision'));
stats.staticSmokeCount += 1;
stats.manifestReadyTaskStaticSmokeCount += 1;
}
return readyCallsResponse(agentId, runId, tools, calls);
}
if (phase >= 3) {
if (
phase >= 5 &&
!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`,
'补写可验证的正式美术任务产物',
),
]);
}
if (completedReadyTaskRuns.has(runId)) {
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
const data = runData.get(runId) ?? {
existing: false,
hasCanvas: false,
willGenerate: false,
};
if (
data.willGenerate &&
projectRevisionDriftObservation(context, 'canvas.asset_generate')
) {
if (!tools.has(runtimeFunction('canvas.asset_generate'))) {
throw providerError('provider-ready-art-canvas-tool-missing');
}
const retryCount = readyTaskPreCompletionRetryCounts.get(runId) ?? 0;
if (retryCount >= 16) {
throw providerError('provider-ready-art-canvas-retry-exhausted');
}
readyTaskPreCompletionRetryCounts.set(runId, retryCount + 1);
stats.manifestReadyTaskCanvasGenerationCount += 1;
return readyCallsResponse(agentId, runId, tools, [
canvasAssetCall(agentId),
]);
}
if (data.willGenerate) {
const observation = latestToolObservation(context);
if (
observation?.tool === 'canvas.asset_generate' &&
observation?.status === 'ok'
) {
stats.manifestReadyTaskFileReadCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('assets/manifest.art.json', '回读并核对美术清单 JSON'),
assetListCall('核对新图片已登记到项目资产清单'),
]);
}
if (phase === 3) {
stats.manifestReadyTaskFileReadCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileReadCall('assets/manifest.art.json', '回读并核对美术清单 JSON'),
assetListCall('核对新图片已登记到项目资产清单'),
]);
}
}
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', '读取当前入口以保留既有试玩行为'),
assetListCall('核对 Canvas 登记、图集与四类切片的权威资产身份'),
]);
}
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 relaxedManifestReadyCalls(agentId, runId, tools, context) {
const phase = ensureReadyTaskRun(runId, agentId);
const writeSpecs = {
'code-prototype': {
path: 'game/index.html',
content: deterministicLaneDefenseCanonicalHtml(),
reason: '写入可运行的游戏入口',
},
'design-foundation': {
path: 'memory/project.md',
content: deterministicProjectMemory,
reason: '写入项目基础说明',
},
'balance-seed': {
path: 'game/balance.json',
content: `${JSON.stringify(deterministicLaneDefenseBalance, null, 2)}\n`,
reason: '写入初版数值',
},
'art-asset-plan': {
path: 'assets/manifest.art.json',
content: `${JSON.stringify(deterministicArtManifest(false, 'relaxed'), null, 2)}\n`,
reason: '写入美术清单(平台素材可后续补齐)',
},
'audio-asset-plan': {
path: 'assets/manifest.audio.json',
content: `${JSON.stringify(deterministicAudioManifest, null, 2)}\n`,
reason: '写入声音清单',
},
'publish-package': {
path: 'exports/README.md',
content: deterministicPublishReadme,
reason: '写入发布说明',
},
};
const spec = writeSpecs[agentId];
if (phase === 0 && spec && tools.has(runtimeFunction('file.write'))) {
stats.sourceWriteCount += 1;
stats.manifestReadyTaskFileWriteCount += 1;
return readyCallsResponse(agentId, runId, tools, [
fileWriteCall(spec.path, spec.content, spec.reason),
]);
}
return readyTaskCompleteResponse(agentId, runId, tools, context);
}
function manifestReadyCalls(identity, tools, context) {
const { agentId, runId } = identity;
if (relaxedAutonomous) {
return relaxedManifestReadyCalls(agentId, runId, tools, context);
}
if (
[
'design-director',
'balance-director',
'art-polish',
'audio-director',
'code-director',
'publish-strategy',
].includes(agentId)
) {
return readOnlyReadyCalls(agentId, runId, tools, context);
}
if (agentId === 'art-director') {
return artDirectorReadyCalls(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 finalReply(identity, context) {
const key = `${identity.agentId}\0${identity.runId}`;
if (finalReplyRuns.has(key)) {
throw providerError('provider-duplicate-final-reply-request');
}
if (
!relaxedAutonomous &&
!context.includes('给用户一个正常中文回复') &&
!context.includes('给开发者一个正常中文回复')
) {
throw providerError('provider-unexpected-text-request');
}
if (
!relaxedAutonomous &&
identity.agentId === 'project-supervisor' &&
parentStage !== 'done'
) {
throw providerError('provider-parent-final-reply-before-acceptance');
}
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')
) {
if (payload.reasoning_effort !== 'max') {
throw providerError('provider-interaction-reasoning-effort-invalid');
}
if (stats.interactionExecuteCount !== 0) {
throw providerError('provider-interaction-execute-duplicate');
}
if (
stats.manifestReadyTaskRunCount !== 0 ||
stats.manifestReadyTaskCompletionCount !== 0
) {
throw providerError('provider-interaction-execute-out-of-order');
}
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: {
informationHud: true,
gameplaySurface: true,
objectiveEntities: true,
primaryControls: true,
failureRestartFlow: true,
responsiveLayout: 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);
}
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);
}
// Debug builds intentionally exercise the first-party account surface. The
// deterministic fixture itself is written against the canonical External
// Editor contract so that the same responses cover both build modes. Keep
// this translation local to the fixture; production routing remains owned by
// the Rust client and the server's public contracts.
function canonicalCanvasApiPath(pathname) {
if (pathname.startsWith('/api/external/v1/')) return pathname;
if (pathname.startsWith('/api/editor/')) {
return `/api/external/v1${pathname.slice('/api'.length)}`;
}
if (pathname.startsWith('/api/assets/')) {
return `/api/external/v1${pathname.slice('/api'.length)}`;
}
const generationJobsPrefix = '/api/runtime/external-generation/jobs/';
if (pathname.startsWith(generationJobsPrefix)) {
return `/api/external/v1/generations/${pathname.slice(generationJobsPrefix.length)}`;
}
return null;
}
function createDeterministicCanvasFixture(apiKey) {
const projectId = 'deterministic-canvas-project';
const folderId = 'deterministic-canvas-folder';
const images = new Map();
const generationOperations = new Map();
const uploadedObjects = new Map();
const registeredResources = new Map();
const imageCache = new Map();
const stats = {
canvasApiRequestCount: 0,
canvasGenerationRequestCount: 0,
canvasDownloadRequestCount: 0,
canvasSliceDownloadRequestCount: 0,
canvasGeneratedAspectRatios: {},
};
let projectTitle = null;
let folderLabel = null;
let generationSequence = 0;
let uploadSequence = 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 canonicalPath = canonicalCanvasApiPath(parsed.pathname);
const isCanvasApi = canonicalPath !== null;
// Direct-upload tickets point back at this loopback server. The fixture
// does not need to inspect multipart bytes; draining the request and
// acknowledging the configured success status is sufficient because the
// subsequent confirm call is the authoritative object registration step.
const isDirectUpload =
request.method === 'POST' && parsed.pathname === '/' && !isCanvasApi;
if (!isSignedImage && !isCanvasApi && !isDirectUpload) return null;
if (isDirectUpload) {
request.resume();
return {
statusCode: 204,
contentType: 'text/plain; charset=utf-8',
bytes: Buffer.alloc(0),
};
}
stats.canvasApiRequestCount += 1;
if (
!isSignedImage &&
request.headers.authorization !== `Bearer ${apiKey}`
) {
request.resume();
return json(401, { error: { message: 'unauthorized' } });
}
if (
request.method === 'GET' &&
canonicalPath === '/api/external/v1/editor/projects'
) {
request.resume();
return json(200, {
data: {
projects: projectTitle ? [{ projectId, title: projectTitle }] : [],
},
});
}
if (
request.method === 'POST' &&
canonicalPath === '/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' &&
canonicalPath === '/api/external/v1/editor/assets/library'
) {
request.resume();
return json(200, {
data: {
library: {
folders: folderLabel ? [{ folderId, label: folderLabel }] : [],
},
},
});
}
if (
request.method === 'POST' &&
canonicalPath === '/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' &&
canonicalPath === '/api/external/v1/editor/icon-spritesheets/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 aspectRatio = body?.aspectRatio === '16:9' ? '16:9' : '1:1';
const width = aspectRatio === '16:9' ? 1280 : 1024;
const height = aspectRatio === '16:9' ? 720 : 1024;
generationSequence += 1;
stats.canvasGenerationRequestCount += 1;
stats.canvasGeneratedAspectRatios[aspectRatio] =
(stats.canvasGeneratedAspectRatios[aspectRatio] ?? 0) + 1;
const imageId = `deterministic-${generationSequence}`;
const objectKey = `generated/deterministic/${imageId}.png`;
const assetObjectId = `asset-object-${imageId}`;
const resourceId = `resource-${imageId}`;
const taskId = `task-${imageId}`;
images.set(imageId, {
aspectRatio,
width,
height,
bytes: deterministicPng(width, height, {
variant: generationSequence,
transparent: true,
}),
objectKey,
downloadKind: 'generation',
});
const sliceNames = ['玩家主体', '目标与危险物', '场景与资源', '反馈特效'];
const iconImageSrcs = sliceNames.map((name, index) => {
const sliceImageId = `${imageId}-slice-${index + 1}`;
const sliceObjectKey = `generated/deterministic/${sliceImageId}.png`;
const sliceResourceId = `resource-${sliceImageId}`;
const sliceAssetObjectId = `asset-object-${sliceImageId}`;
images.set(sliceImageId, {
aspectRatio: '1:1',
width: 256,
height: 256,
bytes: deterministicPng(256, 256, {
variant: generationSequence * 10 + index + 1,
// The canonical art contract requires every independently
// usable slice to retain transparent pixels. Keep the fixture
// faithful to the External Editor response instead of making the
// runtime relax that final validation.
transparent: true,
}),
objectKey: sliceObjectKey,
downloadKind: 'slice',
});
return {
name,
imageSrc: `/${sliceObjectKey}`,
width: 256,
height: 256,
projectId,
taskId,
sourceResourceId: resourceId,
resource: {
resourceId: sliceResourceId,
projectId,
imageSrc: `/${sliceObjectKey}`,
objectKey: sliceObjectKey,
assetObjectId: sliceAssetObjectId,
taskId,
sourceResourceId: resourceId,
},
asset: {
assetId: `asset-${sliceImageId}`,
assetObjectId: sliceAssetObjectId,
assetKind: 'art-spritesheet-slice',
projectId,
taskId,
},
};
});
const operationId = `task-${imageId}`;
generationOperations.set(operationId, {
spritesheetImageSrc: `/${objectKey}`,
objectKey,
assetObjectId,
resourceId,
projectId,
taskId,
width,
height,
sourceType: 'generated',
prompt:
body?.iconDescriptions?.join('\n') ??
'deterministic spritesheet fixture',
actualPrompt:
body?.iconDescriptions?.join('\n') ??
'deterministic spritesheet fixture',
model: 'deterministic-canvas-v1',
provider: 'deterministic-loopback',
sliceMode: 'connected-components',
spritesheetResource: {
resourceId,
projectId,
imageSrc: `/${objectKey}`,
objectKey,
assetObjectId,
width,
height,
sourceType: 'generated',
taskId,
},
spritesheetAsset: {
assetId: `asset-${imageId}`,
assetObjectId,
assetKind: 'art-spritesheet',
projectId,
taskId,
},
iconImageSrcs,
});
return json(202, {
data: {
operationId,
kind: 'editor_icon_spritesheet_generation',
status: 'queued',
statusUrl: `/api/external/v1/generations/${operationId}`,
pollAfterMs: 1,
updatedAtMicros: generationSequence,
},
});
}
if (
request.method === 'POST' &&
canonicalPath === '/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,
downloadKind: 'generation',
});
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' &&
canonicalPath?.startsWith('/api/external/v1/generations/')
) {
request.resume();
const operationId = canonicalPath.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' &&
canonicalPath === '/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 === 'POST' &&
canonicalPath === '/api/external/v1/assets/direct-upload-tickets'
) {
const body = await readJsonBody(request);
uploadSequence += 1;
const pathSegments = Array.isArray(body?.pathSegments)
? body.pathSegments.filter(
(segment) => typeof segment === 'string' && segment.trim(),
)
: [];
const objectKey =
pathSegments.length > 0
? `${pathSegments.join('/')}/deterministic-reference-${uploadSequence}.png`
: `generated/deterministic/reference-${uploadSequence}.png`;
const assetObjectId = `asset-object-reference-${uploadSequence}`;
uploadedObjects.set(objectKey, { assetObjectId });
const host = request.headers.host
? `http://${request.headers.host}`
: `http://${LOOPBACK_HOST}`;
return json(200, {
data: {
upload: {
host,
bucket: 'deterministic',
objectKey,
successActionStatus: 204,
maxSizeBytes: MAX_REQUEST_BYTES,
formFields: { key: objectKey },
},
},
});
}
if (
request.method === 'POST' &&
canonicalPath === '/api/external/v1/assets/objects/confirm'
) {
const body = await readJsonBody(request);
const objectKey =
typeof body?.objectKey === 'string' && body.objectKey.trim()
? body.objectKey.trim()
: null;
if (!objectKey) {
return json(400, { error: { message: 'objectKey is required' } });
}
const existing = uploadedObjects.get(objectKey);
const assetObjectId =
existing?.assetObjectId ?? `asset-object-confirmed-${++uploadSequence}`;
uploadedObjects.set(objectKey, { assetObjectId });
return json(200, {
data: { assetObject: { objectKey, assetObjectId } },
});
}
if (
request.method === 'POST' &&
canonicalPath?.match(
/^\/api\/external\/v1\/editor\/projects\/[^/]+\/resources$/,
)
) {
const body = await readJsonBody(request);
const objectKey =
typeof body?.objectKey === 'string' && body.objectKey.trim()
? body.objectKey.trim()
: `resource-${registeredResources.size + 1}`;
if (!registeredResources.has(objectKey)) {
registeredResources.set(objectKey, {
resourceId: `resource-reference-${registeredResources.size + 1}`,
});
}
return json(200, {
data: {
resource: {
...(body && typeof body === 'object' ? body : {}),
resourceId: registeredResources.get(objectKey).resourceId,
},
},
});
}
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' } });
if (image.downloadKind === 'slice') {
stats.canvasSliceDownloadRequestCount += 1;
} else {
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,
relaxed = false,
fallbackPorts = DEFAULT_FALLBACK_PORTS,
} = {}) {
const router = createDeterministicLaneDefenseRouter({
apiKey,
model,
relaxed,
});
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;
},
});
}