补齐自主可玩项目确定性门禁

- 新增确定性 OpenAI Chat Provider 与安全 E2E wrapper
- 修复成功试玩后历史失败 observation 仍阻断收束的问题
- 覆盖塔防专业返工、双视口和 37 条试玩断言并同步文档
This commit is contained in:
AIGameCreator App
2026-07-21 18:24:52 +08:00
parent 1fce5460f9
commit d8a070f8f8
7 changed files with 1217 additions and 5 deletions
+2
View File
@@ -19,6 +19,8 @@
"agent-runtime:mixed-swarm-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-static-isolated-autonomous-chat",
"agent-runtime:supervisor-swarm-autonomous-chat-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-autonomous-chat",
"agent-runtime:supervisor-autonomous-playable-lane-defense-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-autonomous-playable-lane-defense",
"agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e": "node scripts/agent-runtime-deterministic-playable-e2e.mjs",
"agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-self-test": "node scripts/agent-runtime-deterministic-playable-e2e.mjs --self-test",
"agent-runtime:supervisor-swarm-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-transient-retry",
"agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-final-reply-transient-retry",
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
@@ -0,0 +1,395 @@
import { spawn } from 'node:child_process';
import { createHash, randomUUID } from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
createDeterministicLaneDefenseRouter,
deterministicLaneDefenseInitialHtml,
deterministicLaneDefenseModel,
hiddenCanvasCss,
startDeterministicLaneDefenseProvider,
visibleCanvasCss,
} from './deterministic-lane-defense-provider.mjs';
import { withLoopbackNoProxy } from './llm-transient-fault-proxy.mjs';
const appRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
const repoRoot = path.resolve(appRoot, '../..');
const realE2eScript = path.join(appRoot, 'scripts/agent-runtime-real-e2e.mjs');
const suite = 'supervisor-autonomous-playable-lane-defense';
const wrapperSuite =
'supervisor-autonomous-playable-lane-defense-deterministic';
const configFileName = 'game-creator.config.json';
const configSentinelName = '.deterministic-provider-e2e.json';
const configSentinelSchema = 'genarrative-deterministic-provider-e2e-config.v1';
const outputLimit = 32 * 1024 * 1024;
function hashValue(value) {
return createHash('sha256').update(value).digest('hex');
}
function assert(condition, code) {
if (condition) return;
const error = new Error(code);
error.code = code;
throw error;
}
function parseArguments(args) {
let keepProject = false;
let selfTest = false;
for (const arg of args) {
if (arg === '--keep-project') keepProject = true;
else if (arg === '--self-test') selfTest = true;
else throw new Error('unknown-argument');
}
assert(!(keepProject && selfTest), 'self-test-keep-project-conflict');
return { keepProject, selfTest };
}
function appendBounded(current, chunk) {
const combined = Buffer.concat([current, chunk]);
if (combined.length > outputLimit) throw new Error('child-output-too-large');
return combined;
}
function runChild(args, environment) {
return new Promise((resolve) => {
const child = spawn(process.execPath, args, {
cwd: appRoot,
env: environment,
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = Buffer.alloc(0);
let stderr = Buffer.alloc(0);
let outputError = null;
child.stdout.on('data', (chunk) => {
try {
stdout = appendBounded(stdout, chunk);
} catch (error) {
outputError = error;
child.kill('SIGTERM');
}
});
child.stderr.on('data', (chunk) => {
try {
stderr = appendBounded(stderr, chunk);
} catch (error) {
outputError = error;
child.kill('SIGTERM');
}
});
child.once('error', (error) =>
resolve({ code: null, signal: null, error, stdout, stderr }),
);
child.once('close', (code, signal) =>
resolve({ code, signal, error: outputError, stdout, stderr }),
);
});
}
function safeChildDiagnostic(result) {
return {
exitCode: Number.isInteger(result.code) ? result.code : null,
signal: typeof result.signal === 'string' ? result.signal : null,
errorCode:
typeof result.error?.code === 'string' ? result.error.code : null,
stdoutBytes: result.stdout.length,
stdoutSha256: hashValue(result.stdout),
stderrBytes: result.stderr.length,
stderrSha256: hashValue(result.stderr),
};
}
function parseChildReport(result) {
try {
const report = JSON.parse(result.stdout.toString('utf8'));
assert(report && typeof report === 'object', 'child-report-root-invalid');
return report;
} catch (error) {
if (error?.code) throw error;
const wrapped = new Error('child-report-json-invalid');
wrapped.code = 'child-report-json-invalid';
throw wrapped;
}
}
function expectedProviderStats(stats) {
return (
stats.requestCount === 17 &&
stats.planningRequestCount === 17 &&
stats.finalReplyRequestCount === 0 &&
stats.initialDelegationCount === 2 &&
stats.followupDelegationCount === 1 &&
stats.runStatusCount === 2 &&
stats.sourceWriteCount === 1 &&
stats.sourcePatchCount === 1 &&
stats.staticSmokeCount === 4 &&
stats.previewValidationCount === 2 &&
stats.supervisorDirectMutationAttemptCount === 1 &&
stats.unexpectedRequestCount === 0 &&
Object.keys(stats.rejectionCodes ?? {}).length === 0 &&
stats.byAgent?.['project-supervisor']?.planning === 9 &&
stats.byAgent?.['project-supervisor']?.finalReply === 0 &&
stats.byAgent?.['code-prototype']?.planning === 6 &&
stats.byAgent?.['code-prototype']?.finalReply === 0 &&
stats.byAgent?.['quality-review']?.planning === 2 &&
stats.byAgent?.['quality-review']?.finalReply === 0
);
}
function syntheticPayload(agentId, runId, tools, extraContext = '') {
return {
model: deterministicLaneDefenseModel,
stream: false,
messages: [
{
role: 'user',
content: `- templateAgentId: ${agentId}\n- runId: ${runId}\n${extraContext}`,
},
],
tools: tools.map((name) => ({ type: 'function', function: { name } })),
};
}
function responseFunctionNames(response) {
return response.choices[0].message.tool_calls.map(
(call) => call.function.name,
);
}
async function runSelfTest() {
const html = deterministicLaneDefenseInitialHtml();
assert([...html].length <= 8_000, 'self-test-html-source-budget-invalid');
assert(
html.includes(hiddenCanvasCss) &&
!html.includes(visibleCanvasCss) &&
html.includes('playable-web-game-state.v1') &&
html.includes('data-playtest-id="next-level"') &&
html.includes('Goal: defend the garden and win every wave.') &&
html.includes('requestAnimationFrame'),
'self-test-html-contract-invalid',
);
const apiKey = `deterministic-self-test-${randomUUID()}`;
const router = createDeterministicLaneDefenseRouter({ apiKey });
const allTools = [
'update_agent_plan',
'respond_to_user',
'runtime_tool_agent_delegate',
'runtime_tool_agent_run_status',
'runtime_tool_command_run_limited',
'runtime_tool_file_patch',
'runtime_tool_file_write',
'runtime_tool_preview_validate',
];
const route = (agentId, runId, tools = allTools, extraContext = '') =>
router.route({
authorization: `Bearer ${apiKey}`,
payload: syntheticPayload(agentId, runId, tools, extraContext),
});
assert(
responseFunctionNames(route('project-supervisor', 'parent-run')).join(
',',
) === 'runtime_tool_agent_delegate,runtime_tool_agent_delegate',
'self-test-initial-delegation-invalid',
);
const qualityPlan =
'计划进度:\n- #1 [in_progress] 核对完整玩法\n- #2 [pending] 回传结论\n工具策略:auto=无';
const builderPlan =
'计划进度:\n- #1 [completed] 生成入口\n- #2 [completed] 静态验证\n- #3 [in_progress] 回传结论\n工具策略:auto=无';
route('quality-review', 'quality-run', allTools, qualityPlan);
route('code-prototype', 'initial-code-run');
route('code-prototype', 'initial-code-run');
assert(
responseFunctionNames(
route('code-prototype', 'initial-code-run', allTools, builderPlan),
).join(',') === 'update_agent_plan,respond_to_user',
'self-test-builder-plan-completion-invalid',
);
assert(
responseFunctionNames(
route('quality-review', 'quality-run', allTools, qualityPlan),
).join(',') === 'update_agent_plan,respond_to_user',
'self-test-quality-stale-replan-invalid',
);
route('project-supervisor', 'parent-run');
route('project-supervisor', 'parent-run');
route('project-supervisor', 'parent-run');
assert(
responseFunctionNames(route('project-supervisor', 'parent-run'))[0] ===
'runtime_tool_file_patch',
'self-test-forbidden-parent-mutation-invalid',
);
assert(
responseFunctionNames(
route(
'project-supervisor',
'parent-run',
['runtime_tool_agent_delegate'],
'当前父 run 已进入只编排模式,本次修复的原生工具目录只保留 agent.delegate',
),
)[0] === 'runtime_tool_agent_delegate',
'self-test-followup-delegation-invalid',
);
route('code-prototype', 'repair-code-run');
route('code-prototype', 'repair-code-run');
route('code-prototype', 'repair-code-run', allTools, builderPlan);
assert(
responseFunctionNames(route('project-supervisor', 'parent-run')).join(
',',
) === 'runtime_tool_command_run_limited,runtime_tool_preview_validate',
'self-test-repair-verification-batch-invalid',
);
route('project-supervisor', 'parent-run');
route('project-supervisor', 'parent-run');
const stats = router.getStats();
assert(expectedProviderStats(stats), 'self-test-provider-stats-invalid');
const rootPackage = JSON.parse(
await fs.readFile(path.join(repoRoot, 'package.json'), 'utf8'),
);
const shellPackage = JSON.parse(
await fs.readFile(path.join(appRoot, 'package.json'), 'utf8'),
);
assert(
shellPackage.scripts?.[
'agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e'
] === 'node scripts/agent-runtime-deterministic-playable-e2e.mjs' &&
rootPackage.scripts?.[
'ai-game-creator-shell:agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e'
] ===
'npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e --',
'self-test-package-command-invalid',
);
return {
status: 'PASS',
suite: `${wrapperSuite}-self-test`,
providerUsed: false,
htmlChars: [...html].length,
providerStats: stats,
packageCommandsRegistered: true,
};
}
async function runE2e(options) {
const token = randomUUID();
const apiKey = `deterministic-runtime-${randomUUID()}`;
const configDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'genarrative-deterministic-provider-config-'),
);
let provider = null;
let childResult = null;
let childReport = null;
let configRemoved = false;
let providerStats = null;
let failureCode = null;
try {
if (process.platform !== 'win32') await fs.chmod(configDir, 0o700);
await fs.writeFile(
path.join(configDir, configSentinelName),
`${JSON.stringify({ schemaVersion: configSentinelSchema, token })}\n`,
{ flag: 'wx', mode: 0o600 },
);
provider = await startDeterministicLaneDefenseProvider({ apiKey });
const config = {
llm: {
apiKey,
baseUrl: provider.baseUrl,
model: deterministicLaneDefenseModel,
apiKind: 'openai_chat',
reasoningEffort: 'low',
stream: false,
requestTimeoutMs: 30_000,
maxRetries: 0,
retryBackoffMs: 100,
},
};
await fs.writeFile(
path.join(configDir, configFileName),
`${JSON.stringify(config)}\n`,
{ flag: 'wx', mode: 0o600 },
);
const childArgs = [
realE2eScript,
'--suite',
suite,
'--config-dir',
configDir,
];
if (options.keepProject) childArgs.push('--keep-project');
childResult = await runChild(
childArgs,
withLoopbackNoProxy({ ...process.env, NO_COLOR: '1' }),
);
childReport = parseChildReport(childResult);
} catch (error) {
failureCode = error?.code ?? 'deterministic-e2e-unexpected-error';
} finally {
if (provider) {
try {
await provider.stop();
providerStats = provider.getStats();
} catch {
failureCode ??= 'deterministic-provider-stop-failed';
}
}
try {
const sentinel = JSON.parse(
await fs.readFile(path.join(configDir, configSentinelName), 'utf8'),
);
assert(
sentinel.schemaVersion === configSentinelSchema &&
sentinel.token === token,
'deterministic-config-sentinel-invalid',
);
await fs.rm(configDir, { recursive: true, force: false });
configRemoved = true;
} catch (error) {
failureCode ??= error?.code ?? 'deterministic-config-cleanup-failed';
}
}
const childPassed =
childResult?.code === 0 &&
childResult?.signal === null &&
!childResult?.error &&
childReport?.status === 'PASS' &&
childReport?.suite === suite &&
childReport?.cleanup?.performed === !options.keepProject &&
childReport?.cleanup?.kept === options.keepProject;
const providerPassed =
providerStats?.stopped === true && expectedProviderStats(providerStats);
const status =
!failureCode && childPassed && providerPassed && configRemoved
? 'PASS'
: 'FAIL';
if (status !== 'PASS' && !failureCode) {
failureCode = !childPassed
? 'deterministic-child-e2e-failed'
: !providerPassed
? 'deterministic-provider-contract-failed'
: 'deterministic-config-not-cleaned';
}
return {
status,
suite: wrapperSuite,
providerMode: 'deterministic-loopback-openai-chat',
delegatedSuite: suite,
child: childReport,
provider: providerStats,
cleanup: {
providerStopped: providerStats?.stopped === true,
configRemoved,
projectKept: options.keepProject,
},
childDiagnostic: childResult ? safeChildDiagnostic(childResult) : null,
failureCode,
};
}
const options = parseArguments(process.argv.slice(2));
const report = options.selfTest ? await runSelfTest() : await runE2e(options);
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
process.exitCode = report.status === 'PASS' ? 0 : 1;
File diff suppressed because it is too large Load Diff
@@ -17397,6 +17397,74 @@ mod autonomous_completion_contract_tests {
.iter()
.any(|message| message.role == "assistant" && message.content == response));
}
#[test]
fn autonomous_playtest_liveness_only_enforces_the_latest_preview_result() {
let verification_gate = AgentRuntimeVerificationGate {
schema_version: AGENT_RUNTIME_VERIFICATION_GATE_SCHEMA_VERSION.to_string(),
project_id: "autonomous-playtest-liveness-project".to_string(),
agent_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(),
run_id: "autonomous-playtest-liveness-run".to_string(),
requires_verification: false,
mutation_revision: None,
verified_revision: Some(2),
last_mutation_tool: None,
last_verification_tool: Some("game.static_smoke".to_string()),
last_verification_status: Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED.to_string()),
failed_playtest_revision: None,
updated_at: 0,
};
let failed_preview = AgentRuntimeToolObservation {
tool: "preview.validate".to_string(),
status: "failed".to_string(),
summary: "浏览器验证未通过,请根据诊断修复后重试".to_string(),
detail: None,
};
let passed_preview = AgentRuntimeToolObservation {
tool: "preview.validate".to_string(),
status: "ok".to_string(),
summary: "浏览器验证已通过,已生成桌面与移动证据".to_string(),
detail: None,
};
let non_progress = || AgentRuntimeToolObservation {
tool: "memory.read".to_string(),
status: "ok".to_string(),
summary: "已读取记忆".to_string(),
detail: None,
};
let response_plan = AgentRuntimeToolPlan {
response: "项目已完成真实试玩验证。".to_string(),
..AgentRuntimeToolPlan::default()
};
let mut superseded_failure = vec![failed_preview.clone(), passed_preview.clone()];
superseded_failure.extend((0..4).map(|_| non_progress()));
assert!(validate_agent_runtime_autonomous_plan_liveness(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
7,
2,
&verification_gate,
&superseded_failure,
&response_plan,
true,
)
.is_ok());
let mut latest_failure = vec![passed_preview, failed_preview];
latest_failure.extend((0..4).map(|_| non_progress()));
let error = validate_agent_runtime_autonomous_plan_liveness(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
7,
2,
&verification_gate,
&latest_failure,
&response_plan,
true,
)
.expect_err("latest failed preview must still require delegated repair");
assert!(error
.starts_with(AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX));
}
}
fn game_creator_agent_runtime_verification_gate_relative_path(
@@ -28438,13 +28506,19 @@ fn validate_agent_runtime_autonomous_plan_liveness(
));
}
}
let Some(failed_playtest_index) = observations.iter().rposition(|observation| {
observation.tool == "preview.validate"
&& observation.status == "failed"
&& observation.summary == "浏览器验证未通过,请根据诊断修复后重试"
}) else {
let Some(latest_playtest_index) = observations
.iter()
.rposition(|observation| observation.tool == "preview.validate")
else {
return Ok(());
};
let latest_playtest = &observations[latest_playtest_index];
if latest_playtest.status != "failed"
|| latest_playtest.summary != "浏览器验证未通过,请根据诊断修复后重试"
{
return Ok(());
}
let failed_playtest_index = latest_playtest_index;
let observations_after_failure = &observations[failed_playtest_index + 1..];
if observations_after_failure
.iter()
@@ -5194,3 +5194,11 @@
- 当前 run 状态与项目历史成果是两个投影:前者继续按当前 `parentRunId` 过滤,后者只从专业 Agent 持久对话中合法 `agent-finalization-<32 lower hex>` assistant 恢复。新 run 失败或待确认不清除旧成果,普通失败 assistant 不得进入资源管理。
- 历史成果读取采用项目内单调合并:新的合法 finalization 可以替换同 Agent 的旧回执,但 Runtime 轮询引发的持久对话瞬时读取失败、空结果或新 run 普通失败消息都不得清空已恢复成果。资源卡必须明确标记“历史成果”,继续与 manifest 正式资产和项目文件区分。
- Tauri `read_local_conversation` 的公开消息 DTO 必须把持久 JSONL 的可选 `messageId` 原样投影给前端;否则真实 finalization 在客户端边界丢失身份,前端只能看到普通 assistant 并把资源区错误显示为 0。旧消息缺少 ID 时保持 `null`,不按正文或时间猜测成果。
## 2026-07-21 AI 游戏创作自主可玩项目确定性真实门禁
- 决策:新增独立 loopback OpenAI Chat Provider 与 `supervisor-autonomous-playable-lane-defense-deterministic` wrapper,复用正式自主构建 suite、真实 Runner、真实 Runtime 工具、真实 Chrome 双视口和 disposable 项目。Provider 只能返回原生 function calls,不能直接写项目或伪造验证结果;临时配置必须位于仓库外并由 sentinel 约束清理。
- 协作顺序:Supervisor 首轮并行委派程序与只读验收;验收回复因并行写入变成 stale 时必须基于最新 revision 再规划。首轮浏览器失败后,Supervisor 直接 mutation 必须被 orchestrator-only 策略拒绝,再创建后续程序委派;新 revision 的静态复验和浏览器复验必须放在同一 planning 批次,成功后再认领后续回执,最后才允许唯一 Supervisor 回复。
- liveness 语义:observation 兜底只判断最新一条 `preview.validate`。最新成功结果会取代同一 run 更早的失败 observation,不能在成功试玩后因历史失败再次强制委派;最新结果仍为失败时继续阻断收束。持久 verification gate 的 `failedPlaytestRevision` 仍是优先事实源,不因本次修正放宽。
- 验收证据:本轮确定性 wrapper 与正式子 suite 同轮 PASS。Provider `17` 次 planning、异常 `0`;项目 revision `0 -> 2``game.static_smoke` 与桌面/移动浏览器通过,`lane-defense-v1` 固定试玩 `37/37`;三份委派回执全部认领,唯一 Supervisor assistantpending、sidecar、reconciliation、重复、密钥/正文/路径泄漏均为 `0`,隔离 Runner、AppData、配置和项目全部清理。
- 边界:本门禁证明本地确定性 OpenAI-compatible 路由可以驱动完整正式链路,不代表任何外部 Provider 已通过。外部 Provider 的鉴权、网络与模型行为必须用独立同轮 E2E 记录,不能与本结果拼接。
@@ -667,3 +667,6 @@ game-project/
- tool-plan arguments 只允许出现在 `0600` 原子 sidecar 及后续 pending/action batch,不得进入 task/event/Agent DB/CLI/report。公共 protocol/repair 审计共同保存 Agent/task/Session/run/source、loop/repair/slot、响应指纹、Provider request ID SHA-256 和 protocolprotocol 只保存 function call 数量、call ID SHA-256 数组、catalog-bound function names、response ID SHA-256/字符数及 normalization 元数据,repair 只保存 attempt/maxAttempts、协议错误/preview 哈希和 call ID/function name SHA-256,不保存原始 callId/callIds/responseId/providerRequestId,并在 Agent DB append 锁内按完整身份全历史幂等追加。为了保持执行语义,参数禁止静默脱敏;命中密钥、配置痕迹、敏感 JSON key、Provider ID 中的秘密/绝对路径、结构化可执行路径中的项目或其它绝对路径、大小/顺序/身份冲突时直接 reconciliation。源码正文和计划叙述只做密钥检查,不能把 HTML 闭合标签当绝对路径;未闭合 thinking 只留无正文无效元数据并继续 repair。账本保留到 run 终态或明确作废;steer/cancel/漂移/终态清理前先闭合整本账本的实际 requestIdRunner 恢复严格扫描 hash/primary/`.previous`/安全临时文件并回收合法终态残留,确保单动作、多动作、confirmation、协作 batch 与直接回复在下一 durable owner 建立前都有恢复来源;未知、冲突、primary、`.previous` 或损坏账本都阻止 Runner idle shutdown。
- V1.43 的确定性门禁必须覆盖 base handoff 与 repair handoff 两个 lifecycle-completed 前断点,关闭 mock Provider 后恢复零网络、原 requestId 唯一闭合、repair/protocol audit 幂等、唯一 assistant/completed/committed stream及终局零 sidecar。独立非默认真实门禁 `supervisor-swarm-tool-plan-handoff-runner-kill` 已实现并完成 Shell/Root 两级注册:它使用 sentinel-owned sibling AppData 与 metadata-only zero-fault proxy,以每轮随机 capability 严格绑定 project/Agent/run/实际 request slot;只有 tool-plan handoff 原子落盘并回读一致、同一实际 requestId lifecycle 尚未 `completed` 时才 ACK,随后通过 pidfd `SIGKILL` 强杀 suite 自有 Runner。恢复必须证明同一 requestId 唯一闭合且 `networkReplayCount=0`、protocol/repair audit 幂等、handoff 与 durable batch plan fingerprint 对应、恢复消费前 action/pending/delivery/claim 等副作用为 `0`,并在终局把 sidecar、重复记录、临时 capability/Runner/AppData 资源及公共正文、凭据、URL、项目/正式配置路径泄漏全部清零。2026-07-20 的真实外部 Provider 单轮已到达并通过 checkpoint,但随后专业 Agent 连续连接失败使整轮 FAIL;另一独立轮首批工具数不满足 fixture,也未通过。两轮不得拼接,当前仍无该 suite 的完整外部 PASS。Provider 成功到 handoff 原子落盘回读前的 unknown-result 和手动 context-compaction 仍不在本切片承诺内。
- V1.43 当前确定性实现已通过本轮 `tool_plan_handoff_ 44/44`、Supervisor collaboration 相关过滤 `55/55`、权威返工合同 `1/1`,以及 Tauri/Rust 串行全量 `1054 passed / 4 ignored / 0 failed`Linux `cargo check --tests``x86_64-pc-windows-gnu cargo check --tests` 均通过。E2E self-test、typecheck、变更脚本 ESLint、encoding 与 `git diff --check` 通过;默认并发全量只作竞态诊断,不替代串行门禁。Supervisor 真实 E2E 报告已把 `toolPlanHandoffSidecarCount` 纳入终局残留。handoff 跨平台存储使用 Unix 固定目录句柄、目录 `flock`、exchange/quarantine 与 Windows 相对父句柄、句柄枚举、独占 temp,不再根据 PID 推断写入方是否存活;主动忽略锁的同 UID 进程仍属于宿主 OS 信任边界。
- 2026-07-21 起,同一 Runtime 文档的“V1.44 自主可玩塔防确定性真实门禁”增加独立 loopback OpenAI Chat Provider 和 wrapper 命令。Provider 只返回原生 function calls,不直接修改项目、不伪造工具 observationwrapper 在仓库外创建带 sentinel 的临时配置,复用正式 `supervisor-autonomous-playable-lane-defense` suite,并在终局停止 Provider、删除配置和 disposable 项目。
- V1.44 固定验证两份首轮并行专业委派、只读验收回复因 revision 更新而重新规划、程序 Agent 写入并通过静态自检、首轮真实浏览器因隐藏 canvas 失败、Supervisor 直接修改被 orchestrator-only 策略拒绝、后续程序委派产生新 revision、同一 planning 批次完成 `game.static_smoke + preview.validate`、成功后再用 `agent.run_status` 认领后续回执,最后只由 Supervisor 回复。试玩 liveness 只以最新一条 `preview.validate` observation 为准:后续成功会取代历史失败,最新失败仍继续强制专业返工;持久 `failedPlaytestRevision` 门禁保持不变。
- 本轮 V1.44 wrapper 与正式子 suite 均为 **PASS**Provider 共 `17` 次 planning、异常请求 `0`;项目从 revision `0` 推进到 `2`,最终 `game/index.html``4924` 字节;`lane-defense-v1` 的植物选择、放置、敌人移动与受伤、胜利、下一关和重开共 `37/37` 断言通过,桌面与移动浏览器验证通过;三份专业回执全部认领,Supervisor assistant 唯一,pending、confirmation、user-input、provider batch/retry/handoff、tool-plan handoff、finalization journal、reconciliation、重复和泄漏计数均为 `0`,隔离 Runner、AppData、配置和项目已清理。该确定性 loopback PASS 不能替代外部 Provider 可用性验收;外部路由仍须单独形成同轮完整 PASS。
+2
View File
@@ -148,6 +148,8 @@
"ai-game-creator-shell:agent-runtime:real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:real-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-swarm-autonomous-chat-real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-swarm-autonomous-chat-real-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-autonomous-playable-lane-defense-real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-real-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-self-test": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-self-test --",
"ai-game-creator-shell:agent-runtime:supervisor-swarm-transient-retry-real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-swarm-transient-retry-real-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e --",
"ai-game-creator-shell:agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e --",