From f4294660d2e5694676d23f7ced437b0529219417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 23 Sep 2026 11:13:03 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=80=E5=BD=B9AGC=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=96=9C=E6=9D=A0=E5=91=BD=E4=BB=A4=E4=B8=8E?= =?UTF-8?q?=E7=BB=88=E7=AB=AFswarm=20chat=E5=85=A5=E5=8F=A3=EF=BC=9A?= =?UTF-8?q?=E7=9C=9F=E5=AE=9EE2E=E3=80=81=E8=84=9A=E6=9C=AC=E4=B8=8E?= =?UTF-8?q?=E9=97=A8=E7=A6=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 scripts/agent-swarm-test-chat.mjs、agent-runtime-deterministic-playable-e2e.mjs、deterministic-lane-defense-provider.mjs - 删除 user-input、supervisor-autonomous-playable-lane-defense、supervisor-swarm-autonomous-chat、static-isolated 与 collaboration-policy-mixed 套件 - 删除仅服务终端入口的交互式 CLI harness 管道与其进程清理、残留校验逻辑 - 清理 collaboration-assertions、evidence 模板与校验、self-test 中随之失效的混合与静态隔离断言及占位字段 - 移除 check-config.mjs 中钉住已删脚本、命令与套件的断言 - 删除 root 与应用 package.json 的 agc:test*、agc:chat、agc:swarm、各 *-real-e2e 与 test:chat* 脚本 - game-creator-config-wizard.mjs 改为从 channel-identity.mjs 取应用标识 --- apps/ai-game-creator-shell/package.json | 10 - ...ent-runtime-deterministic-playable-e2e.mjs | 2030 ----------- .../assertions/core.mjs | 63 +- .../scripts/agent-runtime-real-e2e/entry.mjs | 378 +- .../harness/app-data.mjs | 332 +- .../agent-runtime-real-e2e/harness/config.mjs | 10 - .../harness/process.mjs | 407 +-- .../harness/project.mjs | 35 +- .../harness/reporting.mjs | 19 +- .../harness/runtime.mjs | 13 +- .../agent-runtime-real-e2e/runtime-state.mjs | 278 -- .../suites/self-test.mjs | 959 +----- .../suites/supervisor-autonomous-playable.mjs | 1890 ---------- .../suites/supervisor-swarm.mjs | 1 - .../suites/supervisor-swarm/chat-session.mjs | 108 - .../collaboration-assertions.mjs | 606 ---- .../supervisor-swarm/collaboration-policy.mjs | 530 +-- .../supervisor-swarm/evidence-schema.mjs | 63 - .../supervisor-swarm/evidence-validation.mjs | 605 +--- .../suites/supervisor-swarm/execution.mjs | 140 +- .../supervisor-swarm/partial-evidence.mjs | 415 +-- .../supervisor-swarm/protocol-validation.mjs | 339 +- .../supervisor-swarm/repair-recovery.mjs | 202 +- .../suites/supervisor-swarm/setup.mjs | 209 +- .../suites/supervisor-swarm/shared.mjs | 22 - .../supervisor-swarm/suite-selection.mjs | 48 +- .../suites/user-input.mjs | 720 ---- .../scripts/agent-swarm-test-chat.mjs | 2149 ------------ .../scripts/check-config.mjs | 102 +- .../deterministic-lane-defense-provider.mjs | 3040 ----------------- .../scripts/game-creator-config-wizard.mjs | 77 +- package.json | 11 - 32 files changed, 260 insertions(+), 15551 deletions(-) delete mode 100644 apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs delete mode 100644 apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-autonomous-playable.mjs delete mode 100644 apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/chat-session.mjs delete mode 100644 apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/user-input.mjs delete mode 100644 apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs delete mode 100644 apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index 385f52277..c9c7a2d3a 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -15,20 +15,10 @@ "skill-pack:test": "node --test scripts/check-skill-pack.test.mjs", "llm-status": "node scripts/run-cli-with-config.mjs --llm-status", "agent-task": "node scripts/run-cli-with-config.mjs --agent-task", - "chat": "node scripts/run-cli-with-config.mjs --swarm-chat", - "swarm": "node scripts/run-cli-with-config.mjs --swarm-chat", "config": "node scripts/game-creator-config-wizard.mjs", - "test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open", - "test:chat:manual": "node scripts/agent-swarm-test-chat.mjs", "agent-run": "node scripts/run-cli-with-config.mjs --agent-run", "agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs", "agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs", - "agent-runtime:collaboration-policy-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-collaboration-policy-mixed-recovery", - "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", diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs deleted file mode 100644 index ef18c3780..000000000 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs +++ /dev/null @@ -1,2030 +0,0 @@ -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, - deterministicLaneDefenseCanonicalHtml, - deterministicLaneDefenseInitialHtml, - deterministicLaneDefenseModel, - deterministicManifestReadyAgentIds, - 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 platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE'; -const platformSessionFixtureName = '.deterministic-platform-session.json'; -const platformSessionFixtureSchema = - 'genarrative-agc-platform-session-fixture.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 deterministicRuntimeConfig(apiKey, provider) { - return { - agentMode: 'provider', - editorApi: { - apiKey, - baseUrl: provider.editorBaseUrl, - }, - llm: { - apiKey, - baseUrl: provider.baseUrl, - model: deterministicLaneDefenseModel, - apiKind: 'openai_chat', - reasoningEffort: 'max', - stream: false, - requestTimeoutMs: 30_000, - maxRetries: 0, - retryBackoffMs: 100, - }, - agentLlm: { - 'project-supervisor': { - reasoningEffort: 'max', - }, - }, - }; -} - -function deterministicPlatformSessionFixture(apiKey, provider) { - return { - schemaVersion: platformSessionFixtureSchema, - userId: 'deterministic-e2e-user', - accessToken: apiKey, - apiBaseUrl: provider.editorBaseUrl, - generation: 1, - }; -} - -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) { - const manifestReadyTasks = manifestReadyTaskEvidence(stats); - const projectSupervisorRuntimePlanningCount = - Number.isInteger(stats.byAgent?.['project-supervisor']?.planning) && - Number.isInteger(stats.interactionExecuteCount) - ? stats.byAgent['project-supervisor'].planning - - stats.interactionExecuteCount - : null; - return ( - Number.isInteger(stats.requestCount) && - stats.requestCount >= 40 && - stats.planningRequestCount + - stats.finalReplyRequestCount + - (stats.contextCompactionRequestCount ?? 0) + - (stats.imageInspectionRequestCount ?? 0) === - stats.requestCount && - stats.finalReplyRequestCount === 0 && - stats.interactionExecuteCount === 1 && - stats.goalContractCount === 1 && - stats.actionHistoryCount === 1 && - stats.acceptanceUpdateCount === 1 && - stats.delegateActionCount === 0 && - stats.runStatusCount >= 1 && - stats.sourceWriteCount >= 7 && - stats.staticSmokeCount >= 4 && - stats.previewValidationCount >= 2 && - manifestReadyTasks.exactlyOnce && - stats.manifestReadyTaskFileWriteCount >= 7 && - stats.manifestReadyTaskPreviewValidationCount >= 1 && - stats.manifestReadyTaskCanvasGenerationCount >= - stats.canvasGenerationRequestCount && - stats.manifestReadyTaskCanvasGenerationCount <= - stats.canvasGenerationRequestCount + 16 && - stats.imageInspectionRequestCount === 1 && - stats.canvasGenerationRequestCount === 3 && - stats.canvasDownloadRequestCount === 3 && - stats.canvasSliceDownloadRequestCount === 4 && - stats.canvasGeneratedAspectRatios?.['16:9'] === 1 && - stats.canvasGeneratedAspectRatios?.['1:1'] === 2 && - stats.canonicalCodeRunCount === 1 && - stats.canonicalCodeAssetListCount === 1 && - stats.unexpectedRequestCount === 0 && - Object.keys(stats.rejectionCodes ?? {}).length === 0 && - projectSupervisorRuntimePlanningCount >= 5 && - stats.byAgent?.['project-supervisor']?.finalReply === 0 && - stats.byAgent?.['project-supervisor']?.projectMutation === 0 && - stats.byAgent?.['code-prototype']?.planning >= 5 && - stats.byAgent?.['code-prototype']?.finalReply === 0 && - stats.byAgent?.['code-prototype']?.projectMutation === 1 && - stats.byAgent?.['quality-review']?.finalReply === 0 && - stats.byAgent?.['quality-review']?.projectMutation === 0 - ); -} - -function manifestReadyTaskEvidence(stats) { - const rawCounts = stats?.readyTaskCountsByAgent; - const byAgent = - rawCounts && typeof rawCounts === 'object' && !Array.isArray(rawCounts) - ? Object.fromEntries( - Object.entries(rawCounts) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([agentId, counts]) => [ - agentId, - { - run: counts?.run ?? null, - completion: counts?.completion ?? null, - }, - ]), - ) - : {}; - const actualAgentIds = Object.keys(byAgent); - const expectedAgentIds = [...deterministicManifestReadyAgentIds].sort(); - const exactAgentSet = - actualAgentIds.length === expectedAgentIds.length && - actualAgentIds.every( - (agentId, index) => agentId === expectedAgentIds[index], - ); - const runTotal = Object.values(byAgent).reduce( - (total, counts) => total + (Number.isInteger(counts.run) ? counts.run : 0), - 0, - ); - const completionTotal = Object.values(byAgent).reduce( - (total, counts) => - total + (Number.isInteger(counts.completion) ? counts.completion : 0), - 0, - ); - const perAgentExactlyOnce = - exactAgentSet && - expectedAgentIds.every( - (agentId) => - byAgent[agentId]?.run === 1 && byAgent[agentId]?.completion === 1, - ); - return { - expectedAgentIds, - byAgent, - runTotal, - completionTotal, - exactAgentSet, - perAgentExactlyOnce, - exactlyOnce: - perAgentExactlyOnce && - runTotal === deterministicManifestReadyAgentIds.length && - completionTotal === deterministicManifestReadyAgentIds.length && - stats?.manifestReadyTaskRunCount === - deterministicManifestReadyAgentIds.length && - stats?.manifestReadyTaskCompletionCount === - deterministicManifestReadyAgentIds.length, - }; -} - -const requiredZeroChildEvidenceFields = Object.freeze([ - 'activeRunnerKillCount', - 'approveInputCount', - 'answerInputCount', - 'steerInputCount', - 'turnReportWaitingForConfirmationCount', - 'turnReportWaitingForUserInputCount', - 'turnReportReconciliationAgentCount', - 'providerLifecycleFailedCount', - 'openProviderLifecycleCount', - 'pendingActionCount', - 'confirmationSidecarCount', - 'userInputSidecarCount', - 'providerActionBatchSidecarCount', - 'providerRetrySidecarCount', - 'providerHandoffSidecarCount', - 'toolPlanHandoffSidecarCount', - 'finalizationJournalCount', - 'reconciliationResidueCount', -]); - -function expectedChildReport(report, options, providerStats) { - const evidence = report?.evidence; - const providerRequestCount = providerStats?.requestCount; - const runtimeProviderRequestCount = - Number.isInteger(providerRequestCount) && - Number.isInteger(providerStats?.interactionExecuteCount) && - Number.isInteger(providerStats?.imageInspectionRequestCount) - ? providerRequestCount - - providerStats.interactionExecuteCount - - providerStats.imageInspectionRequestCount - : null; - return ( - report?.status === 'PASS' && - report?.suite === suite && - report?.errorCount === 0 && - Array.isArray(report?.blocked) && - report.blocked.length === 0 && - report?.cleanup?.performed === !options.keepProject && - report?.cleanup?.kept === options.keepProject && - evidence?.evidenceCompleteness === 'complete' && - evidence?.dedicatedZeroInterventionPath === true && - evidence?.stdinTaskCount === 1 && - evidence?.stdinEndedAfterTask === true && - evidence?.turnReportOutcome === 'settled' && - evidence?.parentTaskStatus === 'completed' && - evidence?.parentRuntimeStatus === 'idle' && - evidence?.parentRuntimePhase === 'completed' && - evidence?.laneDefensePlaytestPassed === true && - evidence?.laneDefenseAssertionCount === 37 && - evidence?.laneDefensePassedAssertionCount === 37 && - evidence?.browserValidationPassed === true && - evidence?.staticSmokePassed === true && - evidence?.gameIndexChanged === true && - Number.isInteger(evidence?.projectRevisionDelta) && - evidence.projectRevisionDelta > 0 && - evidence?.finalSupervisorAssistantCount === 1 && - evidence?.professionalAssistantCount >= 3 && - evidence?.providerRequestIdentityCount === runtimeProviderRequestCount && - evidence?.providerLifecycleStartedCount === runtimeProviderRequestCount && - evidence?.providerLifecycleTerminalCount === runtimeProviderRequestCount && - evidence?.providerLifecycleCompletedCount === runtimeProviderRequestCount && - requiredZeroChildEvidenceFields.every((field) => evidence?.[field] === 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 syntheticInteractionExecutePayload() { - return { - model: deterministicLaneDefenseModel, - stream: false, - reasoning_effort: 'max', - messages: [ - { - role: 'system', - content: '你现在位于统一的 Agent interaction loop。', - }, - { - role: 'user', - content: '用户这轮输入:\n制作一个三消经营游戏', - }, - ], - tools: ['runtime_execute', 'runtime_resume', 'project_location'].map( - (name) => ({ type: 'function', function: { name } }), - ), - }; -} - -function syntheticUiPrototypeInspectionPayload() { - return { - model: deterministicLaneDefenseModel, - stream: false, - messages: [ - { - role: 'system', - content: '你是游戏界面视觉检查 Agent。只分析画面。', - }, - { - role: 'user', - content: [ - { - type: 'text', - text: '请逐项检查 informationHud、gameplaySurface、objectiveEntities、primaryControls、failureRestartFlow、responsiveLayout、implementationClarity、originalTheme,并只返回严格 JSON。', - }, - { - type: 'image_url', - image_url: { url: 'data:image/png;base64,AA==' }, - }, - ], - }, - ], - }; -} - -function responseFunctionNames(response) { - return response.choices[0].message.tool_calls.map( - (call) => call.function.name, - ); -} - -function responseFunctionCalls(response) { - return response.choices[0].message.tool_calls.map((call) => ({ - name: call.function.name, - arguments: JSON.parse(call.function.arguments), - })); -} - -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 canonicalHtml = deterministicLaneDefenseCanonicalHtml(); - assert( - canonicalHtml.includes(visibleCanvasCss) && - canonicalHtml.includes("atlasArt.src='../assets/art-spritesheet.png'") && - [ - 'player.png', - 'blocks-and-targets.png', - 'obstacles-and-scene.png', - 'feedback-effects.png', - ].every((fileName) => - canonicalHtml.includes(`../assets/art-spritesheet-slices/${fileName}`), - ) && - (canonicalHtml.match(/ctx\.drawImage\(/g) ?? []).length >= 5, - 'self-test-canonical-visible-art-contract-invalid', - ); - const apiKey = `deterministic-self-test-${randomUUID()}`; - const runtimeConfig = deterministicRuntimeConfig(apiKey, { - baseUrl: 'http://127.0.0.1:41001/v1', - editorBaseUrl: 'http://127.0.0.1:41002', - }); - assert( - runtimeConfig.agentMode === 'provider' && - runtimeConfig.llm.apiKind === 'openai_chat' && - runtimeConfig.llm.reasoningEffort === 'max' && - runtimeConfig.agentLlm?.['project-supervisor']?.reasoningEffort === - 'max' && - runtimeConfig.llm.apiKey === apiKey && - runtimeConfig.editorApi.apiKey === apiKey, - 'self-test-provider-runtime-config-invalid', - ); - const captureProviderErrorCode = (operation) => { - try { - operation(); - return null; - } catch (error) { - return error?.code ?? null; - } - }; - const allTools = [ - 'update_agent_plan', - 'respond_to_user', - 'runtime_tool_agent_action_history', - 'runtime_tool_agent_acceptance_update', - 'runtime_tool_agent_run_status', - 'runtime_tool_command_run_limited', - 'runtime_tool_file_patch', - 'runtime_tool_file_write', - 'runtime_tool_preview_validate', - 'runtime_tool_task_list', - ]; - const manifestReadyTools = [ - ...allTools, - 'runtime_tool_asset_list', - 'runtime_tool_file_read', - ]; - const rootRunId = 'parent-run'; - const contractFingerprint = 'a'.repeat(64); - const contractContext = `[Root Goal Contract]\n${JSON.stringify({ contractFingerprint })}`; - const observationContext = (observations) => - `${contractContext}\n已有工具观察:\n${JSON.stringify(observations)}`; - const goalContractTools = ['runtime_tool_agent_goal_contract']; - const goalContractRouter = createDeterministicLaneDefenseRouter({ apiKey }); - goalContractRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'goal-contract-order-run', - goalContractTools, - ), - }); - assert( - captureProviderErrorCode(() => - goalContractRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'goal-contract-order-run', - goalContractTools, - ), - }), - ) === 'provider-goal-contract-out-of-order', - 'self-test-goal-contract-duplicate-not-rejected', - ); - - const router = createDeterministicLaneDefenseRouter({ apiKey }); - const route = (agentId, runId, tools = allTools, extraContext = '') => - router.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload(agentId, runId, tools, extraContext), - }); - const interactionExecuteResponse = router.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticInteractionExecutePayload(), - }); - const interactionExecuteCalls = responseFunctionCalls( - interactionExecuteResponse, - ); - const interactionExecuteStats = router.getStats(); - assert( - interactionExecuteCalls.length === 1 && - interactionExecuteCalls[0]?.name === 'runtime_execute' && - Object.keys(interactionExecuteCalls[0]?.arguments ?? {}).length === 0 && - interactionExecuteStats.requestCount === 1 && - interactionExecuteStats.planningRequestCount === 1 && - interactionExecuteStats.interactionExecuteCount === 1 && - interactionExecuteStats.manifestReadyTaskRunCount === 0 && - interactionExecuteStats.manifestReadyTaskCompletionCount === 0 && - Object.keys(interactionExecuteStats.readyTaskCountsByAgent).length === 0, - 'self-test-interaction-max-execute-sequence-invalid', - ); - const visualInspectionResponse = router.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticUiPrototypeInspectionPayload(), - }); - const visualInspection = JSON.parse( - visualInspectionResponse.choices[0].message.content, - ); - assert( - Object.keys(visualInspection?.checks ?? {}).join(',') === - 'informationHud,gameplaySurface,objectiveEntities,primaryControls,failureRestartFlow,responsiveLayout,implementationClarity,originalTheme' && - Object.values(visualInspection.checks).every((value) => value === true) && - Array.isArray(visualInspection.issues) && - visualInspection.issues.length === 0 && - router.getStats().imageInspectionRequestCount === 1 && - router.getStats().unexpectedRequestCount === 0, - 'self-test-ui-prototype-inspection-request-not-routed', - ); - const goalContractResponse = route( - 'project-supervisor', - rootRunId, - goalContractTools, - ); - const goalContractCalls = responseFunctionCalls(goalContractResponse); - const acceptanceNodes = - goalContractCalls[0]?.arguments?.input?.acceptanceNodes ?? []; - assert( - goalContractCalls.length === 1 && - goalContractCalls[0]?.name === 'runtime_tool_agent_goal_contract' && - goalContractCalls[0]?.arguments?.input?.outcome?.includes( - '可完整游玩的原创植物塔防网页游戏', - ) && - acceptanceNodes.length === 2 && - acceptanceNodes[0]?.criterionId === 'static-current-revision' && - JSON.stringify(acceptanceNodes[0]?.requiredEvidence) === - JSON.stringify(['tool:command.run_limited']) && - acceptanceNodes[1]?.criterionId === 'playable-current-revision' && - JSON.stringify(acceptanceNodes[1]?.requiredEvidence) === - JSON.stringify(['tool:preview.validate']) && - JSON.stringify(acceptanceNodes[1]?.dependsOn) === - JSON.stringify(['static-current-revision']), - 'self-test-goal-contract-invalid', - ); - assert( - responseFunctionNames( - route( - 'project-supervisor', - rootRunId, - ['runtime_tool_task_list', 'runtime_tool_agent_run_status'], - `${contractContext}\n当前正式 manifest DAG 仍有专业 task 在运行`, - ), - ).join(',') === 'runtime_tool_task_list,runtime_tool_agent_run_status', - 'self-test-manifest-wait-invalid', - ); - - const visualReadyAgentIds = new Set([ - 'art-director', - 'design-foundation', - 'art-asset-plan', - ]); - const canvasCallsByAgent = new Map(); - const callBatchesByAgent = new Map(); - for (const agentId of deterministicManifestReadyAgentIds) { - const runId = `manifest-ready-${agentId}`; - const extraContext = - `处理 manifest ready 任务:${agentId}\n` + `任务 ID:${agentId}`; - const readyTools = visualReadyAgentIds.has(agentId) - ? [ - ...manifestReadyTools, - 'runtime_tool_canvas_asset_generate', - 'runtime_tool_image_inspect', - ] - : manifestReadyTools; - let terminalCompletionCount = 0; - for (let attempt = 0; attempt < 8; attempt += 1) { - const attemptTools = - agentId === 'art-director' && attempt >= 2 - ? ['respond_to_user'] - : readyTools; - const response = route(agentId, runId, attemptTools, extraContext); - const calls = responseFunctionCalls(response); - const batches = callBatchesByAgent.get(agentId) ?? []; - batches.push(calls.map((call) => call.name)); - callBatchesByAgent.set(agentId, batches); - const canvasCall = calls.find( - (call) => call.name === 'runtime_tool_canvas_asset_generate', - ); - if (canvasCall) { - assert( - !canvasCallsByAgent.has(agentId), - `self-test-canvas-generation-duplicate:${agentId}`, - ); - canvasCallsByAgent.set(agentId, canvasCall); - } - if (calls.some((call) => call.name === 'respond_to_user')) { - terminalCompletionCount += 1; - break; - } - } - assert( - terminalCompletionCount === 1, - `self-test-manifest-ready-terminal-invalid:${agentId}`, - ); - } - const artDirectorAssetCall = canvasCallsByAgent.get('art-director'); - const designFoundationAssetCall = canvasCallsByAgent.get('design-foundation'); - const artAssetPlanAssetCall = canvasCallsByAgent.get('art-asset-plan'); - assert( - JSON.stringify(callBatchesByAgent.get('art-asset-plan')) === - JSON.stringify([ - ['runtime_tool_asset_list'], - ['runtime_tool_file_write'], - ['runtime_tool_canvas_asset_generate'], - ['runtime_tool_file_read', 'runtime_tool_asset_list'], - ['update_agent_plan', 'respond_to_user'], - ]), - 'self-test-art-asset-plan-revision-stages-invalid', - ); - assert( - JSON.stringify(callBatchesByAgent.get('code-prototype')) === - JSON.stringify([ - [ - 'runtime_tool_file_read', - 'runtime_tool_file_read', - 'runtime_tool_file_read', - ], - [ - 'runtime_tool_file_read', - 'runtime_tool_file_read', - 'runtime_tool_file_read', - 'runtime_tool_asset_list', - ], - ['runtime_tool_file_write'], - ['runtime_tool_command_run_limited'], - ['update_agent_plan', 'respond_to_user'], - ]), - 'self-test-code-prototype-asset-audit-sequence-invalid', - ); - assert( - canvasCallsByAgent.size === 3 && - artDirectorAssetCall?.arguments?.input?.outputPath === - 'assets/art-spec.png' && - artDirectorAssetCall.arguments.input.aspectRatio === '1:1' && - artDirectorAssetCall.arguments.input.assetKind === 'icon-spec' && - designFoundationAssetCall?.arguments?.input?.outputPath === - 'assets/ui-prototype.png' && - designFoundationAssetCall.arguments.input.aspectRatio === '16:9' && - designFoundationAssetCall.arguments.input.assetKind === 'ui-design' && - artAssetPlanAssetCall?.arguments?.input?.outputPath === - 'assets/art-spritesheet.png' && - artAssetPlanAssetCall.arguments.input.aspectRatio === '1:1' && - artAssetPlanAssetCall.arguments.input.assetKind === 'icon-spritesheet', - 'self-test-visual-assets-invalid', - ); - - const visualRecoveryRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const visualRecoveryRunId = 'visual-recovery-design-foundation'; - const visualRecoveryTools = [ - ...manifestReadyTools, - 'runtime_tool_canvas_asset_generate', - 'runtime_tool_image_inspect', - ]; - const visualRecoveryRoute = ( - tools = visualRecoveryTools, - extraContext = '', - ) => - visualRecoveryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'design-foundation', - visualRecoveryRunId, - tools, - `处理 manifest ready 任务:design-foundation\n任务 ID:design-foundation${extraContext}`, - ), - }); - visualRecoveryRoute(); - visualRecoveryRoute(); - const visualRecoveryCanvasResponse = visualRecoveryRoute(); - const visualRecoveryInitialInspectResponse = visualRecoveryRoute(); - assert( - responseFunctionNames(visualRecoveryCanvasResponse).join(',') === - 'runtime_tool_canvas_asset_generate' && - responseFunctionNames(visualRecoveryInitialInspectResponse).includes( - 'runtime_tool_image_inspect', - ), - 'self-test-ui-prototype-initial-inspection-sequence-invalid', - ); - const failedInspectObservation = { - tool: 'image.inspect', - status: 'failed', - summary: '视觉模型调用失败', - detail: 'upstream-422', - }; - const prematureCompletionResponse = visualRecoveryRoute( - ['update_agent_plan', 'respond_to_user'], - `\n已有工具观察:\n${JSON.stringify([failedInspectObservation])}`, - ); - assert( - responseFunctionNames(prematureCompletionResponse).includes( - 'respond_to_user', - ), - 'self-test-ui-prototype-premature-completion-fixture-invalid', - ); - const visualBlockedObservation = { - tool: 'runtime.visual_asset', - status: 'blocked', - summary: 'UI 原型视觉检查尚未通过', - detail: - 'expectedPath=assets/ui-prototype.png · requiredInspection=image.inspect', - }; - const blockedRetryResponse = visualRecoveryRoute( - visualRecoveryTools, - `\n已有工具观察:\n${JSON.stringify([visualBlockedObservation])}`, - ); - assert( - responseFunctionNames(blockedRetryResponse).join(',') === - 'runtime_tool_image_inspect', - 'self-test-ui-prototype-visual-block-did-not-retry-inspection', - ); - const failedRetryResponse = visualRecoveryRoute( - visualRecoveryTools, - `\n已有工具观察:\n${JSON.stringify([failedInspectObservation])}`, - ); - assert( - responseFunctionNames(failedRetryResponse).join(',') === - 'runtime_tool_image_inspect', - 'self-test-ui-prototype-failed-inspection-did-not-retry', - ); - const successfulInspectObservation = { - tool: 'image.inspect', - status: 'ok', - summary: '视觉检查已完成,共分析 1 张图片', - detail: null, - }; - const visualRecoveryFinalResponse = visualRecoveryRoute( - ['update_agent_plan', 'respond_to_user'], - `\n已有工具观察:\n${JSON.stringify([successfulInspectObservation])}`, - ); - const visualRecoveryStats = visualRecoveryRouter.getStats(); - assert( - responseFunctionNames(visualRecoveryFinalResponse).includes( - 'respond_to_user', - ) && - visualRecoveryStats.manifestReadyTaskRunCount === 1 && - visualRecoveryStats.manifestReadyTaskCompletionCount === 1 && - visualRecoveryStats.manifestReadyTaskCanvasGenerationCount === 1 && - visualRecoveryStats.byAgent?.['design-foundation']?.projectMutation === - 3 && - visualRecoveryStats.unexpectedRequestCount === 0, - 'self-test-ui-prototype-visual-recovery-not-exactly-once', - ); - - const finalStaticResponse = route( - 'project-supervisor', - rootRunId, - ['runtime_tool_command_run_limited'], - `${contractContext}\n已有工具观察:\nseedTaskCounts: completed=16 running=0 pending=0 waiting=0 failed=0 total=16`, - ); - assert( - responseFunctionNames(finalStaticResponse).join(',') === - 'runtime_tool_command_run_limited', - 'self-test-final-static-not-exclusive', - ); - const successfulFinalStaticObservation = { - tool: 'command.run_limited', - status: 'ok', - summary: 'game.static_smoke 已完成', - detail: null, - }; - const finalPreviewResponse = route( - 'project-supervisor', - rootRunId, - ['runtime_tool_preview_validate'], - observationContext([successfulFinalStaticObservation]), - ); - assert( - responseFunctionNames(finalPreviewResponse).join(',') === - 'runtime_tool_preview_validate', - 'self-test-final-preview-not-exclusive', - ); - const successfulFinalPreviewObservation = { - tool: 'preview.validate', - status: 'ok', - summary: '桌面与移动视口真实试玩已通过', - detail: null, - }; - const finalHistoryResponse = route( - 'project-supervisor', - rootRunId, - ['runtime_tool_agent_action_history'], - observationContext([successfulFinalPreviewObservation]), - ); - const finalHistoryCalls = responseFunctionCalls(finalHistoryResponse); - assert( - finalHistoryCalls.length === 1 && - finalHistoryCalls[0]?.name === 'runtime_tool_agent_action_history' && - JSON.stringify(finalHistoryCalls[0]?.arguments?.input) === - JSON.stringify({ - runId: null, - actionId: null, - tool: null, - status: 'ok', - limit: 10, - }), - 'self-test-final-action-history-invalid', - ); - const staticActionId = `action-${'1'.repeat(24)}`; - const previewActionId = `action-${'2'.repeat(24)}`; - const actionHistoryDetail = JSON.stringify({ - runId: rootRunId, - count: 2, - truncated: false, - outputTruncated: false, - actions: [ - { - agentId: 'project-supervisor', - runId: rootRunId, - actionId: staticActionId, - tool: 'command.run_limited', - status: 'ok', - }, - { - agentId: 'project-supervisor', - runId: rootRunId, - actionId: previewActionId, - tool: 'preview.validate', - status: 'ok', - }, - ], - }); - const finalAcceptanceResponse = route( - 'project-supervisor', - rootRunId, - ['runtime_tool_agent_acceptance_update'], - observationContext([ - { - tool: 'agent.action_history', - status: 'ok', - summary: '已读取当前 Agent 的 2 条终态动作', - detail: actionHistoryDetail, - }, - ]), - ); - const finalAcceptanceCalls = responseFunctionCalls(finalAcceptanceResponse); - const acceptanceInput = finalAcceptanceCalls[0]?.arguments?.input; - assert( - finalAcceptanceCalls.length === 1 && - finalAcceptanceCalls[0]?.name === - 'runtime_tool_agent_acceptance_update' && - acceptanceInput?.contractFingerprint === contractFingerprint && - acceptanceInput?.evaluations?.length === 2 && - acceptanceInput.evaluations[0]?.criterionId === - 'static-current-revision' && - acceptanceInput.evaluations[0]?.evidence?.[0]?.actionId === - staticActionId && - acceptanceInput.evaluations[1]?.criterionId === - 'playable-current-revision' && - acceptanceInput.evaluations[1]?.evidence?.[0]?.actionId === - previewActionId, - 'self-test-final-acceptance-update-invalid', - ); - const finalRespondResponse = route( - 'project-supervisor', - rootRunId, - ['respond_to_user'], - observationContext([ - { - tool: 'agent.acceptance_update', - status: 'ok', - summary: 'Acceptance Graph 已更新', - detail: null, - }, - ]), - ); - assert( - responseFunctionNames(finalRespondResponse).join(',') === 'respond_to_user', - 'self-test-final-respond-not-exclusive', - ); - - const planRejectionRouter = createDeterministicLaneDefenseRouter({ apiKey }); - planRejectionRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'plan-rejection-run', - goalContractTools, - ), - }); - const planRejectionResponse = planRejectionRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'plan-rejection-run', - ['respond_to_user', 'runtime_tool_file_patch', 'runtime_tool_file_write'], - '当前父 run 已进入只编排模式;本轮只允许收束或安全编排。', - ), - }); - const planRejectionWaitResponse = planRejectionRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'plan-rejection-run', - ['runtime_tool_task_list', 'runtime_tool_agent_run_status'], - '当前正式 manifest DAG 仍有专业 task 在运行', - ), - }); - const planRejectionStats = planRejectionRouter.getStats(); - assert( - responseFunctionNames(planRejectionResponse).join(',') === - 'respond_to_user' && - responseFunctionNames(planRejectionWaitResponse).join(',') === - 'runtime_tool_task_list,runtime_tool_agent_run_status' && - planRejectionStats.byAgent?.['project-supervisor']?.projectMutation === - 0 && - planRejectionStats.delegateActionCount === 0 && - planRejectionStats.runStatusCount === 1 && - planRejectionStats.unexpectedRequestCount === 0, - 'self-test-supervisor-plan-rejection-did-not-converge-read-only', - ); - - const earlyFinalReplyRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - assert( - captureProviderErrorCode(() => - earlyFinalReplyRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - 'early-final-reply-run', - [], - '给用户一个正常中文回复', - ), - }), - ) === 'provider-parent-final-reply-before-acceptance', - 'self-test-parent-final-reply-bypassed-acceptance', - ); - - const acceptanceGuardRunId = 'acceptance-guard-run'; - const acceptanceGuardRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const acceptanceGuardRoute = (tools, extraContext = '') => - acceptanceGuardRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'project-supervisor', - acceptanceGuardRunId, - tools, - extraContext, - ), - }); - acceptanceGuardRoute(goalContractTools); - acceptanceGuardRoute( - ['runtime_tool_command_run_limited'], - `${contractContext}\n已有工具观察:\nseedTaskCounts: completed=16 running=0 pending=0 waiting=0 failed=0 total=16`, - ); - acceptanceGuardRoute( - ['runtime_tool_preview_validate'], - observationContext([successfulFinalStaticObservation]), - ); - acceptanceGuardRoute( - ['runtime_tool_agent_action_history'], - observationContext([successfulFinalPreviewObservation]), - ); - const acceptanceGuardDetail = JSON.parse(actionHistoryDetail); - acceptanceGuardDetail.runId = acceptanceGuardRunId; - for (const action of acceptanceGuardDetail.actions) { - action.runId = acceptanceGuardRunId; - } - const actionHistoryObservationFor = (detail) => ({ - tool: 'agent.action_history', - status: 'ok', - summary: '已读取当前 Agent 的 2 条终态动作', - detail: JSON.stringify(detail), - }); - const ambiguousContractContext = `[Root Goal Contract]\n{"contractFingerprint":"${contractFingerprint}","nested":{"contractFingerprint":"${'b'.repeat(64)}"}}`; - assert( - captureProviderErrorCode(() => - acceptanceGuardRoute( - ['runtime_tool_agent_acceptance_update'], - `${ambiguousContractContext}\n已有工具观察:\n${JSON.stringify([ - actionHistoryObservationFor(acceptanceGuardDetail), - ])}`, - ), - ) === 'provider-goal-contract-fingerprint-missing', - 'self-test-ambiguous-goal-contract-fingerprint-accepted', - ); - const invalidActionHistoryDetail = structuredClone(acceptanceGuardDetail); - invalidActionHistoryDetail.actions[0].actionId = 'action-invalid'; - assert( - captureProviderErrorCode(() => - acceptanceGuardRoute( - ['runtime_tool_agent_acceptance_update'], - `${contractContext}\n已有工具观察:\n${JSON.stringify([ - actionHistoryObservationFor(invalidActionHistoryDetail), - ])}`, - ), - ) === 'provider-final-acceptance-evidence-missing', - 'self-test-invalid-action-history-id-accepted', - ); - assert( - responseFunctionNames( - acceptanceGuardRoute( - ['runtime_tool_agent_acceptance_update'], - `${contractContext}\n已有工具观察:\n${JSON.stringify([ - actionHistoryObservationFor(acceptanceGuardDetail), - ])}`, - ), - ).join(',') === 'runtime_tool_agent_acceptance_update', - 'self-test-acceptance-stage-advanced-after-rejected-history', - ); - - const stats = router.getStats(); - const manifestReadyTasks = manifestReadyTaskEvidence(stats); - assert( - manifestReadyTasks.exactlyOnce && - stats.goalContractCount === 1 && - stats.actionHistoryCount === 1 && - stats.acceptanceUpdateCount === 1 && - stats.interactionExecuteCount === 1 && - stats.delegateActionCount === 0 && - stats.imageInspectionRequestCount === 1 && - stats.manifestReadyTaskCanvasGenerationCount === 3 && - stats.byAgent?.['project-supervisor']?.projectMutation === 0 && - stats.byAgent?.['code-prototype']?.projectMutation === 1 && - stats.byAgent?.['quality-review']?.projectMutation === 0, - 'self-test-manifest-ready-exactly-once-invalid', - ); - - const missingReadyTaskStats = structuredClone(stats); - const missingAgentId = deterministicManifestReadyAgentIds.at(-1); - delete missingReadyTaskStats.readyTaskCountsByAgent[missingAgentId]; - missingReadyTaskStats.manifestReadyTaskRunCount -= 1; - missingReadyTaskStats.manifestReadyTaskCompletionCount -= 1; - assert( - !manifestReadyTaskEvidence(missingReadyTaskStats).exactlyOnce, - 'self-test-manifest-ready-missing-accepted', - ); - - const duplicateReadyTaskStats = structuredClone(stats); - const duplicateAgentId = deterministicManifestReadyAgentIds[0]; - duplicateReadyTaskStats.readyTaskCountsByAgent[duplicateAgentId].completion += - 1; - duplicateReadyTaskStats.manifestReadyTaskCompletionCount += 1; - assert( - !manifestReadyTaskEvidence(duplicateReadyTaskStats).exactlyOnce, - 'self-test-manifest-ready-duplicate-accepted', - ); - - const retryObservationContext = (observations) => - `\n已有工具观察:\n${JSON.stringify(observations)}`; - const staleObservation = { - tool: 'runtime.verification', - status: 'blocked', - summary: '最终回复生成期间项目 revision 已变化', - detail: 'responseRevision=1, currentRevision=2', - }; - const successfulSmokeObservation = { - tool: 'command.run_limited', - status: 'ok', - summary: 'game.static_smoke 已完成', - detail: null, - }; - const blockedSmokeObservation = { - tool: 'command.run_limited', - status: 'blocked', - summary: '仓库规范或启动上下文已漂移,旧动作未执行', - detail: - 'repositoryContextDrift=true · 请在同一 run 下一轮 planning 重新确认适用规范', - }; - const projectLockSmokeObservations = [ - { - tool: 'command.run_limited', - status: 'failed', - summary: 'game.static_smoke 无法取得项目验证锁', - detail: '项目正在被其他写操作占用:$PROJECT_ROOT/.agent/project.lock', - }, - { - tool: 'command.run_limited', - status: 'failed', - summary: 'game.static_smoke 无法取得项目验证锁', - detail: '项目正在被其他写操作占用:$PROJECT_ROOT\\.agent\\project.lock', - }, - ]; - const projectRevisionDriftSmokeObservation = { - tool: 'command.run_limited', - status: 'blocked', - summary: '并行项目变更使旧动作过期,旧动作未执行', - detail: - 'projectRevisionDrift=true · expectedRevision=4 · currentRevision=5 · replanRequired=true', - }; - const projectRevisionDriftCanvasObservation = { - tool: 'canvas.asset_generate', - status: 'blocked', - summary: '并行项目变更使旧动作过期,旧动作未执行', - detail: - 'projectRevisionDrift=true · expectedRevision=5 · currentRevision=6 · replanRequired=true', - }; - const successfulProjectMutationObservation = { - tool: 'project.patchset', - status: 'ok', - summary: 'project.patchset 已原子应用 2 项变更', - detail: 'checkpointId=checkpoint-concurrent-ready-batch', - }; - const missingArtAssetObservation = { - tool: 'runtime.visual_asset', - status: 'blocked', - summary: '首版美术素材图尚未按正式视觉流程生成并登记,不能完成任务', - detail: '[redacted sensitive context]', - }; - const successfulCanvasMutationObservation = { - tool: 'canvas.asset_generate', - status: 'ok', - summary: '素材画布图片已生成并登记', - detail: null, - }; - - const artAssetRecoveryAgentId = 'art-asset-plan'; - const artAssetRecoveryRunId = 'art-asset-visual-recovery-ready-run'; - const artAssetRecoveryTools = [ - ...manifestReadyTools, - 'runtime_tool_canvas_asset_generate', - ]; - const artAssetRecoveryRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const artAssetRecoveryRoute = (extraContext = '') => - artAssetRecoveryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - artAssetRecoveryAgentId, - artAssetRecoveryRunId, - artAssetRecoveryTools, - `处理 manifest ready 任务:${artAssetRecoveryAgentId}\n任务 ID:${artAssetRecoveryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(artAssetRecoveryRoute()).includes('respond_to_user') - ) { - break; - } - } - const artAssetRegenerationResponse = artAssetRecoveryRoute( - retryObservationContext([missingArtAssetObservation]), - ); - const artAssetReverificationResponse = artAssetRecoveryRoute( - retryObservationContext([successfulCanvasMutationObservation]), - ); - const artAssetRefinalizationResponse = artAssetRecoveryRoute( - retryObservationContext([successfulSmokeObservation]), - ); - const artAssetRecoveryStats = artAssetRecoveryRouter.getStats(); - assert( - responseFunctionNames(artAssetRegenerationResponse).join(',') === - 'runtime_tool_canvas_asset_generate' && - responseFunctionNames(artAssetReverificationResponse).join(',') === - 'runtime_tool_command_run_limited' && - responseFunctionNames(artAssetRefinalizationResponse).includes( - 'respond_to_user', - ) && - artAssetRecoveryStats.readyTaskCountsByAgent[artAssetRecoveryAgentId] - ?.completion === 1 && - artAssetRecoveryStats.manifestReadyTaskCanvasGenerationCount === 2 && - artAssetRecoveryStats.unexpectedRequestCount === 0, - 'self-test-art-asset-visual-recovery-not-exactly-once', - ); - - const artAssetRevisionDriftRunId = 'art-asset-revision-drift-ready-run'; - const artAssetRevisionDriftRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const artAssetRevisionDriftRoute = (extraContext = '') => - artAssetRevisionDriftRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - artAssetRecoveryAgentId, - artAssetRevisionDriftRunId, - artAssetRecoveryTools, - `处理 manifest ready 任务:${artAssetRecoveryAgentId}\n任务 ID:${artAssetRecoveryAgentId}${extraContext}`, - ), - }); - const artAssetInitialListResponse = artAssetRevisionDriftRoute(); - const artAssetManifestWriteResponse = artAssetRevisionDriftRoute(); - const artAssetInitialCanvasResponse = artAssetRevisionDriftRoute(); - const artAssetRetryCanvasResponse = artAssetRevisionDriftRoute( - retryObservationContext([projectRevisionDriftCanvasObservation]), - ); - const artAssetPostRetryReadResponse = artAssetRevisionDriftRoute( - retryObservationContext([successfulCanvasMutationObservation]), - ); - const artAssetRevisionDriftFinalizationResponse = - artAssetRevisionDriftRoute(); - const artAssetRevisionDriftStats = artAssetRevisionDriftRouter.getStats(); - assert( - responseFunctionNames(artAssetInitialListResponse).join(',') === - 'runtime_tool_asset_list' && - responseFunctionNames(artAssetManifestWriteResponse).join(',') === - 'runtime_tool_file_write' && - responseFunctionNames(artAssetInitialCanvasResponse).join(',') === - 'runtime_tool_canvas_asset_generate' && - responseFunctionNames(artAssetRetryCanvasResponse).join(',') === - 'runtime_tool_canvas_asset_generate' && - responseFunctionNames(artAssetPostRetryReadResponse).join(',') === - 'runtime_tool_file_read,runtime_tool_asset_list' && - responseFunctionNames(artAssetRevisionDriftFinalizationResponse).includes( - 'respond_to_user', - ) && - artAssetRevisionDriftStats.readyTaskCountsByAgent[artAssetRecoveryAgentId] - ?.run === 1 && - artAssetRevisionDriftStats.readyTaskCountsByAgent[artAssetRecoveryAgentId] - ?.completion === 1 && - artAssetRevisionDriftStats.manifestReadyTaskCanvasGenerationCount === 2 && - artAssetRevisionDriftStats.unexpectedRequestCount === 0, - 'self-test-art-asset-revision-drift-recovery-not-exactly-once', - ); - - const artAssetRevisionDriftLimitRunId = - 'art-asset-revision-drift-limit-ready-run'; - const artAssetRevisionDriftLimitRouter = createDeterministicLaneDefenseRouter( - { apiKey }, - ); - const artAssetRevisionDriftLimitRoute = (extraContext = '') => - artAssetRevisionDriftLimitRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - artAssetRecoveryAgentId, - artAssetRevisionDriftLimitRunId, - artAssetRecoveryTools, - `处理 manifest ready 任务:${artAssetRecoveryAgentId}\n任务 ID:${artAssetRecoveryAgentId}${extraContext}`, - ), - }); - artAssetRevisionDriftLimitRoute(); - artAssetRevisionDriftLimitRoute(); - artAssetRevisionDriftLimitRoute(); - for (let retry = 0; retry < 16; retry += 1) { - assert( - responseFunctionNames( - artAssetRevisionDriftLimitRoute( - retryObservationContext([projectRevisionDriftCanvasObservation]), - ), - ).join(',') === 'runtime_tool_canvas_asset_generate', - `self-test-art-asset-revision-drift-retry-invalid:${retry}`, - ); - } - const artAssetRevisionDriftLimitCode = captureProviderErrorCode(() => - artAssetRevisionDriftLimitRoute( - retryObservationContext([projectRevisionDriftCanvasObservation]), - ), - ); - const artAssetRevisionDriftLimitStats = - artAssetRevisionDriftLimitRouter.getStats(); - assert( - artAssetRevisionDriftLimitCode === - 'provider-ready-art-canvas-retry-exhausted' && - artAssetRevisionDriftLimitStats.readyTaskCountsByAgent[ - artAssetRecoveryAgentId - ]?.run === 1 && - artAssetRevisionDriftLimitStats.readyTaskCountsByAgent[ - artAssetRecoveryAgentId - ]?.completion === 0 && - artAssetRevisionDriftLimitStats.manifestReadyTaskCanvasGenerationCount === - 17, - 'self-test-art-asset-revision-drift-limit-invalid', - ); - - const readOnlyCommandRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const readOnlyCommandRunId = 'read-only-command-only-ready-run'; - const readOnlyCommandRoute = (tools) => - readOnlyCommandRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - 'quality-review', - readOnlyCommandRunId, - tools, - '处理 manifest ready 任务:quality-review\n任务 ID:quality-review', - ), - }); - readOnlyCommandRoute(manifestReadyTools); - readOnlyCommandRoute(manifestReadyTools); - const readOnlyCommandCode = captureProviderErrorCode(() => - readOnlyCommandRoute(['runtime_tool_command_run_limited']), - ); - const readOnlyCommandStats = readOnlyCommandRouter.getStats(); - assert( - readOnlyCommandCode === - 'provider-ready-finalization-tools-invalid:quality-review:runtime_tool_command_run_limited' && - readOnlyCommandStats.readyTaskCountsByAgent['quality-review'] - ?.completion === 0 && - readOnlyCommandStats.manifestReadyTaskStaticSmokeCount === 0, - 'self-test-read-only-command-only-not-rejected', - ); - - const preCompletionRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const preCompletionAgentId = 'preview-readiness'; - const preCompletionRunId = 'pre-completion-transient-ready-run'; - const preCompletionRoute = (tools, extraContext = '') => - preCompletionRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - preCompletionAgentId, - preCompletionRunId, - tools, - `处理 manifest ready 任务:${preCompletionAgentId}\n任务 ID:${preCompletionAgentId}${extraContext}`, - ), - }); - preCompletionRoute(manifestReadyTools); - for (let retry = 0; retry < 16; retry += 1) { - assert( - responseFunctionNames( - preCompletionRoute( - ['runtime_tool_command_run_limited'], - retryObservationContext([blockedSmokeObservation]), - ), - ).join(',') === 'runtime_tool_command_run_limited', - `self-test-ready-precompletion-transient-invalid:${retry}`, - ); - } - const preCompletionLimitCode = captureProviderErrorCode(() => - preCompletionRoute( - ['runtime_tool_command_run_limited'], - retryObservationContext([blockedSmokeObservation]), - ), - ); - const preCompletionStats = preCompletionRouter.getStats(); - assert( - preCompletionLimitCode === - `provider-ready-precompletion-verification-exhausted:${preCompletionAgentId}` && - preCompletionStats.readyTaskCountsByAgent[preCompletionAgentId] - ?.completion === 0 && - preCompletionStats.manifestReadyTaskStaticSmokeCount === 17, - 'self-test-ready-precompletion-limit-invalid', - ); - - const projectLockRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const projectLockRunId = 'project-lock-transient-ready-run'; - const projectLockRoute = (tools, extraContext = '') => - projectLockRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - preCompletionAgentId, - projectLockRunId, - tools, - `处理 manifest ready 任务:${preCompletionAgentId}\n任务 ID:${preCompletionAgentId}${extraContext}`, - ), - }); - projectLockRoute(manifestReadyTools); - for (const observation of projectLockSmokeObservations) { - assert( - responseFunctionNames( - projectLockRoute( - ['runtime_tool_command_run_limited'], - retryObservationContext([observation]), - ), - ).join(',') === 'runtime_tool_command_run_limited', - 'self-test-ready-project-lock-path-separator-not-retried', - ); - } - - const duplicateRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const duplicateRoute = (runId, extraContext = '') => - duplicateRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - duplicateAgentId, - runId, - manifestReadyTools, - `处理 manifest ready 任务:${duplicateAgentId}\n任务 ID:${duplicateAgentId}${extraContext}`, - ), - }); - duplicateRoute('duplicate-ready-run'); - duplicateRoute('duplicate-ready-run'); - let duplicateTerminalCode = null; - try { - duplicateRoute('duplicate-ready-run'); - } catch (error) { - duplicateTerminalCode = error?.code ?? null; - } - let duplicateRunCode = null; - try { - duplicateRoute('second-ready-run'); - } catch (error) { - duplicateRunCode = error?.code ?? null; - } - assert( - duplicateTerminalCode === - `provider-ready-run-terminal-duplicate:${duplicateAgentId}` && - duplicateRunCode === - `provider-ready-agent-run-duplicate:${duplicateAgentId}`, - 'self-test-manifest-ready-provider-duplicate-not-rejected', - ); - - const retryAgentId = 'preview-readiness'; - const retryRunId = 'retry-ready-run'; - const retryRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const retryRoute = (extraContext = '') => - retryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - retryAgentId, - retryRunId, - manifestReadyTools, - `处理 manifest ready 任务:${retryAgentId}\n任务 ID:${retryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if (responseFunctionNames(retryRoute()).includes('respond_to_user')) break; - } - for (let retry = 0; retry < 16; retry += 1) { - assert( - responseFunctionNames( - retryRoute(retryObservationContext([staleObservation])), - ).join(',') === 'runtime_tool_command_run_limited', - `self-test-ready-retry-verification-call-invalid:${retry}`, - ); - if (retry === 0) { - let missingObservationCode = null; - try { - retryRoute(); - } catch (error) { - missingObservationCode = error?.code ?? null; - } - assert( - missingObservationCode === - `provider-ready-retry-verification-invalid:${retryAgentId}`, - 'self-test-ready-retry-missing-observation-accepted', - ); - } - assert( - responseFunctionNames( - retryRoute(retryObservationContext([successfulSmokeObservation])), - ).includes('respond_to_user'), - `self-test-ready-retry-finalization-invalid:${retry}`, - ); - } - let retryLimitCode = null; - try { - retryRoute(retryObservationContext([staleObservation])); - } catch (error) { - retryLimitCode = error?.code ?? null; - } - const retryStats = retryRouter.getStats(); - assert( - retryLimitCode === - `provider-ready-run-terminal-duplicate:${retryAgentId}` && - retryStats.readyTaskCountsByAgent[retryAgentId]?.run === 1 && - retryStats.readyTaskCountsByAgent[retryAgentId]?.completion === 1, - 'self-test-ready-retry-limit-or-exactly-once-invalid', - ); - - const verifiedDeliveryRepairRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const verifiedDeliveryRepairRunId = 'verified-delivery-repair-ready-run'; - const verifiedDeliveryRepairRoute = (tools, extraContext = '') => - verifiedDeliveryRepairRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - retryAgentId, - verifiedDeliveryRepairRunId, - tools, - `处理 manifest ready 任务:${retryAgentId}\n任务 ID:${retryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames( - verifiedDeliveryRepairRoute(manifestReadyTools), - ).includes('respond_to_user') - ) { - break; - } - } - const rejectedVerificationPlan = verifiedDeliveryRepairRoute( - manifestReadyTools, - retryObservationContext([staleObservation]), - ); - const verifiedDeliveryRepairResponse = verifiedDeliveryRepairRoute( - ['respond_to_user'], - `${retryObservationContext([staleObservation])}\n上一条输出不符合工具计划协议:当前 revision 已通过验证。本次修复的原生工具目录只保留 respond_to_user;必须立即调用它交付专业合同结论。`, - ); - const verifiedDeliveryRepairStats = verifiedDeliveryRepairRouter.getStats(); - assert( - responseFunctionNames(rejectedVerificationPlan).join(',') === - 'runtime_tool_command_run_limited' && - responseFunctionNames(verifiedDeliveryRepairResponse).join(',') === - 'respond_to_user' && - verifiedDeliveryRepairStats.readyTaskCountsByAgent[retryAgentId] - ?.completion === 1 && - verifiedDeliveryRepairStats.unexpectedRequestCount === 0, - 'self-test-ready-verified-delivery-repair-invalid', - ); - - const transientRetryRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const transientRetryRunId = 'transient-retry-ready-run'; - const transientRetryRoute = (extraContext = '') => - transientRetryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - retryAgentId, - transientRetryRunId, - manifestReadyTools, - `处理 manifest ready 任务:${retryAgentId}\n任务 ID:${retryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(transientRetryRoute()).includes('respond_to_user') - ) { - break; - } - } - transientRetryRoute(retryObservationContext([staleObservation])); - assert( - responseFunctionNames( - transientRetryRoute(retryObservationContext([blockedSmokeObservation])), - ).join(',') === 'runtime_tool_command_run_limited' && - responseFunctionNames( - transientRetryRoute( - retryObservationContext([successfulSmokeObservation]), - ), - ).includes('respond_to_user') && - transientRetryRouter.getStats().readyTaskCountsByAgent[retryAgentId] - ?.completion === 1, - 'self-test-ready-transient-verification-retry-invalid', - ); - - const standaloneRetryAgentId = 'design-foundation'; - const standaloneRetryRunId = 'standalone-retry-ready-run'; - const standaloneRetryRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const standaloneRetryRoute = (extraContext = '') => - standaloneRetryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - standaloneRetryAgentId, - standaloneRetryRunId, - manifestReadyTools, - `处理 manifest ready 任务:${standaloneRetryAgentId}\n任务 ID:${standaloneRetryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(standaloneRetryRoute()).includes('respond_to_user') - ) { - break; - } - } - const standaloneReplayResponse = standaloneRetryRoute( - retryObservationContext([successfulSmokeObservation]), - ); - const standaloneReplayDuplicateCode = captureProviderErrorCode(() => - standaloneRetryRoute(retryObservationContext([successfulSmokeObservation])), - ); - assert( - responseFunctionNames(standaloneReplayResponse).includes( - 'respond_to_user', - ) && - standaloneReplayDuplicateCode === - `provider-ready-run-terminal-duplicate:${standaloneRetryAgentId}` && - standaloneRetryRouter.getStats().readyTaskCountsByAgent[ - standaloneRetryAgentId - ]?.completion === 1, - 'self-test-ready-standalone-verification-replay-invalid', - ); - - const concurrentMutationAgentId = 'design-foundation'; - const concurrentMutationRunId = 'concurrent-mutation-ready-run'; - const concurrentMutationRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const concurrentMutationRoute = (extraContext = '') => - concurrentMutationRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - concurrentMutationAgentId, - concurrentMutationRunId, - manifestReadyTools, - `处理 manifest ready 任务:${concurrentMutationAgentId}\n任务 ID:${concurrentMutationAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(concurrentMutationRoute()).includes( - 'respond_to_user', - ) - ) { - break; - } - } - assert( - responseFunctionNames( - concurrentMutationRoute( - retryObservationContext([successfulProjectMutationObservation]), - ), - ).join(',') === 'runtime_tool_command_run_limited' && - responseFunctionNames( - concurrentMutationRoute( - retryObservationContext([successfulSmokeObservation]), - ), - ).includes('respond_to_user') && - concurrentMutationRouter.getStats().readyTaskCountsByAgent[ - concurrentMutationAgentId - ]?.completion === 1, - 'self-test-ready-concurrent-mutation-reverification-invalid', - ); - - const repairedWriterAgentId = 'balance-seed'; - const repairedWriterRunId = 'repaired-writer-ready-run'; - const repairedWriterRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const repairedWriterRoute = (tools = manifestReadyTools, extraContext = '') => - repairedWriterRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - repairedWriterAgentId, - repairedWriterRunId, - tools, - `处理 manifest ready 任务:${repairedWriterAgentId}\n任务 ID:${repairedWriterAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(repairedWriterRoute()).includes('respond_to_user') - ) { - break; - } - } - const repairedWriterMutation = repairedWriterRoute( - ['runtime_tool_file_write'], - retryObservationContext([successfulSmokeObservation]), - ); - const repairedWriterVerification = repairedWriterRoute( - manifestReadyTools, - retryObservationContext([successfulProjectMutationObservation]), - ); - const repairedWriterFinalization = repairedWriterRoute( - manifestReadyTools, - retryObservationContext([successfulSmokeObservation]), - ); - const repairedWriterDuplicateCode = captureProviderErrorCode(() => - repairedWriterRoute( - manifestReadyTools, - retryObservationContext([successfulSmokeObservation]), - ), - ); - assert( - responseFunctionNames(repairedWriterMutation).join(',') === - 'runtime_tool_file_write' && - responseFunctionNames(repairedWriterVerification).join(',') === - 'runtime_tool_command_run_limited' && - responseFunctionNames(repairedWriterFinalization).includes( - 'respond_to_user', - ) && - repairedWriterDuplicateCode === - `provider-ready-run-terminal-duplicate:${repairedWriterAgentId}` && - repairedWriterRouter.getStats().readyTaskCountsByAgent[ - repairedWriterAgentId - ]?.completion === 1, - 'self-test-ready-repaired-writer-reverification-invalid', - ); - - const projectRevisionDriftAgentId = 'preview-readiness'; - const projectRevisionDriftRunId = 'project-revision-drift-ready-run'; - const projectRevisionDriftRouter = createDeterministicLaneDefenseRouter({ - apiKey, - }); - const projectRevisionDriftRoute = (extraContext = '') => - projectRevisionDriftRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - projectRevisionDriftAgentId, - projectRevisionDriftRunId, - manifestReadyTools, - `处理 manifest ready 任务:${projectRevisionDriftAgentId}\n任务 ID:${projectRevisionDriftAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(projectRevisionDriftRoute()).includes( - 'respond_to_user', - ) - ) { - break; - } - } - assert( - responseFunctionNames( - projectRevisionDriftRoute( - retryObservationContext([projectRevisionDriftSmokeObservation]), - ), - ).join(',') === 'runtime_tool_command_run_limited' && - responseFunctionNames( - projectRevisionDriftRoute( - retryObservationContext([successfulSmokeObservation]), - ), - ).includes('respond_to_user') && - projectRevisionDriftRouter.getStats().readyTaskCountsByAgent[ - projectRevisionDriftAgentId - ]?.completion === 1, - 'self-test-ready-project-revision-drift-retry-invalid', - ); - - const readOnlyRetryAgentId = 'quality-review'; - const readOnlyRetryRunId = 'read-only-retry-ready-run'; - const readOnlyRetryRouter = createDeterministicLaneDefenseRouter({ apiKey }); - const readOnlyRetryRoute = (extraContext = '') => - readOnlyRetryRouter.route({ - authorization: `Bearer ${apiKey}`, - payload: syntheticPayload( - readOnlyRetryAgentId, - readOnlyRetryRunId, - manifestReadyTools, - `处理 manifest ready 任务:${readOnlyRetryAgentId}\n任务 ID:${readOnlyRetryAgentId}${extraContext}`, - ), - }); - for (let attempt = 0; attempt < 8; attempt += 1) { - if ( - responseFunctionNames(readOnlyRetryRoute()).includes('respond_to_user') - ) { - break; - } - } - assert( - responseFunctionNames( - readOnlyRetryRoute(retryObservationContext([staleObservation])), - ).includes('respond_to_user') && - readOnlyRetryRouter.getStats().manifestReadyTaskStaticSmokeCount === 0, - 'self-test-read-only-retry-used-forbidden-verification', - ); - assert( - stats.goalContractCount === 1 && - stats.actionHistoryCount === 1 && - stats.acceptanceUpdateCount === 1 && - stats.delegateActionCount === 0 && - stats.manifestReadyTaskCanvasGenerationCount === 3 && - stats.byAgent?.['project-supervisor']?.projectMutation === 0 && - stats.byAgent?.['code-prototype']?.projectMutation === 1 && - stats.byAgent?.['quality-review']?.projectMutation === 0 && - stats.unexpectedRequestCount === 0, - 'self-test-provider-core-stats-invalid', - ); - - const syntheticRuntimeProviderRequestCount = - stats.requestCount - - stats.interactionExecuteCount - - stats.imageInspectionRequestCount; - - const syntheticChildReport = { - status: 'PASS', - suite, - errorCount: 0, - blocked: [], - cleanup: { performed: true, kept: false }, - evidence: { - ...Object.fromEntries( - requiredZeroChildEvidenceFields.map((field) => [field, 0]), - ), - evidenceCompleteness: 'complete', - dedicatedZeroInterventionPath: true, - stdinTaskCount: 1, - stdinEndedAfterTask: true, - turnReportOutcome: 'settled', - parentTaskStatus: 'completed', - parentRuntimeStatus: 'idle', - parentRuntimePhase: 'completed', - laneDefensePlaytestPassed: true, - laneDefenseAssertionCount: 37, - laneDefensePassedAssertionCount: 37, - browserValidationPassed: true, - staticSmokePassed: true, - gameIndexChanged: true, - projectRevisionDelta: 2, - finalSupervisorAssistantCount: 1, - professionalAssistantCount: 3, - providerRequestIdentityCount: syntheticRuntimeProviderRequestCount, - providerLifecycleStartedCount: syntheticRuntimeProviderRequestCount, - providerLifecycleTerminalCount: syntheticRuntimeProviderRequestCount, - providerLifecycleCompletedCount: syntheticRuntimeProviderRequestCount, - }, - }; - const incompletePlaytestReport = structuredClone(syntheticChildReport); - incompletePlaytestReport.evidence.laneDefensePassedAssertionCount = 36; - const manualInputReport = structuredClone(syntheticChildReport); - manualInputReport.evidence.approveInputCount = 1; - const residualSidecarReport = structuredClone(syntheticChildReport); - residualSidecarReport.evidence.providerHandoffSidecarCount = 1; - assert( - expectedChildReport(syntheticChildReport, { keepProject: false }, stats) && - !expectedChildReport( - incompletePlaytestReport, - { keepProject: false }, - stats, - ) && - !expectedChildReport(manualInputReport, { keepProject: false }, stats) && - !expectedChildReport( - residualSidecarReport, - { keepProject: false }, - stats, - ), - 'self-test-child-hard-gates-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, - manifestReadyTasks, - manifestReadyFailureSamplesValidated: ['missing', 'duplicate'], - manifestReadyProviderDuplicateRejections: { - terminal: duplicateTerminalCode, - run: duplicateRunCode, - }, - terminalRetryContractsValidated: { - readOnlyCommandOnlyRejected: true, - preCompletionTransientRetryLimit: 16, - projectLockPathSeparatorsRetried: ['/', '\\'], - }, - providerRuntimeModeValidated: true, - interactionExecutionValidated: { - reasoningEffort: 'max', - requestCount: stats.interactionExecuteCount, - responseTools: interactionExecuteCalls.map((call) => call.name), - readyTaskRunCountBeforeRuntime: - interactionExecuteStats.manifestReadyTaskRunCount, - readyTaskCompletionCountBeforeRuntime: - interactionExecuteStats.manifestReadyTaskCompletionCount, - }, - goalContractValidated: true, - finalizationSequenceValidated: [ - 'command.run_limited', - 'preview.validate', - 'agent.action_history', - 'agent.acceptance_update', - 'respond_to_user', - ], - canvasArtifactsValidated: [ - 'assets/art-spec.png', - 'assets/ui-prototype.png', - 'assets/art-spritesheet.png', - ], - uiPrototypeInspectionValidated: true, - uiPrototypeVisualRecoveryExactlyOnceValidated: true, - artAssetVisualRecoveryExactlyOnceValidated: true, - artAssetRevisionDriftRecoveryExactlyOnceValidated: true, - artAssetRevisionDriftRetryLimitValidated: 16, - supervisorPlanRejectionMode: 'respond-only-then-read-only-wait', - childHardGatesValidated: true, - 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 = deterministicRuntimeConfig(apiKey, provider); - const fixturePath = path.join(configDir, platformSessionFixtureName); - await fs.writeFile( - fixturePath, - `${JSON.stringify(deterministicPlatformSessionFixture(apiKey, provider))}\n`, - { flag: 'wx', mode: 0o600 }, - ); - 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', - [platformSessionFixtureEnv]: fixturePath, - GENARRATIVE_AGC_DEBUG_PROVIDER_E2E: '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 && - expectedChildReport(childReport, options, providerStats); - const providerPassed = - providerStats?.stopped === true && expectedProviderStats(providerStats); - const manifestReadyTasks = manifestReadyTaskEvidence(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, - manifestReadyTasks, - finalizationContract: { - goalContractCount: providerStats?.goalContractCount ?? null, - actionHistoryCount: providerStats?.actionHistoryCount ?? null, - acceptanceUpdateCount: providerStats?.acceptanceUpdateCount ?? null, - delegateActionCount: providerStats?.delegateActionCount ?? null, - supervisorProjectMutationCount: - providerStats?.byAgent?.['project-supervisor']?.projectMutation ?? null, - }, - 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; diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/assertions/core.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/assertions/core.mjs index f91c928e8..e2e02ff7f 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/assertions/core.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/assertions/core.mjs @@ -1,69 +1,8 @@ import { createHash } from '../dependencies.mjs'; import { isPlainObject } from '../harness/config.mjs'; -import { - interactiveCliOutput, - writeInteractiveCliLine, -} from '../harness/process.mjs'; -import { - shutdownWaiters, - state, - userInputAnswerText, -} from '../runtime-state.mjs'; +import { shutdownWaiters, state } from '../runtime-state.mjs'; import { disposableProjectPathVariants } from './runtime.mjs'; -export async function answerRemainingInteractiveQuestions(session) { - let answeredPromptCount = 1; - const deadline = Date.now() + 60_000; - while (Date.now() < deadline) { - const output = interactiveCliOutput(session); - if (output.includes(`[\u5df2\u56de\u7b54] ${state.userInput.requestId}`)) - return; - const promptCount = output.split('或直接输入其他答案:').length - 1; - while (answeredPromptCount < promptCount && answeredPromptCount < 3) { - writeInteractiveCliLine(session, userInputAnswerText); - answeredPromptCount += 1; - } - if (output.includes('[待确认]')) { - throw codedError('user-input-unexpected-tool-confirmation'); - } - if (session.closed) throw codedError('user-input-cli-closed-before-answer'); - await sleep(100); - } - throw codedError('user-input-answer-timeout'); -} - -export function parseSingleSwarmTurnReport(output, codePrefix) { - const reportLines = output - .split(/\r?\n/u) - .filter((line) => line.startsWith('[turn.report] ')); - assert(reportLines.length === 1, `${codePrefix}-turn-report-count-invalid`); - const report = JSON.parse(reportLines[0].slice('[turn.report] '.length)); - assert( - isPlainObject(report) && - JSON.stringify(Object.keys(report).sort()) === - JSON.stringify( - [ - 'schemaVersion', - 'outcome', - 'parentAgentId', - 'sessionId', - 'parentRunId', - 'runtimeCount', - 'busyRuntimeCount', - 'pendingTaskCount', - 'runningTaskCount', - 'waitingForConfirmationCount', - 'waitingForUserInputCount', - 'newAssistantMessageCount', - 'finalReplyChars', - 'reconciliationAgentCount', - ].sort(), - ), - `${codePrefix}-turn-report-shape-invalid`, - ); - return report; -} - export function isFailedTask(task) { return ( ['failed', 'cancelled', 'budget-exhausted'].includes(task.status) || diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/entry.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/entry.mjs index 8b20293b9..56c6f0231 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/entry.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/entry.mjs @@ -20,15 +20,6 @@ import { stopOwnedIsolatedRunner, } from './harness/app-data.mjs'; import { loadConfig, parseArguments } from './harness/config.mjs'; -import { - activeInteractiveCliSessions, - captureOwnedProcessCleanupSnapshot, - closeInteractiveCli, - destroyInteractiveCliOutputStreams, - interactiveCliOutput, - verifyOwnedProcessCleanupSnapshot, - waitForInteractiveCliStdioClose, -} from './harness/process.mjs'; import { checkPrerequisites } from './harness/project.mjs'; import { buildSummary, @@ -48,8 +39,6 @@ import { state, steerInstruction, StreamingSecretScanner, - userInputAnswerCanary, - userInputAnswerText, } from './runtime-state.mjs'; import { collectPartialContextCompactionEvidence, @@ -101,29 +90,14 @@ import { isSteerRunnerKillSuite, runSteerRunnerKillE2e, } from './suites/steer-runner-kill.mjs'; -import { - collectPartialSupervisorAutonomousPlayableEvidence, - emptySupervisorAutonomousPlayableEvidence, - isSupervisorAutonomousPlayableLaneDefenseSuite, - runSupervisorAutonomousPlayableLaneDefenseE2e, -} from './suites/supervisor-autonomous-playable.mjs'; import { collectPartialSupervisorSwarmEvidence, emptySupervisorSwarmEvidence, - isSupervisorSwarmInteractiveChatSuite, isSupervisorSwarmSuite, isSupervisorSwarmTransientRetrySuite, - recordSupervisorSwarmChatSessionFailureDiagnostic, runSupervisorSwarmE2e, - supervisorSwarmChatSessionFailureEvidence, supervisorSwarmToolPlanHandoffProxyNeedsCleanup, } from './suites/supervisor-swarm.mjs'; -import { - collectPartialUserInputEvidence, - emptyUserInputEvidence, - isUserInputRuntimeSuite, - runUserInputRuntimeE2e, -} from './suites/user-input.mjs'; import { collectPartialWebSearchEvidence, emptyWebSearchEvidence, @@ -175,13 +149,9 @@ if (selfTestRequested) { if (isContextCompactionSuite()) { state.evidence = emptyContextCompactionEvidence(); } - if (isUserInputRuntimeSuite()) state.evidence = emptyUserInputEvidence(); if (isScopedAgentsSuite()) state.evidence = emptyScopedAgentsEvidence(); if (isProjectSkillSuite()) state.evidence = emptyProjectSkillEvidence(); if (isParallelReadSuite()) state.evidence = emptyParallelReadEvidence(); - if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - state.evidence = emptySupervisorAutonomousPlayableEvidence(); - } if (isSupervisorSwarmSuite()) { state.evidence = emptySupervisorSwarmEvidence(); } @@ -192,11 +162,9 @@ if (selfTestRequested) { if ( isWebSearchSuite() || isContextCompactionSuite() || - isUserInputRuntimeSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmSuite() || isSteerRunnerKillSuite() ) { @@ -208,9 +176,8 @@ if (selfTestRequested) { state.transcriptScanner = new StreamingSecretScanner(state.secrets); state.config = await checkPrerequisites(loaded.config); - const required = isSupervisorAutonomousPlayableLaneDefenseSuite() - ? ['llmConfigured', 'chromeAvailable'] - : isProcessSessionSuite() || isIsolatedRunnerSuite() + const required = + isProcessSessionSuite() || isIsolatedRunnerSuite() ? ['llmConfigured'] : ['llmConfigured', 'chromeAvailable']; if (state.suite === 'full') { @@ -232,16 +199,12 @@ if (selfTestRequested) { await runWebSearchE2e(); } else if (isContextCompactionSuite()) { await runContextCompactionE2e(); - } else if (isUserInputRuntimeSuite()) { - await runUserInputRuntimeE2e(); } else if (isScopedAgentsSuite()) { await runScopedAgentsE2e(); } else if (isProjectSkillSuite()) { await runProjectSkillE2e(); } else if (isParallelReadSuite()) { await runParallelReadE2e(); - } else if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - await runSupervisorAutonomousPlayableLaneDefenseE2e(); } else if (isSupervisorSwarmSuite()) { await runSupervisorSwarmE2e(); } else if (isProcessSessionSuite()) { @@ -262,107 +225,6 @@ if (selfTestRequested) { recordError(error?.code ?? 'unexpected-error', error); } finally { state.cleanupInProgress = true; - const stateTrackedInteractiveCliSessions = new Set( - [ - state.userInputCliSession, - state.supervisorAutonomousPlayableCliSession, - state.supervisorSwarmCliSession, - ].filter(Boolean), - ); - const interactiveCliSessions = [ - ...new Set([ - ...stateTrackedInteractiveCliSessions, - ...activeInteractiveCliSessions, - ]), - ]; - const supervisorAutonomousPlayableCliSession = - state.supervisorAutonomousPlayableCliSession; - if (isUserInputRuntimeSuite() && state.userInputCliSession) { - try { - await closeInteractiveCli(state.userInputCliSession); - } catch (error) { - state.status = 'FAIL'; - recordError('user-input-cli-cleanup-failed', error); - } - state.userInputCliSession = null; - } - if ( - isSupervisorAutonomousPlayableLaneDefenseSuite() && - state.supervisorAutonomousPlayableCliSession - ) { - try { - await closeInteractiveCli(state.supervisorAutonomousPlayableCliSession); - } catch (error) { - state.status = 'FAIL'; - recordError('supervisor-autonomous-playable-cli-cleanup-failed', error); - } - state.supervisorAutonomousPlayableCliSession = null; - } - if ( - isSupervisorSwarmInteractiveChatSuite() && - state.supervisorSwarmCliSession - ) { - try { - if ( - state.status !== 'PASS' && - state.supervisorSwarmCliSession.closed && - !state.supervisorSwarm.chatSessionFailureDiagnostic - ) { - recordSupervisorSwarmChatSessionFailureDiagnostic( - state.supervisorSwarmCliSession, - ); - } - await closeInteractiveCli(state.supervisorSwarmCliSession); - } catch (error) { - state.status = 'FAIL'; - recordError( - 'supervisor-swarm-autonomous-chat-cli-cleanup-failed', - error, - ); - } - state.supervisorSwarmCliSession = null; - } - for (const session of interactiveCliSessions) { - if (stateTrackedInteractiveCliSessions.has(session)) continue; - try { - await closeInteractiveCli(session); - } catch (error) { - state.status = 'FAIL'; - recordError('interactive-cli-cleanup-failed', error); - } - } - if ( - isSupervisorAutonomousPlayableLaneDefenseSuite() && - state.isolatedRunner.appDataDir - ) { - try { - const runnerPid = state.isolatedRunner.current?.pid ?? null; - const helperPid = - state.isolatedRunner.current?.killHandle?.child?.pid ?? null; - state.supervisorAutonomousPlayable.ownedProcessCleanupSnapshot = - await captureOwnedProcessCleanupSnapshot({ - runnerPid, - helperPids: Number.isSafeInteger(helperPid) ? [helperPid] : [], - rootPids: [ - ...interactiveCliSessions.map((session) => session.child?.pid), - ...[...activeCommandChildren].map((child) => child.pid), - ].filter((pid) => Number.isSafeInteger(pid) && pid > 0), - }); - const observed = - state.supervisorAutonomousPlayable.ownedProcessCleanupSnapshot - .observedCounts; - assert( - observed.runner === 1 && observed.helper === 1, - 'supervisor-autonomous-playable-owned-process-snapshot-incomplete', - ); - } catch (error) { - state.status = 'FAIL'; - recordError( - 'supervisor-autonomous-playable-owned-process-snapshot-failed', - error, - ); - } - } if (isIsolatedRunnerSuite() && state.isolatedRunner.appDataDir) { try { await stopOwnedIsolatedRunner(); @@ -380,25 +242,6 @@ if (selfTestRequested) { ).catch(() => {}); } } - for (const session of interactiveCliSessions) { - try { - await waitForInteractiveCliStdioClose(session, 10_000); - } catch (error) { - destroyInteractiveCliOutputStreams(session); - state.status = 'FAIL'; - recordError( - error?.code === 'interactive-cli-stdio-close-timeout' - ? error.code - : 'interactive-cli-stdio-cleanup-failed', - error, - ); - } - } - if (supervisorAutonomousPlayableCliSession) { - state.supervisorAutonomousPlayable.cliOutput = interactiveCliOutput( - supervisorAutonomousPlayableCliSession, - ); - } if (isIsolatedRunnerSuite() && state.isolatedRunner.appDataDir) { if (state.isolatedRunner.stopped) { try { @@ -500,28 +343,6 @@ if (selfTestRequested) { state.status = 'FAIL'; recordError('web-search-formal-config-cli-call-detected'); } - } else if (isUserInputRuntimeSuite()) { - state.evidence.userInputRunnerStopped = state.isolatedRunner.stopped; - state.evidence.userInputAppDataCleanupPerformed = - state.isolatedRunner.cleanupPerformed; - state.evidence.userInputRunnerKillMethod = killMethod; - state.evidence.userInputRunnerPidfdClaimCount = - state.isolatedRunner.pidfdClaimCount; - state.evidence.userInputRunnerPidfdSignalCount = - state.isolatedRunner.pidfdSignalCount; - state.evidence.formalConfigCliCallCount = - state.isolatedRunner.sourceConfigCliCallCount; - state.evidence.sourceRunnerEndpointUnchanged = - state.isolatedRunner.sourceRunnerEndpointUnchanged; - state.evidence.sourceConfigHardlinkCount = - state.isolatedRunner.configLinks.length; - state.evidence.sourceConfigLinksVerified = - state.isolatedRunner.sourceConfigLinksVerified; - state.evidence.isolatedAppDataUsed = true; - if (state.isolatedRunner.sourceConfigCliCallCount > 0) { - state.status = 'FAIL'; - recordError('user-input-formal-config-cli-call-detected'); - } } else if (isScopedAgentsSuite()) { state.evidence.scopedAgentsRunnerStopped = state.isolatedRunner.stopped; state.evidence.scopedAgentsAppDataCleanupPerformed = @@ -588,28 +409,6 @@ if (selfTestRequested) { state.status = 'FAIL'; recordError('parallel-read-formal-config-cli-call-detected'); } - } else if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - state.evidence.supervisorAutonomousPlayableRunnerStopped = - state.isolatedRunner.stopped; - state.evidence.supervisorAutonomousPlayableAppDataCleanupPerformed = - state.isolatedRunner.cleanupPerformed; - state.evidence.formalConfigCliCallCount = - state.isolatedRunner.sourceConfigCliCallCount; - state.evidence.sourceRunnerEndpointUnchanged = - state.isolatedRunner.sourceRunnerEndpointUnchanged; - state.evidence.sourceAppDataDirectoryUntouched = - state.isolatedRunner.sourceAppDataDirectoryUntouched; - state.evidence.sourceConfigReplicaCount = - state.isolatedRunner.configLinks.length; - state.evidence.sourceConfigReplicasVerified = - state.isolatedRunner.sourceConfigLinksVerified; - state.evidence.isolatedAppDataUsed = true; - if (state.isolatedRunner.sourceConfigCliCallCount > 0) { - state.status = 'FAIL'; - recordError( - 'supervisor-autonomous-playable-formal-config-cli-call-detected', - ); - } } else if (isSupervisorSwarmSuite()) { state.evidence.supervisorSwarmRunnerStopped = state.isolatedRunner.stopped; @@ -719,61 +518,6 @@ if (selfTestRequested) { ); } } - if ( - isSupervisorAutonomousPlayableLaneDefenseSuite() && - state.isolatedRunner.appDataDir - ) { - const snapshot = - state.supervisorAutonomousPlayable.ownedProcessCleanupSnapshot; - if (snapshot) { - try { - const cleanup = await verifyOwnedProcessCleanupSnapshot(snapshot); - state.evidence.ownedProcessIdentityCaptured = true; - state.evidence.ownedRunnerObservedCount = - snapshot.observedCounts.runner; - state.evidence.ownedHelperObservedCount = - snapshot.observedCounts.helper; - state.evidence.ownedNodeDescendantObservedCount = - snapshot.observedCounts.node; - state.evidence.ownedBrowserDescendantObservedCount = - snapshot.observedCounts.browser; - state.evidence.ownedCommandDescendantObservedCount = - snapshot.observedCounts.command; - state.evidence.ownedRunnerResidualCount = - cleanup.residualCounts.runner; - state.evidence.ownedHelperResidualCount = - cleanup.residualCounts.helper; - state.evidence.ownedNodeDescendantResidualCount = - cleanup.residualCounts.node; - state.evidence.ownedBrowserDescendantResidualCount = - cleanup.residualCounts.browser; - state.evidence.ownedCommandDescendantResidualCount = - cleanup.residualCounts.command; - state.evidence.activeCommandChildrenAfterCleanup = - cleanup.activeCommandChildCount; - state.evidence.activeInteractiveCliSessionsAfterCleanup = - cleanup.activeInteractiveCliSessionCount; - state.evidence.ownedProcessCleanupPassed = cleanup.clean; - if (!cleanup.clean) { - state.status = 'FAIL'; - recordError( - 'supervisor-autonomous-playable-owned-process-residual-detected', - ); - } - } catch (error) { - state.status = 'FAIL'; - recordError( - 'supervisor-autonomous-playable-owned-process-verification-failed', - error, - ); - } - } else { - state.status = 'FAIL'; - recordError( - 'supervisor-autonomous-playable-owned-process-snapshot-missing', - ); - } - } if ( isSteerRunnerKillSuite() && state.projectRoot && @@ -836,20 +580,6 @@ if (selfTestRequested) { recordError('context-compaction-partial-evidence-read-failed', error); } } - if ( - isUserInputRuntimeSuite() && - state.projectRoot && - state.status !== 'PASS' - ) { - try { - state.evidence = { - ...state.evidence, - ...(await collectPartialUserInputEvidence()), - }; - } catch (error) { - recordError('user-input-partial-evidence-read-failed', error); - } - } if (isScopedAgentsSuite() && state.projectRoot && state.status !== 'PASS') { try { state.evidence = { @@ -880,24 +610,6 @@ if (selfTestRequested) { recordError('parallel-read-partial-evidence-read-failed', error); } } - if ( - isSupervisorAutonomousPlayableLaneDefenseSuite() && - state.projectRoot && - state.status !== 'PASS' && - state.evidence.evidenceCompleteness !== 'complete' - ) { - try { - state.evidence = { - ...state.evidence, - ...(await collectPartialSupervisorAutonomousPlayableEvidence()), - }; - } catch (error) { - recordError( - 'supervisor-autonomous-playable-partial-evidence-read-failed', - error, - ); - } - } if ( isSupervisorSwarmSuite() && state.projectRoot && @@ -1062,23 +774,6 @@ if (selfTestRequested) { report = JSON.stringify(summary, null, 2); } } - if (isUserInputRuntimeSuite()) { - state.userInput.reportLeakCount = countExactSecrets( - Buffer.from(report), - [ - userInputAnswerCanary, - userInputAnswerText, - ...state.userInput.privateValues, - ].filter(isNonEmptyString), - ); - state.evidence.userInputReportLeakCount = state.userInput.reportLeakCount; - if (state.userInput.reportLeakCount > 0) { - state.status = 'FAIL'; - recordError('user-input-private-body-report-leak-detected'); - summary = buildSummary(); - report = JSON.stringify(summary, null, 2); - } - } if (isScopedAgentsSuite()) { state.scopedAgents.reportLeakCount = countExactSecrets( Buffer.from(report), @@ -1121,20 +816,6 @@ if (selfTestRequested) { report = JSON.stringify(summary, null, 2); } } - if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - state.supervisorAutonomousPlayable.reportLeakCount = countExactSecrets( - Buffer.from(report), - state.supervisorAutonomousPlayable.privateValues, - ); - state.evidence.supervisorAutonomousPlayableReportLeakCount = - state.supervisorAutonomousPlayable.reportLeakCount; - if (state.supervisorAutonomousPlayable.reportLeakCount > 0) { - state.status = 'FAIL'; - recordError('supervisor-autonomous-playable-report-body-leak-detected'); - summary = buildSummary(); - report = JSON.stringify(summary, null, 2); - } - } if (isSupervisorSwarmSuite()) { state.supervisorSwarm.reportLeakCount = countExactSecrets( Buffer.from(report), @@ -1164,11 +845,9 @@ if (selfTestRequested) { if ( isWebSearchSuite() || isContextCompactionSuite() || - isUserInputRuntimeSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmSuite() || isSteerRunnerKillSuite() ) { @@ -1211,16 +890,6 @@ if (selfTestRequested) { const remainingWebSearchReportLeakCount = isWebSearchSuite() ? countExactSecrets(Buffer.from(report), webSearchPrivateLeakValues()) : 0; - const remainingUserInputReportLeakCount = isUserInputRuntimeSuite() - ? countExactSecrets( - Buffer.from(report), - [ - userInputAnswerCanary, - userInputAnswerText, - ...state.userInput.privateValues, - ].filter(isNonEmptyString), - ) - : 0; const remainingScopedAgentsReportLeakCount = isScopedAgentsSuite() ? countExactSecrets(Buffer.from(report), state.scopedAgents.privateValues) : 0; @@ -1230,13 +899,6 @@ if (selfTestRequested) { const remainingParallelReadReportLeakCount = isParallelReadSuite() ? countExactSecrets(Buffer.from(report), state.parallelRead.privateValues) : 0; - const remainingSupervisorAutonomousPlayableReportLeakCount = - isSupervisorAutonomousPlayableLaneDefenseSuite() - ? countExactSecrets( - Buffer.from(report), - state.supervisorAutonomousPlayable.privateValues, - ) - : 0; const remainingSupervisorSwarmReportLeakCount = isSupervisorSwarmSuite() ? countExactSecrets( Buffer.from(report), @@ -1246,11 +908,9 @@ if (selfTestRequested) { const remainingFormalConfigPathReportLeakCount = isWebSearchSuite() || isContextCompactionSuite() || - isUserInputRuntimeSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmSuite() || isSteerRunnerKillSuite() ? countExactSecrets(Buffer.from(report), formalConfigPathVariants()) @@ -1259,11 +919,9 @@ if (selfTestRequested) { remainingProjectPathReportLeakCount > 0 || remainingResponseStreamReportLeakCount > 0 || remainingWebSearchReportLeakCount > 0 || - remainingUserInputReportLeakCount > 0 || remainingScopedAgentsReportLeakCount > 0 || remainingProjectSkillReportLeakCount > 0 || remainingParallelReadReportLeakCount > 0 || - remainingSupervisorAutonomousPlayableReportLeakCount > 0 || remainingSupervisorSwarmReportLeakCount > 0 || remainingFormalConfigPathReportLeakCount > 0 ) { @@ -1273,21 +931,17 @@ if (selfTestRequested) { ? 'disposable-project-path-report-redaction-required' : remainingResponseStreamReportLeakCount > 0 ? 'response-stream-report-redaction-required' - : remainingUserInputReportLeakCount > 0 - ? 'user-input-report-redaction-required' - : remainingScopedAgentsReportLeakCount > 0 - ? 'scoped-agents-report-redaction-required' - : remainingProjectSkillReportLeakCount > 0 - ? 'project-skill-report-redaction-required' - : remainingParallelReadReportLeakCount > 0 - ? 'parallel-read-report-redaction-required' - : remainingSupervisorAutonomousPlayableReportLeakCount > 0 - ? 'supervisor-autonomous-playable-report-redaction-required' - : remainingSupervisorSwarmReportLeakCount > 0 - ? 'supervisor-swarm-report-redaction-required' - : remainingFormalConfigPathReportLeakCount > 0 - ? 'formal-config-path-report-redaction-required' - : 'web-search-report-redaction-required', + : remainingScopedAgentsReportLeakCount > 0 + ? 'scoped-agents-report-redaction-required' + : remainingProjectSkillReportLeakCount > 0 + ? 'project-skill-report-redaction-required' + : remainingParallelReadReportLeakCount > 0 + ? 'parallel-read-report-redaction-required' + : remainingSupervisorSwarmReportLeakCount > 0 + ? 'supervisor-swarm-report-redaction-required' + : remainingFormalConfigPathReportLeakCount > 0 + ? 'formal-config-path-report-redaction-required' + : 'web-search-report-redaction-required', ); const safeSummary = { status: state.status, @@ -1302,19 +956,13 @@ if (selfTestRequested) { projectPathReportLeakCount: remainingProjectPathReportLeakCount, responseStreamReportLeakCount: remainingResponseStreamReportLeakCount, webSearchReportLeakCount: remainingWebSearchReportLeakCount, - userInputReportLeakCount: remainingUserInputReportLeakCount, scopedAgentsReportLeakCount: remainingScopedAgentsReportLeakCount, projectSkillReportLeakCount: remainingProjectSkillReportLeakCount, parallelReadReportLeakCount: remainingParallelReadReportLeakCount, - supervisorAutonomousPlayableReportLeakCount: - remainingSupervisorAutonomousPlayableReportLeakCount, supervisorSwarmReportLeakCount: remainingSupervisorSwarmReportLeakCount, formalConfigPathReportLeakCount: remainingFormalConfigPathReportLeakCount, - ...(isSupervisorSwarmSuite() - ? supervisorSwarmChatSessionFailureEvidence() - : {}), }, errorCount: state.errors.length, errorHashes: state.errors.map(summarizeRecordedError), diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/app-data.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/app-data.mjs index 3d8e052c6..c5ed62fa2 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/app-data.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/app-data.mjs @@ -37,26 +37,16 @@ import { state, steerRunnerKillAppDataSentinelFileName, steerRunnerKillAppDataSentinelSchema, - supervisorAutonomousPlayableAppDataSentinelFileName, - supervisorAutonomousPlayableAppDataSentinelSchema, supervisorSwarmAppDataSentinelFileName, supervisorSwarmAppDataSentinelSchema, - supervisorSwarmAutonomousChatAppDataSentinelFileName, - supervisorSwarmAutonomousChatAppDataSentinelSchema, - supervisorSwarmCollaborationPolicyAppDataSentinelFileName, - supervisorSwarmCollaborationPolicyAppDataSentinelSchema, supervisorSwarmFinalReplyTransientRetryAppDataSentinelFileName, supervisorSwarmFinalReplyTransientRetryAppDataSentinelSchema, supervisorSwarmRequiredAgentIds, - supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName, - supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelFileName, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelSchema, supervisorSwarmTransientRetryAppDataSentinelFileName, supervisorSwarmTransientRetryAppDataSentinelSchema, supervisorSwarmTransientRetryTargetAgentId, - userInputAppDataSentinelFileName, - userInputAppDataSentinelSchema, webSearchAppDataSentinelFileName, webSearchAppDataSentinelSchema, windowsProcessHandleHelperSource, @@ -68,21 +58,14 @@ import { isProjectSkillSuite } from '../suites/project-skill.mjs'; import { isResponseStreamSuite } from '../suites/response-stream.mjs'; import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs'; import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs'; -import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs'; import { - isSupervisorSwarmAutonomousChatSuite, - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite, isSupervisorSwarmFinalReplyTransientRetrySuite, isSupervisorSwarmInitialTransientRetrySuite, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmStaticIsolatedAutonomousChatSuite, isSupervisorSwarmSuite, isSupervisorSwarmToolPlanHandoffRunnerKillSuite, isSupervisorSwarmTransientRetrySuite, rebuildSupervisorSwarmTranscriptScanner, } from '../suites/supervisor-swarm.mjs'; -import { isUserInputRuntimeSuite } from '../suites/user-input.mjs'; import { isWebSearchSuite, sameEffectiveAgentLlmWithoutWebSearch, @@ -100,19 +83,10 @@ import { appendBounded, runProcess } from './process.mjs'; import { decodeUtf8Fatal, isIsolatedRunnerSuite } from './reporting.mjs'; import { killRunnerOnce, readRunnerStatus, runnerBootId } from './runtime.mjs'; -const platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE'; -const platformSessionFixtureMaxBytes = 16 * 1024; -const isolatedPlatformSessionFixtureName = - '.deterministic-platform-session.json'; -const platformSessionFixtureSchema = - 'genarrative-agc-platform-session-fixture.v1'; - export function isolatedSuiteProtectsSourceAppData() { return ( isSupervisorSwarmTransientRetrySuite() || - isSupervisorSwarmToolPlanHandoffRunnerKillSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || - isSupervisorSwarmInteractiveChatSuite() + isSupervisorSwarmToolPlanHandoffRunnerKillSuite() ); } @@ -120,27 +94,6 @@ export function isolatedSuiteUsesSiblingAppData() { return isWebSearchSuite() || isolatedSuiteProtectsSourceAppData(); } -export function sameSupervisorPlayableProviderBinding(left, right) { - const scalarFields = [ - 'providerAgentMode', - 'providerModel', - 'providerApiKind', - 'providerReasoningEffort', - 'providerBaseUrlSha256', - ]; - return ( - left != null && - right != null && - scalarFields.every((field) => left[field] === right[field]) && - Array.isArray(left.boundAgentIds) && - Array.isArray(right.boundAgentIds) && - left.boundAgentIds.length === right.boundAgentIds.length && - left.boundAgentIds.every( - (agentId, index) => agentId === right.boundAgentIds[index], - ) - ); -} - export function isolatedSuiteAppDataProfile() { if (isSteerRunnerKillSuite()) { return { @@ -174,14 +127,6 @@ export function isolatedSuiteAppDataProfile() { codePrefix: 'context-compaction-appdata', }; } - if (isUserInputRuntimeSuite()) { - return { - prefix: '.agent-runtime-real-e2e-user-input-', - sentinelName: userInputAppDataSentinelFileName, - sentinelSchema: userInputAppDataSentinelSchema, - codePrefix: 'user-input-appdata', - }; - } if (isScopedAgentsSuite()) { return { prefix: '.agent-runtime-real-e2e-scoped-agents-', @@ -236,41 +181,6 @@ export function isolatedSuiteAppDataProfile() { codePrefix: 'supervisor-swarm-transient-retry-appdata', }; } - if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - return { - prefix: '.agent-runtime-real-e2e-supervisor-autonomous-playable-', - sentinelName: supervisorAutonomousPlayableAppDataSentinelFileName, - sentinelSchema: supervisorAutonomousPlayableAppDataSentinelSchema, - codePrefix: 'supervisor-autonomous-playable-appdata', - }; - } - if (isSupervisorSwarmAutonomousChatSuite()) { - return { - prefix: '.agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-', - sentinelName: supervisorSwarmAutonomousChatAppDataSentinelFileName, - sentinelSchema: supervisorSwarmAutonomousChatAppDataSentinelSchema, - codePrefix: 'supervisor-swarm-autonomous-chat-appdata', - }; - } - if (isSupervisorSwarmStaticIsolatedAutonomousChatSuite()) { - return { - prefix: - '.agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-', - sentinelName: - supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName, - sentinelSchema: - supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema, - codePrefix: 'supervisor-swarm-static-isolated-autonomous-chat-appdata', - }; - } - if (isSupervisorSwarmCollaborationPolicyMixedRecoverySuite()) { - return { - prefix: '.agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-', - sentinelName: supervisorSwarmCollaborationPolicyAppDataSentinelFileName, - sentinelSchema: supervisorSwarmCollaborationPolicyAppDataSentinelSchema, - codePrefix: 'supervisor-swarm-collaboration-policy-appdata', - }; - } if (isSupervisorSwarmSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-', @@ -505,120 +415,6 @@ export async function verifySourceAppDataDirectoryUntouched() { state.isolatedRunner.sourceAppDataDirectoryUntouched = true; } -async function readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir) { - const rawPath = process.env[platformSessionFixtureEnv]; - assert( - isNonEmptyString(rawPath) && path.isAbsolute(rawPath), - 'supervisor-autonomous-playable-platform-session-fixture-missing', - ); - const sourceRealPath = await fs.realpath(sourceConfigDir); - const requestedPath = path.resolve(rawPath); - const requestedMetadata = await fs.lstat(requestedPath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - assert( - requestedMetadata?.isFile() && !requestedMetadata.isSymbolicLink(), - 'supervisor-autonomous-playable-platform-session-fixture-not-regular', - ); - assert( - requestedMetadata.size <= platformSessionFixtureMaxBytes, - 'supervisor-autonomous-playable-platform-session-fixture-too-large', - ); - const realPath = await fs.realpath(requestedPath); - assert( - isPathInside(sourceRealPath, realPath), - 'supervisor-autonomous-playable-platform-session-fixture-outside-config', - ); - const bytes = await fs.readFile(realPath); - assert( - bytes.length <= platformSessionFixtureMaxBytes, - 'supervisor-autonomous-playable-platform-session-fixture-too-large', - ); - let fixture; - try { - fixture = JSON.parse( - decodeUtf8Fatal(bytes, 'platform-session-fixture-invalid-utf8'), - ); - } catch (error) { - throw codedError( - 'supervisor-autonomous-playable-platform-session-fixture-invalid', - error, - ); - } - const expectedKeys = [ - 'schemaVersion', - 'userId', - 'accessToken', - 'apiBaseUrl', - 'generation', - ]; - assert( - isPlainObject(fixture) && - JSON.stringify(Object.keys(fixture).sort()) === - JSON.stringify([...expectedKeys].sort()) && - fixture.schemaVersion === platformSessionFixtureSchema && - isNonEmptyString(fixture.userId) && - isNonEmptyString(fixture.accessToken) && - isNonEmptyString(fixture.apiBaseUrl) && - Number.isSafeInteger(fixture.generation) && - fixture.generation > 0, - 'supervisor-autonomous-playable-platform-session-fixture-invalid', - ); - return { - sourcePath: realPath, - bytes, - fixture, - sha256: createHash('sha256').update(bytes).digest('hex'), - }; -} - -async function installPlatformSessionFixtureIntoIsolatedAppData( - sourceConfigDir, - appDataDir, -) { - if (!isSupervisorAutonomousPlayableLaneDefenseSuite()) return; - const source = - await readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir); - const isolatedPath = path.join( - appDataDir, - isolatedPlatformSessionFixtureName, - ); - await fs.copyFile( - source.sourcePath, - isolatedPath, - fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE, - ); - await fs.chmod(isolatedPath, 0o600).catch(() => {}); - const isolatedMetadata = await fs.lstat(isolatedPath); - assert( - isolatedMetadata.isFile() && - !isolatedMetadata.isSymbolicLink() && - isolatedMetadata.size === source.bytes.length, - 'supervisor-autonomous-playable-platform-session-fixture-copy-invalid', - ); - const isolatedBytes = await fs.readFile(isolatedPath); - assert( - createHash('sha256').update(isolatedBytes).digest('hex') === source.sha256, - 'supervisor-autonomous-playable-platform-session-fixture-copy-mismatch', - ); - state.isolatedRunner.platformSessionFixtureSourcePath = source.sourcePath; - state.isolatedRunner.platformSessionFixturePath = isolatedPath; - state.isolatedRunner.platformSessionFixtureSha256 = source.sha256; - state.isolatedRunner.platformSessionFixturePreviousEnv = - Object.prototype.hasOwnProperty.call(process.env, platformSessionFixtureEnv) - ? process.env[platformSessionFixtureEnv] - : undefined; - process.env[platformSessionFixtureEnv] = isolatedPath; - state.formalConfigPathTranscriptScanner?.addSecrets( - absolutePathVariants(source.sourcePath, isolatedPath), - ); - const previousLeakCount = state.transcriptScanner?.count ?? 0; - state.secrets = [...new Set([...state.secrets, source.fixture.accessToken])]; - rebuildSupervisorSwarmTranscriptScanner(); - state.transcriptScanner.count = previousLeakCount; -} - export async function prepareIsolatedSuiteAppData({ streamAgentId = null, webSearchAgentId = null, @@ -773,8 +569,7 @@ export async function prepareIsolatedSuiteAppData({ isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorSwarmSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() + isSupervisorSwarmSuite() ? 'private-copy' : 'hardlink'; try { @@ -891,10 +686,6 @@ export async function prepareIsolatedSuiteAppData({ state.secrets = [...suiteSecrets]; rebuildSupervisorSwarmTranscriptScanner(); state.transcriptScanner.count = previousLeakCount; - await installPlatformSessionFixtureIntoIsolatedAppData( - sourceConfigDir, - appDataDir, - ); const unexpectedEndpoint = await fs .lstat(path.join(appDataDir, runnerEndpointFileName)) .catch((error) => { @@ -903,46 +694,6 @@ export async function prepareIsolatedSuiteAppData({ }); assert(!unexpectedEndpoint, `${profile.codePrefix}-endpoint-preexisted`); state.runtimeConfigDir = appDataDir; - if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { - const isolatedConfig = await loadConfig(appDataDir); - const expectedBinding = state.config.providerBinding; - const agentIds = [projectSupervisorAgentId]; - const bindings = agentIds.map((agentId) => { - const effective = effectiveAgentLlmConfig(isolatedConfig.config, agentId); - assert( - ['apiKey', 'baseUrl', 'model', 'apiKind', 'reasoningEffort'].every( - (key) => isNonEmptyString(effective[key]), - ), - 'supervisor-autonomous-playable-effective-provider-incomplete', - ); - return { - providerModel: effective.model.trim(), - providerApiKind: effective.apiKind.trim(), - providerReasoningEffort: effective.reasoningEffort.trim(), - providerBaseUrlSha256: hashValue(effective.baseUrl.trim()), - }; - }); - const effectiveBinding = { - ...bindings[0], - boundAgentIds: [...agentIds].sort(), - }; - assert( - expectedBinding && - bindings.every( - (binding) => JSON.stringify(binding) === JSON.stringify(bindings[0]), - ) && - sameSupervisorPlayableProviderBinding( - effectiveBinding, - expectedBinding, - ), - 'supervisor-autonomous-playable-effective-provider-binding-mismatch', - ); - state.supervisorAutonomousPlayable.expectedProviderBinding = { - ...expectedBinding, - }; - state.supervisorAutonomousPlayable.effectiveProviderBinding = - effectiveBinding; - } if (streamAgentId) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( @@ -981,22 +732,6 @@ export async function prepareIsolatedSuiteAppData({ ); state.webSearch.effectiveEnabled = true; } - if (isUserInputRuntimeSuite()) { - const isolatedConfig = await loadConfig(appDataDir); - const isolatedEffective = effectiveAgentLlmConfig( - isolatedConfig.config, - projectSupervisorAgentId, - ); - assert( - isolatedEffective.model === 'gpt-5.5' && - ['apiKey', 'baseUrl', 'model'].every( - (key) => - typeof isolatedEffective[key] === 'string' && - isolatedEffective[key].trim().length > 0, - ), - 'user-input-effective-gpt-5-5-config-invalid', - ); - } if (isScopedAgentsSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( @@ -1073,9 +808,8 @@ export async function prepareIsolatedSuiteAppData({ effective.requestTimeoutMs > 0 && Number.isSafeInteger(effective.maxRetries) && effective.maxRetries >= - (isSupervisorSwarmInteractiveChatSuite() || (isSupervisorSwarmTransientRetrySuite() && - agentId !== supervisorSwarmTransientRetryTargetAgentId) + agentId !== supervisorSwarmTransientRetryTargetAgentId ? 0 : 1) && effective.maxRetries <= 3 && @@ -1095,24 +829,6 @@ export async function prepareIsolatedSuiteAppData({ { llm: isolatedConfig.config.llm }, '', ); - if (isSupervisorSwarmMixedHarnessSuite()) { - assert( - globalEffective.model === 'gpt-5.5' && - globalEffective.apiKind === 'openai_chat' && - isNonEmptyString(globalEffective.reasoningEffort) && - Number.isSafeInteger(globalEffective.requestTimeoutMs) && - globalEffective.requestTimeoutMs > 0 && - Number.isSafeInteger(globalEffective.maxRetries) && - globalEffective.maxRetries >= 0 && - globalEffective.maxRetries <= 3 && - Number.isSafeInteger(globalEffective.retryBackoffMs) && - globalEffective.retryBackoffMs > 0 && - ['apiKey', 'baseUrl', 'model'].every((key) => - isNonEmptyString(globalEffective[key]), - ), - 'supervisor-swarm-mixed-default-provider-policy-invalid', - ); - } const configuredAgentIds = Object.keys( isPlainObject(isolatedConfig.config.agentLlm) ? isolatedConfig.config.agentLlm @@ -2000,31 +1716,6 @@ export async function verifyIsolatedSuiteConfigLinksUnchanged() { 'isolated-source-config-changed-during-suite', ); } - const fixture = state.isolatedRunner; - if ( - fixture.platformSessionFixtureSourcePath && - fixture.platformSessionFixturePath && - fixture.platformSessionFixtureSha256 - ) { - const [sourceMetadata, isolatedMetadata, sourceBytes, isolatedBytes] = - await Promise.all([ - fs.lstat(fixture.platformSessionFixtureSourcePath), - fs.lstat(fixture.platformSessionFixturePath), - fs.readFile(fixture.platformSessionFixtureSourcePath), - fs.readFile(fixture.platformSessionFixturePath), - ]); - assert( - sourceMetadata.isFile() && - !sourceMetadata.isSymbolicLink() && - isolatedMetadata.isFile() && - !isolatedMetadata.isSymbolicLink() && - createHash('sha256').update(sourceBytes).digest('hex') === - fixture.platformSessionFixtureSha256 && - createHash('sha256').update(isolatedBytes).digest('hex') === - fixture.platformSessionFixtureSha256, - 'supervisor-autonomous-playable-platform-session-fixture-changed', - ); - } } export async function verifySourceConfigLinkCountsRestored() { @@ -2064,22 +1755,7 @@ export async function removeIsolatedSuiteAppData() { } catch (error) { ownershipError = error; } - try { - await fs.rm(appDataDir, { recursive: true, force: false }); - } finally { - if (state.isolatedRunner.platformSessionFixtureSourcePath) { - const previous = state.isolatedRunner.platformSessionFixturePreviousEnv; - if (previous === undefined) { - delete process.env[platformSessionFixtureEnv]; - } else { - process.env[platformSessionFixtureEnv] = previous; - } - } - state.isolatedRunner.platformSessionFixturePath = null; - state.isolatedRunner.platformSessionFixtureSourcePath = null; - state.isolatedRunner.platformSessionFixtureSha256 = null; - state.isolatedRunner.platformSessionFixturePreviousEnv = undefined; - } + await fs.rm(appDataDir, { recursive: true, force: false }); state.runtimeConfigDir = state.options.configDir; try { await verifySourceConfigLinkCountsRestored(); diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/config.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/config.mjs index 54efb5c75..43768d3b3 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/config.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/config.mjs @@ -13,15 +13,10 @@ import { responseStreamSuite, scopedAgentsSuite, steerRunnerKillSuite, - supervisorAutonomousPlayableLaneDefenseSuite, - supervisorSwarmAutonomousChatSuite, - supervisorSwarmCollaborationPolicyMixedRecoverySuite, supervisorSwarmFinalReplyTransientRetrySuite, - supervisorSwarmStaticIsolatedAutonomousChatSuite, supervisorSwarmSuite, supervisorSwarmToolPlanHandoffRunnerKillSuite, supervisorSwarmTransientRetrySuite, - userInputRuntimeSuite, webSearchSuite, } from '../runtime-state.mjs'; import { collectApiKeys, isPathInside } from './io.mjs'; @@ -58,7 +53,6 @@ export function parseArguments(args) { suite === responseStreamSuite || suite === webSearchSuite || suite === contextCompactionSuite || - suite === userInputRuntimeSuite || suite === scopedAgentsSuite || suite === projectSkillSuite || suite === parallelReadSuite || @@ -66,10 +60,6 @@ export function parseArguments(args) { suite === supervisorSwarmTransientRetrySuite || suite === supervisorSwarmFinalReplyTransientRetrySuite || suite === supervisorSwarmToolPlanHandoffRunnerKillSuite || - suite === supervisorSwarmAutonomousChatSuite || - suite === supervisorAutonomousPlayableLaneDefenseSuite || - suite === supervisorSwarmStaticIsolatedAutonomousChatSuite || - suite === supervisorSwarmCollaborationPolicyMixedRecoverySuite || suite === steerRunnerKillSuite || processSessionSuites.has(suite), 'unsupported-suite', diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/process.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/process.mjs index 950b9646a..427b42e25 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/process.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/process.mjs @@ -2,7 +2,6 @@ import { assert, codedError, hashValue, - sleep, throwIfShutdownRequested, } from '../assertions/core.mjs'; import { fs, path, spawn, withLoopbackNoProxy } from '../dependencies.mjs'; @@ -13,14 +12,9 @@ import { manifestPath, state, } from '../runtime-state.mjs'; -import { - isSupervisorSwarmTransientRetrySuite, - recordSupervisorSwarmChatSessionFailureDiagnostic, -} from '../suites/supervisor-swarm.mjs'; +import { isSupervisorSwarmTransientRetrySuite } from '../suites/supervisor-swarm.mjs'; import { isIsolatedRunnerSuite } from './reporting.mjs'; -export const activeInteractiveCliSessions = new Set(); - export async function prepareCliBinary() { const cargo = process.platform === 'win32' ? 'cargo.exe' : 'cargo'; await runProcess( @@ -144,197 +138,6 @@ export function codedProcessError(code, details) { return error; } -export function startInteractiveCli(args) { - assert(Boolean(state.cliBinary), 'interactive-cli-binary-not-ready'); - assert(Boolean(state.runtimeConfigDir), 'interactive-config-dir-not-ready'); - if ( - isIsolatedRunnerSuite() && - state.options?.configDir && - path.resolve(state.runtimeConfigDir) === - path.resolve(state.options.configDir) - ) { - state.isolatedRunner.sourceConfigCliCallCount += 1; - } - const child = spawn( - state.cliBinary, - [...args, '--config-dir', state.runtimeConfigDir], - { - cwd: appRoot, - env: buildCliChildEnvironment(), - stdio: ['pipe', 'pipe', 'pipe'], - }, - ); - return createInteractiveCliSession(child); -} - -export function createInteractiveCliSession(child) { - activeCommandChildren.add(child); - const session = { - child, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - exited: false, - exitInfo: null, - exitPromise: null, - closed: false, - closeInfo: null, - closePromise: null, - stdioClosed: false, - stdioCloseInfo: null, - spawnError: null, - stdinError: null, - }; - activeInteractiveCliSessions.add(session); - session.exitPromise = new Promise((resolve) => { - const settle = (result) => { - if (session.exited) return; - activeCommandChildren.delete(child); - session.exited = true; - session.closed = true; - session.exitInfo = result; - session.closeInfo = result; - resolve(result); - }; - child.once('error', (error) => { - session.spawnError = error; - settle({ code: null, signal: null, error }); - }); - child.once('exit', (code, signal) => { - settle({ code, signal, error: null }); - }); - }); - session.closePromise = new Promise((resolve) => { - child.once('close', (code, signal) => { - activeCommandChildren.delete(child); - activeInteractiveCliSessions.delete(session); - session.stdioClosed = true; - session.stdioCloseInfo = { - code, - signal, - error: session.spawnError, - }; - resolve(session.stdioCloseInfo); - }); - }); - child.stdin?.on('error', (error) => { - session.stdinError ??= error; - }); - child.stdout.on('data', (chunk) => { - state.transcriptScanner?.scan('interactive-stdout', chunk); - state.projectPathTranscriptScanner?.scan('interactive-stdout', chunk); - state.formalConfigPathTranscriptScanner?.scan('interactive-stdout', chunk); - session.stdout = appendBounded(session.stdout, chunk, commandOutputLimit); - }); - child.stderr.on('data', (chunk) => { - state.transcriptScanner?.scan('interactive-stderr', chunk); - state.projectPathTranscriptScanner?.scan('interactive-stderr', chunk); - state.formalConfigPathTranscriptScanner?.scan('interactive-stderr', chunk); - session.stderr = appendBounded(session.stderr, chunk, commandOutputLimit); - }); - return session; -} - -export function interactiveCliOutput(session) { - return `${session.stdout.toString('utf8')}\n${session.stderr.toString('utf8')}`; -} - -export function writeInteractiveCliLine(session, line) { - assert(!session.closed, 'interactive-cli-already-closed'); - assert(session.child.stdin.writable, 'interactive-cli-stdin-not-writable'); - session.child.stdin.write(`${line}\n`); -} - -export async function waitForInteractiveCliOutput( - session, - predicate, - code, - timeoutMs, - { allowAfterProcessExit = false } = {}, -) { - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - const output = interactiveCliOutput(session); - if (predicate(output)) return output; - if (session.exited && !allowAfterProcessExit) { - if (session === state.supervisorSwarmCliSession) { - recordSupervisorSwarmChatSessionFailureDiagnostic(session); - } - throw codedError(`${code}-cli-exited`); - } - if (session.stdioClosed) { - if (session === state.supervisorSwarmCliSession) { - recordSupervisorSwarmChatSessionFailureDiagnostic(session); - } - throw codedError(`${code}-cli-stdio-closed`); - } - await sleep(50); - } - throw codedError(code); -} - -export async function waitForInteractiveCliExit(session, timeoutMs) { - const result = await Promise.race([ - session.exitPromise, - sleep(timeoutMs).then(() => null), - ]); - if (!result) throw codedError('interactive-cli-exit-timeout'); - if (result.error) throw codedError('interactive-cli-process-error'); - assert( - result.code === 0 && result.signal === null, - 'interactive-cli-exit-invalid', - ); - return result; -} - -export async function closeInteractiveCli(session) { - if (!session) return null; - if (session.exited) return session.exitInfo; - if ( - session.child.stdin.writable && - !session.child.stdin.writableEnded && - !session.child.stdin.destroyed - ) { - session.child.stdin.write('/quit\n'); - } - let result = await Promise.race([ - session.exitPromise, - sleep(3_000).then(() => null), - ]); - if (!result && !session.exited) { - session.child.kill('SIGTERM'); - result = await Promise.race([ - session.exitPromise, - sleep(2_000).then(() => null), - ]); - } - if (!result && !session.exited) { - session.child.kill('SIGKILL'); - result = await Promise.race([ - session.exitPromise, - sleep(5_000).then(() => null), - ]); - } - assert(Boolean(result), 'interactive-cli-cleanup-timeout'); - return result; -} - -export async function waitForInteractiveCliStdioClose(session, timeoutMs) { - if (!session || session.stdioClosed) return session?.stdioCloseInfo ?? null; - const result = await Promise.race([ - session.closePromise, - sleep(timeoutMs).then(() => null), - ]); - if (!result) throw codedError('interactive-cli-stdio-close-timeout'); - return result; -} - -export function destroyInteractiveCliOutputStreams(session) { - if (!session) return; - for (const stream of [session.child.stdout, session.child.stderr]) { - if (stream && !stream.destroyed) stream.destroy(); - } -} - export async function runProcess( program, args, @@ -406,214 +209,6 @@ export async function runProcess( }); } -export async function listSystemProcessIdentities() { - if (process.platform === 'win32') { - const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT; - assert( - typeof systemRoot === 'string' && path.isAbsolute(systemRoot), - 'owned-process-snapshot-system-root-invalid', - ); - const powershell = path.join( - systemRoot, - 'System32/WindowsPowerShell/v1.0/powershell.exe', - ); - const metadata = await fs.lstat(powershell); - assert( - metadata.isFile() && !metadata.isSymbolicLink(), - 'owned-process-snapshot-powershell-invalid', - ); - const result = await runProcess( - powershell, - [ - '-NoProfile', - '-NonInteractive', - '-Command', - '$processes = @(Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CreationDate,Name); $processes | ConvertTo-Json -Compress', - ], - { - cwd: appRoot, - timeoutMs: 30_000, - env: { ...process.env, NO_COLOR: '1', RUST_BACKTRACE: '0' }, - }, - ); - const parsed = JSON.parse(result.stdout); - return (Array.isArray(parsed) ? parsed : [parsed]) - .map((record) => ({ - pid: Number(record?.ProcessId), - parentPid: Number(record?.ParentProcessId), - startedAt: String(record?.CreationDate ?? ''), - name: String(record?.Name ?? ''), - })) - .filter(validSystemProcessIdentity); - } - assert( - process.platform === 'linux' || process.platform === 'darwin', - 'owned-process-snapshot-platform-unsupported', - ); - const result = await runProcess( - 'ps', - ['-A', '-o', 'pid=', '-o', 'ppid=', '-o', 'lstart=', '-o', 'comm='], - { cwd: appRoot, timeoutMs: 30_000 }, - ); - return result.stdout - .split(/\r?\n/u) - .map((line) => line.trim()) - .filter(Boolean) - .map((line) => { - const fields = line.split(/\s+/u); - return { - pid: Number(fields[0]), - parentPid: Number(fields[1]), - startedAt: fields.slice(2, 7).join(' '), - name: fields.slice(7).join(' '), - }; - }) - .filter(validSystemProcessIdentity); -} - -function validSystemProcessIdentity(record) { - return ( - Number.isSafeInteger(record?.pid) && - record.pid > 0 && - Number.isSafeInteger(record.parentPid) && - record.parentPid >= 0 && - typeof record.startedAt === 'string' && - record.startedAt.length > 0 && - typeof record.name === 'string' && - record.name.length > 0 - ); -} - -export function buildOwnedProcessCleanupSnapshot( - processRecords, - { rootPids = [], runnerPid = null, helperPids = [] } = {}, -) { - assert( - Array.isArray(processRecords) && - Array.isArray(rootPids) && - Array.isArray(helperPids), - 'owned-process-snapshot-input-invalid', - ); - const records = processRecords.filter(validSystemProcessIdentity); - const byPid = new Map(records.map((record) => [record.pid, record])); - const childrenByParent = new Map(); - for (const record of records) { - const children = childrenByParent.get(record.parentPid) ?? []; - children.push(record.pid); - childrenByParent.set(record.parentPid, children); - } - const normalizedRunnerPid = Number.isSafeInteger(runnerPid) - ? runnerPid - : null; - const helperPidSet = new Set( - helperPids.filter((pid) => Number.isSafeInteger(pid) && pid > 0), - ); - const roots = [ - ...new Set( - [...rootPids, normalizedRunnerPid, ...helperPidSet].filter( - (pid) => Number.isSafeInteger(pid) && pid > 0, - ), - ), - ]; - assert(roots.length > 0, 'owned-process-snapshot-root-missing'); - const ownedPids = new Set(); - const queue = [...roots]; - while (queue.length > 0) { - const pid = queue.shift(); - if (ownedPids.has(pid)) continue; - ownedPids.add(pid); - queue.push(...(childrenByParent.get(pid) ?? [])); - } - const identities = [...ownedPids] - .map((pid) => byPid.get(pid)) - .filter(Boolean) - .map((record) => ({ - pid: record.pid, - startedAt: record.startedAt, - name: record.name, - kind: ownedProcessKind(record, normalizedRunnerPid, helperPidSet), - })) - .sort((left, right) => left.pid - right.pid); - return { - identities, - observedCounts: countOwnedProcessKinds(identities), - }; -} - -function ownedProcessKind(record, runnerPid, helperPids) { - if (record.pid === runnerPid) return 'runner'; - if (helperPids.has(record.pid)) return 'helper'; - const name = path.basename(record.name).toLowerCase(); - if (/^node(?:\.exe)?$/u.test(name)) return 'node'; - if (/^(?:chrome|chromium|msedge|google-chrome)(?:\.exe)?$/u.test(name)) { - return 'browser'; - } - return 'command'; -} - -function countOwnedProcessKinds(identities) { - const counts = { - runner: 0, - helper: 0, - node: 0, - browser: 0, - command: 0, - total: identities.length, - }; - for (const identity of identities) counts[identity.kind] += 1; - return counts; -} - -export function inspectOwnedProcessCleanupResiduals( - snapshot, - processRecords, - { activeCommandChildCount = 0, activeInteractiveCliSessionCount = 0 } = {}, -) { - assert( - Array.isArray(snapshot?.identities) && Array.isArray(processRecords), - 'owned-process-residual-input-invalid', - ); - const currentByPid = new Map( - processRecords - .filter(validSystemProcessIdentity) - .map((record) => [record.pid, record]), - ); - const residualIdentities = snapshot.identities.filter((identity) => { - const current = currentByPid.get(identity.pid); - return ( - current?.startedAt === identity.startedAt && - current?.name === identity.name - ); - }); - return { - residualCounts: countOwnedProcessKinds(residualIdentities), - activeCommandChildCount, - activeInteractiveCliSessionCount, - clean: - residualIdentities.length === 0 && - activeCommandChildCount === 0 && - activeInteractiveCliSessionCount === 0, - }; -} - -export async function captureOwnedProcessCleanupSnapshot(options) { - return buildOwnedProcessCleanupSnapshot( - await listSystemProcessIdentities(), - options, - ); -} - -export async function verifyOwnedProcessCleanupSnapshot(snapshot) { - return inspectOwnedProcessCleanupResiduals( - snapshot, - await listSystemProcessIdentities(), - { - activeCommandChildCount: activeCommandChildren.size, - activeInteractiveCliSessionCount: activeInteractiveCliSessions.size, - }, - ); -} - export function appendBounded(current, chunk, limit) { const combined = Buffer.concat([current, chunk]); return combined.length <= limit diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/project.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/project.mjs index 9fa9ea193..9f787680f 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/project.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/project.mjs @@ -28,12 +28,10 @@ import { isGoalRuntimeSuite, } from '../suites/goal.mjs'; import { isResponseStreamSuite } from '../suites/response-stream.mjs'; -import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs'; import { isSupervisorSwarmSuite, supervisorSwarmVerificationFixtureSource, } from '../suites/supervisor-swarm.mjs'; -import { isUserInputRuntimeSuite } from '../suites/user-input.mjs'; import { isWebSearchSuite } from '../suites/web-search.mjs'; import { createSentinelOwnedTempDirectory } from './app-data.mjs'; import { effectiveAgentLlmConfig } from './config.mjs'; @@ -41,19 +39,15 @@ import { runProcess } from './process.mjs'; import { isIsolatedRunnerSuite } from './reporting.mjs'; export function requiredAgentIdsForSuite() { - return isUserInputRuntimeSuite() - ? [projectSupervisorAgentId] - : isSupervisorAutonomousPlayableLaneDefenseSuite() - ? [projectSupervisorAgentId] - : isSupervisorSwarmSuite() - ? [ - projectSupervisorAgentId, - supervisorSwarmDesignAgentId, - supervisorSwarmQualityAgentId, - ] - : isIsolatedRunnerSuite() - ? [mainAgentId] - : [mainAgentId, 'quality-review']; + return isSupervisorSwarmSuite() + ? [ + projectSupervisorAgentId, + supervisorSwarmDesignAgentId, + supervisorSwarmQualityAgentId, + ] + : isIsolatedRunnerSuite() + ? [mainAgentId] + : [mainAgentId, 'quality-review']; } export function expectedProviderBindingForSuite(config) { @@ -120,11 +114,9 @@ export async function checkPrerequisites(config) { return { llmConfigured, providerBinding, - chromeAvailable: - !isIsolatedRunnerSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() - ? Boolean(await findSupportedBrowser()) - : false, + chromeAvailable: !isIsolatedRunnerSuite() + ? Boolean(await findSupportedBrowser()) + : false, editorApiConfigured, }; } @@ -267,8 +259,7 @@ export async function seedDisposableProject({ ? supervisorSwarmVerificationFixtureSource() : isGoalRuntimeSuite() || isResponseStreamSuite() || - isWebSearchSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() + isWebSearchSuite() ? goalRevisionOneVerificationFixtureSource() : goalRevisionTwoVerificationFixtureSource(), ), diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/reporting.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/reporting.mjs index a176cf335..c59503ec2 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/reporting.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/reporting.mjs @@ -20,9 +20,7 @@ import { isProjectSkillSuite } from '../suites/project-skill.mjs'; import { isResponseStreamSuite } from '../suites/response-stream.mjs'; import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs'; import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs'; -import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs'; import { isSupervisorSwarmSuite } from '../suites/supervisor-swarm.mjs'; -import { isUserInputRuntimeSuite } from '../suites/user-input.mjs'; import { isWebSearchSuite } from '../suites/web-search.mjs'; import { isPathInside, readJson } from './io.mjs'; @@ -88,17 +86,12 @@ export function buildSummary() { config: state.config, blocked: state.blocked, run: { - agentId: - isUserInputRuntimeSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || - isSupervisorSwarmSuite() - ? projectSupervisorAgentId - : mainAgentId, + agentId: isSupervisorSwarmSuite() + ? projectSupervisorAgentId + : mainAgentId, runIdHash: hashValue(state.initialRunId), sessionIdHash: hashValue(state.initialSessionId), - runnerKilled: isSupervisorAutonomousPlayableLaneDefenseSuite() - ? false - : state.runnerKilled, + runnerKilled: state.runnerKilled, resumed: state.resumed, identityStable: state.identityStable, }, @@ -110,11 +103,9 @@ export function buildSummary() { projectPathReportLeakCount: state.projectPathReportLeakCount, ...(isWebSearchSuite() || isContextCompactionSuite() || - isUserInputRuntimeSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmSuite() || isSteerRunnerKillSuite() ? { @@ -408,11 +399,9 @@ export function isIsolatedRunnerSuite() { isResponseStreamSuite() || isWebSearchSuite() || isContextCompactionSuite() || - isUserInputRuntimeSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || - isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmSuite() ); } diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/runtime.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/runtime.mjs index f6f6ae0a4..d2f2cd567 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/runtime.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/harness/runtime.mjs @@ -87,11 +87,7 @@ import { import { isProjectSkillSuite } from '../suites/project-skill.mjs'; import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs'; import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs'; -import { - confirmSupervisorSwarmPendingActionsInChat, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmSuite, -} from '../suites/supervisor-swarm.mjs'; +import { isSupervisorSwarmSuite } from '../suites/supervisor-swarm.mjs'; import { killRunnerPidOnce, verifyOwnedRunnerForKill } from './app-data.mjs'; import { isPlainObject } from './config.mjs'; import { @@ -1038,13 +1034,6 @@ export async function confirmPendingActions( allowedTools = null, shouldConfirm = () => true, ) { - if (isSupervisorSwarmInteractiveChatSuite()) { - await confirmSupervisorSwarmPendingActionsInChat( - allowedTools, - shouldConfirm, - ); - return; - } for (const pending of await findPendingActions()) { if (state.confirmedActionIds.has(pending.actionId)) continue; if (!shouldConfirm(pending)) continue; diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/runtime-state.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/runtime-state.mjs index c27fa5aea..a542f9e8a 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/runtime-state.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/runtime-state.mjs @@ -43,12 +43,6 @@ export const contextCompactionAppDataSentinelFileName = export const contextCompactionAppDataSentinelSchema = 'genarrative-agent-runtime-real-e2e-context-compaction-appdata.v1'; -export const userInputAppDataSentinelFileName = - '.agent-runtime-real-e2e-user-input-appdata.json'; - -export const userInputAppDataSentinelSchema = - 'genarrative-agent-runtime-real-e2e-user-input-appdata.v1'; - export const scopedAgentsAppDataSentinelFileName = '.agent-runtime-real-e2e-scoped-agents-appdata.json'; @@ -109,30 +103,6 @@ export const supervisorSwarmToolPlanHandoffCheckpointReachedFileName = export const supervisorSwarmToolPlanHandoffCheckpointReachedSchema = 'game-creator-tool-plan-handoff-runner-kill-checkpoint-reached.v1'; -export const supervisorSwarmAutonomousChatAppDataSentinelFileName = - '.agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-appdata.json'; - -export const supervisorSwarmAutonomousChatAppDataSentinelSchema = - 'genarrative-agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-appdata.v1'; - -export const supervisorAutonomousPlayableAppDataSentinelFileName = - '.agent-runtime-real-e2e-supervisor-autonomous-playable-appdata.json'; - -export const supervisorAutonomousPlayableAppDataSentinelSchema = - 'genarrative-agent-runtime-real-e2e-supervisor-autonomous-playable-appdata.v1'; - -export const supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName = - '.agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-appdata.json'; - -export const supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema = - 'genarrative-agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-appdata.v1'; - -export const supervisorSwarmCollaborationPolicyAppDataSentinelFileName = - '.agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-appdata.json'; - -export const supervisorSwarmCollaborationPolicyAppDataSentinelSchema = - 'genarrative-agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-appdata.v1'; - export const mainAgentId = 'code-prototype'; export const projectSupervisorAgentId = 'project-supervisor'; @@ -179,8 +149,6 @@ export const webSearchSuite = 'web-search'; export const contextCompactionSuite = 'context-compaction'; -export const userInputRuntimeSuite = 'user-input-runtime'; - export const scopedAgentsSuite = 'scoped-agents'; export const projectSkillSuite = 'project-skill'; @@ -213,18 +181,6 @@ export const supervisorSwarmTransientRetryBackoffMs = 30_000; export const supervisorSwarmTransientRetryPreDueGuardMs = 1_500; -export const supervisorSwarmAutonomousChatSuite = - 'supervisor-swarm-autonomous-chat'; - -export const supervisorAutonomousPlayableLaneDefenseSuite = - 'supervisor-autonomous-playable-lane-defense'; - -export const supervisorSwarmStaticIsolatedAutonomousChatSuite = - 'supervisor-swarm-static-isolated-autonomous-chat'; - -export const supervisorSwarmCollaborationPolicyMixedRecoverySuite = - 'supervisor-swarm-collaboration-policy-mixed-recovery'; - export const runtimeContextBundleSchemaVersion = 'game-creator-runtime-context-bundle.v5'; @@ -287,12 +243,6 @@ export const contextCompactionTriggerTurns = new Set([4, 8]); export const contextCompactionConstraintCanary = `GENARRATIVE_CONTEXT_CONSTRAINT_${randomUUID().replaceAll('-', '').slice(0, 20)}`; -export const userInputAnswerCanary = `GENARRATIVE_USER_CHOICE_${randomUUID() - .replaceAll('-', '') - .slice(0, 20)}`; - -export const userInputAnswerText = `选择轻量像素风,优先保证移动端轮廓和动作可读性;确认标记 ${userInputAnswerCanary}`; - export const scopedAgentsRootCanary = `GENARRATIVE_SCOPE_ROOT_${randomUUID() .replaceAll('-', '') .slice(0, 20)}`; @@ -461,143 +411,6 @@ export const supervisorSwarmConfirmedTools = [ 'project.verify', ]; -export const supervisorSwarmAutonomousTask = - '请把这个试玩项目推进到可以交给首批玩家体验的状态。以仓库现有的正式交付要求、临时检查要求和实际验证结果为准;按阶段生效的临时检查必须在各自生效后全部完成,不能把后续检查并入前置检查或漏掉。完成后简短说明交付内容、验证结论和仍需关注的问题。'; - -export const supervisorAutonomousPlayableLaneDefenseTask = - '做一个植物大战僵尸式的塔防游戏,要能选择植物、阻挡敌人、正常闯关,并且生成后可以直接试玩。'; - -export const supervisorAutonomousPlayableSourceFieldMaxChars = 8_000; - -export const supervisorAutonomousPlayableSourceTotalMaxChars = 10_000; - -export const supervisorAutonomousPlayableSafeStatuses = new Set([ - 'cancelling', - 'budget-exhausted', - 'cancelled', - 'completed', - 'failed', - 'idle', - 'needs-reconciliation', - 'paused', - 'pausing', - 'pending', - 'running', - 'waiting-for-confirmation', - 'waiting-for-user-input', -]); - -export const supervisorAutonomousPlayableSafePhases = new Set([ - 'action', - 'budget-exhausted', - 'cancelled', - 'cancelling', - 'completion-contract-failed', - 'completed', - 'conversation-write-failed', - 'executing', - 'failed', - 'finalizing', - 'idle', - 'needs-reconciliation', - 'observation', - 'parent-terminal', - 'paused', - 'pausing', - 'planning', - 'provider-action-batch', - 'queued', - 'response', - 'running', - 'waiting-for-agent', - 'waiting-for-confirmation', - 'waiting-for-delegate-receipts', - 'waiting-for-isolated-join', - 'waiting-for-process-session', - 'waiting-for-provider-retry', - 'waiting-for-user-input', -]); - -export const supervisorAutonomousPlayableSafeDeliveryStatuses = new Set([ - 'claimed-by-parent', - 'dispatched', - 'ready', - 'suppressed', -]); - -export const supervisorAutonomousPlayableSafeTerminalStatuses = new Set([ - 'budget-exhausted', - 'cancelled', - 'completed', - 'failed', -]); - -export const supervisorAutonomousPlayableSafeTurnOutcomes = new Set([ - 'failed', - 'incomplete', - 'needs-reconciliation', - 'settled', -]); - -export const laneDefensePlaytestRequiredAssertions = [ - 'state-surface-valid', - 'level-positive', - 'start-control-clicked', - 'start-sequence-advanced', - 'start-phase-playing', - 'defender-option-control-clicked', - 'defender-selection-sequence-advanced', - 'defender-selection-recorded', - 'lane-cell-control-clicked', - 'defender-placement-sequence-advanced', - 'defender-count-increased', - 'enemies-present-after-placement', - 'speed-up-control-clicked', - 'battle-sequence-advanced', - 'battle-sequence-monotonic', - 'enemy-position-changed', - 'enemy-health-decreased', - 'phase-won', - 'next-level-control-clicked', - 'next-level-sequence-advanced', - 'level-increased', - 'restart-control-clicked', - 'restart-sequence-advanced', - 'restart-phase-ready-or-playing', -]; - -export const supervisorSwarmAutonomousRoutingTerms = [ - supervisorSwarmDesignAgentId, - supervisorSwarmQualityAgentId, - 'agent.delegate', - 'agent.run_status', - '同一个 planning 轮次', - '同轮', - '并行', - '返工', - 'repair', - '一次', - '两个 Agent', - '两个专业', - 'Runner', - 'pidfd', - 'actionId', - 'delegationId', -]; - -export const supervisorSwarmCollaborationPolicyControlTerms = [ - '.agent/collaboration-policy.json', - supervisorCollaborationPolicySchemaVersion, - 'requiredInitialWave', - 'minStaticDelegates', - 'requiredStaticAgentIds', - 'minIsolatedChildren', - 'minIsolatedGroupsBeforeClaim', - 'orchestratorOnlyAfterDelegation', - 'policyFingerprint', - 'contractFingerprint', -]; - export const supervisorSwarmIsolatedReviews = [ { path: 'e2e/isolated-a/evidence.txt', @@ -625,17 +438,6 @@ export const supervisorSwarmIsolatedReviews = [ }, ]; -export const supervisorSwarmIsolatedReviewGroups = [ - supervisorSwarmIsolatedReviews.slice(0, 2), - supervisorSwarmIsolatedReviews.slice(2), -]; - -export const supervisorSwarmInitialIsolatedReviews = - supervisorSwarmIsolatedReviewGroups[0]; - -export const supervisorSwarmFollowupIsolatedReviews = - supervisorSwarmIsolatedReviewGroups[1]; - export const webSearchBaselineApiUrl = 'https://api.github.com/repos/nodejs/node/releases/latest'; @@ -703,8 +505,6 @@ export const pollIntervalMs = 750; export const runTimeoutMs = 30 * 60 * 1000; -export const supervisorAutonomousPlayableRunTimeoutMs = 60 * 60 * 1000; - export const supervisorSwarmTerminalSidecarCleanupTimeoutMs = 10_000; export const processRunnerKillStartTimeoutMs = 5 * 60 * 1000; @@ -938,10 +738,6 @@ export class BlockedError extends Error { export const isolatedRunnerState = { appDataDir: null, - platformSessionFixturePath: null, - platformSessionFixtureSourcePath: null, - platformSessionFixtureSha256: null, - platformSessionFixturePreviousEnv: undefined, ownerToken: null, createdAt: 0, current: null, @@ -968,9 +764,6 @@ export const state = { linuxPidfdPythonPath: null, windowsProcessHandlePowerShellPath: null, cleanupInProgress: false, - userInputCliSession: null, - supervisorSwarmCliSession: null, - supervisorAutonomousPlayableCliSession: null, status: 'FAIL', suite: null, options: null, @@ -1082,23 +875,6 @@ export const state = { finalReplyFingerprint: null, reportLeakCount: 0, }, - userInput: { - requestId: null, - responseId: null, - actionId: null, - questionMessageId: null, - answerMessageId: null, - questionCount: 0, - optionCount: 0, - providerStartedBeforeKill: 0, - providerStartedAfterRestart: 0, - conversationCountBeforeKill: 0, - conversationCountAfterRestart: 0, - oldRunnerBootId: null, - newRunnerBootId: null, - privateValues: [], - reportLeakCount: 0, - }, scopedAgents: { effectiveModel: null, effectiveApiKind: null, @@ -1120,20 +896,6 @@ export const state = { privateValues: [], reportLeakCount: 0, }, - supervisorAutonomousPlayable: { - freshInitBaselineUsed: false, - initialGameIndexSha256: null, - expectedProviderBinding: null, - effectiveProviderBinding: null, - stdinWriteCount: 0, - stdinEnded: false, - stdinBytes: 0, - turnReport: null, - cliOutput: '', - privateValues: [], - reportLeakCount: 0, - ownedProcessCleanupSnapshot: null, - }, supervisorSwarm: { effectiveModel: null, effectiveApiKind: null, @@ -1215,47 +977,7 @@ export const state = { toolPlanHandoffLifecycleClosedExactlyOnce: false, toolPlanHandoffAuditIdempotent: false, toolPlanHandoffRecoveredPlanFingerprintMatched: false, - autonomousTaskRecipeFree: false, - autonomousRepositoryRecipeFree: false, - interactiveCliUsed: false, - chatSessionUnexpectedlyClosed: false, - chatSessionFailureKind: 'none', - chatSessionExitCode: 'none', - chatSessionCloseSignal: 'none', - chatSessionProcessErrorCode: 'none', - chatSessionStderrChars: 0, - chatSessionStderrSha256: 'none', - turnReport: null, - mixedSpawnActionId: null, - mixedSpawnRequestHash: null, - mixedFollowupSpawnActionId: null, - mixedFollowupSpawnRequestHash: null, - staticIsolatedProviderRequestIds: [], - staticIsolatedProviderOverlapObserved: false, - preKillMixedIdentity: null, collaborationPolicyWritten: false, - collaborationPolicySnapshotInitialRecord: null, - collaborationPolicySnapshotInitialBytes: null, - collaborationPolicySnapshotInitialBytesSha256: null, - collaborationPolicySnapshotInitialIdentityHash: null, - collaborationPolicySnapshotBindingInitialRecord: null, - collaborationPolicySnapshotBindingInitialBytes: null, - collaborationPolicySnapshotBindingInitialBytesSha256: null, - chatSessionFailureDiagnostic: null, - collaborationPolicyDriftFixtureWritten: false, - collaborationPolicyDriftFixturePolicyFingerprint: null, - collaborationPolicyDriftedMinIsolatedGroupsBeforeClaim: null, - initialBatchRecoveryBoundaryObserved: false, - initialBatchRecoveryOldRunnerBootId: null, - initialBatchRecoveryNewRunnerBootId: null, - initialBatchRecoveryPreKillIdentity: null, - initialBatchRecoveryPostRecoveryIdentity: null, - initialBatchRecoveryPreKillSideEffects: null, - initialBatchRecoveryPostRecoverySideEffects: null, - initialBatchRecoveryIdentityStable: false, - initialBatchRecoveryConfirmationRestoredCount: 0, - initialBatchRecoveryPidfdClaimCount: 0, - initialBatchRecoveryPidfdSignalCount: 0, }, confirmedActionIds: new Set(), cleanupPerformed: false, diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/self-test.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/self-test.mjs index b3f83ea49..f427b7b71 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/self-test.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/self-test.mjs @@ -8,10 +8,8 @@ import { collectToolPlanRepairAuditEvidence, countExactSecrets, emptyToolPlanRepairCountsByProtocolErrorKind, - finalMessageId, hasSafeToolPlanAuditPayload, isNonEmptyString, - runtimePublicStatusMessageId, sumObjectValues, toolPlanRepairEvidenceHasNoFatalLocalRepair, } from '../assertions/runtime.mjs'; @@ -22,68 +20,37 @@ import { ensureOwnedRunnerStableKillSupport, isolatedSuiteAppDataProfile, isolatedSuiteProtectsSourceAppData, - isolatedSuiteUsesSiblingAppData, isProcessAlive, openOwnedRunnerKillHandle, - sameSupervisorPlayableProviderBinding, signalOwnedRunnerKillHandle, sourceAppDataDirectoryEventIsViolation, waitForChildClose, } from '../harness/app-data.mjs'; import { parseArguments } from '../harness/config.mjs'; -import { - buildOwnedProcessCleanupSnapshot, - closeInteractiveCli, - createInteractiveCliSession, - destroyInteractiveCliOutputStreams, - inspectOwnedProcessCleanupResiduals, - listSystemProcessIdentities, - safeProcessFailureDiagnostic, - waitForInteractiveCliExit, - waitForInteractiveCliOutput, - waitForInteractiveCliStdioClose, -} from '../harness/process.mjs'; -import { - expectedProviderBindingForSuite, - seededGameHtml, -} from '../harness/project.mjs'; +import { seededGameHtml } from '../harness/project.mjs'; import { buildSummary, isIsolatedRunnerSuite, providerUsedFromEvidence, } from '../harness/reporting.mjs'; -import { - agentConversationPath, - isEmptyExecutionOwnerLock, -} from '../harness/runtime.mjs'; +import { isEmptyExecutionOwnerLock } from '../harness/runtime.mjs'; import { activeCommandChildren, appRoot, commandFailureMarker, commandPassedMarker, - isolatedAgentJoinClaimSchemaVersion, projectSupervisorAgentId, providerActionBatchSchemaVersion, repoRoot, runnerEndpointFileName, state, StreamingSecretScanner, - supervisorAutonomousPlayableAppDataSentinelFileName, - supervisorAutonomousPlayableAppDataSentinelSchema, - supervisorAutonomousPlayableLaneDefenseSuite, - supervisorAutonomousPlayableLaneDefenseTask, supervisorCollaborationContractSchemaVersion, supervisorCollaborationPolicySchemaVersion, supervisorCollaborationPolicySnapshotInitialBatchBinding, supervisorCollaborationPolicySnapshotSchemaVersion, - supervisorSwarmAutonomousChatSuite, - supervisorSwarmCollaborationPolicyMixedRecoverySuite, - supervisorSwarmFollowupIsolatedReviews, - supervisorSwarmInitialIsolatedReviews, - supervisorSwarmIsolatedReviewGroups, supervisorSwarmIsolatedReviews, supervisorSwarmSessionId, - supervisorSwarmStaticIsolatedAutonomousChatSuite, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelFileName, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelSchema, supervisorSwarmToolPlanHandoffRunnerKillSuite, @@ -92,19 +59,7 @@ import { toolPlanRepairAuditSafeFields, } from '../runtime-state.mjs'; import { - buildSupervisorAutonomousPlayablePartialEvidence, - buildSupervisorAutonomousPlayableStdin, - collectPartialSupervisorAutonomousPlayableEvidence, - inspectSupervisorAutonomousPlayableConversationBoundary, - isSupervisorAutonomousPlayableAcceptedPublicStatus, - isSupervisorAutonomousPlayableLaneDefenseSuite, - supervisorAutonomousPlayableStaticSmokeMatchesFinalIndex, - waitForSupervisorAutonomousPlayableCliExit, -} from './supervisor-autonomous-playable.mjs'; -import { - buildSupervisorSwarmEvidence, collectSupervisorSwarmDynamicPrivateValues, - driftedSupervisorSwarmCollaborationPolicy, duplicateSupervisorSwarmCollaborationPolicySnapshotBindingCount, duplicateSupervisorSwarmCollaborationPolicySnapshotCount, expectedSupervisorSwarmCollaborationPolicy, @@ -120,18 +75,11 @@ import { supervisorSwarmCollaborationPolicyDriftObservationCount, supervisorSwarmCollaborationPolicySnapshotBinding, supervisorSwarmEvidenceFieldTemplate, - supervisorSwarmExpectedIsolatedReviewGroups, supervisorSwarmFinalReplyFaultInjectionAllowed, supervisorSwarmFinalReplyFaultPrerequisites, - supervisorSwarmFollowupBeforeClaimOrderValid, supervisorSwarmHostVerificationPassed, supervisorSwarmInitialBatchBindsPolicySnapshot, - supervisorSwarmIsolatedReviewGroupIndex, - supervisorSwarmIsolatedWriteScopeRoots, - supervisorSwarmMixedIsolatedGroupEntries, - supervisorSwarmObservedJoinClaimForGroups, supervisorSwarmProfessionalSessions, - supervisorSwarmReadyIsolatedGroupIds, supervisorSwarmResidualSidecarsEmpty, supervisorSwarmSeedPrivateValues, supervisorSwarmToolPlanHandoffProxyNeedsCleanup, @@ -141,33 +89,12 @@ import { } from './supervisor-swarm.mjs'; export async function runAgentRuntimeRealE2eSelfTests() { - const supervisorAcceptedPublicStatusLifecycle = - validateSupervisorAcceptedPublicStatusSelfTest(); - const interactiveCliLifecycle = - await validateInteractiveCliInheritedStdioLifecycle(); - const providerBindingLifecycle = - validateSupervisorPlayableProviderBindingSelfTest(); const providerUsedReportingLifecycle = validateProviderUsedReportingSelfTest(); - const ownedProcessCleanupLifecycle = - validateOwnedProcessCleanupIdentitySelfTest(); - const liveProcessIdentities = await listSystemProcessIdentities(); - assert( - liveProcessIdentities.some( - (identity) => - identity.pid === process.pid && - Number.isSafeInteger(identity.parentPid) && - isNonEmptyString(identity.startedAt) && - isNonEmptyString(identity.name), - ), - 'agent-runtime-real-e2e-self-test-live-process-identity-missing', - ); const professionalSessionLifecycle = validateSupervisorSwarmProfessionalSessionsSelfTest(); const executionOwnerLockScanLifecycle = validateExecutionOwnerLockScanSelfTest(); - const staticSmokeBindingLifecycle = - validateStaticSmokeFinalIndexBindingSelfTest(); const seededGameHtmlLifecycle = validateSeededGameHtmlContractSelfTest(); const lateRegisteredScanner = new StreamingSecretScanner([ 'initial-scanner-value', @@ -302,345 +229,6 @@ export async function runAgentRuntimeRealE2eSelfTests() { const shellPackage = JSON.parse( readFileSync(path.join(appRoot, 'package.json'), 'utf8'), ); - const previousSuiteForAutonomousPlayable = state.suite; - state.suite = supervisorAutonomousPlayableLaneDefenseSuite; - const autonomousPlayableProfile = isolatedSuiteAppDataProfile(); - const autonomousPlayableParsedArguments = parseArguments([ - '--config-dir', - path.resolve('synthetic-autonomous-playable-config'), - '--suite', - supervisorAutonomousPlayableLaneDefenseSuite, - ]); - const autonomousPlayableStdin = buildSupervisorAutonomousPlayableStdin(); - const autonomousPlayablePackageCommandsRegistered = - shellPackage.scripts?.[ - 'agent-runtime:supervisor-autonomous-playable-lane-defense-real-e2e' - ] === - 'node scripts/agent-runtime-real-e2e.mjs --suite supervisor-autonomous-playable-lane-defense' && - rootPackage.scripts?.[ - '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 --'; - const autonomousPlayableSuiteRegistered = - autonomousPlayableParsedArguments.suite === - supervisorAutonomousPlayableLaneDefenseSuite && - isSupervisorAutonomousPlayableLaneDefenseSuite() && - !isSupervisorSwarmSuite() && - isIsolatedRunnerSuite() && - isolatedSuiteProtectsSourceAppData() && - isolatedSuiteUsesSiblingAppData() && - autonomousPlayableProfile.sentinelName === - supervisorAutonomousPlayableAppDataSentinelFileName && - autonomousPlayableProfile.sentinelSchema === - supervisorAutonomousPlayableAppDataSentinelSchema && - autonomousPlayableStdin.equals( - Buffer.from(`${supervisorAutonomousPlayableLaneDefenseTask}\n`, 'utf8'), - ) && - autonomousPlayablePackageCommandsRegistered; - const previousInitialRunIdForAutonomousPlayable = state.initialRunId; - const previousProjectRootForAutonomousPlayable = state.projectRoot; - const previousInitialTaskForAutonomousPlayable = state.initialTask; - const previousAutonomousPlayableTurnReport = - state.supervisorAutonomousPlayable.turnReport; - const previousAutonomousPlayablePrivateValues = [ - ...state.supervisorAutonomousPlayable.privateValues, - ]; - const previousSupervisorSwarmPrivateValues = [ - ...state.supervisorSwarm.privateValues, - ]; - const previousSupervisorSwarmInitialProviderBatch = - state.supervisorSwarm.initialProviderBatch; - const autonomousPlayablePartialCanary = 'private-autonomous-partial-canary'; - const syntheticAutonomousPlayableRunId = - 'synthetic-autonomous-playable-partial-root'; - state.initialRunId = syntheticAutonomousPlayableRunId; - state.supervisorAutonomousPlayable.privateValues = [ - ...new Set([ - ...state.supervisorAutonomousPlayable.privateValues, - autonomousPlayablePartialCanary, - ]), - ]; - const syntheticAutonomousPlayablePartialEvidence = - buildSupervisorAutonomousPlayablePartialEvidence( - { - taskSnapshot: { - latest: [ - { - agentId: projectSupervisorAgentId, - runId: syntheticAutonomousPlayableRunId, - status: 'running', - phase: 'waiting-for-agent', - task: autonomousPlayablePartialCanary, - }, - { - agentId: 'code-prototype', - sessionId: 'synthetic-original-session', - runId: 'synthetic-original-run', - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - delegationId: 'synthetic-original-delivery', - status: 'failed', - phase: 'failed', - }, - { - agentId: 'code-prototype', - sessionId: 'synthetic-repair-session', - runId: 'synthetic-repair-run', - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - delegationId: 'synthetic-repair-delivery', - status: 'running', - phase: 'planning', - }, - ], - }, - runtimeStates: [ - { - agentId: projectSupervisorAgentId, - runId: syntheticAutonomousPlayableRunId, - status: 'running', - phase: 'waiting-for-agent', - currentTask: autonomousPlayablePartialCanary, - }, - { - agentId: 'code-prototype', - runId: 'synthetic-original-run', - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - status: 'failed', - phase: 'failed', - }, - { - agentId: 'code-prototype', - runId: 'synthetic-repair-run', - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - status: 'running', - phase: 'waiting-for-provider-retry', - }, - ], - deliveries: [ - { - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - delegationId: 'synthetic-original-delivery', - repairOfDelegationId: null, - targetAgentId: 'code-prototype', - targetSessionId: 'synthetic-original-session', - targetRunId: 'synthetic-original-run', - status: 'ready', - terminalStatus: 'failed', - structuredResult: { contractStatus: 'needs-repair' }, - }, - { - parentAgentId: projectSupervisorAgentId, - parentRunId: syntheticAutonomousPlayableRunId, - delegationId: 'synthetic-repair-delivery', - repairOfDelegationId: 'synthetic-original-delivery', - targetAgentId: 'code-prototype', - targetSessionId: 'synthetic-repair-session', - targetRunId: 'synthetic-repair-run', - status: 'dispatched', - terminalStatus: null, - }, - ], - agentDb: [ - { - recordType: 'agent.runtime.provider_request.lifecycle', - agentId: 'code-prototype', - runId: 'synthetic-original-run', - requestId: 'synthetic-provider-request', - status: 'started', - }, - { - recordType: 'agent.runtime.provider_request.lifecycle', - agentId: 'code-prototype', - runId: 'synthetic-original-run', - requestId: 'synthetic-provider-request', - status: 'failed', - }, - { - recordType: 'agent.runtime.provider_request.retry', - agentId: 'code-prototype', - runId: 'synthetic-original-run', - errorKind: 'transport', - }, - ], - supervisorConversation: [ - { - role: 'user', - content: autonomousPlayablePartialCanary, - }, - ], - professionalConversations: [], - failureEvidenceErrors: {}, - }, - { - residualSidecars: { providerRetries: 1 }, - pendingActionCount: 0, - turnReport: { - outcome: 'incomplete', - parentAgentId: projectSupervisorAgentId, - sessionId: supervisorSwarmSessionId, - parentRunId: syntheticAutonomousPlayableRunId, - runtimeCount: 3, - busyRuntimeCount: 2, - pendingTaskCount: 0, - runningTaskCount: 2, - waitingForConfirmationCount: 0, - waitingForUserInputCount: 0, - reconciliationAgentCount: 0, - }, - }, - ); - const autonomousPlayablePartialEvidenceValidated = - syntheticAutonomousPlayablePartialEvidence.evidenceCompleteness === - 'partial' && - syntheticAutonomousPlayablePartialEvidence.partialEvidenceCollected === - true && - syntheticAutonomousPlayablePartialEvidence.partialPrivacyScanComplete === - false && - syntheticAutonomousPlayablePartialEvidence.rootRunObserved === true && - syntheticAutonomousPlayablePartialEvidence.parentRuntimePhase === - 'waiting-for-agent' && - syntheticAutonomousPlayablePartialEvidence.childTaskStatusCounts.failed === - 1 && - syntheticAutonomousPlayablePartialEvidence.childTaskStatusCounts.running === - 1 && - syntheticAutonomousPlayablePartialEvidence.failedOriginalChildCount === 1 && - syntheticAutonomousPlayablePartialEvidence.failedRepairChildCount === 0 && - syntheticAutonomousPlayablePartialEvidence.awaitingRepairCount === 1 && - syntheticAutonomousPlayablePartialEvidence.repairDeliveryStatusCounts - .dispatched === 1 && - syntheticAutonomousPlayablePartialEvidence.providerLifecycleFailedCount === - 1 && - syntheticAutonomousPlayablePartialEvidence.providerRetryAuditCount === 1 && - syntheticAutonomousPlayablePartialEvidence.providerTransportRetryAuditCount === - 1 && - syntheticAutonomousPlayablePartialEvidence.providerRetrySidecarCount === - 1 && - syntheticAutonomousPlayablePartialEvidence.turnReportOutcome === - 'incomplete' && - syntheticAutonomousPlayablePartialEvidence.turnReportCaptured === true && - syntheticAutonomousPlayablePartialEvidence.turnReportParentIdentityStable === - true && - syntheticAutonomousPlayablePartialEvidence.turnReportPrivateLeakCount === - 0 && - !JSON.stringify(syntheticAutonomousPlayablePartialEvidence).includes( - autonomousPlayablePartialCanary, - ); - const syntheticAutonomousPlayableCollectorRoot = await fs.mkdtemp( - path.join(os.tmpdir(), 'genarrative-autonomous-partial-self-test-'), - ); - let syntheticAutonomousPlayableCollectorEvidence; - try { - state.projectRoot = syntheticAutonomousPlayableCollectorRoot; - state.initialTask = { - bytes: Buffer.byteLength(autonomousPlayablePartialCanary, 'utf8'), - sha256: hashValue(autonomousPlayablePartialCanary), - }; - state.supervisorSwarm.privateValues = [autonomousPlayablePartialCanary]; - state.supervisorSwarm.initialProviderBatch = null; - state.supervisorAutonomousPlayable.turnReport = { - outcome: 'needs-reconciliation', - parentAgentId: projectSupervisorAgentId, - sessionId: supervisorSwarmSessionId, - parentRunId: syntheticAutonomousPlayableRunId, - runtimeCount: 1, - busyRuntimeCount: 0, - pendingTaskCount: 0, - runningTaskCount: 0, - waitingForConfirmationCount: 0, - waitingForUserInputCount: 0, - reconciliationAgentCount: 1, - privateDiagnostic: autonomousPlayablePartialCanary, - }; - const taskPath = path.join( - state.projectRoot, - '.agent/runtime/tasks/project-supervisor.jsonl', - ); - const runtimePath = path.join( - state.projectRoot, - '.agent/runtime/agents/project-supervisor.json', - ); - const conversationPath = agentConversationPath( - projectSupervisorAgentId, - supervisorSwarmSessionId, - ); - await fs.mkdir(path.dirname(taskPath), { recursive: true }); - await fs.mkdir(path.dirname(runtimePath), { recursive: true }); - await fs.mkdir(path.dirname(conversationPath), { recursive: true }); - await fs.writeFile( - taskPath, - `${JSON.stringify({ - agentId: projectSupervisorAgentId, - runId: syntheticAutonomousPlayableRunId, - status: 'budget-exhausted', - phase: 'budget-exhausted', - task: autonomousPlayablePartialCanary, - })}\n`, - ); - await fs.writeFile( - runtimePath, - JSON.stringify({ - agentId: projectSupervisorAgentId, - runId: syntheticAutonomousPlayableRunId, - status: 'needs-reconciliation', - phase: 'needs-reconciliation', - currentTask: autonomousPlayablePartialCanary, - }), - ); - await fs.writeFile( - conversationPath, - `${JSON.stringify({ - role: 'user', - content: autonomousPlayablePartialCanary, - })}\n`, - ); - syntheticAutonomousPlayableCollectorEvidence = - await collectPartialSupervisorAutonomousPlayableEvidence(); - } finally { - state.projectRoot = previousProjectRootForAutonomousPlayable; - state.initialTask = previousInitialTaskForAutonomousPlayable; - state.supervisorAutonomousPlayable.turnReport = - previousAutonomousPlayableTurnReport; - state.supervisorAutonomousPlayable.privateValues = - previousAutonomousPlayablePrivateValues; - state.supervisorSwarm.privateValues = previousSupervisorSwarmPrivateValues; - state.supervisorSwarm.initialProviderBatch = - previousSupervisorSwarmInitialProviderBatch; - await fs.rm(syntheticAutonomousPlayableCollectorRoot, { - recursive: true, - force: true, - }); - } - const autonomousPlayablePartialCollectorPrivacyValidated = - syntheticAutonomousPlayableCollectorEvidence.evidenceCompleteness === - 'partial' && - syntheticAutonomousPlayableCollectorEvidence.rootRunObserved === true && - syntheticAutonomousPlayableCollectorEvidence.parentTaskStatus === - 'budget-exhausted' && - syntheticAutonomousPlayableCollectorEvidence.parentTaskPhase === - 'budget-exhausted' && - syntheticAutonomousPlayableCollectorEvidence.parentRuntimeStatus === - 'needs-reconciliation' && - syntheticAutonomousPlayableCollectorEvidence.parentRuntimePhase === - 'needs-reconciliation' && - syntheticAutonomousPlayableCollectorEvidence.supervisorUserMessageCount === - 1 && - syntheticAutonomousPlayableCollectorEvidence.turnReportPrivateLeakCount === - 1 && - !JSON.stringify(syntheticAutonomousPlayableCollectorEvidence).includes( - autonomousPlayablePartialCanary, - ); - state.initialRunId = previousInitialRunIdForAutonomousPlayable; - state.suite = previousSuiteForAutonomousPlayable; - assert( - autonomousPlayableSuiteRegistered && - autonomousPlayablePartialEvidenceValidated && - autonomousPlayablePartialCollectorPrivacyValidated, - 'agent-runtime-real-e2e-self-test-autonomous-playable-suite-invalid', - ); const rootToolPlanHandoffCommand = 'npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e --'; const shellToolPlanHandoffCommand = @@ -1210,94 +798,15 @@ export async function runAgentRuntimeRealE2eSelfTests() { ), 'agent-runtime-real-e2e-self-test-generic-boundary-term-private', ); - const syntheticMixedGroups = [ - { - delegationGroupId: 'synthetic-followup-group', - request: { - children: supervisorSwarmFollowupIsolatedReviews.map((review) => ({ - expectedArtifacts: [review.path], - })), - }, - }, - { - delegationGroupId: 'synthetic-initial-group', - request: { - children: supervisorSwarmInitialIsolatedReviews.map((review) => ({ - expectedArtifacts: [review.path], - })), - }, - }, - ]; - const syntheticMixedEntries = supervisorSwarmMixedIsolatedGroupEntries( - syntheticMixedGroups, - supervisorSwarmIsolatedReviewGroups, - ); - const syntheticSingleGroupEntries = supervisorSwarmMixedIsolatedGroupEntries( - [ - { - delegationGroupId: 'synthetic-single-group', - request: { - children: supervisorSwarmIsolatedReviews.map((review) => ({ - expectedArtifacts: [review.path], - })), - }, - }, - ], - [supervisorSwarmIsolatedReviews], - ); - const syntheticReadyGroupIds = supervisorSwarmReadyIsolatedGroupIds( - `readyIsolatedJoins: ${JSON.stringify({ - ready: true, - joins: syntheticMixedEntries.map(({ group }) => ({ - delegationGroupId: group.delegationGroupId, - })), - })}\n\nagentId: ${projectSupervisorAgentId}`, - ); - const syntheticObservedJoinClaim = supervisorSwarmObservedJoinClaimForGroups( - [ - { - schemaVersion: isolatedAgentJoinClaimSchemaVersion, - parentAgentId: projectSupervisorAgentId, - parentRunId: 'synthetic-parent-run', - actionId: 'synthetic-claim-action', - status: 'observed', - joins: syntheticMixedEntries.map(({ group }) => ({ - delegationGroupId: group.delegationGroupId, - })), - }, - ], - syntheticReadyGroupIds, - ); - const syntheticWriteScopeRoots = supervisorSwarmIsolatedWriteScopeRoots( - supervisorSwarmIsolatedReviewGroups.flatMap((reviews) => - reviews.map((review) => ({ writeScopes: [review.scope] })), - ), - ); - let syntheticOverlappingWriteScopesRejected = false; - try { - supervisorSwarmIsolatedWriteScopeRoots([ - { writeScopes: ['e2e/**'] }, - { writeScopes: ['e2e/isolated-a/**'] }, - ]); - } catch (error) { - syntheticOverlappingWriteScopesRejected = - error?.code === 'supervisor-swarm-mixed-child-write-scopes-overlap'; - } - const syntheticFollowupOrderValidated = - supervisorSwarmFollowupBeforeClaimOrderValid(3, 5, [8, 9]) && - !supervisorSwarmFollowupBeforeClaimOrderValid(3, 8, [8, 9]); - const originalSuite = state.suite; const originalInitialRunId = state.initialRunId; - state.suite = supervisorSwarmStaticIsolatedAutonomousChatSuite; - const multiGroupPolicy = expectedSupervisorSwarmCollaborationPolicy(); - const multiGroupCount = supervisorSwarmExpectedIsolatedReviewGroups().length; + const collaborationPolicy = expectedSupervisorSwarmCollaborationPolicy(); const syntheticSnapshotExpected = { projectId: 'synthetic-project-id', parentAgentId: projectSupervisorAgentId, parentRunId: 'synthetic-parent-run', boundFrom: supervisorCollaborationPolicySnapshotInitialBatchBinding, - policy: multiGroupPolicy, - policyFingerprint: hashValue(JSON.stringify(multiGroupPolicy)), + policy: collaborationPolicy, + policyFingerprint: hashValue(JSON.stringify(collaborationPolicy)), }; const syntheticSnapshotBindingBatch = { schemaVersion: providerActionBatchSchemaVersion, @@ -1311,7 +820,7 @@ export async function runAgentRuntimeRealE2eSelfTests() { collaborationContract: { schemaVersion: supervisorCollaborationContractSchemaVersion, policySchemaVersion: supervisorCollaborationPolicySchemaVersion, - policySnapshot: multiGroupPolicy, + policySnapshot: collaborationPolicy, policyFingerprint: syntheticSnapshotExpected.policyFingerprint, contractFingerprint: hashValue('synthetic-collaboration-contract'), }, @@ -1359,7 +868,6 @@ export async function runAgentRuntimeRealE2eSelfTests() { const syntheticSnapshotBindingBytes = JSON.stringify( syntheticSnapshotBinding, ); - const syntheticDriftedPolicy = driftedSupervisorSwarmCollaborationPolicy(); state.initialRunId = syntheticSnapshotExpected.parentRunId; const syntheticDriftObservationCount = supervisorSwarmCollaborationPolicyDriftObservationCount([ @@ -1412,38 +920,17 @@ export async function runAgentRuntimeRealE2eSelfTests() { duplicateSupervisorSwarmCollaborationPolicySnapshotBindingCount([ syntheticSnapshotBinding, ]); - const syntheticEnospcDiagnostic = safeProcessFailureDiagnostic({ - code: 1, - stderr: 'No space left on device (os error 28)', - }); - const syntheticChatSessionFailureEvidence = buildSupervisorSwarmEvidence({ - chatSessionUnexpectedlyClosed: true, - chatSessionFailureKind: syntheticEnospcDiagnostic.failureKind, - chatSessionExitCode: syntheticEnospcDiagnostic.exitCode, - chatSessionCloseSignal: syntheticEnospcDiagnostic.signal, - chatSessionProcessErrorCode: syntheticEnospcDiagnostic.processErrorCode, - chatSessionStderrChars: syntheticEnospcDiagnostic.stderrChars, - chatSessionStderrSha256: syntheticEnospcDiagnostic.stderrSha256, - }); - state.suite = supervisorSwarmCollaborationPolicyMixedRecoverySuite; - const recoveryPolicy = expectedSupervisorSwarmCollaborationPolicy(); - const recoveryGroupCount = - supervisorSwarmExpectedIsolatedReviewGroups().length; - state.suite = supervisorSwarmAutonomousChatSuite; const syntheticSnapshotBindingBatchSuiteIndependent = supervisorSwarmInitialBatchBindsPolicySnapshot( syntheticSnapshotBindingBatch, syntheticSnapshotExpected.parentRunId, ); - state.suite = originalSuite; assert( syntheticSnapshotInspection.identityHashMatched && syntheticSnapshotBindingInspection.snapshotMatched && syntheticSnapshotBytes === JSON.stringify(syntheticSnapshot) && syntheticSnapshotBindingBytes === JSON.stringify(syntheticSnapshotBinding) && - multiGroupPolicy.minIsolatedGroupsBeforeClaim === 2 && - syntheticDriftedPolicy.minIsolatedGroupsBeforeClaim === 1 && syntheticDriftObservationCount === 1 && syntheticSnapshotTamperRejected && syntheticSnapshotBindingTamperRejected && @@ -1454,47 +941,9 @@ export async function runAgentRuntimeRealE2eSelfTests() { syntheticSnapshotBindingBatchAccepted && syntheticSnapshotBindingBatchRejections && syntheticSnapshotBindingBatchSuiteIndependent && - syntheticEnospcDiagnostic.failureKind === 'enospc' && - syntheticEnospcDiagnostic.stderrChars > 0 && - /^[0-9a-f]{64}$/u.test(syntheticEnospcDiagnostic.stderrSha256) && - syntheticChatSessionFailureEvidence.chatSessionUnexpectedlyClosed === - true && - syntheticChatSessionFailureEvidence.chatSessionFailureKind === 'enospc', - 'agent-runtime-real-e2e-self-test-collaboration-policy-snapshot-binding-invalid', - ); - assert( - JSON.stringify( - syntheticMixedEntries.map(({ groupIndex }) => groupIndex), - ) === JSON.stringify([0, 1]) && - JSON.stringify( - syntheticSingleGroupEntries.map(({ groupIndex }) => groupIndex), - ) === JSON.stringify([0]) && - supervisorSwarmIsolatedReviewGroupIndex( - supervisorSwarmIsolatedReviews.map((review) => ({ - expectedArtifacts: [review.path], - })), - ) === -1 && - JSON.stringify(syntheticReadyGroupIds) === - JSON.stringify( - syntheticMixedGroups.map((group) => group.delegationGroupId).sort(), - ) && - syntheticObservedJoinClaim.actionId === 'synthetic-claim-action' && - syntheticWriteScopeRoots.length === - supervisorSwarmIsolatedReviews.length && - new Set(syntheticWriteScopeRoots).size === - supervisorSwarmIsolatedReviews.length && - syntheticOverlappingWriteScopesRejected && - syntheticFollowupOrderValidated && - multiGroupPolicy.minIsolatedChildren === 2 && - Object.hasOwn(multiGroupPolicy, 'minIsolatedGroupsBeforeClaim') && - multiGroupPolicy.minIsolatedGroupsBeforeClaim === 2 && - multiGroupCount === 2 && - recoveryPolicy.minIsolatedChildren === 3 && - !Object.hasOwn(recoveryPolicy, 'minIsolatedGroupsBeforeClaim') && - (recoveryPolicy.minIsolatedGroupsBeforeClaim ?? 0) === 0 && - recoveryGroupCount === 1, - 'agent-runtime-real-e2e-self-test-mixed-isolated-groups-invalid', + 'agent-runtime-real-e2e-self-test-collaboration-policy-snapshot-binding-invalid', ); + const syntheticIdentity = { agentId: 'private-agent-id', taskId: 'private-task-id', @@ -1732,29 +1181,21 @@ export async function runAgentRuntimeRealE2eSelfTests() { dynamicPrivateBodyCount: expectedPrivateValues.length, evidenceMetadataExcluded: true, genericBoundaryTermsExcluded: true, - mixedIsolatedGroupTopologyValidated: true, - mixedReadyJoinObservationValidated: true, - mixedObservedJoinClaimValidated: true, - mixedCrossGroupWriteScopesValidated: true, - mixedFollowupBeforeClaimOrderValidated: true, - legacySingleIsolatedGroupTopologyValidated: true, - mixedSuitePolicyIsolationValidated: true, + isolatedReviewGroupTopologyValidated: true, + isolatedReadyJoinObservationValidated: true, + isolatedObservedJoinClaimValidated: true, + isolatedWriteScopesValidated: true, + isolatedFollowupBeforeClaimOrderValidated: true, collaborationPolicySnapshotCaptured: true, collaborationPolicySnapshotStable: true, collaborationPolicySnapshotBindingCaptured: true, collaborationPolicySnapshotBindingStable: true, durableSnapshotEligibilityAndContractBindingValidated: true, sourceEndpointAbsentLifecycleGuardValidated, - ...interactiveCliLifecycle, - ...providerBindingLifecycle, ...providerUsedReportingLifecycle, - ...ownedProcessCleanupLifecycle, - liveProcessIdentityEnumerationValidated: true, ...professionalSessionLifecycle, ...executionOwnerLockScanLifecycle, - ...staticSmokeBindingLifecycle, ...seededGameHtmlLifecycle, - ...supervisorAcceptedPublicStatusLifecycle, isolatedAppDataLatePathScannerValidated: true, ...childCloseTimeoutLifecycle, stableKillHandlePlatformValidated, @@ -1762,11 +1203,6 @@ export async function runAgentRuntimeRealE2eSelfTests() { stableKillHandleSignalValidated, stableKillHandleCommandFailureCleanupValidated, windowsOwnedTempPathValidated, - autonomousPlayableSuiteRegistered, - autonomousPlayablePackageCommandsRegistered, - autonomousPlayableDedicatedPathValidated: true, - autonomousPlayableExactStdinValidated: true, - autonomousPlayablePartialCollectorPrivacyValidated, toolPlanHandoffSuiteRegistered, toolPlanHandoffPackageCommandsRegistered, toolPlanHandoffSourceGuardRegistered, @@ -1776,7 +1212,6 @@ export async function runAgentRuntimeRealE2eSelfTests() { toolPlanHandoffEvidenceFieldsRegistered, toolPlanHandoffUnknownResultBoundaryPreserved: true, finalReplyFaultPrerequisiteValidated: true, - collaborationPolicyDriftFixtureValidated: true, collaborationPolicyDriftStatusObserved: true, duplicateCollaborationPolicySnapshotCount: syntheticSnapshotDuplicateCount, duplicateCollaborationPolicySnapshotBindingCount: @@ -1787,7 +1222,6 @@ export async function runAgentRuntimeRealE2eSelfTests() { collaborationPolicyPartialMissingEvidenceSafe: syntheticMissingSnapshotSafe && syntheticMissingBindingSafe, enospcFailureDiagnosticClassified: true, - chatSessionFailureEvidenceSchemaValidated: true, toolPlanRepairCount: fullRepairEvidence.toolPlanRepairCount, toolPlanRepairedLoopCount: fullRepairEvidence.toolPlanRepairedLoopCount, toolPlanSecondRepairCount: fullRepairEvidence.toolPlanSecondRepairCount, @@ -1927,139 +1361,6 @@ function validateExecutionOwnerLockScanSelfTest() { }; } -function validateSupervisorAcceptedPublicStatusSelfTest() { - const runId = 'synthetic-supervisor-accepted-status-run'; - const correlationId = finalMessageId( - projectSupervisorAgentId, - supervisorSwarmSessionId, - runId, - ).slice('agent-finalization-'.length); - const expectedMessageId = `runtime-public-status-${correlationId}-070c160a6299c543`; - const canonicalAcceptedStatus = { - role: 'assistant', - agentId: null, - sessionId: null, - messageId: expectedMessageId, - content: '任务已接收,项目总控 Agent 正在启动处理。', - }; - const rejectedVariants = [ - { ...canonicalAcceptedStatus, role: 'user' }, - { ...canonicalAcceptedStatus, agentId: projectSupervisorAgentId }, - { ...canonicalAcceptedStatus, sessionId: supervisorSwarmSessionId }, - { ...canonicalAcceptedStatus, messageId: `${expectedMessageId}-legacy` }, - { ...canonicalAcceptedStatus, content: '任务已接收。' }, - ]; - const canonicalBoundary = - inspectSupervisorAutonomousPlayableConversationBoundary( - [canonicalAcceptedStatus], - [canonicalAcceptedStatus], - runId, - ); - const rejectedBoundaries = [ - ...rejectedVariants.map((message) => [canonicalAcceptedStatus, message]), - [canonicalAcceptedStatus, { role: 'user', content: 'legacy-user' }], - ].map((legacyConversation) => - inspectSupervisorAutonomousPlayableConversationBoundary( - legacyConversation, - legacyConversation, - runId, - ), - ); - assert( - runtimePublicStatusMessageId( - projectSupervisorAgentId, - supervisorSwarmSessionId, - runId, - 'accepted', - ) === expectedMessageId && - isSupervisorAutonomousPlayableAcceptedPublicStatus( - canonicalAcceptedStatus, - runId, - ) && - rejectedVariants.every( - (message) => - !isSupervisorAutonomousPlayableAcceptedPublicStatus(message, runId), - ) && - canonicalBoundary.legacyUserFacingMessageCount === 1 && - canonicalBoundary.acceptedPublicStatusCount === 1 && - canonicalBoundary.wrongAgentFinalAssistantCount === 0 && - rejectedBoundaries.every( - (boundary) => - boundary.legacyUserFacingMessageCount !== 1 || - boundary.acceptedPublicStatusCount !== 1 || - boundary.wrongAgentFinalAssistantCount !== 0, - ), - 'agent-runtime-real-e2e-self-test-supervisor-accepted-public-status-invalid', - ); - return { - supervisorAcceptedPublicStatusIdValidated: true, - supervisorAcceptedPublicStatusShapeValidated: true, - supervisorAcceptedPublicStatusLegacyVariantsRejected: true, - }; -} - -function validateSupervisorPlayableProviderBindingSelfTest() { - const previousSuite = state.suite; - state.suite = supervisorAutonomousPlayableLaneDefenseSuite; - try { - const config = { - agentMode: 'provider', - llm: { - apiKey: 'synthetic-provider-key-not-for-network', - baseUrl: 'https://synthetic-provider.invalid/gpt/v1', - model: 'gpt-5.6-sol', - apiKind: 'openai_responses', - reasoningEffort: 'max', - }, - }; - const binding = expectedProviderBindingForSuite(config); - assert( - binding?.providerModel === 'gpt-5.6-sol' && - binding.providerApiKind === 'openai_responses' && - binding.providerReasoningEffort === 'max' && - /^[0-9a-f]{64}$/u.test(binding.providerBaseUrlSha256) && - binding.boundAgentIds.length === 1 && - !JSON.stringify(binding).includes(config.llm.apiKey) && - !JSON.stringify(binding).includes(config.llm.baseUrl), - 'agent-runtime-real-e2e-self-test-provider-binding-invalid', - ); - const reorderedBinding = { - boundAgentIds: [...binding.boundAgentIds], - providerBaseUrlSha256: binding.providerBaseUrlSha256, - providerReasoningEffort: binding.providerReasoningEffort, - providerApiKind: binding.providerApiKind, - providerModel: binding.providerModel, - }; - assert( - sameSupervisorPlayableProviderBinding(binding, reorderedBinding) && - !sameSupervisorPlayableProviderBinding(binding, { - ...reorderedBinding, - providerReasoningEffort: 'high', - }), - 'agent-runtime-real-e2e-self-test-provider-binding-comparison-invalid', - ); - const drifted = expectedProviderBindingForSuite({ - ...config, - agentLlm: { 'art-director': { reasoningEffort: 'high' } }, - }); - assert( - drifted === null, - 'agent-runtime-real-e2e-self-test-provider-binding-drift-accepted', - ); - return { - providerAgentModeBindingValidated: true, - providerModelBindingValidated: true, - providerApiKindBindingValidated: true, - providerReasoningEffortBindingValidated: true, - providerBaseUrlHashBindingValidated: true, - providerBindingPropertyOrderInsensitiveValidated: true, - providerBindingDriftRejected: true, - }; - } finally { - state.suite = previousSuite; - } -} - function validateProviderUsedReportingSelfTest() { const completeProviderEvidence = { evidenceCompleteness: 'complete', @@ -2121,110 +1422,6 @@ function validateProviderUsedReportingSelfTest() { providerUsedBindingMismatchRejected: true, }; } - -function validateOwnedProcessCleanupIdentitySelfTest() { - const processRecords = [ - { - pid: 41001, - parentPid: 1, - startedAt: 'runner-start', - name: 'genarrative-ai-game-creator-shell.exe', - }, - { - pid: 41002, - parentPid: 41001, - startedAt: 'helper-start', - name: 'powershell.exe', - }, - { - pid: 41003, - parentPid: 41001, - startedAt: 'node-start', - name: 'node.exe', - }, - { - pid: 41004, - parentPid: 41003, - startedAt: 'browser-start', - name: 'chrome.exe', - }, - { - pid: 41005, - parentPid: 41001, - startedAt: 'command-start', - name: 'cmd.exe', - }, - { - pid: 42000, - parentPid: 1, - startedAt: 'unrelated-start', - name: 'node.exe', - }, - ]; - const snapshot = buildOwnedProcessCleanupSnapshot(processRecords, { - runnerPid: 41001, - helperPids: [41002], - rootPids: [41001, 41002], - }); - const live = inspectOwnedProcessCleanupResiduals(snapshot, processRecords, { - activeCommandChildCount: 1, - activeInteractiveCliSessionCount: 1, - }); - const reusedPids = processRecords.map((record) => - record.pid >= 41001 && record.pid <= 41005 - ? { ...record, startedAt: `${record.startedAt}-reused` } - : record, - ); - const cleaned = inspectOwnedProcessCleanupResiduals(snapshot, reusedPids, { - activeCommandChildCount: 0, - activeInteractiveCliSessionCount: 0, - }); - assert( - snapshot.observedCounts.runner === 1 && - snapshot.observedCounts.helper === 1 && - snapshot.observedCounts.node === 1 && - snapshot.observedCounts.browser === 1 && - snapshot.observedCounts.command === 1 && - live.clean === false && - live.residualCounts.total === 5 && - cleaned.clean === true && - cleaned.residualCounts.total === 0, - 'agent-runtime-real-e2e-self-test-owned-process-cleanup-invalid', - ); - return { - ownedRunnerResidualIdentityValidated: true, - ownedHelperResidualIdentityValidated: true, - ownedNodeResidualIdentityValidated: true, - ownedBrowserResidualIdentityValidated: true, - ownedCommandResidualIdentityValidated: true, - ownedProcessPidReuseRejected: true, - activeChildRegistryCleanupValidated: true, - }; -} - -function validateStaticSmokeFinalIndexBindingSelfTest() { - const sha256 = 'a'.repeat(64); - const gate = { - lastVerificationStatus: 'passed', - verifiedRevision: 7, - staticSmokeVerifiedRevision: 7, - staticSmokeVerifiedGameIndexSha256: sha256, - }; - assert( - supervisorAutonomousPlayableStaticSmokeMatchesFinalIndex(gate, 7, sha256) && - !supervisorAutonomousPlayableStaticSmokeMatchesFinalIndex( - gate, - 7, - 'b'.repeat(64), - ), - 'agent-runtime-real-e2e-self-test-static-smoke-final-index-binding-invalid', - ); - return { - staticSmokeFinalIndexSha256BindingValidated: true, - staticSmokeStaleIndexSha256Rejected: true, - }; -} - function validateSeededGameHtmlContractSelfTest() { const html = seededGameHtml(); const lowerHtml = html.toLowerCase(); @@ -2365,133 +1562,3 @@ async function validateStableKillHandleCommandFailureCleanup() { await stopSelfTestChild(victim); } } - -async function validateInteractiveCliInheritedStdioLifecycle() { - const expectedReport = { - schemaVersion: 'game-creator-swarm-turn-report.v1', - outcome: 'settled', - parentAgentId: 'project-supervisor', - sessionId: 'interactive-cli-fd3-self-test-session', - parentRunId: 'interactive-cli-fd3-self-test-run', - runtimeCount: 1, - busyRuntimeCount: 0, - pendingTaskCount: 0, - runningTaskCount: 0, - waitingForConfirmationCount: 0, - waitingForUserInputCount: 0, - newAssistantMessageCount: 1, - finalReplyChars: 1, - reconciliationAgentCount: 0, - }; - const stderrMarker = 'interactive-cli-fd3-holder-stderr-marker'; - const previousProjectPathScanner = state.projectPathTranscriptScanner; - const projectPathScanner = new StreamingSecretScanner([stderrMarker]); - state.projectPathTranscriptScanner = projectPathScanner; - const reportLine = `[turn.report] ${JSON.stringify(expectedReport)}\n`; - const splitIndex = Math.max(1, Math.floor(reportLine.length / 2)); - const holderSource = ` -process.stdin.setEncoding('utf8'); -let input = ''; -const safetyTimer = setTimeout(() => process.exit(74), 15_000); -process.stdin.on('data', (chunk) => { - input += chunk; - if (!input.includes('CLOSE\\n')) return; - clearTimeout(safetyTimer); - process.exit(0); -}); -process.stdin.on('end', () => process.exit(75)); -process.stdin.resume(); -`; - const wrapperSource = ` -const { spawn } = require('node:child_process'); -const holder = spawn( - process.execPath, - ['--input-type=commonjs', '-e', ${JSON.stringify(holderSource)}], - { stdio: [3, 'inherit', 'inherit'], windowsHide: true }, -); -holder.once('error', () => { - process.exitCode = 73; -}); -holder.unref(); -const reportLine = ${JSON.stringify(reportLine)}; -process.stdout.write(reportLine.slice(0, ${splitIndex}), () => { - setImmediate(() => { - process.stdout.write(reportLine.slice(${splitIndex}), () => { - process.stderr.write(${JSON.stringify(`${stderrMarker}\n`)}, () => { - process.exit(process.exitCode ?? 0); - }); - }); - }); -}); -`; - const wrapper = spawn( - process.execPath, - ['--input-type=commonjs', '-e', wrapperSource], - { - stdio: ['ignore', 'pipe', 'pipe', 'pipe'], - windowsHide: true, - }, - ); - const session = createInteractiveCliSession(wrapper); - const originalKill = wrapper.kill.bind(wrapper); - let killCallCount = 0; - wrapper.kill = (...args) => { - killCallCount += 1; - return originalKill(...args); - }; - let controlStreamError = null; - wrapper.stdio[3].on('error', (error) => { - controlStreamError ??= error; - }); - try { - await waitForInteractiveCliExit(session, 10_000); - assert( - session.exited && !session.stdioClosed, - 'agent-runtime-real-e2e-self-test-interactive-cli-two-phase-not-observed', - ); - const parsedReport = - await waitForSupervisorAutonomousPlayableCliExit(session); - await waitForInteractiveCliOutput( - session, - () => session.stderr.toString('utf8').includes(`${stderrMarker}\n`), - 'agent-runtime-real-e2e-self-test-interactive-cli-output-timeout', - 10_000, - { allowAfterProcessExit: true }, - ); - assert( - JSON.stringify(canonicalJsonValue(parsedReport)) === - JSON.stringify(canonicalJsonValue(expectedReport)), - 'agent-runtime-real-e2e-self-test-interactive-cli-report-mismatch', - ); - await closeInteractiveCli(session); - assert( - killCallCount === 0 && !session.stdioClosed, - 'agent-runtime-real-e2e-self-test-interactive-cli-exit-cleanup-killed', - ); - wrapper.stdio[3].end('CLOSE\n'); - await waitForInteractiveCliStdioClose(session, 10_000); - assert( - controlStreamError === null && - session.stdioClosed && - projectPathScanner.count === 1, - 'agent-runtime-real-e2e-self-test-interactive-cli-stdio-close-invalid', - ); - return { - interactiveCliExitBeforeStdioCloseValidated: true, - interactiveCliPostExitReportValidated: true, - interactiveCliExitCleanupDidNotKillValidated: true, - interactiveCliStdioCloseAfterHolderReleaseValidated: true, - interactiveCliProjectPathScannerValidated: true, - }; - } finally { - if (!wrapper.stdio[3].destroyed && !wrapper.stdio[3].writableEnded) { - wrapper.stdio[3].end('CLOSE\n'); - } - if (!session.stdioClosed) { - await waitForInteractiveCliStdioClose(session, 10_000).catch(() => { - destroyInteractiveCliOutputStreams(session); - }); - } - state.projectPathTranscriptScanner = previousProjectPathScanner; - } -} diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-autonomous-playable.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-autonomous-playable.mjs deleted file mode 100644 index 6d988a64a..000000000 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-autonomous-playable.mjs +++ /dev/null @@ -1,1890 +0,0 @@ -import { - assert, - codedError, - hashJsonValue, - hashValue, - isFailedTask, - parseSingleSwarmTurnReport, - recordError, - sleep, -} from '../assertions/core.mjs'; -import { - absolutePathVariants, - actionAuditIdentity, - countExactSecrets, - duplicateCount, - finalMessageId, - isNonEmptyString, - runtimePublicStatusMessageId, -} from '../assertions/runtime.mjs'; -import { fs, path } from '../dependencies.mjs'; -import { - claimOwnedRunner, - ensureOwnedRunnerStableKillSupport, - prepareIsolatedSuiteAppData, -} from '../harness/app-data.mjs'; -import { isPlainObject } from '../harness/config.mjs'; -import { isLiveTask, isPathInside, listFiles } from '../harness/io.mjs'; -import { - codedProcessError, - interactiveCliOutput, - prepareCliBinary, - startInteractiveCli, - waitForInteractiveCliOutput, -} from '../harness/process.mjs'; -import { - productionDefaultGameIndexHtml, - seedDisposableProject, -} from '../harness/project.mjs'; -import { decodeUtf8Fatal } from '../harness/reporting.mjs'; -import { - countLureLeaks, - findPendingActions, - readRuntime, -} from '../harness/runtime.mjs'; -import { - autonomousCompletionContractSchemaVersion, - autonomousGameBuildRunProfile, - autonomousPlaytestReceiptSchemaVersion, - laneDefensePlaytestRequiredAssertions, - projectSupervisorAgentId, - providerRequestLifecycleSchemaVersion, - runProfileBindingSchemaVersion, - state, - supervisorAutonomousPlayableLaneDefenseSuite, - supervisorAutonomousPlayableLaneDefenseTask, - supervisorAutonomousPlayableRunTimeoutMs, - supervisorAutonomousPlayableSafeDeliveryStatuses, - supervisorAutonomousPlayableSafePhases, - supervisorAutonomousPlayableSafeStatuses, - supervisorAutonomousPlayableSafeTerminalStatuses, - supervisorAutonomousPlayableSafeTurnOutcomes, - supervisorAutonomousPlayableSourceFieldMaxChars, - supervisorAutonomousPlayableSourceTotalMaxChars, - supervisorSwarmSessionId, - supervisorSwarmTerminalSidecarCleanupTimeoutMs, -} from '../runtime-state.mjs'; -import { - collectSupervisorSwarmPublicLeakEvidence, - countSupervisorSwarmSteerRecords, - readSupervisorSwarmJsonDirectory, - readSupervisorSwarmPersistence, - readSupervisorSwarmProjectRevision, - supervisorSwarmParentDeliveries, - validateSupervisorSwarmFinalization, -} from './supervisor-swarm.mjs'; - -const projectSupervisorCliSource = 'project-supervisor-cli'; -const projectSupervisorAcceptedPublicStatus = 'accepted'; -const projectSupervisorAcceptedPublicStatusContent = - '任务已接收,项目总控 Agent 正在启动处理。'; - -export function isSupervisorAutonomousPlayableAcceptedPublicStatus( - message, - runId, -) { - return ( - isPlainObject(message) && - message.role === 'assistant' && - message.agentId == null && - message.sessionId == null && - message.messageId === - runtimePublicStatusMessageId( - projectSupervisorAgentId, - supervisorSwarmSessionId, - runId, - projectSupervisorAcceptedPublicStatus, - ) && - message.content === projectSupervisorAcceptedPublicStatusContent - ); -} - -export function inspectSupervisorAutonomousPlayableConversationBoundary( - legacyConversation, - userFacing, - runId, -) { - const legacyUserFacingMessages = legacyConversation.filter((message) => - ['user', 'assistant'].includes(message.role), - ); - const acceptedPublicStatuses = legacyUserFacingMessages.filter((message) => - isSupervisorAutonomousPlayableAcceptedPublicStatus(message, runId), - ); - const wrongAgentFinalAssistants = userFacing.filter( - (message) => - message.role === 'assistant' && - message.agentId !== projectSupervisorAgentId && - !isSupervisorAutonomousPlayableAcceptedPublicStatus(message, runId), - ); - return { - legacyUserFacingMessageCount: legacyUserFacingMessages.length, - acceptedPublicStatusCount: acceptedPublicStatuses.length, - wrongAgentFinalAssistantCount: wrongAgentFinalAssistants.length, - }; -} - -export function supervisorAutonomousPlayableMode() { - return { - rootSource: projectSupervisorCliSource, - evidenceAgentId: projectSupervisorAgentId, - scenario: 'project-supervisor-autonomous-playable-lane-defense', - cliFlags: [], - }; -} - -export function buildSupervisorAutonomousPlayableStdin() { - const task = supervisorAutonomousPlayableLaneDefenseTask; - assert( - task === - '做一个植物大战僵尸式的塔防游戏,要能选择植物、阻挡敌人、正常闯关,并且生成后可以直接试玩。' && - !/[\r\n]/u.test(task), - 'supervisor-autonomous-playable-task-not-exact', - ); - for (const forbidden of [ - projectSupervisorAgentId, - autonomousGameBuildRunProfile, - 'Agent', - 'Runtime', - 'approve', - 'answer', - 'steer', - 'runner', - 'tool', - 'actionId', - 'runId', - ]) { - assert( - !task.includes(forbidden), - 'supervisor-autonomous-playable-task-recipe-leak', - ); - } - return Buffer.from(`${task}\n`, 'utf8'); -} - -export async function waitForSupervisorAutonomousPlayableParentRuntime(task) { - const deadline = Date.now() + 120_000; - while (Date.now() < deadline) { - const runtime = await readRuntime(projectSupervisorAgentId).catch( - () => null, - ); - if ( - runtime?.agentId === projectSupervisorAgentId && - runtime.sessionId === supervisorSwarmSessionId && - isNonEmptyString(runtime.runId) && - runtime.currentTask === task && - runtime.runProfile === autonomousGameBuildRunProfile && - /^[0-9a-f]{64}$/u.test(runtime.runProfileBindingFingerprint ?? '') - ) { - return runtime; - } - const session = state.supervisorAutonomousPlayableCliSession; - if (session?.closed && session.closeInfo?.code !== 0) { - throw codedProcessError('supervisor-autonomous-playable-cli-failed', { - ...session.closeInfo, - stdout: session.stdout, - stderr: session.stderr, - }); - } - await sleep(50); - } - throw codedError('supervisor-autonomous-playable-parent-runtime-timeout'); -} - -export async function waitForSupervisorAutonomousPlayableCliExit(session) { - let timeoutHandle; - const timeout = new Promise((resolve) => { - timeoutHandle = setTimeout( - () => resolve(null), - supervisorAutonomousPlayableRunTimeoutMs, - ); - }); - let result; - try { - result = await Promise.race([session.exitPromise, timeout]); - } finally { - clearTimeout(timeoutHandle); - } - if (!result) { - throw codedError('supervisor-autonomous-playable-cli-timeout'); - } - if (result.error || result.code !== 0 || result.signal !== null) { - state.supervisorAutonomousPlayable.cliOutput = - interactiveCliOutput(session); - throw codedProcessError('supervisor-autonomous-playable-cli-failed', { - ...result, - stdout: session.stdout, - stderr: session.stderr, - }); - } - const output = await waitForInteractiveCliOutput( - session, - () => - /(?:^|\r?\n)\[turn\.report\] [^\r\n]+\r?\n/u.test( - session.stdout.toString('utf8'), - ), - 'supervisor-autonomous-playable-turn-report-timeout', - 10_000, - { allowAfterProcessExit: true }, - ); - state.supervisorAutonomousPlayable.cliOutput = output; - const report = parseSingleSwarmTurnReport( - output, - 'supervisor-autonomous-playable', - ); - state.supervisorAutonomousPlayable.turnReport = report; - return report; -} - -export async function readSupervisorAutonomousPlayableResidualSidecarCounts() { - const roots = { - confirmations: '.agent/runtime/confirmations', - finalizations: '.agent/runtime/finalizations', - parallelReadBatches: '.agent/runtime/parallel-read-batches', - pendingActions: '.agent/runtime/pending-actions', - providerActionBatches: '.agent/runtime/provider-action-batches', - providerHandoffs: '.agent/runtime/provider-handoffs', - providerRetries: '.agent/runtime/provider-retries', - toolPlanHandoffs: '.agent/runtime/tool-plan-handoffs', - userInput: '.agent/runtime/user-input', - }; - const counts = {}; - for (const [name, relativePath] of Object.entries(roots)) { - counts[name] = ( - await listFiles(path.join(state.projectRoot, relativePath)) - ).length; - } - return counts; -} - -export function supervisorAutonomousPlayableSafeLifecycleLabel(value, allowed) { - if (value == null || value === '') return 'absent'; - return typeof value === 'string' && allowed.has(value) ? value : 'unknown'; -} - -export function countSupervisorAutonomousPlayableLifecycleLabels( - records, - field, - allowed, -) { - const counts = {}; - for (const record of records) { - const label = supervisorAutonomousPlayableSafeLifecycleLabel( - record?.[field], - allowed, - ); - counts[label] = (counts[label] ?? 0) + 1; - } - return counts; -} - -export function supervisorAutonomousPlayableSafeTurnCount(report, field) { - const value = report?.[field]; - return Number.isSafeInteger(value) && value >= 0 ? value : null; -} - -export function summarizeSupervisorAutonomousPlayableSourcePayloadAudits( - records, -) { - const protocols = records.filter( - (record) => record.recordType === 'agent.runtime.tool_plan.protocol', - ); - let sourceMutationActionMax = 0; - let sourcePayloadMaxFieldChars = 0; - let sourcePayloadMaxTotalChars = 0; - let sourcePayloadPolicyViolationCount = 0; - for (const record of protocols) { - const mutationCount = record.autonomousSourceMutationActionCount; - const maxFieldChars = record.autonomousSourceMaxFieldChars; - const totalChars = record.autonomousSourceTotalChars; - const valid = - record.autonomousSourcePayloadValidated === true && - Number.isSafeInteger(mutationCount) && - mutationCount >= 0 && - mutationCount <= 1 && - Number.isSafeInteger(maxFieldChars) && - maxFieldChars >= 0 && - maxFieldChars <= supervisorAutonomousPlayableSourceFieldMaxChars && - Number.isSafeInteger(totalChars) && - totalChars >= maxFieldChars && - totalChars <= supervisorAutonomousPlayableSourceTotalMaxChars; - if (!valid) sourcePayloadPolicyViolationCount += 1; - if (Number.isSafeInteger(mutationCount)) { - sourceMutationActionMax = Math.max( - sourceMutationActionMax, - mutationCount, - ); - } - if (Number.isSafeInteger(maxFieldChars)) { - sourcePayloadMaxFieldChars = Math.max( - sourcePayloadMaxFieldChars, - maxFieldChars, - ); - } - if (Number.isSafeInteger(totalChars)) { - sourcePayloadMaxTotalChars = Math.max( - sourcePayloadMaxTotalChars, - totalChars, - ); - } - } - return { - acceptedAutonomousToolPlanCount: protocols.length, - sourceMutationActionMax, - sourcePayloadMaxFieldChars, - sourcePayloadMaxTotalChars, - sourcePayloadPolicyViolationCount, - }; -} - -export function buildSupervisorAutonomousPlayablePartialEvidence( - persistence, - { - residualSidecars = {}, - pendingActionCount = 0, - turnReport = null, - partialEvidenceReadErrorCount = 0, - } = {}, -) { - const productionDefaultGameIndexSha256 = hashValue( - Buffer.from(productionDefaultGameIndexHtml(), 'utf8'), - ); - const parentTask = persistence.taskSnapshot.latest.find( - (task) => - task.agentId === projectSupervisorAgentId && - task.runId === state.initialRunId, - ); - const parentRuntime = persistence.runtimeStates.find( - (runtime) => - runtime.agentId === projectSupervisorAgentId && - runtime.runId === state.initialRunId, - ); - const childTasks = persistence.taskSnapshot.latest.filter( - (task) => - task.parentAgentId === projectSupervisorAgentId && - task.parentRunId === state.initialRunId, - ); - const childRuntimes = persistence.runtimeStates.filter( - (runtime) => - runtime.parentAgentId === projectSupervisorAgentId && - runtime.parentRunId === state.initialRunId, - ); - const deliveries = (persistence.deliveries ?? []).filter( - (delivery) => - delivery.parentAgentId === projectSupervisorAgentId && - delivery.parentRunId === state.initialRunId, - ); - const initialDeliveries = deliveries.filter( - (delivery) => delivery.repairOfDelegationId == null, - ); - const repairDeliveries = deliveries.filter( - (delivery) => delivery.repairOfDelegationId != null, - ); - const repairDeliveryIds = new Set( - repairDeliveries.map((delivery) => delivery.delegationId), - ); - const failedRepairChildCount = childTasks.filter( - (task) => isFailedTask(task) && repairDeliveryIds.has(task.delegationId), - ).length; - const failedOriginalTasks = childTasks.filter( - (task) => isFailedTask(task) && !repairDeliveryIds.has(task.delegationId), - ); - let recoveredOriginalFailureCount = 0; - for (const task of failedOriginalTasks) { - const original = initialDeliveries.find( - (delivery) => - delivery.delegationId === task.delegationId && - delivery.targetAgentId === task.agentId && - delivery.targetSessionId === task.sessionId && - delivery.targetRunId === task.runId, - ); - if ( - original && - repairDeliveries.some( - (repair) => - repair.repairOfDelegationId === original.delegationId && - repair.terminalStatus === 'completed' && - repair.structuredResult?.contractStatus === 'evidence-ready', - ) - ) { - recoveredOriginalFailureCount += 1; - } - } - - const relevantRunKeys = new Set(); - for (const record of [ - parentTask, - parentRuntime, - ...childTasks, - ...childRuntimes, - ]) { - if (isNonEmptyString(record?.agentId) && isNonEmptyString(record?.runId)) { - relevantRunKeys.add(`${record.agentId}\0${record.runId}`); - } - } - const lifecycle = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.provider_request.lifecycle' && - relevantRunKeys.has(`${record.agentId}\0${record.runId}`), - ); - const retryAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.provider_request.retry' && - relevantRunKeys.has(`${record.agentId}\0${record.runId}`), - ); - const lifecycleByRequest = new Map(); - let duplicateProviderLifecycleCount = 0; - for (const record of lifecycle) { - if (!isNonEmptyString(record.requestId)) continue; - const statuses = lifecycleByRequest.get(record.requestId) ?? new Set(); - if (statuses.has(record.status)) duplicateProviderLifecycleCount += 1; - statuses.add(record.status); - lifecycleByRequest.set(record.requestId, statuses); - } - const terminalTaskCount = persistence.taskSnapshot.latest.filter((task) => - ['budget-exhausted', 'cancelled', 'completed', 'failed'].includes( - task.status, - ), - ).length; - const supervisorMessages = persistence.supervisorConversation ?? []; - const turnReportCaptured = isPlainObject(turnReport); - const turnReportOutcome = turnReportCaptured - ? supervisorAutonomousPlayableSafeTurnOutcomes.has(turnReport.outcome) - ? turnReport.outcome - : 'unknown' - : null; - const turnReportParentIdentityStable = turnReportCaptured - ? turnReport.parentAgentId === projectSupervisorAgentId && - turnReport.sessionId === supervisorSwarmSessionId && - turnReport.parentRunId === state.initialRunId - : false; - const turnReportPrivateLeakCount = turnReportCaptured - ? countExactSecrets( - Buffer.from(JSON.stringify(turnReport)), - state.supervisorAutonomousPlayable.privateValues, - ) - : null; - const sourcePayload = - summarizeSupervisorAutonomousPlayableSourcePayloadAudits( - persistence.agentDb, - ); - const providerBinding = - state.supervisorAutonomousPlayable.effectiveProviderBinding ?? - state.supervisorAutonomousPlayable.expectedProviderBinding; - - return { - evidenceCompleteness: 'partial', - partialEvidenceCollected: true, - partialEvidenceReadErrorCount, - partialPrivacyScanComplete: false, - rootRunObserved: Boolean(parentTask || parentRuntime), - parentTaskStatus: supervisorAutonomousPlayableSafeLifecycleLabel( - parentTask?.status, - supervisorAutonomousPlayableSafeStatuses, - ), - parentTaskPhase: supervisorAutonomousPlayableSafeLifecycleLabel( - parentTask?.phase, - supervisorAutonomousPlayableSafePhases, - ), - parentRuntimeStatus: supervisorAutonomousPlayableSafeLifecycleLabel( - parentRuntime?.status, - supervisorAutonomousPlayableSafeStatuses, - ), - parentRuntimePhase: supervisorAutonomousPlayableSafeLifecycleLabel( - parentRuntime?.phase, - supervisorAutonomousPlayableSafePhases, - ), - childTaskStatusCounts: countSupervisorAutonomousPlayableLifecycleLabels( - childTasks, - 'status', - supervisorAutonomousPlayableSafeStatuses, - ), - childTaskPhaseCounts: countSupervisorAutonomousPlayableLifecycleLabels( - childTasks, - 'phase', - supervisorAutonomousPlayableSafePhases, - ), - childRuntimeStatusCounts: countSupervisorAutonomousPlayableLifecycleLabels( - childRuntimes, - 'status', - supervisorAutonomousPlayableSafeStatuses, - ), - childRuntimePhaseCounts: countSupervisorAutonomousPlayableLifecycleLabels( - childRuntimes, - 'phase', - supervisorAutonomousPlayableSafePhases, - ), - initialDeliveryStatusCounts: - countSupervisorAutonomousPlayableLifecycleLabels( - initialDeliveries, - 'status', - supervisorAutonomousPlayableSafeDeliveryStatuses, - ), - repairDeliveryStatusCounts: - countSupervisorAutonomousPlayableLifecycleLabels( - repairDeliveries, - 'status', - supervisorAutonomousPlayableSafeDeliveryStatuses, - ), - repairTerminalStatusCounts: - countSupervisorAutonomousPlayableLifecycleLabels( - repairDeliveries, - 'terminalStatus', - supervisorAutonomousPlayableSafeTerminalStatuses, - ), - failedOriginalChildCount: failedOriginalTasks.length, - failedRepairChildCount, - recoveredSpecialistFailureCount: recoveredOriginalFailureCount, - recoveredOriginalFailureCount, - awaitingRepairCount: - failedOriginalTasks.length - recoveredOriginalFailureCount, - stdinTaskCount: state.supervisorAutonomousPlayable.stdinWriteCount, - stdinEndedAfterTask: state.supervisorAutonomousPlayable.stdinEnded, - stdinBytes: state.supervisorAutonomousPlayable.stdinBytes, - taskSha256: state.initialTask?.sha256 ?? null, - providerAgentMode: providerBinding?.providerAgentMode ?? null, - providerModel: providerBinding?.providerModel ?? null, - providerApiKind: providerBinding?.providerApiKind ?? null, - providerReasoningEffort: providerBinding?.providerReasoningEffort ?? null, - providerBaseUrlSha256: providerBinding?.providerBaseUrlSha256 ?? null, - providerBoundAgentCount: providerBinding?.boundAgentIds?.length ?? 0, - providerBindingMatched: - state.supervisorAutonomousPlayable.effectiveProviderBinding != null && - JSON.stringify( - state.supervisorAutonomousPlayable.effectiveProviderBinding, - ) === - JSON.stringify( - state.supervisorAutonomousPlayable.expectedProviderBinding, - ), - turnReportCaptured, - turnReportParentIdentityStable, - turnReportNewAssistantMessageCount: - supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'newAssistantMessageCount', - ), - turnReportFinalReplyChars: supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'finalReplyChars', - ), - turnReportPrivateLeakCount, - turnReportOutcome, - turnReportRuntimeCount: supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'runtimeCount', - ), - turnReportBusyRuntimeCount: supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'busyRuntimeCount', - ), - turnReportPendingTaskCount: supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'pendingTaskCount', - ), - turnReportRunningTaskCount: supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'runningTaskCount', - ), - turnReportWaitingForConfirmationCount: - supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'waitingForConfirmationCount', - ), - turnReportWaitingForUserInputCount: - supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'waitingForUserInputCount', - ), - turnReportReconciliationAgentCount: - supervisorAutonomousPlayableSafeTurnCount( - turnReport, - 'reconciliationAgentCount', - ), - taskCount: persistence.taskSnapshot.latest.length, - terminalTaskCount, - childTaskCount: childTasks.length, - runtimeCount: persistence.runtimeStates.length, - childRuntimeCount: childRuntimes.length, - finalSupervisorAssistantCount: supervisorMessages.filter( - (message) => message.role === 'assistant', - ).length, - supervisorUserMessageCount: supervisorMessages.filter( - (message) => message.role === 'user', - ).length, - professionalAssistantCount: ( - persistence.professionalConversations ?? [] - ).reduce( - (count, conversation) => - count + - conversation.messages.filter((message) => message.role === 'assistant') - .length, - 0, - ), - providerRequestIdentityCount: lifecycleByRequest.size, - providerLifecycleStartedCount: lifecycle.filter( - (record) => record.status === 'started', - ).length, - providerLifecycleTerminalCount: lifecycle.filter((record) => - ['completed', 'failed'].includes(record.status), - ).length, - providerLifecycleCompletedCount: lifecycle.filter( - (record) => record.status === 'completed', - ).length, - providerLifecycleFailedCount: lifecycle.filter( - (record) => record.status === 'failed', - ).length, - providerRetryAuditCount: retryAudits.length, - providerConnectivityRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'connectivity', - ).length, - providerTransportRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'transport', - ).length, - providerTimeoutRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'timeout', - ).length, - ...sourcePayload, - openProviderLifecycleCount: [...lifecycleByRequest.values()].filter( - (statuses) => - statuses.has('started') && - !statuses.has('completed') && - !statuses.has('failed'), - ).length, - duplicateProviderLifecycleCount, - pendingActionCount, - confirmationSidecarCount: residualSidecars.confirmations ?? 0, - userInputSidecarCount: residualSidecars.userInput ?? 0, - providerActionBatchSidecarCount: - residualSidecars.providerActionBatches ?? 0, - providerRetrySidecarCount: residualSidecars.providerRetries ?? 0, - providerHandoffSidecarCount: residualSidecars.providerHandoffs ?? 0, - toolPlanHandoffSidecarCount: residualSidecars.toolPlanHandoffs ?? 0, - finalizationJournalCount: residualSidecars.finalizations ?? 0, - reconciliationResidueCount: - supervisorAutonomousPlayableReconciliationResidueCount(persistence), - freshInitBaselineUsed: - state.supervisorAutonomousPlayable.freshInitBaselineUsed, - initialGameIndexMatchesProductionDefault: - state.supervisorAutonomousPlayable.initialGameIndexSha256 === - productionDefaultGameIndexSha256, - initialGameIndexSha256: - state.supervisorAutonomousPlayable.initialGameIndexSha256, - }; -} - -export async function collectPartialSupervisorAutonomousPlayableEvidence() { - const persistence = await readSupervisorSwarmPersistence({ - tolerateErrors: true, - }); - let partialEvidenceReadErrorCount = Object.keys( - persistence.failureEvidenceErrors, - ).length; - let residualSidecars = {}; - try { - residualSidecars = - await readSupervisorAutonomousPlayableResidualSidecarCounts(); - } catch { - partialEvidenceReadErrorCount += 1; - recordError('supervisor-autonomous-playable-partial-sidecar-read-failed'); - } - let pendingActionCount = 0; - try { - pendingActionCount = (await findPendingActions()).length; - } catch { - partialEvidenceReadErrorCount += 1; - recordError('supervisor-autonomous-playable-partial-pending-read-failed'); - } - state.supervisorAutonomousPlayable.privateValues = [ - ...new Set([ - ...state.supervisorAutonomousPlayable.privateValues, - ...state.supervisorSwarm.privateValues, - ]), - ]; - return buildSupervisorAutonomousPlayablePartialEvidence(persistence, { - residualSidecars, - pendingActionCount, - turnReport: state.supervisorAutonomousPlayable.turnReport, - partialEvidenceReadErrorCount, - }); -} - -export function supervisorAutonomousPlayableReconciliationResidueCount( - persistence, -) { - const taskCount = persistence.taskSnapshot.latest.filter( - (task) => task.phase === 'needs-reconciliation', - ).length; - const runtimeCount = persistence.runtimeStates.filter( - (runtime) => runtime.phase === 'needs-reconciliation', - ).length; - const auditCount = persistence.agentDb.filter( - (record) => - record.status === 'needs-reconciliation' || - record.phase === 'needs-reconciliation' || - String(record.recordType ?? '').includes('needs_reconciliation'), - ).length; - return taskCount + runtimeCount + auditCount; -} - -export function supervisorAutonomousPlayableFailureDisposition( - persistence, - { requireRecovered = false } = {}, -) { - const deliveries = supervisorSwarmParentDeliveries( - persistence.deliveries ?? [], - ); - const parentTask = persistence.taskSnapshot.latest.find( - (task) => - task.agentId === projectSupervisorAgentId && - task.runId === state.initialRunId, - ); - const parentRuntime = persistence.runtimeStates.find( - (runtime) => - runtime.agentId === projectSupervisorAgentId && - runtime.runId === state.initialRunId, - ); - assert( - !isFailedTask(parentTask ?? {}) && !isFailedTask(parentRuntime ?? {}), - 'supervisor-autonomous-playable-root-failed', - ); - const parentActive = - (parentTask && isLiveTask(parentTask)) || - [ - 'pending', - 'running', - 'waiting-for-confirmation', - 'waiting-for-user-input', - ].includes(parentRuntime?.status) || - ['queued', 'running', 'executing', 'finalizing'].includes( - parentRuntime?.phase, - ); - let recoveredOriginalFailureCount = 0; - let awaitingRepairCount = 0; - for (const task of persistence.taskSnapshot.latest.filter( - (candidate) => - candidate.parentAgentId === projectSupervisorAgentId && - candidate.parentRunId === state.initialRunId && - isFailedTask(candidate), - )) { - const delivery = deliveries.find( - (candidate) => - candidate.delegationId === task.delegationId && - candidate.targetAgentId === task.agentId && - candidate.targetSessionId === task.sessionId && - candidate.targetRunId === task.runId, - ); - assert( - delivery && delivery.repairOfDelegationId == null, - delivery?.repairOfDelegationId != null - ? 'supervisor-autonomous-playable-repair-child-failed' - : 'supervisor-autonomous-playable-unrecoverable-child-failed', - ); - const repairs = deliveries.filter( - (candidate) => candidate.repairOfDelegationId === delivery.delegationId, - ); - assert( - repairs.length <= 1, - 'supervisor-autonomous-playable-duplicate-repair', - ); - const repairable = - delivery.status !== 'suppressed' && - ((delivery.acceptanceCriteria?.length ?? 0) > 0 || - (delivery.expectedArtifacts?.length ?? 0) > 0) && - (delivery.structuredResult == null || - delivery.structuredResult.contractStatus === 'needs-repair'); - const recovered = repairs.some( - (repair) => - repair.terminalStatus === 'completed' && - repair.structuredResult?.contractStatus === 'evidence-ready', - ); - assert( - repairable && (recovered || parentActive || !requireRecovered), - 'supervisor-autonomous-playable-specialist-failure-unrecovered', - ); - if (recovered) recoveredOriginalFailureCount += 1; - else awaitingRepairCount += 1; - } - if (requireRecovered) { - assert( - awaitingRepairCount === 0, - 'supervisor-autonomous-playable-terminal-repair-incomplete', - ); - } - return { recoveredOriginalFailureCount, awaitingRepairCount }; -} - -export function assertSupervisorAutonomousPlayableRuntimeHealthy(persistence) { - for (const task of persistence.taskSnapshot.latest) { - if (task.phase === 'needs-reconciliation') { - throw codedError( - 'supervisor-autonomous-playable-runtime-needs-reconciliation', - ); - } - } - for (const runtime of persistence.runtimeStates) { - if (runtime.phase === 'needs-reconciliation') { - throw codedError( - 'supervisor-autonomous-playable-runtime-needs-reconciliation', - ); - } - } - return supervisorAutonomousPlayableFailureDisposition(persistence); -} - -export async function waitForSupervisorAutonomousPlayableDurableQuiescence() { - const deadline = Date.now() + supervisorSwarmTerminalSidecarCleanupTimeoutMs; - let quietPolls = 0; - while (Date.now() < deadline) { - const persistence = await readSupervisorSwarmPersistence(); - assertSupervisorAutonomousPlayableRuntimeHealthy(persistence); - const pending = await findPendingActions(); - const residualSidecars = - await readSupervisorAutonomousPlayableResidualSidecarCounts(); - const noLiveTasks = persistence.taskSnapshot.latest.every( - (task) => - !isLiveTask(task) && - !['waiting-for-user-input', 'waiting-for-confirmation'].includes( - task.status, - ), - ); - const noLiveRuntimes = persistence.runtimeStates.every( - (runtime) => - !isNonEmptyString(runtime.runId) || - ['completed', 'cancelled'].includes(runtime.phase), - ); - const settled = - noLiveTasks && - noLiveRuntimes && - pending.length === 0 && - Object.values(residualSidecars).every((count) => count === 0) && - supervisorAutonomousPlayableReconciliationResidueCount(persistence) === 0; - if (settled) { - quietPolls += 1; - if (quietPolls >= 3) { - const failureDisposition = - supervisorAutonomousPlayableFailureDisposition(persistence, { - requireRecovered: true, - }); - return { persistence, residualSidecars, failureDisposition }; - } - } else { - quietPolls = 0; - } - await sleep(50); - } - throw codedError('supervisor-autonomous-playable-residue-timeout'); -} - -export function supervisorAutonomousPlayableStaticSmokeMatchesFinalIndex( - gate, - revision, - finalGameIndexSha256, -) { - return ( - gate?.lastVerificationStatus === 'passed' && - gate.verifiedRevision === revision && - gate.staticSmokeVerifiedRevision === revision && - gate.staticSmokeVerifiedGameIndexSha256 === finalGameIndexSha256 - ); -} - -export async function validateSupervisorAutonomousPlayableEvidence( - persistence, - residualSidecars, -) { - const task = supervisorAutonomousPlayableLaneDefenseTask; - const mode = supervisorAutonomousPlayableMode(); - const report = state.supervisorAutonomousPlayable.turnReport; - assert( - report?.schemaVersion === 'game-creator-swarm-turn-report.v1' && - report.outcome === 'settled' && - report.parentAgentId === projectSupervisorAgentId && - report.sessionId === supervisorSwarmSessionId && - report.parentRunId === state.initialRunId && - report.runtimeCount >= 1 && - report.busyRuntimeCount === 0 && - report.pendingTaskCount === 0 && - report.runningTaskCount === 0 && - report.waitingForConfirmationCount === 0 && - report.waitingForUserInputCount === 0 && - report.newAssistantMessageCount === 1 && - report.finalReplyChars > 0 && - report.reconciliationAgentCount === 0, - 'supervisor-autonomous-playable-turn-report-invalid', - ); - - const failureDisposition = supervisorAutonomousPlayableFailureDisposition( - persistence, - { - requireRecovered: true, - }, - ); - const parentTask = persistence.taskSnapshot.latest.find( - (candidate) => - candidate.agentId === projectSupervisorAgentId && - candidate.sessionId === supervisorSwarmSessionId && - candidate.runId === state.initialRunId, - ); - const parentRuntime = persistence.runtimeStates.find( - (candidate) => - candidate.agentId === projectSupervisorAgentId && - candidate.runId === state.initialRunId, - ); - assert( - parentTask?.task === task && - parentTask.source === mode.rootSource && - parentTask.status === 'completed' && - parentTask.phase === 'completed' && - parentTask.runProfile === autonomousGameBuildRunProfile && - parentRuntime?.sessionId === supervisorSwarmSessionId && - parentRuntime.phase === 'completed' && - parentRuntime.pendingToolAction == null && - parentRuntime.pendingAction == null && - parentRuntime.queuedSteerCount === 0 && - parentRuntime.runProfile === autonomousGameBuildRunProfile && - persistence.taskSnapshot.latest.every( - (candidate) => - !isLiveTask(candidate) && - !['waiting-for-user-input', 'waiting-for-confirmation'].includes( - candidate.status, - ), - ) && - persistence.runtimeStates.every( - (candidate) => - candidate.phase !== 'needs-reconciliation' && - ![ - 'pending', - 'running', - 'waiting-for-user-input', - 'waiting-for-confirmation', - ].includes(candidate.status), - ), - 'supervisor-autonomous-playable-runtime-not-terminal', - ); - - const supervisorUsers = persistence.supervisorConversation.filter( - (message) => message.role === 'user', - ); - const supervisorAssistants = persistence.supervisorConversation.filter( - (message) => message.role === 'assistant', - ); - const assistant = supervisorAssistants[0]; - const professionalMessages = persistence.professionalConversations.flatMap( - (entry) => entry.messages, - ); - const isolatedMessages = persistence.isolatedConversations.flatMap( - (entry) => entry.messages, - ); - const userFacing = [ - ...persistence.supervisorConversation, - ...persistence.legacyConversation, - ]; - const conversationBoundary = - inspectSupervisorAutonomousPlayableConversationBoundary( - persistence.legacyConversation, - userFacing, - state.initialRunId, - ); - const allMessages = [ - ...userFacing, - ...professionalMessages, - ...isolatedMessages, - ]; - const duplicateMessageCount = duplicateCount( - allMessages.map((message) => message.messageId).filter(Boolean), - ); - const parentAssistantAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'conversation.message' && - record.role === 'assistant' && - record.agentId === projectSupervisorAgentId && - record.sessionId === supervisorSwarmSessionId && - record.messageId === assistant?.messageId, - ); - const parentCompletedAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.completed' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId, - ); - assert( - supervisorUsers.length === 1 && - supervisorUsers[0].content === task && - supervisorAssistants.length === 1 && - assistant.agentId === projectSupervisorAgentId && - assistant.messageId === - finalMessageId( - projectSupervisorAgentId, - supervisorSwarmSessionId, - state.initialRunId, - ) && - conversationBoundary.legacyUserFacingMessageCount === 1 && - conversationBoundary.acceptedPublicStatusCount === 1 && - conversationBoundary.wrongAgentFinalAssistantCount === 0 && - parentAssistantAudits.length === 1 && - parentCompletedAudits.length === 1 && - duplicateMessageCount === 0, - 'supervisor-autonomous-playable-final-assistant-invalid', - ); - const finalization = validateSupervisorSwarmFinalization( - persistence.agentDb, - { - agentId: projectSupervisorAgentId, - taskId: parentTask.taskId, - sessionId: supervisorSwarmSessionId, - runId: state.initialRunId, - }, - assistant, - ); - - const actionRecords = persistence.agentDb.filter( - (record) => - isNonEmptyString(record.actionId) && - [ - 'agent.runtime.tool_action.executing', - 'agent.runtime.tool_action.observed', - 'agent.runtime.tool_observation', - 'agent.runtime.action_receipt', - ].includes(record.recordType), - ); - const actionReceipts = actionRecords.filter( - (record) => record.recordType === 'agent.runtime.action_receipt', - ); - const duplicateActionLifecycleCount = duplicateCount( - actionRecords.map(actionAuditIdentity), - ); - const duplicateReceiptCount = duplicateCount( - actionReceipts.map( - (record) => `${record.agentId}\0${record.runId}\0${record.actionId}`, - ), - ); - assert( - duplicateActionLifecycleCount === 0 && duplicateReceiptCount === 0, - 'supervisor-autonomous-playable-duplicate-action-or-receipt', - ); - - const runProfileBindings = await readSupervisorSwarmJsonDirectory( - '.agent/runtime/run-profile-bindings', - ); - const autonomousRunKeys = new Set( - runProfileBindings - .filter( - (candidate) => - candidate.profile === autonomousGameBuildRunProfile && - candidate.rootAgentId === projectSupervisorAgentId && - candidate.rootRunId === state.initialRunId, - ) - .map((candidate) => `${candidate.agentId}\0${candidate.runId}`), - ); - const sourcePayload = - summarizeSupervisorAutonomousPlayableSourcePayloadAudits( - persistence.agentDb.filter((record) => - autonomousRunKeys.has(`${record.agentId}\0${record.runId}`), - ), - ); - assert( - sourcePayload.acceptedAutonomousToolPlanCount > 0 && - sourcePayload.sourcePayloadPolicyViolationCount === 0, - 'supervisor-autonomous-playable-source-payload-policy-invalid', - ); - const matchingBindings = runProfileBindings.filter( - (binding) => - binding.agentId === projectSupervisorAgentId && - binding.runId === state.initialRunId, - ); - const binding = matchingBindings[0]; - const bindingIdentity = binding && { - schemaVersion: binding.schemaVersion, - projectId: binding.projectId, - agentId: binding.agentId, - runId: binding.runId, - rootAgentId: binding.rootAgentId, - rootRunId: binding.rootRunId, - parentAgentId: binding.parentAgentId, - parentRunId: binding.parentRunId, - source: binding.source, - profile: binding.profile, - profileFingerprint: binding.profileFingerprint, - parentBindingFingerprint: binding.parentBindingFingerprint, - boundAt: binding.boundAt, - }; - assert( - matchingBindings.length === 1 && - binding.schemaVersion === runProfileBindingSchemaVersion && - binding.rootAgentId === projectSupervisorAgentId && - binding.rootRunId === state.initialRunId && - binding.parentAgentId == null && - binding.parentRunId == null && - binding.source === mode.rootSource && - binding.profile === autonomousGameBuildRunProfile && - binding.profileFingerprint === - hashJsonValue({ - schemaVersion: runProfileBindingSchemaVersion, - profile: autonomousGameBuildRunProfile, - }) && - binding.bindingFingerprint === hashJsonValue(bindingIdentity), - 'supervisor-autonomous-playable-run-profile-binding-invalid', - ); - - const contracts = await readSupervisorSwarmJsonDirectory( - '.agent/runtime/autonomous-completion-contracts', - ); - const receipts = await readSupervisorSwarmJsonDirectory( - '.agent/runtime/autonomous-playtest-receipts', - ); - assert( - contracts.length === 1 && receipts.length === 1, - 'supervisor-autonomous-playable-autonomous-artifact-count-invalid', - ); - const contract = contracts[0]; - const receipt = receipts[0]; - const evidenceBinding = binding; - const evidenceRunId = state.initialRunId; - const contractIdentity = { - schemaVersion: contract.schemaVersion, - projectId: contract.projectId, - agentId: contract.agentId, - runId: contract.runId, - runProfileBindingFingerprint: contract.runProfileBindingFingerprint, - taskSha256: contract.taskSha256, - baselineRevision: contract.baselineRevision, - baselineIndexSha256: contract.baselineIndexSha256, - baselineArtifacts: contract.baselineArtifacts, - playtestScenario: contract.playtestScenario, - createdAt: contract.createdAt, - }; - assert( - contract.schemaVersion === autonomousCompletionContractSchemaVersion && - contract.projectId === binding.projectId && - contract.agentId === projectSupervisorAgentId && - contract.runId === state.initialRunId && - contract.runProfileBindingFingerprint === binding.bindingFingerprint && - contract.taskSha256 === hashValue(task) && - contract.baselineIndexSha256 === - state.supervisorAutonomousPlayable.initialGameIndexSha256 && - Array.isArray(contract.baselineArtifacts) && - contract.baselineArtifacts.length === 1 && - contract.baselineArtifacts[0]?.path === 'game/index.html' && - contract.baselineArtifacts[0]?.sha256 === - state.supervisorAutonomousPlayable.initialGameIndexSha256 && - Number.isInteger(contract.baselineArtifacts[0]?.sizeBytes) && - contract.baselineArtifacts[0].sizeBytes > 0 && - contract.playtestScenario === 'lane-defense-v1' && - contract.contractFingerprint === hashJsonValue(contractIdentity), - 'supervisor-autonomous-playable-completion-contract-invalid', - ); - - const revision = await readSupervisorSwarmProjectRevision(); - const finalGameIndex = await fs.readFile( - path.join(state.projectRoot, 'game/index.html'), - ); - const finalGameIndexSha256 = hashValue(finalGameIndex); - assert( - revision.schemaVersion === 'game-creator-project-revision.v1' && - revision.projectId === contract.projectId && - revision.revision > contract.baselineRevision && - finalGameIndexSha256 !== - state.supervisorAutonomousPlayable.initialGameIndexSha256 && - finalGameIndexSha256 !== contract.baselineIndexSha256, - 'supervisor-autonomous-playable-project-output-invalid', - ); - - const gates = ( - await readSupervisorSwarmJsonDirectory('.agent/runtime/verification') - ).filter( - (gate) => - gate.agentId === mode.evidenceAgentId && gate.runId === evidenceRunId, - ); - const gate = gates[0]; - const staticSmokeAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.command.run_limited' && - record.agentId === mode.evidenceAgentId && - record.runId === evidenceRunId && - record.commandId === 'game.static_smoke' && - record.status === 'completed', - ); - assert( - gates.length === 1 && - supervisorAutonomousPlayableStaticSmokeMatchesFinalIndex( - gate, - revision.revision, - finalGameIndexSha256, - ) && - staticSmokeAudits.length >= 1, - 'supervisor-autonomous-playable-static-smoke-invalid', - ); - - const receiptIdentity = { - schemaVersion: receipt.schemaVersion, - projectId: receipt.projectId, - agentId: receipt.agentId, - runId: receipt.runId, - runProfileBindingFingerprint: receipt.runProfileBindingFingerprint, - executorAgentId: receipt.executorAgentId, - executorRunId: receipt.executorRunId, - executorSource: receipt.executorSource, - executorRunProfileBindingFingerprint: - receipt.executorRunProfileBindingFingerprint, - actionId: receipt.actionId, - actionFingerprint: receipt.actionFingerprint, - revision: receipt.revision, - gameIndex: receipt.gameIndex, - playtestScenario: receipt.playtestScenario, - scenarioFingerprint: receipt.scenarioFingerprint, - report: receipt.report, - screenshots: receipt.screenshots, - createdAt: receipt.createdAt, - }; - assert( - receipt.schemaVersion === autonomousPlaytestReceiptSchemaVersion && - receipt.projectId === contract.projectId && - receipt.agentId === mode.evidenceAgentId && - receipt.runId === evidenceRunId && - receipt.runProfileBindingFingerprint === - evidenceBinding.bindingFingerprint && - receipt.revision === revision.revision && - receipt.gameIndex.path === 'game/index.html' && - receipt.gameIndex.sha256 === finalGameIndexSha256 && - receipt.gameIndex.sizeBytes === finalGameIndex.length && - receipt.playtestScenario === 'lane-defense-v1' && - receipt.receiptFingerprint === hashJsonValue(receiptIdentity) && - actionReceipts.filter( - (record) => - record.agentId === mode.evidenceAgentId && - record.runId === evidenceRunId && - record.actionId === receipt.actionId && - record.actionFingerprint === receipt.actionFingerprint && - record.tool === 'preview.validate' && - record.status === 'ok', - ).length === 1, - 'supervisor-autonomous-playable-playtest-receipt-invalid', - ); - - const readDigest = async (digest, expectedSuffix) => { - assert( - isNonEmptyString(digest?.path) && - !path.isAbsolute(digest.path) && - digest.path.endsWith(expectedSuffix), - 'supervisor-autonomous-playable-evidence-path-invalid', - ); - const file = path.resolve(state.projectRoot, digest.path); - assert( - isPathInside(state.projectRoot, file), - 'supervisor-autonomous-playable-evidence-path-escape', - ); - const metadata = await fs.lstat(file); - assert( - metadata.isFile() && !metadata.isSymbolicLink(), - 'supervisor-autonomous-playable-evidence-file-invalid', - ); - const bytes = await fs.readFile(file); - assert( - hashValue(bytes) === digest.sha256 && bytes.length === digest.sizeBytes, - 'supervisor-autonomous-playable-evidence-digest-invalid', - ); - return bytes; - }; - const reportBytes = await readDigest(receipt.report, '/validation.json'); - const browserReport = JSON.parse( - decodeUtf8Fatal( - reportBytes, - 'supervisor-autonomous-playable-browser-report-invalid-utf8', - ), - ); - const screenshotEntries = await Promise.all( - receipt.screenshots.map(async (digest) => ({ - digest, - bytes: await readDigest(digest, '.png'), - })), - ); - assert( - screenshotEntries.length === 2 && - screenshotEntries.every(({ bytes }) => - bytes.subarray(0, 8).equals(Buffer.from('\x89PNG\r\n\x1a\n', 'binary')), - ), - 'supervisor-autonomous-playable-screenshot-invalid', - ); - const desktop = screenshotEntries.find(({ digest }) => - digest.path.endsWith('/desktop.png'), - ); - const mobile = screenshotEntries.find(({ digest }) => - digest.path.endsWith('/mobile.png'), - ); - const desktopViewport = browserReport.viewportResults?.find( - (entry) => entry.viewport === 'desktop', - ); - const mobileViewport = browserReport.viewportResults?.find( - (entry) => entry.viewport === 'mobile', - ); - const playtest = browserReport.playtest; - const assertionNames = new Set( - (playtest?.assertions ?? []).map((entry) => entry.name), - ); - assert( - desktop && - mobile && - browserReport.schemaVersion === 'browser-validation.v1' && - browserReport.passed === true && - Array.isArray(browserReport.viewportResults) && - browserReport.viewportResults.length === 2 && - JSON.stringify( - browserReport.viewportResults.map((entry) => entry.viewport).sort(), - ) === JSON.stringify(['desktop', 'mobile']) && - desktopViewport?.passed === true && - mobileViewport?.passed === true && - playtest?.scenario === 'lane-defense-v1' && - playtest.passed === true && - playtest.scenarioFingerprint === receipt.scenarioFingerprint && - playtest.assertions.every((entry) => entry.passed === true) && - laneDefensePlaytestRequiredAssertions.every((name) => - assertionNames.has(name), - ), - 'supervisor-autonomous-playable-lane-defense-playtest-invalid', - ); - const lifecycle = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.provider_request.lifecycle', - ); - const retryAudits = persistence.agentDb.filter( - (record) => record.recordType === 'agent.runtime.provider_request.retry', - ); - const lifecycleByRequest = new Map(); - for (const record of lifecycle) { - assert( - record.auditSchemaVersion === providerRequestLifecycleSchemaVersion && - isNonEmptyString(record.requestId), - 'supervisor-autonomous-playable-provider-lifecycle-invalid', - ); - const records = lifecycleByRequest.get(record.requestId) ?? []; - records.push(record); - lifecycleByRequest.set(record.requestId, records); - } - for (const records of lifecycleByRequest.values()) { - assert( - records.length === 2 && - records[0].status === 'started' && - ['completed', 'failed'].includes(records[1].status) && - [ - 'agentId', - 'taskId', - 'sessionId', - 'runId', - 'source', - 'requestKind', - 'requestSlot', - ].every((field) => records[0][field] === records[1][field]), - 'supervisor-autonomous-playable-provider-lifecycle-open-or-duplicate', - ); - } - assert( - lifecycleByRequest.size > 0 && - retryAudits.every( - (retry) => - lifecycleByRequest.get(retry.requestId)?.[1]?.status === 'failed' && - lifecycle.some( - (record) => - record.status === 'started' && - record.agentId === retry.agentId && - record.runId === retry.runId && - record.requestSlot === retry.nextRequestSlot, - ), - ), - 'supervisor-autonomous-playable-provider-retry-invalid', - ); - - const pendingActions = await findPendingActions(); - const reconciliationResidueCount = - supervisorAutonomousPlayableReconciliationResidueCount(persistence); - const steerRecordCount = - (await countSupervisorSwarmSteerRecords()) + - persistence.agentDb.filter((record) => - String(record.recordType ?? '').startsWith('agent.runtime.steer'), - ).length; - const approvalAuditCount = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.tool_confirmation.approved', - ).length; - const rejectionAuditCount = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.tool_confirmation.rejected', - ).length; - const confirmationAuditCount = persistence.agentDb.filter((record) => - String(record.recordType ?? '').includes('confirmation'), - ).length; - const userInputAuditCount = persistence.agentDb.filter((record) => - String(record.recordType ?? '').startsWith('agent.runtime.user_input.'), - ).length; - assert( - pendingActions.length === 0 && - Object.values(residualSidecars).every((count) => count === 0) && - reconciliationResidueCount === 0 && - approvalAuditCount === 0 && - rejectionAuditCount === 0 && - confirmationAuditCount === 0 && - userInputAuditCount === 0 && - steerRecordCount === 0 && - !/\[待确认\]|\[Needs input\]|输入 approve 或 reject|请选择 1-/u.test( - state.supervisorAutonomousPlayable.cliOutput, - ), - 'supervisor-autonomous-playable-intervention-or-residue-detected', - ); - - const publicLeaks = collectSupervisorSwarmPublicLeakEvidence(persistence, { - requireZero: true, - }); - const projectLureLeakCount = await countLureLeaks(); - const cliLureLeakCount = countExactSecrets( - Buffer.from(state.supervisorAutonomousPlayable.cliOutput), - state.lures, - ); - state.lureLeakCount = projectLureLeakCount + cliLureLeakCount; - assert(state.lureLeakCount === 0, 'sensitive-lure-leak-detected'); - state.supervisorAutonomousPlayable.privateValues = [ - ...new Set(state.supervisorSwarm.privateValues), - ]; - const absolutePaths = absolutePathVariants( - state.projectRoot, - state.options?.configDir, - state.isolatedRunner.appDataDir, - ); - let logApiKeyLeakCount = 0; - let logPrivateBodyLeakCount = 0; - let logAbsolutePathLeakCount = 0; - for (const file of await listFiles( - path.join(state.projectRoot, '.agent/logs'), - )) { - const metadata = await fs.lstat(file); - if (!metadata.isFile() || metadata.isSymbolicLink()) continue; - const bytes = await fs.readFile(file); - logApiKeyLeakCount += countExactSecrets(bytes, state.secrets); - logPrivateBodyLeakCount += countExactSecrets( - bytes, - state.supervisorAutonomousPlayable.privateValues, - ); - logAbsolutePathLeakCount += countExactSecrets(bytes, absolutePaths); - } - const browserReportApiKeyLeakCount = countExactSecrets( - reportBytes, - state.secrets, - ); - const browserReportPrivateBodyLeakCount = countExactSecrets( - reportBytes, - state.supervisorAutonomousPlayable.privateValues, - ); - const browserReportAbsolutePathLeakCount = countExactSecrets( - reportBytes, - absolutePaths, - ); - assert( - logApiKeyLeakCount === 0 && - logPrivateBodyLeakCount === 0 && - logAbsolutePathLeakCount === 0 && - browserReportApiKeyLeakCount === 0 && - browserReportPrivateBodyLeakCount === 0 && - browserReportAbsolutePathLeakCount === 0, - 'supervisor-autonomous-playable-log-or-report-leak-detected', - ); - const lifecycleProjection = buildSupervisorAutonomousPlayablePartialEvidence( - persistence, - { - residualSidecars, - pendingActionCount: pendingActions.length, - turnReport: report, - }, - ); - const providerBinding = - state.supervisorAutonomousPlayable.effectiveProviderBinding; - assert( - providerBinding && - JSON.stringify(providerBinding) === - JSON.stringify( - state.supervisorAutonomousPlayable.expectedProviderBinding, - ) && - isNonEmptyString(providerBinding.providerModel) && - isNonEmptyString(providerBinding.providerApiKind) && - isNonEmptyString(providerBinding.providerReasoningEffort) && - /^[0-9a-f]{64}$/u.test(providerBinding.providerBaseUrlSha256), - 'supervisor-autonomous-playable-provider-binding-invalid', - ); - - return { - ...emptySupervisorAutonomousPlayableEvidence(), - ...lifecycleProjection, - evidenceCompleteness: 'complete', - partialEvidenceCollected: false, - partialEvidenceReadErrorCount: 0, - partialPrivacyScanComplete: true, - stdinTaskCount: state.supervisorAutonomousPlayable.stdinWriteCount, - stdinEndedAfterTask: state.supervisorAutonomousPlayable.stdinEnded, - stdinBytes: state.supervisorAutonomousPlayable.stdinBytes, - taskSha256: hashValue(task), - providerAgentMode: providerBinding.providerAgentMode, - providerModel: providerBinding.providerModel, - providerApiKind: providerBinding.providerApiKind, - providerReasoningEffort: providerBinding.providerReasoningEffort, - providerBaseUrlSha256: providerBinding.providerBaseUrlSha256, - providerBoundAgentCount: providerBinding.boundAgentIds.length, - providerBindingMatched: true, - turnReportCaptured: true, - turnReportParentIdentityStable: true, - turnReportNewAssistantMessageCount: report.newAssistantMessageCount, - turnReportFinalReplyChars: report.finalReplyChars, - turnReportPrivateLeakCount: 0, - turnReportOutcome: report.outcome, - turnReportRuntimeCount: report.runtimeCount, - turnReportBusyRuntimeCount: report.busyRuntimeCount, - turnReportPendingTaskCount: report.pendingTaskCount, - turnReportRunningTaskCount: report.runningTaskCount, - turnReportWaitingForConfirmationCount: report.waitingForConfirmationCount, - turnReportWaitingForUserInputCount: report.waitingForUserInputCount, - turnReportReconciliationAgentCount: report.reconciliationAgentCount, - taskCount: persistence.taskSnapshot.latest.length, - terminalTaskCount: persistence.taskSnapshot.latest.length, - childTaskCount: persistence.taskSnapshot.latest.filter( - (candidate) => candidate.parentRunId === state.initialRunId, - ).length, - runtimeCount: persistence.runtimeStates.length, - childRuntimeCount: persistence.runtimeStates.filter( - (candidate) => candidate.parentRunId === state.initialRunId, - ).length, - recoveredSpecialistFailureCount: - failureDisposition.recoveredOriginalFailureCount, - recoveredOriginalFailureCount: - failureDisposition.recoveredOriginalFailureCount, - finalSupervisorAssistantCount: supervisorAssistants.length, - supervisorUserMessageCount: supervisorUsers.length, - professionalAssistantCount: professionalMessages.filter( - (message) => message.role === 'assistant', - ).length, - isolatedAssistantCount: isolatedMessages.filter( - (message) => message.role === 'assistant', - ).length, - professionalUserFacingAssistantCount: 0, - completedAuditCount: parentCompletedAudits.length, - finalizationStageCount: finalization.stageCount, - providerRequestIdentityCount: lifecycleByRequest.size, - providerLifecycleStartedCount: lifecycle.filter( - (record) => record.status === 'started', - ).length, - providerLifecycleTerminalCount: lifecycle.filter((record) => - ['completed', 'failed'].includes(record.status), - ).length, - providerLifecycleCompletedCount: lifecycle.filter( - (record) => record.status === 'completed', - ).length, - providerLifecycleFailedCount: lifecycle.filter( - (record) => record.status === 'failed', - ).length, - providerRetryAuditCount: retryAudits.length, - providerConnectivityRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'connectivity', - ).length, - providerTransportRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'transport', - ).length, - providerTimeoutRetryAuditCount: retryAudits.filter( - (record) => record.errorKind === 'timeout', - ).length, - ...sourcePayload, - openProviderLifecycleCount: 0, - duplicateProviderLifecycleCount: 0, - duplicateMessageCount, - duplicateActionLifecycleCount, - duplicateReceiptCount, - runProfileBindingCount: matchingBindings.length, - baselineRevision: contract.baselineRevision, - projectRevision: revision.revision, - projectRevisionDelta: revision.revision - contract.baselineRevision, - initialGameIndexSha256: - state.supervisorAutonomousPlayable.initialGameIndexSha256, - finalGameIndexSha256, - gameIndexChanged: true, - gameIndexBytes: finalGameIndex.length, - staticSmokePassed: true, - staticSmokeAuditCount: staticSmokeAudits.length, - autonomousCompletionContractCount: contracts.length, - autonomousPlaytestReceiptCount: receipts.length, - playtestScenario: playtest.scenario, - laneDefensePlaytestPassed: true, - laneDefenseAssertionCount: playtest.assertions.length, - laneDefensePassedAssertionCount: playtest.assertions.filter( - (entry) => entry.passed, - ).length, - browserValidationPassed: true, - desktopScreenshotSha256: desktop.digest.sha256, - desktopScreenshotBytes: desktop.bytes.length, - mobileScreenshotSha256: mobile.digest.sha256, - mobileScreenshotBytes: mobile.bytes.length, - browserReportSha256: receipt.report.sha256, - browserReportBytes: reportBytes.length, - pendingActionCount: pendingActions.length, - confirmationSidecarCount: residualSidecars.confirmations, - userInputSidecarCount: residualSidecars.userInput, - providerActionBatchSidecarCount: residualSidecars.providerActionBatches, - providerRetrySidecarCount: residualSidecars.providerRetries, - providerHandoffSidecarCount: residualSidecars.providerHandoffs, - toolPlanHandoffSidecarCount: residualSidecars.toolPlanHandoffs, - finalizationJournalCount: residualSidecars.finalizations, - reconciliationResidueCount, - approvalAuditCount, - rejectionAuditCount, - userInputAuditCount, - steerRecordCount, - providerPayloadPublicLeakCount: publicLeaks.providerPayloadPublicLeakCount, - privateBodyPublicLeakCount: publicLeaks.privateBodyPublicLeakCount, - apiKeyPublicLeakCount: publicLeaks.apiKeyPublicLeakCount, - projectPathPublicLeakCount: publicLeaks.projectPathPublicLeakCount, - formalConfigPathPublicLeakCount: - publicLeaks.formalConfigPathPublicLeakCount, - logApiKeyLeakCount, - logPrivateBodyLeakCount, - logAbsolutePathLeakCount, - browserReportApiKeyLeakCount, - browserReportPrivateBodyLeakCount, - browserReportAbsolutePathLeakCount, - paths: [ - receipt.gameIndex.path, - receipt.report.path, - desktop.digest.path, - mobile.digest.path, - ], - }; -} - -export async function runSupervisorAutonomousPlayableLaneDefenseE2e() { - await ensureOwnedRunnerStableKillSupport(); - const mode = supervisorAutonomousPlayableMode(); - state.supervisorAutonomousPlayable.freshInitBaselineUsed = false; - await seedDisposableProject({ preserveProductionInitBaseline: false }); - const initialGameIndex = await fs.readFile( - path.join(state.projectRoot, 'game/index.html'), - ); - state.supervisorAutonomousPlayable.initialGameIndexSha256 = - hashValue(initialGameIndex); - state.cliBinary = await prepareCliBinary(); - await prepareIsolatedSuiteAppData(); - state.isolatedRunner.launchAttempted = true; - - const stdin = buildSupervisorAutonomousPlayableStdin(); - const task = supervisorAutonomousPlayableLaneDefenseTask; - state.initialTask = { - chars: [...task].length, - sha256: hashValue(task), - }; - state.supervisorAutonomousPlayable.privateValues = [task]; - state.supervisorSwarm.userTask = task; - state.supervisorSwarm.privateValues = [task]; - state.supervisorAutonomousPlayableCliSession = startInteractiveCli([ - '--swarm-chat', - '--init', - '--autonomous-game-build', - ...mode.cliFlags, - state.projectRoot, - ]); - state.supervisorAutonomousPlayable.stdinWriteCount = 1; - state.supervisorAutonomousPlayable.stdinBytes = stdin.length; - state.supervisorAutonomousPlayable.stdinEnded = true; - state.supervisorAutonomousPlayableCliSession.child.stdin.end(stdin); - - const started = await waitForSupervisorAutonomousPlayableParentRuntime(task); - state.initialRunId = started.runId; - state.initialSessionId = started.sessionId; - await claimOwnedRunner(); - const report = await waitForSupervisorAutonomousPlayableCliExit( - state.supervisorAutonomousPlayableCliSession, - ); - assert( - report.outcome === 'settled', - 'supervisor-autonomous-playable-turn-not-settled', - ); - - const { persistence, residualSidecars } = - await waitForSupervisorAutonomousPlayableDurableQuiescence(); - state.identityStable = true; - state.evidence = await validateSupervisorAutonomousPlayableEvidence( - persistence, - residualSidecars, - ); - assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected'); -} - -export function emptySupervisorAutonomousPlayableEvidence() { - const mode = supervisorAutonomousPlayableMode(); - return { - scenario: mode.scenario, - targetAgentId: projectSupervisorAgentId, - runProfile: autonomousGameBuildRunProfile, - dedicatedZeroInterventionPath: true, - faultInjectionUsed: false, - activeRunnerKillCount: 0, - approveInputCount: 0, - answerInputCount: 0, - steerInputCount: 0, - stdinTaskCount: 0, - stdinEndedAfterTask: false, - stdinBytes: 0, - taskSha256: null, - providerAgentMode: null, - providerModel: null, - providerApiKind: null, - providerReasoningEffort: null, - providerBaseUrlSha256: null, - providerBoundAgentCount: 0, - providerBindingMatched: false, - evidenceCompleteness: 'none', - partialEvidenceCollected: false, - partialEvidenceReadErrorCount: 0, - partialPrivacyScanComplete: false, - rootRunObserved: false, - parentTaskStatus: null, - parentTaskPhase: null, - parentRuntimeStatus: null, - parentRuntimePhase: null, - childTaskStatusCounts: {}, - childTaskPhaseCounts: {}, - childRuntimeStatusCounts: {}, - childRuntimePhaseCounts: {}, - initialDeliveryStatusCounts: {}, - repairDeliveryStatusCounts: {}, - repairTerminalStatusCounts: {}, - failedOriginalChildCount: 0, - failedRepairChildCount: 0, - recoveredSpecialistFailureCount: 0, - recoveredOriginalFailureCount: 0, - awaitingRepairCount: 0, - isolatedAppDataUsed: false, - formalConfigCliCallCount: 0, - sourceRunnerEndpointUnchanged: false, - sourceAppDataDirectoryUntouched: false, - sourceConfigReplicaCount: 0, - sourceConfigReplicasVerified: false, - turnReportOutcome: null, - turnReportCaptured: false, - turnReportParentIdentityStable: false, - turnReportNewAssistantMessageCount: null, - turnReportFinalReplyChars: null, - turnReportPrivateLeakCount: null, - turnReportRuntimeCount: 0, - turnReportBusyRuntimeCount: 0, - turnReportPendingTaskCount: 0, - turnReportRunningTaskCount: 0, - turnReportWaitingForConfirmationCount: 0, - turnReportWaitingForUserInputCount: 0, - turnReportReconciliationAgentCount: 0, - taskCount: 0, - terminalTaskCount: 0, - childTaskCount: 0, - runtimeCount: 0, - childRuntimeCount: 0, - uniqueRootRunCount: 0, - recursiveDescendantCount: 0, - completedDescendantCount: 0, - artChildCount: 0, - artDeliveryClaimCount: 0, - assetListBeforeArtDelegation: false, - finalSupervisorAssistantCount: 0, - supervisorUserMessageCount: 0, - professionalAssistantCount: 0, - isolatedAssistantCount: 0, - professionalUserFacingAssistantCount: 0, - completedAuditCount: 0, - finalizationStageCount: 0, - providerRequestIdentityCount: 0, - providerLifecycleStartedCount: 0, - providerLifecycleTerminalCount: 0, - providerLifecycleCompletedCount: 0, - providerLifecycleFailedCount: 0, - providerRetryAuditCount: 0, - providerConnectivityRetryAuditCount: 0, - providerTransportRetryAuditCount: 0, - providerTimeoutRetryAuditCount: 0, - acceptedAutonomousToolPlanCount: 0, - sourceMutationActionMax: 0, - sourcePayloadMaxFieldChars: 0, - sourcePayloadMaxTotalChars: 0, - sourcePayloadPolicyViolationCount: 0, - openProviderLifecycleCount: 0, - duplicateProviderLifecycleCount: 0, - duplicateMessageCount: 0, - duplicateActionLifecycleCount: 0, - duplicateReceiptCount: 0, - runProfileBindingCount: 0, - baselineRevision: 0, - projectRevision: 0, - projectRevisionDelta: 0, - freshInitBaselineUsed: false, - initialGameIndexMatchesProductionDefault: false, - initialGameIndexSha256: null, - finalGameIndexSha256: null, - gameIndexChanged: false, - finalGameIndexDiffersFromBaseline: false, - uniqueFixedChildCodePrototype: false, - gameIndexBytes: 0, - staticSmokePassed: false, - staticSmokeGameIndexSha256Matched: false, - staticSmokeAuditCount: 0, - autonomousCompletionContractCount: 0, - autonomousPlaytestReceiptCount: 0, - playtestScenario: null, - laneDefensePlaytestPassed: false, - laneDefenseAssertionCount: 0, - laneDefensePassedAssertionCount: 0, - browserValidationPassed: false, - desktopPlaytestPassed: false, - mobilePlaytestPassed: false, - desktopScreenshotSha256: null, - desktopScreenshotBytes: 0, - mobileScreenshotSha256: null, - mobileScreenshotBytes: 0, - browserReportSha256: null, - browserReportBytes: 0, - pendingActionCount: 0, - confirmationSidecarCount: 0, - userInputSidecarCount: 0, - providerActionBatchSidecarCount: 0, - providerRetrySidecarCount: 0, - providerHandoffSidecarCount: 0, - toolPlanHandoffSidecarCount: 0, - finalizationJournalCount: 0, - reconciliationResidueCount: 0, - approvalAuditCount: 0, - rejectionAuditCount: 0, - userInputAuditCount: 0, - steerRecordCount: 0, - providerPayloadPublicLeakCount: 0, - privateBodyPublicLeakCount: 0, - apiKeyPublicLeakCount: 0, - projectPathPublicLeakCount: 0, - formalConfigPathPublicLeakCount: 0, - logApiKeyLeakCount: 0, - logPrivateBodyLeakCount: 0, - logAbsolutePathLeakCount: 0, - browserReportApiKeyLeakCount: 0, - browserReportPrivateBodyLeakCount: 0, - browserReportAbsolutePathLeakCount: 0, - supervisorAutonomousPlayableReportLeakCount: 0, - supervisorAutonomousPlayableRunnerStopped: false, - supervisorAutonomousPlayableAppDataCleanupPerformed: false, - ownedProcessIdentityCaptured: false, - ownedRunnerObservedCount: 0, - ownedHelperObservedCount: 0, - ownedNodeDescendantObservedCount: 0, - ownedBrowserDescendantObservedCount: 0, - ownedCommandDescendantObservedCount: 0, - ownedRunnerResidualCount: 0, - ownedHelperResidualCount: 0, - ownedNodeDescendantResidualCount: 0, - ownedBrowserDescendantResidualCount: 0, - ownedCommandDescendantResidualCount: 0, - activeCommandChildrenAfterCleanup: 0, - activeInteractiveCliSessionsAfterCleanup: 0, - ownedProcessCleanupPassed: false, - secretLeakCount: 0, - lureLeakCount: 0, - paths: [], - }; -} - -export function isSupervisorAutonomousPlayableLaneDefenseSuite() { - return state.suite === supervisorAutonomousPlayableLaneDefenseSuite; -} diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm.mjs index 65f2668b1..80f725314 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm.mjs @@ -1,4 +1,3 @@ -export * from './supervisor-swarm/chat-session.mjs'; export * from './supervisor-swarm/collaboration-assertions.mjs'; export * from './supervisor-swarm/collaboration-policy.mjs'; export * from './supervisor-swarm/evidence-schema.mjs'; diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/chat-session.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/chat-session.mjs deleted file mode 100644 index b968340a5..000000000 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/chat-session.mjs +++ /dev/null @@ -1,108 +0,0 @@ -import { - assert, - codedError, - commandPassedMarker, - findPendingActions, - readRuntime, - safeProcessFailureDiagnostic, - state, - supervisorSwarmConfirmedTools, - supervisorSwarmDesignContent, - supervisorSwarmDesignPath, - supervisorSwarmQualityContent, - supervisorSwarmQualityPath, - supervisorSwarmWeakQualityContent, - visibleText, - waitForInteractiveCliOutput, - writeInteractiveCliLine, -} from './shared.mjs'; - -export function supervisorSwarmVerificationFixtureSource() { - return `import fs from 'node:fs';\nconst html = fs.readFileSync('game/index.html', 'utf8');\nconst agents = fs.readFileSync('AGENTS.md', 'utf8');\nconst optionalExact = (file, expected) => !fs.existsSync(file) || fs.readFileSync(file, 'utf8') === expected;\nconst passed = html.includes(${JSON.stringify(visibleText)}) && html.includes(' { - if (state.confirmedActionIds.has(pending.actionId)) return false; - if (!shouldConfirm(pending)) return false; - assert( - allowed.has(pending.tool), - `pending-tool-not-allowed-in-scenario:${pending.tool}`, - ); - return true; - }); - if (candidates.length === 0) return; - const output = await waitForInteractiveCliOutput( - session, - (value) => - candidates.some((pending) => - value.includes( - `[待确认] agent=${pending.agentId} run=${pending.runId} action=${pending.actionId} tool=${pending.tool}`, - ), - ), - 'supervisor-swarm-chat-confirmation-prompt-timeout', - 120_000, - ); - const pending = candidates.find((candidate) => - output.includes( - `[待确认] agent=${candidate.agentId} run=${candidate.runId} action=${candidate.actionId} tool=${candidate.tool}`, - ), - ); - assert(pending, 'supervisor-swarm-chat-prompted-action-missing'); - const runtime = await readRuntime(pending.agentId); - assert(runtime.runId === pending.runId, 'pending-run-mismatch'); - writeInteractiveCliLine(session, 'approve'); - await waitForInteractiveCliOutput( - session, - (output) => output.includes(`[已批准] ${pending.actionId}`), - 'supervisor-swarm-chat-confirmation-result-timeout', - 120_000, - ); - state.confirmedActionIds.add(pending.actionId); -} diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-assertions.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-assertions.mjs index 321937131..fae07caa3 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-assertions.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-assertions.mjs @@ -1,35 +1,15 @@ -import { - assertSupervisorSwarmIsolatedChildBusinessContract, - supervisorSwarmIsolatedReviewGroupIndex, - supervisorSwarmIsolatedReviewsForGroupIndex, - supervisorSwarmIsolatedWriteScopeRoots, - validateSupervisorSwarmIsolatedSpawnInput, -} from './collaboration-policy.mjs'; import { supervisorSwarmDeliveryIdentity, supervisorSwarmEffectiveAgentPolicy, - supervisorSwarmIsolatedGroupIdentity, - supervisorSwarmIsolatedInstanceIdentity, - supervisorSwarmIsolatedJoinClaimIdentity, - supervisorSwarmIsolatedJoinIdentity, - supervisorSwarmIsolatedResultIdentity, supervisorSwarmParentIsolatedRecords, - supervisorSwarmTaskIdentity, } from './persistence.mjs'; import { assert, codedError, - hashJsonValue, hashValue, isFailedTask, isLiveTask, isNonEmptyString, - isolatedAgentGroupSchemaVersion, - isolatedAgentInstanceSchemaVersion, - isolatedAgentJoinClaimSchemaVersion, - isolatedAgentJoinDeliverySchemaVersion, - isolatedAgentResultSchemaVersion, - isolatedJoinDeliveryTarget, isPlainObject, projectSupervisorAgentId, state, @@ -37,18 +17,12 @@ import { staticDelegateDeliverySchemaVersion, supervisorSwarmDesignAgentId, supervisorSwarmDesignPath, - supervisorSwarmIsolatedReviews, supervisorSwarmProviderRetryAuditGraceSeconds, supervisorSwarmQualityAgentId, supervisorSwarmQualityPath, supervisorSwarmSessionId, supervisorSwarmWeakQualityContent, } from './shared.mjs'; -import { - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, - supervisorSwarmExpectedIsolatedReviewGroups, -} from './suite-selection.mjs'; export function supervisorSwarmProviderIntervals(agentDb, agentId, runId) { const records = agentDb @@ -113,46 +87,6 @@ export function observeSupervisorSwarmInitialProviderOverlap( return false; } -export function observeSupervisorSwarmStaticIsolatedProviderOverlap( - agentDb, - deliveries, - instances, -) { - if (!isSupervisorSwarmMixedHarnessSuite()) return false; - for (const delivery of deliveries) { - const staticIntervals = supervisorSwarmProviderIntervals( - agentDb, - delivery.targetAgentId, - delivery.targetRunId, - ); - for (const instance of instances) { - const isolatedIntervals = supervisorSwarmProviderIntervals( - agentDb, - instance.instanceId, - instance.runId, - ); - for (const staticInterval of staticIntervals) { - for (const isolatedInterval of isolatedIntervals) { - if ( - supervisorSwarmProviderIntervalsOverlap( - staticInterval, - isolatedInterval, - ) - ) { - state.supervisorSwarm.staticIsolatedProviderRequestIds = [ - staticInterval.started.record.requestId, - isolatedInterval.started.record.requestId, - ]; - state.supervisorSwarm.staticIsolatedProviderOverlapObserved = true; - return true; - } - } - } - } - } - return false; -} - export function supervisorSwarmParentDeliveries(deliveries) { return deliveries.filter( (delivery) => @@ -526,546 +460,6 @@ export function supervisorSwarmArtifactHash(delivery, expectedPath) { return artifact?.sha256 ?? null; } -export function supervisorSwarmMixedIsolatedGroupEntries( - groups, - expectedReviewGroups = supervisorSwarmExpectedIsolatedReviewGroups(), -) { - assert( - Array.isArray(groups) && groups.length === expectedReviewGroups.length, - 'supervisor-swarm-mixed-group-count-invalid', - ); - const entries = groups.map((group) => ({ - group, - groupIndex: supervisorSwarmIsolatedReviewGroupIndex( - group?.request?.children, - expectedReviewGroups, - ), - })); - assert( - entries.every(({ groupIndex }) => groupIndex >= 0) && - new Set(entries.map(({ groupIndex }) => groupIndex)).size === - expectedReviewGroups.length, - 'supervisor-swarm-mixed-group-review-partition-invalid', - ); - return entries.sort((left, right) => left.groupIndex - right.groupIndex); -} - -export function supervisorSwarmMixedSpawnRequestHashesStable(groups) { - const expectedReviewGroups = supervisorSwarmExpectedIsolatedReviewGroups(); - if (!Array.isArray(groups) || groups.length !== expectedReviewGroups.length) { - return false; - } - const entries = groups - .map((group) => ({ - group, - groupIndex: supervisorSwarmIsolatedReviewGroupIndex( - group?.request?.children, - expectedReviewGroups, - ), - })) - .sort((left, right) => left.groupIndex - right.groupIndex); - return ( - entries.every(({ groupIndex }) => groupIndex >= 0) && - new Set(entries.map(({ groupIndex }) => groupIndex)).size === - expectedReviewGroups.length && - hashJsonValue(entries[0].group.request) === - state.supervisorSwarm.mixedSpawnRequestHash && - (!isSupervisorSwarmMultiIsolatedHarnessSuite() || - hashJsonValue(entries[1].group.request) === - state.supervisorSwarm.mixedFollowupSpawnRequestHash) - ); -} - -export function supervisorSwarmReadyIsolatedGroupIds(detail) { - assert( - isNonEmptyString(detail), - 'supervisor-swarm-mixed-ready-join-detail-missing', - ); - const payloadBlocks = detail - .split('\n\n') - .filter((block) => block.startsWith('readyIsolatedJoins: ')); - assert( - payloadBlocks.length === 1, - 'supervisor-swarm-mixed-ready-join-prefix-count-invalid', - ); - let payload; - try { - payload = JSON.parse(payloadBlocks[0].slice('readyIsolatedJoins: '.length)); - } catch (error) { - throw codedError('supervisor-swarm-mixed-ready-join-json-invalid', error); - } - const groupIds = payload?.joins?.map((join) => join?.delegationGroupId); - assert( - payload?.ready === true && - Array.isArray(groupIds) && - groupIds.length > 0 && - groupIds.every(isNonEmptyString) && - new Set(groupIds).size === groupIds.length, - 'supervisor-swarm-mixed-ready-join-payload-invalid', - ); - return [...groupIds].sort(); -} - -export function supervisorSwarmObservedJoinClaimForGroups( - claims, - expectedGroupIds, -) { - assert( - Array.isArray(claims) && - claims.length === 1 && - Array.isArray(expectedGroupIds) && - expectedGroupIds.length > 0, - 'supervisor-swarm-mixed-observed-join-claim-count-invalid', - ); - const claim = claims[0]; - const claimedGroupIds = (claim?.joins ?? []) - .map((join) => join?.delegationGroupId) - .filter(isNonEmptyString) - .sort(); - assert( - claim?.schemaVersion === isolatedAgentJoinClaimSchemaVersion && - claim.status === 'observed' && - isNonEmptyString(claim.actionId) && - Array.isArray(claim.joins) && - claimedGroupIds.length === claim.joins.length && - new Set(claimedGroupIds).size === claimedGroupIds.length && - JSON.stringify(claimedGroupIds) === JSON.stringify(expectedGroupIds), - 'supervisor-swarm-mixed-observed-join-claim-invalid', - ); - return claim; -} - -export function supervisorSwarmFollowupBeforeClaimOrderValid( - initialParentWakeIndex, - followupSpawnIndex, - claimAuditIndexes, -) { - return ( - Number.isSafeInteger(initialParentWakeIndex) && - Number.isSafeInteger(followupSpawnIndex) && - Array.isArray(claimAuditIndexes) && - claimAuditIndexes.length > 0 && - claimAuditIndexes.every(Number.isSafeInteger) && - initialParentWakeIndex >= 0 && - initialParentWakeIndex < followupSpawnIndex && - followupSpawnIndex < Math.min(...claimAuditIndexes) - ); -} - -export function supervisorSwarmMixedIsolatedClaimsReady(persistence) { - const records = supervisorSwarmParentIsolatedRecords(persistence); - const expectedGroupCount = - supervisorSwarmExpectedIsolatedReviewGroups().length; - return ( - records.groups.length === expectedGroupCount && - records.instances.length === supervisorSwarmIsolatedReviews.length && - records.results.length === supervisorSwarmIsolatedReviews.length && - records.results.every((record) => record.result?.status === 'completed') && - records.joinDeliveries.length === expectedGroupCount && - records.joinDeliveries.every( - (delivery) => delivery.status === 'claimed-by-parent', - ) - ); -} - -export function validateSupervisorSwarmMixedIsolatedPersistence(persistence) { - assert( - isSupervisorSwarmMixedHarnessSuite(), - 'supervisor-swarm-mixed-validation-outside-suite', - ); - const { groups, instances, results, joinDeliveries } = - supervisorSwarmParentIsolatedRecords(persistence); - const groupEntries = supervisorSwarmMixedIsolatedGroupEntries(groups); - assert( - persistence.isolatedGroups.length === groupEntries.length && - isNonEmptyString(state.supervisorSwarm.mixedSpawnActionId) && - (!isSupervisorSwarmMultiIsolatedHarnessSuite() || - (isNonEmptyString(state.supervisorSwarm.mixedFollowupSpawnActionId) && - state.supervisorSwarm.mixedSpawnActionId !== - state.supervisorSwarm.mixedFollowupSpawnActionId)), - 'supervisor-swarm-mixed-group-invalid', - ); - for (const { group, groupIndex } of groupEntries) { - const expectedReviewGroups = supervisorSwarmExpectedIsolatedReviewGroups(); - const reviews = supervisorSwarmIsolatedReviewsForGroupIndex( - groupIndex, - expectedReviewGroups, - ); - const expectedActionId = - groupIndex === 0 - ? state.supervisorSwarm.mixedSpawnActionId - : state.supervisorSwarm.mixedFollowupSpawnActionId; - const expectedRequestHash = - groupIndex === 0 - ? state.supervisorSwarm.mixedSpawnRequestHash - : state.supervisorSwarm.mixedFollowupSpawnRequestHash; - assert( - group.schemaVersion === isolatedAgentGroupSchemaVersion && - group.parentAgentId === projectSupervisorAgentId && - group.parentSessionId === supervisorSwarmSessionId && - group.parentRunId === state.initialRunId && - group.parentActionId === expectedActionId && - group.joinMode === 'all' && - group.depth === 1 && - Array.isArray(group.instanceIds) && - group.instanceIds.length === reviews.length && - hashJsonValue(group.request) === expectedRequestHash, - 'supervisor-swarm-mixed-group-contract-invalid', - ); - validateSupervisorSwarmIsolatedSpawnInput( - group.request, - groupIndex, - 'supervisor-swarm-mixed-persisted', - expectedReviewGroups, - ); - } - supervisorSwarmIsolatedWriteScopeRoots( - groupEntries.flatMap(({ group }) => group.request.children), - ); - const groupById = new Map( - groupEntries.map((entry) => [entry.group.delegationGroupId, entry]), - ); - const expectedByPath = new Map( - supervisorSwarmIsolatedReviews.map((review) => [review.path, review]), - ); - assert( - instances.length === supervisorSwarmIsolatedReviews.length && - persistence.isolatedInstances.length === instances.length && - new Set(instances.map((instance) => instance.instanceId)).size === - instances.length && - new Set(instances.map((instance) => instance.delegationId)).size === - instances.length && - new Set(instances.map((instance) => instance.sessionId)).size === - instances.length && - new Set(instances.map((instance) => instance.runId)).size === - instances.length, - 'supervisor-swarm-mixed-instance-cardinality-invalid', - ); - for (const { group, groupIndex } of groupEntries) { - const groupInstances = instances.filter( - (instance) => instance.delegationGroupId === group.delegationGroupId, - ); - const expectedIndexes = supervisorSwarmIsolatedReviewsForGroupIndex( - groupIndex, - supervisorSwarmExpectedIsolatedReviewGroups(), - ).map((_, index) => index); - assert( - groupInstances.length === expectedIndexes.length && - JSON.stringify( - groupInstances - .map((instance) => instance.childIndex) - .sort((left, right) => left - right), - ) === JSON.stringify(expectedIndexes) && - JSON.stringify( - groupInstances.map((instance) => instance.instanceId).sort(), - ) === JSON.stringify([...group.instanceIds].sort()), - 'supervisor-swarm-mixed-group-instance-set-invalid', - ); - } - for (const instance of instances) { - const groupEntry = groupById.get(instance.delegationGroupId); - const group = groupEntry?.group; - const expectedPath = instance.expectedArtifacts?.[0]; - const review = expectedByPath.get(expectedPath); - const requestChild = group?.request?.children?.[instance.childIndex]; - assert( - groupEntry && - review && - instance.schemaVersion === isolatedAgentInstanceSchemaVersion && - instance.parentAgentId === projectSupervisorAgentId && - instance.parentSessionId === supervisorSwarmSessionId && - instance.parentRunId === state.initialRunId && - instance.parentActionId === group.parentActionId && - instance.delegationGroupId === group.delegationGroupId && - group.instanceIds.includes(instance.instanceId) && - isNonEmptyString(instance.templateAgentId) && - isNonEmptyString(instance.task) && - instance.depth === 1 && - JSON.stringify(instance.acceptanceCriteria) === - JSON.stringify(requestChild?.acceptanceCriteria) && - JSON.stringify(instance.expectedArtifacts) === - JSON.stringify([review.path]) && - JSON.stringify(requestChild?.expectedArtifacts) === - JSON.stringify(instance.expectedArtifacts) && - JSON.stringify(requestChild?.writeScopes) === - JSON.stringify(instance.writeScopes) && - requestChild?.templateAgentId === instance.templateAgentId && - requestChild?.task === instance.task, - 'supervisor-swarm-mixed-instance-contract-invalid', - ); - assertSupervisorSwarmIsolatedChildBusinessContract(requestChild, review); - } - assert( - results.length === instances.length && - persistence.isolatedResults.length === results.length && - new Set(results.map((record) => record.result?.instanceId)).size === - results.length, - 'supervisor-swarm-mixed-result-cardinality-invalid', - ); - for (const record of results) { - const instance = instances.find( - (candidate) => candidate.instanceId === record.result?.instanceId, - ); - const groupEntry = groupById.get(record.delegationGroupId); - const review = expectedByPath.get(instance?.expectedArtifacts?.[0]); - const artifact = record.result?.artifacts?.find( - (candidate) => candidate.path === review?.path, - ); - assert( - instance && - groupEntry && - review && - record.schemaVersion === isolatedAgentResultSchemaVersion && - record.delegationGroupId === instance.delegationGroupId && - record.childIndex === instance.childIndex && - record.result.delegationId === instance.delegationId && - record.result.templateAgentId === instance.templateAgentId && - record.result.runId === instance.runId && - record.result.status === 'completed' && - isNonEmptyString(record.result.summary) && - Array.isArray(record.result.artifacts) && - JSON.stringify(record.result.artifacts) === - JSON.stringify([ - { path: review.path, sha256: hashValue(review.content) }, - ]) && - artifact?.sha256 === hashValue(review.content) && - Array.isArray(record.result.evidence) && - (record.result.verifiedRevision == null || - (Number.isSafeInteger(record.result.verifiedRevision) && - record.result.verifiedRevision >= 0)) && - record.result.error == null, - 'supervisor-swarm-mixed-result-invalid', - ); - } - assert( - joinDeliveries.length === groupEntries.length && - persistence.isolatedJoinDeliveries.length === groupEntries.length, - 'supervisor-swarm-mixed-join-delivery-invalid', - ); - const joinDeliveryByGroupId = new Map( - joinDeliveries.map((delivery) => [delivery.delegationGroupId, delivery]), - ); - for (const { group } of groupEntries) { - const delivery = joinDeliveryByGroupId.get(group.delegationGroupId); - assert( - delivery?.schemaVersion === isolatedAgentJoinDeliverySchemaVersion && - delivery.parentAgentId === projectSupervisorAgentId && - delivery.parentRunId === state.initialRunId && - delivery.joinRunId === group.joinRunId && - delivery.status === 'claimed-by-parent' && - isolatedJoinDeliveryTarget(delivery) === 'parent-wake' && - delivery.queuedRunId == null && - isNonEmptyString(delivery.claimedByActionId), - 'supervisor-swarm-mixed-group-join-delivery-invalid', - ); - } - const isolatedTasks = persistence.taskSnapshot.latest.filter((task) => - instances.some( - (instance) => - task.agentId === instance.instanceId && - task.sessionId === instance.sessionId && - task.runId === instance.runId && - task.delegationId === instance.delegationId, - ), - ); - const joinRunIds = new Set(groupEntries.map(({ group }) => group.joinRunId)); - const continuationTasks = persistence.taskSnapshot.all.filter( - (task) => - task.source === 'agent-isolated-join' && joinRunIds.has(task.runId), - ); - assert( - isolatedTasks.length === instances.length && - isolatedTasks.every( - (task) => - task.source === 'agent-isolated-child' && - task.parentAgentId === projectSupervisorAgentId && - task.parentRunId === state.initialRunId && - task.status === 'completed' && - task.phase === 'completed', - ) && - continuationTasks.length === 0, - 'supervisor-swarm-mixed-child-or-continuation-task-invalid', - ); - const spawnAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.agent.spawn_isolated' && - record.agentId === projectSupervisorAgentId && - record.sessionId === supervisorSwarmSessionId && - record.runId === state.initialRunId && - groupById.has(record.delegationGroupId), - ); - const parentWakeAudits = persistence.agentDb.filter( - (record) => - record.recordType === - 'agent.runtime.agent.isolated_join.parent_wake.dispatched' && - record.agentId === projectSupervisorAgentId && - record.sessionId === supervisorSwarmSessionId && - record.parentRunId === state.initialRunId && - groupById.has(record.delegationGroupId), - ); - const continuationAudits = persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.agent.isolated_join.dispatched' && - record.parentRunId === state.initialRunId && - groupById.has(record.delegationGroupId), - ); - const claimAudits = persistence.agentDb - .map((record, index) => ({ record, index })) - .filter( - ({ record }) => - record.recordType === - 'agent.runtime.agent.isolated_join.claimed_by_parent' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - groupById.has(record.delegationGroupId), - ); - const claimObservations = persistence.agentDb - .map((record, index) => ({ record, index })) - .filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_observation' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - record.tool === 'agent.run_status' && - record.status === 'ok' && - String(record.summary ?? '').includes('ready all-join'), - ); - const expectedGroupIds = [...groupById.keys()].sort(); - const joinClaims = (persistence.isolatedJoinClaims ?? []).filter( - (claim) => - claim.parentAgentId === projectSupervisorAgentId && - claim.parentRunId === state.initialRunId, - ); - const joinClaim = supervisorSwarmObservedJoinClaimForGroups( - joinClaims, - expectedGroupIds, - ); - assert( - spawnAudits.length === groupEntries.length && - parentWakeAudits.length === groupEntries.length && - continuationAudits.length === 0 && - claimAudits.length === groupEntries.length && - claimObservations.length === 1 && - persistence.isolatedJoinClaims.length === 1 && - joinClaim.actionId === claimObservations[0].record.actionId && - joinClaim.joins.length === groupEntries.length && - String(claimObservations[0].record.summary ?? '').includes( - `${groupEntries.length} 个 ready all-join`, - ) && - JSON.stringify( - spawnAudits.map((record) => record.delegationGroupId).sort(), - ) === JSON.stringify(expectedGroupIds) && - JSON.stringify( - parentWakeAudits.map((record) => record.delegationGroupId).sort(), - ) === JSON.stringify(expectedGroupIds) && - JSON.stringify( - claimAudits.map(({ record }) => record.delegationGroupId).sort(), - ) === JSON.stringify(expectedGroupIds) && - claimAudits.every( - ({ record, index }) => - record.actionId === claimObservations[0].record.actionId && - joinDeliveryByGroupId.get(record.delegationGroupId) - ?.claimedByActionId === record.actionId && - index < claimObservations[0].index, - ), - 'supervisor-swarm-mixed-spawn-or-claim-audit-invalid', - ); - if (isSupervisorSwarmMultiIsolatedHarnessSuite()) { - const initialGroupId = groupEntries[0].group.delegationGroupId; - const followupGroupId = groupEntries[1].group.delegationGroupId; - const initialParentWakeIndex = persistence.agentDb.findIndex( - (record) => - record.recordType === - 'agent.runtime.agent.isolated_join.parent_wake.dispatched' && - record.delegationGroupId === initialGroupId, - ); - const followupSpawnIndex = persistence.agentDb.findIndex( - (record) => - record.recordType === 'agent.runtime.agent.spawn_isolated' && - record.delegationGroupId === followupGroupId, - ); - assert( - supervisorSwarmFollowupBeforeClaimOrderValid( - initialParentWakeIndex, - followupSpawnIndex, - claimAudits.map(({ index }) => index), - ), - 'supervisor-swarm-mixed-followup-before-claim-order-invalid', - ); - } - for (const { group } of groupEntries) { - assert( - spawnAudits.filter( - (record) => - record.actionId === group.parentActionId && - record.delegationGroupId === group.delegationGroupId && - record.joinRunId === group.joinRunId && - Array.isArray(record.children) && - record.children.length === group.instanceIds.length, - ).length === 1 && - parentWakeAudits.filter( - (record) => - record.parentActionId === group.parentActionId && - record.delegationGroupId === group.delegationGroupId && - record.joinRunId === group.joinRunId, - ).length === 1 && - claimAudits.filter( - ({ record }) => - record.parentActionId === group.parentActionId && - record.delegationGroupId === group.delegationGroupId && - record.joinRunId === group.joinRunId, - ).length === 1 && - joinClaim.joins.filter( - (join) => - join.parentActionId === group.parentActionId && - join.delegationGroupId === group.delegationGroupId && - join.joinRunId === group.joinRunId && - join.parentAgentId === projectSupervisorAgentId && - join.parentSessionId === supervisorSwarmSessionId && - join.parentRunId === state.initialRunId && - join.source === 'agent-isolated-join', - ).length === 1, - 'supervisor-swarm-mixed-group-audit-identity-invalid', - ); - } - return { - groups: groupEntries.map(({ group }) => group), - instances, - results, - joinDeliveries: groupEntries.map(({ group }) => - joinDeliveryByGroupId.get(group.delegationGroupId), - ), - joinClaims, - isolatedTasks, - continuationTasks, - continuationAudits, - claimAuditIndex: Math.max(...claimAudits.map(({ index }) => index)), - claimObservationIndex: claimObservations[0].index, - identity: { - groups: groupEntries - .map(({ group }) => supervisorSwarmIsolatedGroupIdentity(group)) - .sort((left, right) => - left.delegationGroupId.localeCompare(right.delegationGroupId), - ), - instances: instances - .map(supervisorSwarmIsolatedInstanceIdentity) - .sort((left, right) => left.instanceId.localeCompare(right.instanceId)), - results: results - .map(supervisorSwarmIsolatedResultIdentity) - .sort((left, right) => left.instanceId.localeCompare(right.instanceId)), - joinDeliveries: joinDeliveries - .map(supervisorSwarmIsolatedJoinIdentity) - .sort((left, right) => - left.delegationGroupId.localeCompare(right.delegationGroupId), - ), - joinClaims: joinClaims.map(supervisorSwarmIsolatedJoinClaimIdentity), - tasks: isolatedTasks - .map(supervisorSwarmTaskIdentity) - .sort((left, right) => left.agentId.localeCompare(right.agentId)), - }, - }; -} - export function assertSupervisorSwarmWeakQualityDelivery(delivery) { assert( delivery.status === 'claimed-by-parent' && diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-policy.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-policy.mjs index 4a39435e1..fd13dbbdd 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-policy.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/collaboration-policy.mjs @@ -1,45 +1,23 @@ import { assertSupervisorSwarmProviderFailureRecoverable } from './collaboration-assertions.mjs'; -import { - readSupervisorSwarmPersistence, - supervisorSwarmParentIsolatedRecords, -} from './persistence.mjs'; -import { - confirmSupervisorSwarmPendingActions, - readSupervisorSwarmExecutionOwner, - supervisorSwarmProviderStartedIdentities, -} from './repair-recovery.mjs'; -import { readSupervisorSwarmProjectRevision } from './runtime-setup.mjs'; -import { - expectedSupervisorSwarmCollaborationPolicy, - writeSupervisorSwarmCollaborationPolicyDriftFixture, -} from './setup.mjs'; +import { expectedSupervisorSwarmCollaborationPolicy } from './setup.mjs'; import { assert, canonicalJsonValue, - claimOwnedRunner, codedError, - configFileName, duplicateCount, fs, - gitSensitivePath, hasExactKeys, hashJsonValue, hashValue, isFailedTask, isNonEmptyString, isPlainObject, - killRunnerOnce, - listFiles, - localConfigFileName, path, projectSupervisorAgentId, providerActionBatchSchemaVersion, readJson, readOptionalJsonl, readTaskSnapshot, - runCli, - runProcess, - sentinelFileName, sleep, state, supervisorCollaborationContractSchemaVersion, @@ -49,23 +27,11 @@ import { supervisorCollaborationPolicySnapshotSchemaVersion, supervisorSwarmDesignAgentId, supervisorSwarmDesignPath, - supervisorSwarmIsolatedReviewGroups, - supervisorSwarmProjectMutationTools, + supervisorSwarmIsolatedReviews, supervisorSwarmQualityAgentId, supervisorSwarmQualityPath, supervisorSwarmSessionId, - verifyOwnedRunnerForKill, - waitForRunnerBootChange, } from './shared.mjs'; -import { - isSupervisorSwarmAutonomousChatSuite, - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, - supervisorSwarmExpectedIsolatedReviewGroups, - supervisorSwarmInitialIsolatedReviewsForSuite, -} from './suite-selection.mjs'; export function assertSupervisorSwarmContractShape( input, @@ -145,7 +111,7 @@ export function supervisorSwarmIsolatedWriteScopeRoots(children) { export function supervisorSwarmIsolatedReviewGroupIndex( children, - reviewGroups = supervisorSwarmIsolatedReviewGroups, + reviewGroups = [supervisorSwarmIsolatedReviews], ) { if (!Array.isArray(children) || children.length === 0) return -1; const actualPaths = children @@ -162,7 +128,7 @@ export function supervisorSwarmIsolatedReviewGroupIndex( export function supervisorSwarmIsolatedReviewsForGroupIndex( groupIndex, - reviewGroups = supervisorSwarmIsolatedReviewGroups, + reviewGroups = [supervisorSwarmIsolatedReviews], ) { const reviews = reviewGroups[groupIndex]; assert( @@ -192,7 +158,7 @@ export function validateSupervisorSwarmIsolatedSpawnInput( input, groupIndex, codePrefix, - reviewGroups = supervisorSwarmIsolatedReviewGroups, + reviewGroups = [supervisorSwarmIsolatedReviews], ) { const reviews = supervisorSwarmIsolatedReviewsForGroupIndex( groupIndex, @@ -467,7 +433,7 @@ export function supervisorSwarmCollaborationPolicyDriftObservationCount( ).length; } -export function validateSupervisorSwarmCollaborationContract(contract, mixed) { +export function validateSupervisorSwarmCollaborationContract(contract) { const expectedPolicy = expectedSupervisorSwarmCollaborationPolicy(); const expectedStaticAgentIds = [ supervisorSwarmDesignAgentId, @@ -494,9 +460,8 @@ export function validateSupervisorSwarmCollaborationContract(contract, mixed) { JSON.stringify(contract.initialStaticAgentIds) === JSON.stringify(expectedStaticAgentIds) && contract.repairDelegateCount === 0 && - contract.isolatedSpawnCount === (mixed ? 1 : 0) && - contract.isolatedChildCount === - (mixed ? supervisorSwarmInitialIsolatedReviewsForSuite().length : 0) && + contract.isolatedSpawnCount === 0 && + contract.isolatedChildCount === 0 && /^[0-9a-f]{64}$/u.test(contract.policyFingerprint ?? '') && /^[0-9a-f]{64}$/u.test(contract.contractFingerprint ?? ''), 'supervisor-swarm-initial-collaboration-contract-invalid', @@ -563,8 +528,7 @@ export function supervisorSwarmInitialProviderBatchRecoveryIdentity(batch) { } export function validateSupervisorSwarmInitialProviderBatch(batch) { - const mixed = isSupervisorSwarmMixedHarnessSuite(); - const expectedActionCount = mixed ? 3 : 2; + const expectedActionCount = 2; assert( batch?.schemaVersion === providerActionBatchSchemaVersion && isNonEmptyString(batch.batchId) && @@ -587,22 +551,12 @@ export function validateSupervisorSwarmInitialProviderBatch(batch) { ); const collaborationContract = validateSupervisorSwarmCollaborationContract( batch.collaborationContract, - mixed, ); - if (mixed) { - assert( - batch.status === 'waiting-confirmation' && batch.nextActionIndex === 0, - 'supervisor-swarm-mixed-initial-confirmation-gate-missing', - ); - } const agentIds = new Set(); const actionIds = new Set(); const delegateActionIds = new Set(); - let spawnActionId = null; for (const [index, pending] of batch.actions.entries()) { const action = pending.action; - const confirmationExpected = - mixed && action?.tool === 'agent.spawn_isolated'; assert( pending.agentId === projectSupervisorAgentId && pending.sessionId === supervisorSwarmSessionId && @@ -611,74 +565,39 @@ export function validateSupervisorSwarmInitialProviderBatch(batch) { isNonEmptyString(pending.actionId) && /^[0-9a-f]{64}$/u.test(pending.actionFingerprint ?? '') && !actionIds.has(pending.actionId) && - pending.executionMode === - (confirmationExpected ? 'confirmation' : 'auto') && - (confirmationExpected - ? [ - 'pending-confirmation', - 'approved', - 'executing', - 'observed-approved', - ].includes(pending.status) - : ['approved', 'executing', 'observed-approved'].includes( - pending.status, - )) && + pending.executionMode === 'auto' && + ['approved', 'executing', 'observed-approved'].includes( + pending.status, + ) && + action?.tool === 'agent.delegate' && action?.tool === batch.plan.actions[index]?.tool && JSON.stringify(action.input) === JSON.stringify(batch.plan.actions[index].input), 'supervisor-swarm-initial-provider-batch-action-invalid', ); - if (action.tool === 'agent.delegate') { - const expectedPath = - action.input?.agentId === supervisorSwarmDesignAgentId - ? supervisorSwarmDesignPath - : supervisorSwarmQualityPath; - assertSupervisorSwarmContractShape( - action.input, - expectedPath, - 'supervisor-swarm-initial-provider-batch', - ); - assert( - [supervisorSwarmDesignAgentId, supervisorSwarmQualityAgentId].includes( - action.input.agentId, - ), - 'supervisor-swarm-initial-provider-batch-target-invalid', - ); - agentIds.add(action.input.agentId); - delegateActionIds.add(pending.actionId); - } else { - assert( - mixed && - action.tool === 'agent.spawn_isolated' && - spawnActionId == null, - 'supervisor-swarm-mixed-spawn-action-invalid', - ); - spawnActionId = pending.actionId; - state.supervisorSwarm.mixedSpawnRequestHash = - validateSupervisorSwarmIsolatedSpawnInput( - action.input, - 0, - 'supervisor-swarm-mixed-initial', - supervisorSwarmExpectedIsolatedReviewGroups(), - ); - } + const expectedPath = + action.input?.agentId === supervisorSwarmDesignAgentId + ? supervisorSwarmDesignPath + : supervisorSwarmQualityPath; + assertSupervisorSwarmContractShape( + action.input, + expectedPath, + 'supervisor-swarm-initial-provider-batch', + ); + assert( + [supervisorSwarmDesignAgentId, supervisorSwarmQualityAgentId].includes( + action.input.agentId, + ), + 'supervisor-swarm-initial-provider-batch-target-invalid', + ); + agentIds.add(action.input.agentId); + delegateActionIds.add(pending.actionId); actionIds.add(pending.actionId); } assert( - agentIds.size === 2 && (!mixed || isNonEmptyString(spawnActionId)), + agentIds.size === 2, 'supervisor-swarm-initial-provider-batch-target-count-invalid', ); - assert( - !mixed || - batch.actions.filter( - (pending) => - pending.actionId === spawnActionId && - pending.executionMode === 'confirmation' && - pending.status === 'pending-confirmation', - ).length === 1, - 'supervisor-swarm-initial-provider-batch-waiting-state-invalid', - ); - state.supervisorSwarm.mixedSpawnActionId = spawnActionId; state.supervisorSwarm.initialProviderBatch = { batchId: batch.batchId, projectId: batch.projectId, @@ -829,88 +748,6 @@ export async function readSupervisorSwarmCollaborationPolicySnapshotBindingFile( return { bindingPath, bytes, binding }; } -export async function captureSupervisorSwarmCollaborationPolicySnapshotAndDrift() { - if (!isSupervisorSwarmMultiIsolatedHarnessSuite()) return; - const expectedSnapshotPath = supervisorSwarmCollaborationPolicySnapshotPath(); - const expectedBindingPath = - supervisorSwarmCollaborationPolicySnapshotBindingPath(); - const deadline = Date.now() + 30_000; - while (Date.now() < deadline) { - const [snapshotFiles, bindingFiles] = await Promise.all([ - listFiles(supervisorSwarmCollaborationPolicySnapshotDirectory()), - listFiles(supervisorSwarmCollaborationPolicySnapshotBindingDirectory()), - ]); - if ( - !snapshotFiles.includes(expectedSnapshotPath) || - !bindingFiles.includes(expectedBindingPath) - ) { - await sleep(50); - continue; - } - assert( - snapshotFiles.length === 1 && - snapshotFiles[0] === expectedSnapshotPath && - bindingFiles.length === 1 && - bindingFiles[0] === expectedBindingPath, - 'supervisor-swarm-collaboration-policy-snapshot-surface-not-unique', - ); - const [initial, initialBinding] = await Promise.all([ - readSupervisorSwarmCollaborationPolicySnapshotFile(), - readSupervisorSwarmCollaborationPolicySnapshotBindingFile(), - ]); - const inspected = validateSupervisorSwarmCollaborationPolicySnapshot( - initial.snapshot, - supervisorSwarmExpectedCollaborationPolicySnapshot(), - ); - validateSupervisorSwarmCollaborationPolicySnapshotBinding( - initialBinding.binding, - initial.snapshot, - ); - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord = JSON.parse( - JSON.stringify(initial.snapshot), - ); - state.supervisorSwarm.collaborationPolicySnapshotInitialBytes = - initial.bytes; - state.supervisorSwarm.collaborationPolicySnapshotInitialBytesSha256 = - hashValue(initial.bytes); - state.supervisorSwarm.collaborationPolicySnapshotInitialIdentityHash = - inspected.identityHash; - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialRecord = - JSON.parse(JSON.stringify(initialBinding.binding)); - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes = - initialBinding.bytes; - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytesSha256 = - hashValue(initialBinding.bytes); - - await writeSupervisorSwarmCollaborationPolicyDriftFixture(); - const [afterDrift, bindingAfterDrift] = await Promise.all([ - readSupervisorSwarmCollaborationPolicySnapshotFile(), - readSupervisorSwarmCollaborationPolicySnapshotBindingFile(), - ]); - validateSupervisorSwarmCollaborationPolicySnapshot( - afterDrift.snapshot, - supervisorSwarmExpectedCollaborationPolicySnapshot(), - ); - validateSupervisorSwarmCollaborationPolicySnapshotBinding( - bindingAfterDrift.binding, - afterDrift.snapshot, - ); - assert( - afterDrift.bytes === initial.bytes && - JSON.stringify(afterDrift.snapshot) === - JSON.stringify(initial.snapshot) && - bindingAfterDrift.bytes === initialBinding.bytes && - JSON.stringify(bindingAfterDrift.binding) === - JSON.stringify(initialBinding.binding), - 'supervisor-swarm-collaboration-policy-snapshot-surface-changed-after-drift', - ); - return; - } - throw codedError( - 'supervisor-swarm-collaboration-policy-snapshot-capture-timeout', - ); -} - export async function captureSupervisorSwarmInitialProviderBatch() { const batchPath = supervisorSwarmInitialProviderBatchPath(); const deadline = Date.now() + 5 * 60 * 1000; @@ -920,33 +757,12 @@ export async function captureSupervisorSwarmInitialProviderBatch() { if (error?.code === 'ENOENT') return null; throw error; }); - const batchTools = Array.isArray(batch?.actions) - ? batch.actions.map((pending) => pending?.action?.tool) - : []; - const interactiveCollaborationBatch = - (isSupervisorSwarmAutonomousChatSuite() && - batchTools.length === 2 && - batchTools.every((tool) => tool === 'agent.delegate')) || - (isSupervisorSwarmMixedHarnessSuite() && - batchTools.length === 3 && - batchTools.filter((tool) => tool === 'agent.delegate').length === 2 && - batchTools.filter((tool) => tool === 'agent.spawn_isolated').length === - 1); - if ( - batch && - (!isSupervisorSwarmInteractiveChatSuite() || - interactiveCollaborationBatch) - ) { + if (batch) { validateSupervisorSwarmInitialProviderBatch(batch); return; } pollCount += 1; if (pollCount % 20 === 0) { - if (isSupervisorSwarmInteractiveChatSuite()) { - await confirmSupervisorSwarmPendingActions( - new Set([`${projectSupervisorAgentId}\0${state.initialRunId}`]), - ); - } const agentDb = await readOptionalJsonl( path.join(state.projectRoot, '.agent/agent.db'), ); @@ -980,283 +796,3 @@ export async function captureSupervisorSwarmInitialProviderBatch() { } throw codedError('supervisor-swarm-initial-provider-batch-timeout'); } - -export function supervisorSwarmPublicChangedPaths(statusOutput) { - return statusOutput - .split(/\r?\n/u) - .map((line) => line.slice(3).trim()) - .filter(Boolean) - .filter( - (changedPath) => - !changedPath.startsWith('.agent/') && - ![ - sentinelFileName, - '.env', - configFileName, - localConfigFileName, - gitSensitivePath, - ].includes(changedPath), - ) - .sort(); -} - -export async function captureSupervisorSwarmInitialBatchSideEffects() { - const [persistence, revision, changedFiles] = await Promise.all([ - readSupervisorSwarmPersistence(), - readSupervisorSwarmProjectRevision(), - runProcess('git', ['status', '--porcelain=v1', '--untracked-files=all'], { - cwd: state.projectRoot, - timeoutMs: 30_000, - }), - ]); - const actionIds = new Set( - state.supervisorSwarm.initialProviderBatch?.actionIds ?? [], - ); - const isolated = supervisorSwarmParentIsolatedRecords(persistence); - const parentRunKey = new Set([ - `${projectSupervisorAgentId}\0${state.initialRunId}`, - ]); - const initialActionRecords = persistence.agentDb.filter( - (record) => - record.agentId === projectSupervisorAgentId && - (record.runId === state.initialRunId || - record.parentRunId === state.initialRunId) && - actionIds.has(record.actionId), - ); - return { - deliveryCount: persistence.deliveries.length, - claimCount: persistence.claims.length, - isolatedGroupCount: isolated.groups.length, - isolatedChildCount: isolated.instances.length, - isolatedResultCount: isolated.results.length, - isolatedJoinDeliveryCount: isolated.joinDeliveries.length, - delegatedChildTaskCount: persistence.taskSnapshot.latest.filter( - (task) => - task.parentRunId === state.initialRunId && - task.agentId !== projectSupervisorAgentId, - ).length, - projectRevision: revision.revision, - projectModifiedPathCount: supervisorSwarmPublicChangedPaths( - changedFiles.stdout, - ).length, - projectMutationActionCount: initialActionRecords.filter( - (record) => - supervisorSwarmProjectMutationTools.has(record.tool) && - [ - 'agent.runtime.tool_action.executing', - 'agent.runtime.tool_action.observed', - 'agent.runtime.tool_observation', - 'agent.runtime.action_receipt', - ].includes(record.recordType), - ).length, - initialActionExecutionCount: initialActionRecords.filter( - (record) => record.recordType === 'agent.runtime.tool_action.executing', - ).length, - initialActionReceiptCount: initialActionRecords.filter( - (record) => record.recordType === 'agent.runtime.action_receipt', - ).length, - initialActionSideEffectCount: initialActionRecords.filter((record) => - [ - 'agent.runtime.agent.delegate', - 'agent.runtime.agent.spawn_isolated', - ].includes(record.recordType), - ).length, - confirmationRequiredCount: persistence.agentDb.filter( - (record) => - record.recordType === - 'agent.runtime.provider_action_batch.confirmation_required' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - record.batchId === - state.supervisorSwarm.initialProviderBatch?.batchId && - record.actionId === state.supervisorSwarm.mixedSpawnActionId, - ).length, - confirmationRestoredCount: persistence.events.filter((event) => { - if ( - event.agentId !== projectSupervisorAgentId || - event.runId !== state.initialRunId - ) { - return false; - } - if (event.eventType === 'tool_confirmation.restored') { - return String(event.detail ?? '').includes( - state.supervisorSwarm.mixedSpawnActionId, - ); - } - return ( - event.eventType === 'provider_action_batch.confirmation_restored' && - event.actionId === state.supervisorSwarm.mixedSpawnActionId && - String(event.detail ?? '').includes( - `batchId=${state.supervisorSwarm.initialProviderBatch?.batchId}`, - ) - ); - }).length, - recoveryFailedCount: persistence.agentDb.filter( - (record) => - [ - 'agent.runtime.pending_action.recovery_failed', - 'agent.runtime.provider_action_batch.recovery_failed', - ].includes(record.recordType) && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId, - ).length, - providerStartedIdentities: supervisorSwarmProviderStartedIdentities( - persistence.agentDb, - parentRunKey, - ), - }; -} - -export function assertSupervisorSwarmInitialBatchZeroSideEffects( - evidence, - codePrefix, -) { - for (const field of [ - 'deliveryCount', - 'claimCount', - 'isolatedGroupCount', - 'isolatedChildCount', - 'isolatedResultCount', - 'isolatedJoinDeliveryCount', - 'delegatedChildTaskCount', - 'projectRevision', - 'projectModifiedPathCount', - 'projectMutationActionCount', - 'initialActionExecutionCount', - 'initialActionReceiptCount', - 'initialActionSideEffectCount', - 'recoveryFailedCount', - ]) { - assert(evidence?.[field] === 0, `${codePrefix}-${field}-not-zero`); - } - assert( - evidence.confirmationRequiredCount === 1, - `${codePrefix}-confirmation-required-count-invalid`, - ); -} - -export async function waitForSupervisorSwarmInitialBatchKillBoundary( - batchPath, -) { - const deadline = Date.now() + 30_000; - while (Date.now() < deadline) { - const [batch, sideEffects] = await Promise.all([ - readJson(batchPath), - captureSupervisorSwarmInitialBatchSideEffects(), - ]); - const identity = supervisorSwarmInitialProviderBatchRecoveryIdentity(batch); - assert( - batch.schemaVersion === providerActionBatchSchemaVersion && - batch.status === 'waiting-confirmation' && - batch.nextActionIndex === 0 && - JSON.stringify(identity) === - JSON.stringify( - state.supervisorSwarm.initialProviderBatch?.recoveryIdentity, - ) && - sideEffects.confirmationRequiredCount <= 1 && - sideEffects.confirmationRestoredCount === 0, - 'supervisor-swarm-collaboration-policy-initial-kill-boundary-invalid', - ); - if (sideEffects.confirmationRequiredCount === 1) { - return { batch, identity, sideEffects }; - } - await sleep(50); - } - throw codedError( - 'supervisor-swarm-collaboration-policy-initial-kill-boundary-timeout', - ); -} - -export async function restartSupervisorSwarmRunnerAtInitialBatchBoundary() { - if (!isSupervisorSwarmCollaborationPolicyMixedRecoverySuite()) return; - const batchPath = supervisorSwarmInitialProviderBatchPath(); - const { - batch: batchBefore, - identity: identityBefore, - sideEffects: sideEffectsBefore, - } = await waitForSupervisorSwarmInitialBatchKillBoundary(batchPath); - validateSupervisorSwarmCollaborationContract( - batchBefore.collaborationContract, - true, - ); - assertSupervisorSwarmInitialBatchZeroSideEffects( - sideEffectsBefore, - 'supervisor-swarm-collaboration-policy-pre-kill', - ); - - const ownerBefore = await readSupervisorSwarmExecutionOwner(); - const currentRunner = await verifyOwnedRunnerForKill(); - assert( - ownerBefore.bootId === currentRunner.bootId && - ownerBefore.pid === currentRunner.pid, - 'supervisor-swarm-collaboration-policy-owner-before-kill-invalid', - ); - state.supervisorSwarm.initialBatchRecoveryBoundaryObserved = true; - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId = - currentRunner.bootId; - state.supervisorSwarm.initialBatchRecoveryPreKillIdentity = identityBefore; - state.supervisorSwarm.initialBatchRecoveryPreKillSideEffects = - sideEffectsBefore; - - await killRunnerOnce(); - await runCli(['--agent-resume', state.projectRoot], { timeoutMs: 120_000 }); - state.resumed = true; - const restarted = await waitForRunnerBootChange(currentRunner.bootId); - const claimed = await claimOwnedRunner(restarted); - const ownerAfter = await readSupervisorSwarmExecutionOwner(claimed.bootId); - assert( - claimed.bootId !== currentRunner.bootId && - ownerAfter.bootId === claimed.bootId && - ownerAfter.recoveredFromBootId === currentRunner.bootId, - 'supervisor-swarm-collaboration-policy-owner-recovery-invalid', - ); - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId = claimed.bootId; - - const deadline = Date.now() + 120_000; - while (Date.now() < deadline) { - const [batchAfter, sideEffectsAfter] = await Promise.all([ - readJson(batchPath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }), - captureSupervisorSwarmInitialBatchSideEffects(), - ]); - if (!batchAfter || sideEffectsAfter.confirmationRestoredCount !== 1) { - await sleep(100); - continue; - } - const identityAfter = - supervisorSwarmInitialProviderBatchRecoveryIdentity(batchAfter); - validateSupervisorSwarmCollaborationContract( - batchAfter.collaborationContract, - true, - ); - assertSupervisorSwarmInitialBatchZeroSideEffects( - sideEffectsAfter, - 'supervisor-swarm-collaboration-policy-post-recovery', - ); - assert( - batchAfter.status === 'waiting-confirmation' && - batchAfter.nextActionIndex === 0 && - JSON.stringify(identityAfter) === JSON.stringify(identityBefore) && - JSON.stringify(sideEffectsAfter.providerStartedIdentities) === - JSON.stringify(sideEffectsBefore.providerStartedIdentities), - 'supervisor-swarm-collaboration-policy-batch-recovery-identity-invalid', - ); - state.supervisorSwarm.initialBatchRecoveryPostRecoveryIdentity = - identityAfter; - state.supervisorSwarm.initialBatchRecoveryPostRecoverySideEffects = - sideEffectsAfter; - state.supervisorSwarm.initialBatchRecoveryIdentityStable = true; - state.supervisorSwarm.initialBatchRecoveryConfirmationRestoredCount = - sideEffectsAfter.confirmationRestoredCount; - state.supervisorSwarm.initialBatchRecoveryPidfdClaimCount = - state.isolatedRunner.pidfdClaimCount; - state.supervisorSwarm.initialBatchRecoveryPidfdSignalCount = - state.isolatedRunner.pidfdSignalCount; - return; - } - throw codedError( - 'supervisor-swarm-collaboration-policy-initial-batch-recovery-timeout', - ); -} diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-schema.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-schema.mjs index 5ac4a15a5..852cd1a61 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-schema.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-schema.mjs @@ -8,30 +8,6 @@ export function supervisorSwarmEvidenceFieldTemplate() { return { scenario: 'project-supervisor-dual-delegate-single-repair-runner-recovery', targetAgentId: projectSupervisorAgentId, - autonomousModeEnabled: false, - autonomousTaskRecipeFree: false, - autonomousRepositoryRecipeFree: false, - interactiveCliUsed: false, - chatSessionUnexpectedlyClosed: false, - chatSessionFailureKind: 'none', - chatSessionExitCode: 'none', - chatSessionCloseSignal: 'none', - chatSessionProcessErrorCode: 'none', - chatSessionStderrChars: 0, - chatSessionStderrSha256: 'none', - turnReportCaptured: false, - turnReportOutcome: 'not-requested', - turnReportParentIdentityStable: false, - turnReportRuntimeCount: 0, - turnReportBusyRuntimeCount: 0, - turnReportPendingTaskCount: 0, - turnReportRunningTaskCount: 0, - turnReportWaitingForConfirmationCount: 0, - turnReportWaitingForUserInputCount: 0, - turnReportNewAssistantMessageCount: 0, - turnReportFinalReplyChars: 0, - turnReportReconciliationAgentCount: 0, - turnReportPrivateLeakCount: 0, providerModel: null, providerApiKind: null, providerReasoningEffort: null, @@ -124,46 +100,7 @@ export function supervisorSwarmEvidenceFieldTemplate() { duplicateCollaborationPolicySnapshotBindingCount: 0, collaborationPolicySnapshotResidualArtifactCount: 0, collaborationPolicySnapshotBindingResidualArtifactCount: 0, - initialBatchRecoveryRequired: false, - initialBatchRecoveryBoundaryObserved: false, - initialBatchRecoveryBatchIdStable: false, - initialBatchRecoveryPolicyFingerprintStable: false, - initialBatchRecoveryContractFingerprintStable: false, - initialBatchRecoveryAllActionIdsStable: false, - initialBatchRecoveryProviderStartedIdentitySetStable: false, - initialBatchRecoveryWaitingConfirmationStable: false, - initialBatchRecoveryZeroSideEffects: false, - initialBatchRecoveryPreKillDeliveryCount: 0, - initialBatchRecoveryPostRecoveryDeliveryCount: 0, - initialBatchRecoveryPreKillGroupCount: 0, - initialBatchRecoveryPostRecoveryGroupCount: 0, - initialBatchRecoveryPreKillChildCount: 0, - initialBatchRecoveryPostRecoveryChildCount: 0, - initialBatchRecoveryPreKillProjectRevision: 0, - initialBatchRecoveryPostRecoveryProjectRevision: 0, - initialBatchRecoveryPreKillProjectModificationCount: 0, - initialBatchRecoveryPostRecoveryProjectModificationCount: 0, - initialBatchRecoveryPreKillActionExecutionCount: 0, - initialBatchRecoveryPostRecoveryActionExecutionCount: 0, - initialBatchRecoveryPreKillActionReceiptCount: 0, - initialBatchRecoveryPostRecoveryActionReceiptCount: 0, - initialBatchRecoveryConfirmationRestoredCount: 0, - initialBatchRecoveryRunnerBootChanged: false, - initialBatchRecoveryPidfdClaimCount: 0, - initialBatchRecoveryPidfdSignalCount: 0, nativeDualDelegatePlanCount: 0, - mixedModeEnabled: false, - mixedSpawnActionCaptured: false, - mixedFollowupSpawnActionCaptured: false, - mixedSpawnRequestHashStable: false, - mixedSpawnConfirmationRequiredCount: 0, - mixedSpawnApprovalCount: 0, - mixedSpawnConfirmationOrderValid: false, - nativeMixedCollaborationPlanCount: 0, - nativeFollowupIsolatedPlanCount: 0, - staticIsolatedProviderOverlapObserved: false, - staticIsolatedProviderRequestIdentityCount: 0, - mixedParentIdentityStable: false, initialDeliveryCount: 0, repairDeliveryCount: 0, repairDeliveryStatus: 'absent', diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-validation.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-validation.mjs index 1d4963b05..a9f3a460b 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-validation.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/evidence-validation.mjs @@ -4,14 +4,10 @@ import { assertSupervisorSwarmRuntimeHealthy, assertSupervisorSwarmWeakQualityDelivery, observeSupervisorSwarmInitialProviderOverlap, - observeSupervisorSwarmStaticIsolatedProviderOverlap, supervisorSwarmArtifactHash, - supervisorSwarmMixedSpawnRequestHashesStable, supervisorSwarmParentDeliveries, - validateSupervisorSwarmMixedIsolatedPersistence, } from './collaboration-assertions.mjs'; import { - assertSupervisorSwarmInitialBatchZeroSideEffects, duplicateSupervisorSwarmCollaborationPolicySnapshotBindingCount, duplicateSupervisorSwarmCollaborationPolicySnapshotCount, inspectSupervisorSwarmCollaborationPolicySnapshot, @@ -48,28 +44,22 @@ import { readSupervisorSwarmExecutionOwner, } from './repair-recovery.mjs'; import { - driftedSupervisorSwarmCollaborationPolicy, expectedSupervisorSwarmCollaborationPolicy, supervisorSwarmCollaborationPolicyPath, } from './setup.mjs'; import { - absolutePathVariants, assert, canonicalJsonValue, commandPassedMarker, configFileName, - countExactSecrets, countLureLeaks, countSecretsInProject, duplicateCount, finalMessageId, - formalConfigPathVariants, fs, gitSensitivePath, hashValue, isNonEmptyString, - isolatedJoinDeliveryTarget, - isPlainObject, localConfigFileName, path, projectSupervisorAgentId, @@ -91,13 +81,8 @@ import { validateSupervisorSwarmFinalReplyResponseStream, } from './shared.mjs'; import { - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite, isSupervisorSwarmFinalReplyTransientRetrySuite, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, isSupervisorSwarmTransientRetrySuite, - supervisorSwarmInitialIsolatedReviewsForSuite, } from './suite-selection.mjs'; export async function validateSupervisorSwarmEvidence() { @@ -121,12 +106,8 @@ export async function validateSupervisorSwarmEvidence() { const collaborationContractCountsMatched = JSON.stringify(collaborationContract?.initialStaticAgentIds) === JSON.stringify(expectedInitialStaticAgentIds) && - collaborationContract?.isolatedSpawnCount === - (isSupervisorSwarmMixedHarnessSuite() ? 1 : 0) && - collaborationContract?.isolatedChildCount === - (isSupervisorSwarmMixedHarnessSuite() - ? supervisorSwarmInitialIsolatedReviewsForSuite().length - : 0); + collaborationContract?.isolatedSpawnCount === 0 && + collaborationContract?.isolatedChildCount === 0; assert( initialBatch?.schemaVersion === providerActionBatchSchemaVersion && collaborationContract?.schemaVersion === @@ -142,8 +123,6 @@ export async function validateSupervisorSwarmEvidence() { ); const collaborationPolicySnapshotRequired = supervisorSwarmInitialBatchBindsPolicySnapshot(initialBatch); - const collaborationPolicySnapshotDriftRequired = - isSupervisorSwarmMultiIsolatedHarnessSuite(); const collaborationPolicySnapshots = persistence.collaborationPolicySnapshots ?? []; const collaborationPolicySnapshotBindings = @@ -161,7 +140,6 @@ export async function validateSupervisorSwarmEvidence() { collaborationPolicySnapshots[0], supervisorSwarmExpectedCollaborationPolicySnapshot(), ); - let collaborationPolicySnapshotBytesStable = false; let collaborationPolicySnapshotFieldsStable = false; let collaborationPolicySnapshotFingerprintStable = false; let collaborationPolicySnapshotPolicyFingerprintStable = false; @@ -170,7 +148,6 @@ export async function validateSupervisorSwarmEvidence() { collaborationPolicySnapshotBindings[0], collaborationPolicySnapshots[0], ); - let collaborationPolicySnapshotBindingBytesStable = false; let collaborationPolicySnapshotBindingFieldsStable = false; const collaborationPolicyDriftObservationCount = supervisorSwarmCollaborationPolicyDriftObservationCount( @@ -224,76 +201,17 @@ export async function validateSupervisorSwarmEvidence() { collaborationPolicySnapshotBindingFieldsStable, 'supervisor-swarm-final-collaboration-policy-snapshot-integrity-invalid', ); - if (collaborationPolicySnapshotDriftRequired) { - collaborationPolicySnapshotBytesStable = - isNonEmptyString( - state.supervisorSwarm.collaborationPolicySnapshotInitialBytes, - ) && - finalSnapshotFile.bytes === - state.supervisorSwarm.collaborationPolicySnapshotInitialBytes && - hashValue(finalSnapshotFile.bytes) === - state.supervisorSwarm.collaborationPolicySnapshotInitialBytesSha256; - collaborationPolicySnapshotBindingBytesStable = - isNonEmptyString( - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes, - ) && - finalBindingFile.bytes === - state.supervisorSwarm - .collaborationPolicySnapshotBindingInitialBytes && - hashValue(finalBindingFile.bytes) === - state.supervisorSwarm - .collaborationPolicySnapshotBindingInitialBytesSha256; - assert( - isPlainObject( - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord, - ) && - isPlainObject( - state.supervisorSwarm - .collaborationPolicySnapshotBindingInitialRecord, - ) && - collaborationPolicySnapshotBytesStable && - collaborationPolicySnapshotBindingBytesStable && - JSON.stringify(finalSnapshotFile.snapshot) === - JSON.stringify( - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord, - ) && - JSON.stringify(finalBindingFile.binding) === - JSON.stringify( - state.supervisorSwarm - .collaborationPolicySnapshotBindingInitialRecord, - ) && - state.supervisorSwarm.collaborationPolicyDriftFixtureWritten && - state.supervisorSwarm - .collaborationPolicyDriftedMinIsolatedGroupsBeforeClaim === 1 && - /^[0-9a-f]{64}$/u.test( - state.supervisorSwarm - .collaborationPolicyDriftFixturePolicyFingerprint ?? '', - ) && - collaborationPolicyDriftObservationCount >= 1, - 'supervisor-swarm-final-collaboration-policy-snapshot-drift-invalid', - ); - } } let finalCollaborationPolicySidecar = null; - if ( - isSupervisorSwarmMixedHarnessSuite() || - isSupervisorSwarmTransientRetrySuite() - ) { + if (isSupervisorSwarmTransientRetrySuite()) { finalCollaborationPolicySidecar = await readJson( supervisorSwarmCollaborationPolicyPath(), ); - const expectedFinalPolicy = collaborationPolicySnapshotDriftRequired - ? driftedSupervisorSwarmCollaborationPolicy() - : expectedCollaborationPolicy; + const expectedFinalPolicy = expectedCollaborationPolicy; assert( state.supervisorSwarm.collaborationPolicyWritten && JSON.stringify(canonicalJsonValue(finalCollaborationPolicySidecar)) === - JSON.stringify(canonicalJsonValue(expectedFinalPolicy)) && - (!collaborationPolicySnapshotDriftRequired || - (finalCollaborationPolicySidecar.minIsolatedGroupsBeforeClaim === 1 && - hashValue(JSON.stringify(finalCollaborationPolicySidecar)) === - state.supervisorSwarm - .collaborationPolicyDriftFixturePolicyFingerprint)), + JSON.stringify(canonicalJsonValue(expectedFinalPolicy)), 'supervisor-swarm-collaboration-policy-sidecar-invalid', ); } @@ -362,19 +280,6 @@ export async function validateSupervisorSwarmEvidence() { repairClaims[0].receipts.length === 1, 'supervisor-swarm-claim-shape-invalid', ); - const mixedState = isSupervisorSwarmMixedHarnessSuite() - ? validateSupervisorSwarmMixedIsolatedPersistence(persistence) - : null; - if (collaborationPolicySnapshotDriftRequired) { - assert( - mixedState?.groups.length === 2 && - mixedState.instances.length === 3 && - mixedState.joinClaims.length === 1 && - mixedState.joinClaims[0].status === 'observed' && - mixedState.joinClaims[0].joins.length === 2, - 'supervisor-swarm-final-collaboration-policy-drift-topology-invalid', - ); - } const [ designContent, @@ -500,34 +405,17 @@ export async function validateSupervisorSwarmEvidence() { assert(steerRecordCount === 0, 'supervisor-swarm-unexpected-steer-record'); assert( - observeSupervisorSwarmInitialProviderOverlap( - persistence.agentDb, - initial, - ) && - (!mixedState || - observeSupervisorSwarmStaticIsolatedProviderOverlap( - persistence.agentDb, - initial, - mixedState.instances, - )), + observeSupervisorSwarmInitialProviderOverlap(persistence.agentDb, initial), 'supervisor-swarm-final-provider-overlap-missing', ); - if (mixedState) { - assert( - state.supervisorSwarm.staticIsolatedProviderRequestIds.length === 2 && - new Set(state.supervisorSwarm.staticIsolatedProviderRequestIds).size === - 2, - 'supervisor-swarm-mixed-provider-overlap-identity-invalid', - ); - } const protocol = validateSupervisorSwarmNativeProtocol( persistence.agentDb, - mixedState?.instances ?? [], + [], ); const provider = validateSupervisorSwarmProviderLifecycle( persistence.agentDb, deliveries, - mixedState?.instances ?? [], + [], ); const transientRetry = supervisorSwarmTransientRetryEvidence(provider); const toolPlanHandoff = supervisorSwarmToolPlanHandoffEvidence(persistence); @@ -535,7 +423,7 @@ export async function validateSupervisorSwarmEvidence() { persistence.agentDb, deliveries, persistence.contextBundles, - mixedState?.instances ?? [], + [], ); const batchEvents = persistence.events.filter( (event) => @@ -545,9 +433,7 @@ export async function validateSupervisorSwarmEvidence() { String(event.detail ?? '').includes( `batchId=${state.supervisorSwarm.initialProviderBatch.batchId}`, ) && - String(event.detail ?? '').includes( - `actionCount=${isSupervisorSwarmMixedHarnessSuite() ? 3 : 2}`, - ), + String(event.detail ?? '').includes('actionCount=2'), ); assert( batchEvents.length === 1, @@ -620,9 +506,7 @@ export async function validateSupervisorSwarmEvidence() { supervisorSwarmDesignAgentId, supervisorSwarmQualityAgentId, ]); - const isolatedAgentIds = new Set( - (mixedState?.instances ?? []).map((instance) => instance.instanceId), - ); + const isolatedAgentIds = new Set(); const professionalUserFacingAssistantCount = [ ...persistence.supervisorConversation, ...persistence.legacyConversation, @@ -693,68 +577,7 @@ export async function validateSupervisorSwarmEvidence() { assistants[0], ).stageCount; } - let isolatedFinalizationStageCount = 0; - for (const instance of mixedState?.instances ?? []) { - const conversation = persistence.isolatedConversations.find( - (entry) => - entry.agentId === instance.instanceId && - entry.sessionId === instance.sessionId && - entry.runId === instance.runId, - ); - const expectedMessageId = finalMessageId( - instance.instanceId, - instance.sessionId, - instance.runId, - ); - const assistants = (conversation?.messages ?? []).filter( - (message) => - message.role === 'assistant' && - message.messageId === expectedMessageId && - message.agentId === instance.instanceId, - ); - const childTask = mixedState.isolatedTasks.find( - (task) => - task.agentId === instance.instanceId && - task.sessionId === instance.sessionId && - task.runId === instance.runId && - task.delegationId === instance.delegationId, - ); - assert( - childTask && - assistants.length === 1 && - assistantAudits.filter( - (record) => - record.agentId === instance.instanceId && - record.sessionId === instance.sessionId && - record.messageId === expectedMessageId, - ).length === 1 && - completedAudits.filter( - (record) => - record.agentId === instance.instanceId && - record.sessionId === instance.sessionId && - record.runId === instance.runId && - record.messageId === expectedMessageId, - ).length === 1 && - backgroundCompletedAudits.filter( - (record) => - record.agentId === instance.instanceId && - record.sessionId === instance.sessionId && - record.runId === instance.runId && - record.messageId === expectedMessageId, - ).length === 1, - 'supervisor-swarm-mixed-isolated-finalization-invalid', - ); - isolatedFinalizationStageCount += validateSupervisorSwarmFinalization( - persistence.agentDb, - { - agentId: instance.instanceId, - taskId: childTask.taskId, - sessionId: instance.sessionId, - runId: instance.runId, - }, - assistants[0], - ).stageCount; - } + const isolatedFinalizationStageCount = 0; const professionalFinalMessageIds = new Set( deliveries.map((delivery) => finalMessageId( @@ -764,28 +587,21 @@ export async function validateSupervisorSwarmEvidence() { ), ), ); - const isolatedFinalMessageIds = new Set( - (mixedState?.instances ?? []).map((instance) => - finalMessageId(instance.instanceId, instance.sessionId, instance.runId), - ), - ); + const isolatedFinalMessageIds = new Set(); assert( supervisorUsers.length === 1 && supervisorAssistants.length === 1 && supervisorAssistants[0].agentId === projectSupervisorAgentId && professionalUsers.length === deliveries.length && professionalAssistants.length === deliveries.length && - isolatedUsers.length === (mixedState?.instances.length ?? 0) && - isolatedAssistants.length === (mixedState?.instances.length ?? 0) && + isolatedUsers.length === 0 && + isolatedAssistants.length === 0 && professionalUserFacingAssistantCount === 0 && persistence.legacyConversation.length === 0 && duplicateMessageCount === 0 && - assistantAudits.length === - 1 + deliveries.length + (mixedState?.instances.length ?? 0) && - completedAudits.length === - 1 + deliveries.length + (mixedState?.instances.length ?? 0) && - backgroundCompletedAudits.length === - 1 + deliveries.length + (mixedState?.instances.length ?? 0) && + assistantAudits.length === 1 + deliveries.length && + completedAudits.length === 1 + deliveries.length && + backgroundCompletedAudits.length === 1 + deliveries.length && assistantAudits.filter( (record) => record.agentId === projectSupervisorAgentId && @@ -801,7 +617,7 @@ export async function validateSupervisorSwarmEvidence() { (record) => isolatedAgentIds.has(record.agentId) && isolatedFinalMessageIds.has(record.messageId), - ).length === (mixedState?.instances.length ?? 0) && + ).length === 0 && completedAudits.filter( (record) => record.agentId === projectSupervisorAgentId && @@ -811,7 +627,7 @@ export async function validateSupervisorSwarmEvidence() { professionalAgentIds.has(record.agentId), ).length === deliveries.length && completedAudits.filter((record) => isolatedAgentIds.has(record.agentId)) - .length === (mixedState?.instances.length ?? 0) && + .length === 0 && backgroundCompletedAudits.filter( (record) => record.agentId === projectSupervisorAgentId && @@ -822,7 +638,7 @@ export async function validateSupervisorSwarmEvidence() { ).length === deliveries.length && backgroundCompletedAudits.filter((record) => isolatedAgentIds.has(record.agentId), - ).length === (mixedState?.instances.length ?? 0), + ).length === 0, 'supervisor-swarm-user-reply-ownership-invalid', ); const finalization = validateSupervisorSwarmFinalization( @@ -857,92 +673,10 @@ export async function validateSupervisorSwarmEvidence() { assert( staticClaimObservationIndexes.every( (index) => index >= 0 && index < finalization.preparedIndex, - ) && - (!mixedState || - (mixedState.claimAuditIndex < mixedState.claimObservationIndex && - mixedState.claimObservationIndex < finalization.preparedIndex)), + ), 'supervisor-swarm-claim-observation-finalization-order-invalid', ); - const initialBatchRecoveryRequired = - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite(); - const initialBatchIdentityBefore = - state.supervisorSwarm.initialBatchRecoveryPreKillIdentity; - const initialBatchIdentityAfter = - state.supervisorSwarm.initialBatchRecoveryPostRecoveryIdentity; - const initialBatchSideEffectsBefore = - state.supervisorSwarm.initialBatchRecoveryPreKillSideEffects; - const initialBatchSideEffectsAfter = - state.supervisorSwarm.initialBatchRecoveryPostRecoverySideEffects; - const initialBatchRecoveryBatchIdStable = - initialBatchRecoveryRequired && - initialBatchIdentityBefore?.batchId === initialBatchIdentityAfter?.batchId; - const initialBatchRecoveryPolicyFingerprintStable = - initialBatchRecoveryRequired && - initialBatchIdentityBefore?.policyFingerprint === - initialBatchIdentityAfter?.policyFingerprint; - const initialBatchRecoveryContractFingerprintStable = - initialBatchRecoveryRequired && - initialBatchIdentityBefore?.contractFingerprint === - initialBatchIdentityAfter?.contractFingerprint; - const initialBatchRecoveryAllActionIdsStable = - initialBatchRecoveryRequired && - JSON.stringify( - initialBatchIdentityBefore?.actions?.map((action) => action.actionId), - ) === - JSON.stringify( - initialBatchIdentityAfter?.actions?.map((action) => action.actionId), - ) && - initialBatchIdentityAfter?.actions?.length === - initialBatch?.actionIds.length; - const initialBatchRecoveryProviderStartedIdentitySetStable = - initialBatchRecoveryRequired && - JSON.stringify(initialBatchSideEffectsBefore?.providerStartedIdentities) === - JSON.stringify(initialBatchSideEffectsAfter?.providerStartedIdentities); - const initialBatchRecoveryWaitingConfirmationStable = - initialBatchRecoveryRequired && - initialBatchIdentityBefore?.status === 'waiting-confirmation' && - initialBatchIdentityBefore?.nextActionIndex === 0 && - initialBatchIdentityAfter?.status === 'waiting-confirmation' && - initialBatchIdentityAfter?.nextActionIndex === 0; - let initialBatchRecoveryZeroSideEffects = false; - if (initialBatchRecoveryRequired) { - assertSupervisorSwarmInitialBatchZeroSideEffects( - initialBatchSideEffectsBefore, - 'supervisor-swarm-collaboration-policy-final-pre-kill', - ); - assertSupervisorSwarmInitialBatchZeroSideEffects( - initialBatchSideEffectsAfter, - 'supervisor-swarm-collaboration-policy-final-post-recovery', - ); - initialBatchRecoveryZeroSideEffects = true; - assert( - state.supervisorSwarm.initialBatchRecoveryBoundaryObserved && - state.supervisorSwarm.initialBatchRecoveryIdentityStable && - initialBatchRecoveryBatchIdStable && - initialBatchRecoveryPolicyFingerprintStable && - initialBatchRecoveryContractFingerprintStable && - initialBatchRecoveryAllActionIdsStable && - initialBatchRecoveryProviderStartedIdentitySetStable && - initialBatchRecoveryWaitingConfirmationStable && - initialBatchSideEffectsBefore.confirmationRestoredCount === 0 && - initialBatchSideEffectsAfter.confirmationRestoredCount === 1 && - state.supervisorSwarm.initialBatchRecoveryConfirmationRestoredCount === - 1 && - isNonEmptyString( - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId, - ) && - isNonEmptyString( - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId, - ) && - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId !== - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId && - state.supervisorSwarm.initialBatchRecoveryPidfdClaimCount >= 2 && - state.supervisorSwarm.initialBatchRecoveryPidfdSignalCount >= 1, - 'supervisor-swarm-collaboration-policy-initial-recovery-evidence-invalid', - ); - } - const owner = await readSupervisorSwarmExecutionOwner( state.supervisorSwarm.newRunnerBootId, ); @@ -957,10 +691,8 @@ export async function validateSupervisorSwarmEvidence() { state.supervisorSwarm.newRunnerBootId && owner.bootId === state.supervisorSwarm.newRunnerBootId && owner.recoveredFromBootId === state.supervisorSwarm.oldRunnerBootId && - state.isolatedRunner.pidfdClaimCount >= - (initialBatchRecoveryRequired ? 3 : 2) && - state.isolatedRunner.pidfdSignalCount >= - (initialBatchRecoveryRequired ? 2 : 1), + state.isolatedRunner.pidfdClaimCount >= 2 && + state.isolatedRunner.pidfdSignalCount >= 1, 'supervisor-swarm-runner-recovery-evidence-invalid', ); @@ -982,93 +714,12 @@ export async function validateSupervisorSwarmEvidence() { const secretLeakCount = (state.transcriptScanner?.count ?? 0) + projectSecretLeakCount; assert(secretLeakCount === 0, 'loaded-key-leak-detected'); - const autonomousModeEnabled = isSupervisorSwarmInteractiveChatSuite(); - const chatSessionFailureDiagnostic = - state.supervisorSwarm.chatSessionFailureDiagnostic; - const turnReport = state.supervisorSwarm.turnReport; - const turnReportPrivateLeakCount = autonomousModeEnabled - ? countExactSecrets(Buffer.from(JSON.stringify(turnReport ?? {})), [ - ...state.supervisorSwarm.privateValues, - ...state.secrets, - ...absolutePathVariants(state.projectRoot), - ...formalConfigPathVariants(), - ]) - : 0; - if (autonomousModeEnabled) { - assert( - state.supervisorSwarm.autonomousTaskRecipeFree && - state.supervisorSwarm.autonomousRepositoryRecipeFree && - state.supervisorSwarm.interactiveCliUsed && - turnReport?.schemaVersion === 'game-creator-swarm-turn-report.v1' && - turnReport.outcome === 'settled' && - turnReport.parentAgentId === projectSupervisorAgentId && - turnReport.sessionId === supervisorSwarmSessionId && - turnReport.parentRunId === state.initialRunId && - Number.isSafeInteger(turnReport.runtimeCount) && - turnReport.runtimeCount >= - (isSupervisorSwarmMixedHarnessSuite() ? 6 : 3) && - turnReport.busyRuntimeCount === 0 && - turnReport.pendingTaskCount === 0 && - turnReport.runningTaskCount === 0 && - turnReport.waitingForConfirmationCount === 0 && - turnReport.waitingForUserInputCount === 0 && - turnReport.newAssistantMessageCount === 1 && - turnReport.finalReplyChars === - [...supervisorAssistants[0].content].length && - turnReport.reconciliationAgentCount === 0 && - turnReportPrivateLeakCount === 0, - 'supervisor-swarm-autonomous-turn-report-invalid', - ); - } - return buildSupervisorSwarmEvidence( { - scenario: isSupervisorSwarmCollaborationPolicyMixedRecoverySuite() - ? 'project-supervisor-collaboration-policy-mixed-initial-batch-recovery' - : isSupervisorSwarmFinalReplyTransientRetrySuite() - ? 'project-supervisor-final-reply-transient-retry-runner-recovery' - : isSupervisorSwarmMixedHarnessSuite() - ? 'project-supervisor-autonomous-chat-static-multi-isolated-single-repair-runner-recovery' - : autonomousModeEnabled - ? 'project-supervisor-autonomous-chat-dual-delegate-single-repair-runner-recovery' - : 'project-supervisor-dual-delegate-single-repair-runner-recovery', + scenario: isSupervisorSwarmFinalReplyTransientRetrySuite() + ? 'project-supervisor-final-reply-transient-retry-runner-recovery' + : 'project-supervisor-dual-delegate-single-repair-runner-recovery', targetAgentId: projectSupervisorAgentId, - autonomousModeEnabled, - autonomousTaskRecipeFree: state.supervisorSwarm.autonomousTaskRecipeFree, - autonomousRepositoryRecipeFree: - state.supervisorSwarm.autonomousRepositoryRecipeFree, - interactiveCliUsed: state.supervisorSwarm.interactiveCliUsed, - chatSessionUnexpectedlyClosed: Boolean(chatSessionFailureDiagnostic), - chatSessionFailureKind: - chatSessionFailureDiagnostic?.failureKind ?? 'none', - chatSessionExitCode: chatSessionFailureDiagnostic?.exitCode ?? 'none', - chatSessionCloseSignal: chatSessionFailureDiagnostic?.signal ?? 'none', - chatSessionProcessErrorCode: - chatSessionFailureDiagnostic?.processErrorCode ?? 'none', - chatSessionStderrChars: chatSessionFailureDiagnostic?.stderrChars ?? 0, - chatSessionStderrSha256: - chatSessionFailureDiagnostic?.stderrSha256 ?? 'none', - turnReportCaptured: turnReport != null, - turnReportOutcome: turnReport?.outcome ?? 'not-requested', - turnReportParentIdentityStable: autonomousModeEnabled - ? turnReport?.parentAgentId === projectSupervisorAgentId && - turnReport?.sessionId === supervisorSwarmSessionId && - turnReport?.parentRunId === state.initialRunId - : false, - turnReportRuntimeCount: turnReport?.runtimeCount ?? 0, - turnReportBusyRuntimeCount: turnReport?.busyRuntimeCount ?? 0, - turnReportPendingTaskCount: turnReport?.pendingTaskCount ?? 0, - turnReportRunningTaskCount: turnReport?.runningTaskCount ?? 0, - turnReportWaitingForConfirmationCount: - turnReport?.waitingForConfirmationCount ?? 0, - turnReportWaitingForUserInputCount: - turnReport?.waitingForUserInputCount ?? 0, - turnReportNewAssistantMessageCount: - turnReport?.newAssistantMessageCount ?? 0, - turnReportFinalReplyChars: turnReport?.finalReplyChars ?? 0, - turnReportReconciliationAgentCount: - turnReport?.reconciliationAgentCount ?? 0, - turnReportPrivateLeakCount, providerModel: state.supervisorSwarm.effectiveModel, providerApiKind: state.supervisorSwarm.effectiveApiKind, providerReasoningEffort: state.supervisorSwarm.effectiveReasoningEffort, @@ -1195,12 +846,11 @@ export async function validateSupervisorSwarmEvidence() { collaborationPolicySnapshotInspection.identityHashMatched, collaborationPolicySnapshotBoundAtValid: collaborationPolicySnapshotInspection.boundAtValid, - collaborationPolicySnapshotBytesStable, + collaborationPolicySnapshotBytesStable: false, collaborationPolicySnapshotFieldsStable, collaborationPolicySnapshotFingerprintStable, collaborationPolicySnapshotPolicyFingerprintStable, collaborationPolicySnapshotStable: - collaborationPolicySnapshotBytesStable && collaborationPolicySnapshotFieldsStable && collaborationPolicySnapshotFingerprintStable && collaborationPolicySnapshotPolicyFingerprintStable, @@ -1222,10 +872,9 @@ export async function validateSupervisorSwarmEvidence() { collaborationPolicySnapshotBindingInspection.boundAtMatched, collaborationPolicySnapshotBindingSnapshotMatched: collaborationPolicySnapshotBindingInspection.snapshotMatched, - collaborationPolicySnapshotBindingBytesStable, + collaborationPolicySnapshotBindingBytesStable: false, collaborationPolicySnapshotBindingFieldsStable, collaborationPolicySnapshotBindingStable: - collaborationPolicySnapshotBindingBytesStable && collaborationPolicySnapshotBindingFieldsStable && collaborationPolicySnapshotBindingInspection.snapshotMatched, collaborationPolicyDriftFixtureWritten: @@ -1241,77 +890,7 @@ export async function validateSupervisorSwarmEvidence() { residualSidecars.collaborationPolicySnapshotArtifacts, collaborationPolicySnapshotBindingResidualArtifactCount: residualSidecars.collaborationPolicySnapshotBindingArtifacts, - initialBatchRecoveryRequired, - initialBatchRecoveryBoundaryObserved: - state.supervisorSwarm.initialBatchRecoveryBoundaryObserved, - initialBatchRecoveryBatchIdStable, - initialBatchRecoveryPolicyFingerprintStable, - initialBatchRecoveryContractFingerprintStable, - initialBatchRecoveryAllActionIdsStable, - initialBatchRecoveryProviderStartedIdentitySetStable, - initialBatchRecoveryWaitingConfirmationStable, - initialBatchRecoveryZeroSideEffects, - initialBatchRecoveryPreKillDeliveryCount: - initialBatchSideEffectsBefore?.deliveryCount ?? 0, - initialBatchRecoveryPostRecoveryDeliveryCount: - initialBatchSideEffectsAfter?.deliveryCount ?? 0, - initialBatchRecoveryPreKillGroupCount: - initialBatchSideEffectsBefore?.isolatedGroupCount ?? 0, - initialBatchRecoveryPostRecoveryGroupCount: - initialBatchSideEffectsAfter?.isolatedGroupCount ?? 0, - initialBatchRecoveryPreKillChildCount: - initialBatchSideEffectsBefore?.isolatedChildCount ?? 0, - initialBatchRecoveryPostRecoveryChildCount: - initialBatchSideEffectsAfter?.isolatedChildCount ?? 0, - initialBatchRecoveryPreKillProjectRevision: - initialBatchSideEffectsBefore?.projectRevision ?? 0, - initialBatchRecoveryPostRecoveryProjectRevision: - initialBatchSideEffectsAfter?.projectRevision ?? 0, - initialBatchRecoveryPreKillProjectModificationCount: - initialBatchSideEffectsBefore?.projectModifiedPathCount ?? 0, - initialBatchRecoveryPostRecoveryProjectModificationCount: - initialBatchSideEffectsAfter?.projectModifiedPathCount ?? 0, - initialBatchRecoveryPreKillActionExecutionCount: - initialBatchSideEffectsBefore?.initialActionExecutionCount ?? 0, - initialBatchRecoveryPostRecoveryActionExecutionCount: - initialBatchSideEffectsAfter?.initialActionExecutionCount ?? 0, - initialBatchRecoveryPreKillActionReceiptCount: - initialBatchSideEffectsBefore?.initialActionReceiptCount ?? 0, - initialBatchRecoveryPostRecoveryActionReceiptCount: - initialBatchSideEffectsAfter?.initialActionReceiptCount ?? 0, - initialBatchRecoveryConfirmationRestoredCount: - state.supervisorSwarm.initialBatchRecoveryConfirmationRestoredCount, - initialBatchRecoveryRunnerBootChanged: - initialBatchRecoveryRequired && - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId !== - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId, - initialBatchRecoveryPidfdClaimCount: - state.supervisorSwarm.initialBatchRecoveryPidfdClaimCount, - initialBatchRecoveryPidfdSignalCount: - state.supervisorSwarm.initialBatchRecoveryPidfdSignalCount, nativeDualDelegatePlanCount: protocol.nativeDualDelegatePlanCount, - mixedModeEnabled: Boolean(mixedState), - mixedSpawnActionCaptured: mixedState - ? isNonEmptyString(state.supervisorSwarm.mixedSpawnActionId) - : false, - mixedFollowupSpawnActionCaptured: mixedState - ? isNonEmptyString(state.supervisorSwarm.mixedFollowupSpawnActionId) - : false, - mixedSpawnRequestHashStable: mixedState - ? supervisorSwarmMixedSpawnRequestHashesStable(mixedState.groups) - : false, - mixedSpawnConfirmationRequiredCount: - actions.mixedSpawnConfirmationRequiredCount, - mixedSpawnApprovalCount: actions.mixedSpawnApprovalCount, - mixedSpawnConfirmationOrderValid: - actions.mixedSpawnConfirmationOrderValid, - staticIsolatedProviderOverlapObserved: mixedState - ? state.supervisorSwarm.staticIsolatedProviderOverlapObserved - : false, - staticIsolatedProviderRequestIdentityCount: mixedState - ? new Set(state.supervisorSwarm.staticIsolatedProviderRequestIds).size - : 0, - mixedParentIdentityStable: Boolean(mixedState), initialDeliveryCount: initial.length, repairDeliveryCount: repairs.length, totalDeliveryCount: deliveries.length, @@ -1329,50 +908,28 @@ export async function validateSupervisorSwarmEvidence() { finalQualityArtifactMatched: true, hostVerificationPassed: state.supervisorSwarm.hostVerificationPassed, changedProjectFileCount: changedPaths.length, - isolatedGroupCount: mixedState?.groups.length ?? 0, - isolatedInstanceCount: mixedState?.instances.length ?? 0, - isolatedTaskCount: mixedState?.isolatedTasks.length ?? 0, - isolatedResultCount: mixedState?.results.length ?? 0, - isolatedCompletedResultCount: - mixedState?.results.filter( - (record) => record.result?.status === 'completed', - ).length ?? 0, - isolatedJoinDeliveryCount: mixedState?.joinDeliveries.length ?? 0, - isolatedClaimedJoinCount: - mixedState?.joinDeliveries.filter( - (delivery) => delivery.status === 'claimed-by-parent', - ).length ?? 0, - isolatedParentWakeJoinCount: - mixedState?.joinDeliveries.filter( - (delivery) => isolatedJoinDeliveryTarget(delivery) === 'parent-wake', - ).length ?? 0, - isolatedJoinClaimJournalCount: mixedState?.joinClaims.length ?? 0, - isolatedObservedJoinClaimCount: - mixedState?.joinClaims.filter((claim) => claim.status === 'observed') - .length ?? 0, - isolatedMaxGroupsPerClaim: mixedState - ? Math.max( - 0, - ...mixedState.joinClaims.map((claim) => claim.joins.length), - ) - : 0, - isolatedClaimAuditCount: mixedState?.groups.length ?? 0, - isolatedClaimObservationCount: mixedState ? 1 : 0, - isolatedContinuationTaskCount: mixedState?.continuationTasks.length ?? 0, - isolatedContinuationAuditCount: - mixedState?.continuationAudits.length ?? 0, + isolatedGroupCount: 0, + isolatedInstanceCount: 0, + isolatedTaskCount: 0, + isolatedResultCount: 0, + isolatedCompletedResultCount: 0, + isolatedJoinDeliveryCount: 0, + isolatedClaimedJoinCount: 0, + isolatedParentWakeJoinCount: 0, + isolatedJoinClaimJournalCount: 0, + isolatedObservedJoinClaimCount: 0, + isolatedMaxGroupsPerClaim: 0, + isolatedClaimAuditCount: 0, + isolatedClaimObservationCount: 0, + isolatedContinuationTaskCount: 0, + isolatedContinuationAuditCount: 0, isolatedProjectMutationCount: changedPaths.filter((changedPath) => supervisorSwarmIsolatedReviews.some( (review) => review.path === changedPath, ), ).length, isolatedMutationActionCount: actions.isolatedMutationActionCount, - isolatedEvidenceFilesUnchanged: mixedState - ? isolatedEvidenceContents.every( - (content, index) => - content === supervisorSwarmIsolatedReviews[index].content, - ) - : false, + isolatedEvidenceFilesUnchanged: false, runnerKillBoundaryObserved: state.supervisorSwarm.runnerKillBoundaryObserved, runnerBootChanged: true, @@ -1382,26 +939,14 @@ export async function validateSupervisorSwarmEvidence() { claimIdentitiesStableAcrossRecovery: true, pendingActionIdentityStableAcrossRecovery: true, providerStartedCountStableAcrossRecovery: true, - isolatedGroupIdentityStableAcrossRecovery: mixedState - ? state.identityStable - : false, - isolatedInstanceIdentitiesStableAcrossRecovery: mixedState - ? state.identityStable - : false, - isolatedResultIdentitiesStableAcrossRecovery: mixedState - ? state.identityStable - : false, - isolatedJoinIdentityStableAcrossRecovery: mixedState - ? state.identityStable - : false, - parentContextStableAcrossRecovery: mixedState - ? state.identityStable - : false, - providerIdentitySetStableAcrossRecovery: mixedState - ? state.identityStable - : false, + isolatedGroupIdentityStableAcrossRecovery: false, + isolatedInstanceIdentitiesStableAcrossRecovery: false, + isolatedResultIdentitiesStableAcrossRecovery: false, + isolatedJoinIdentityStableAcrossRecovery: false, + parentContextStableAcrossRecovery: false, + providerIdentitySetStableAcrossRecovery: false, staticClaimObservationBeforeFinalization: true, - isolatedClaimObservationBeforeFinalization: mixedState ? true : false, + isolatedClaimObservationBeforeFinalization: false, confirmedActionCount: state.supervisorSwarm.confirmedActionCount, ...protocol, providerRequestIdentityCount: provider.requestIdentityCount, @@ -1450,47 +995,11 @@ export async function validateSupervisorSwarmEvidence() { duplicateDeliveryCount: duplicateCount( deliveries.map((delivery) => delivery.delegationId), ), - duplicateIsolatedGroupCount: mixedState - ? duplicateCount( - persistence.isolatedGroups.map((group) => group.delegationGroupId), - ) - : 0, - duplicateIsolatedInstanceCount: mixedState - ? duplicateCount( - persistence.isolatedInstances.map( - (instance) => instance.instanceId, - ), - ) - : 0, - duplicateIsolatedResultCount: mixedState - ? duplicateCount( - persistence.isolatedResults.map( - (record) => record.result?.instanceId, - ), - ) - : 0, - duplicateIsolatedJoinDeliveryCount: mixedState - ? duplicateCount( - persistence.isolatedJoinDeliveries.map( - (delivery) => delivery.delegationGroupId, - ), - ) - : 0, - duplicateIsolatedClaimAuditCount: mixedState - ? duplicateCount( - persistence.agentDb - .filter( - (record) => - record.recordType === - 'agent.runtime.agent.isolated_join.claimed_by_parent' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId, - ) - .map( - (record) => `${record.delegationGroupId}:${record.actionId}`, - ), - ) - : 0, + duplicateIsolatedGroupCount: 0, + duplicateIsolatedInstanceCount: 0, + duplicateIsolatedResultCount: 0, + duplicateIsolatedJoinDeliveryCount: 0, + duplicateIsolatedClaimAuditCount: 0, duplicateMessageCount, duplicateActionLifecycleCount: actions.duplicateActionLifecycleCount, duplicateExecutingActionIdCount: actions.duplicateExecutingActionIdCount, diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/execution.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/execution.mjs index c8116694a..b810bbba2 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/execution.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/execution.mjs @@ -1,12 +1,4 @@ -import { - recordSupervisorSwarmChatSessionFailureDiagnostic, - requireOpenSupervisorSwarmChatSession, -} from './chat-session.mjs'; -import { - captureSupervisorSwarmCollaborationPolicySnapshotAndDrift, - captureSupervisorSwarmInitialProviderBatch, - restartSupervisorSwarmRunnerAtInitialBatchBoundary, -} from './collaboration-policy.mjs'; +import { captureSupervisorSwarmInitialProviderBatch } from './collaboration-policy.mjs'; import { validateSupervisorSwarmEvidence } from './evidence-validation.mjs'; import { driveSupervisorSwarmRuntimeToCompletion, @@ -21,28 +13,19 @@ import { import { assert, claimOwnedRunner, - codedError, ensureOwnedRunnerStableKillSupport, hashValue, - isNonEmptyString, - isPlainObject, prepareCliBinary, projectSupervisorAgentId, readRuntime, requestedRunId, runCli, - sleep, - startInteractiveCli, state, supervisorSwarmSessionId, - waitForInteractiveCliExit, - waitForInteractiveCliOutput, - writeInteractiveCliLine, } from './shared.mjs'; import { isSupervisorSwarmFinalReplyTransientRetrySuite, isSupervisorSwarmInitialTransientRetrySuite, - isSupervisorSwarmInteractiveChatSuite, isSupervisorSwarmToolPlanHandoffRunnerKillSuite, } from './suite-selection.mjs'; import { captureSupervisorSwarmToolPlanHandoffRunnerKillCheckpoint } from './tool-plan-handoff.mjs'; @@ -62,42 +45,20 @@ export async function runSupervisorSwarmE2e() { chars: [...task].length, sha256: hashValue(task), }; - if (isSupervisorSwarmInteractiveChatSuite()) { - state.isolatedRunner.launchAttempted = true; - state.supervisorSwarmCliSession = startInteractiveCli([ - '--swarm-chat', + state.initialRunId = requestedRunId; + state.initialSessionId = supervisorSwarmSessionId; + await runCli( + [ + '--agent-enqueue', '--init', state.projectRoot, - ]); - await waitForInteractiveCliOutput( - state.supervisorSwarmCliSession, - (output) => - output.includes('Agent Swarm Chat') && - output.includes(`父 Agent:${projectSupervisorAgentId}`), - 'supervisor-swarm-autonomous-chat-banner-timeout', - 30_000, - ); - state.supervisorSwarm.interactiveCliUsed = true; - writeInteractiveCliLine(state.supervisorSwarmCliSession, task); - const started = await waitForSupervisorSwarmAutonomousParentRuntime(task); - state.initialRunId = started.runId; - state.initialSessionId = started.sessionId; - } else { - state.initialRunId = requestedRunId; - state.initialSessionId = supervisorSwarmSessionId; - await runCli( - [ - '--agent-enqueue', - '--init', - state.projectRoot, - projectSupervisorAgentId, - state.initialRunId, - task, - ], - { timeoutMs: 120_000 }, - ); - state.isolatedRunner.launchAttempted = true; - } + projectSupervisorAgentId, + state.initialRunId, + task, + ], + { timeoutMs: 120_000 }, + ); + state.isolatedRunner.launchAttempted = true; await claimOwnedRunner(); const runtime = await readRuntime(projectSupervisorAgentId); assert( @@ -114,86 +75,11 @@ export async function runSupervisorSwarmE2e() { await captureSupervisorSwarmToolPlanHandoffRunnerKillCheckpoint(); } await captureSupervisorSwarmInitialProviderBatch(); - await captureSupervisorSwarmCollaborationPolicySnapshotAndDrift(); - await restartSupervisorSwarmRunnerAtInitialBatchBoundary(); await driveSupervisorSwarmToRepairKillBoundary(); if (isSupervisorSwarmFinalReplyTransientRetrySuite()) { await captureSupervisorSwarmTransientRetryCheckpoint(); } await driveSupervisorSwarmRuntimeToCompletion(); - if (isSupervisorSwarmInteractiveChatSuite()) { - await captureSupervisorSwarmAutonomousTurnReport(); - writeInteractiveCliLine(state.supervisorSwarmCliSession, '/quit'); - await waitForInteractiveCliExit(state.supervisorSwarmCliSession, 30_000); - state.supervisorSwarmCliSession = null; - } state.evidence = await validateSupervisorSwarmEvidence(); assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected'); } - -export async function waitForSupervisorSwarmAutonomousParentRuntime(task) { - const deadline = Date.now() + 120_000; - while (Date.now() < deadline) { - const runtime = await readRuntime(projectSupervisorAgentId).catch( - () => null, - ); - if ( - runtime?.agentId === projectSupervisorAgentId && - runtime.sessionId === supervisorSwarmSessionId && - isNonEmptyString(runtime.runId) && - runtime.currentTask === task - ) { - return runtime; - } - if (state.supervisorSwarmCliSession?.closed) { - recordSupervisorSwarmChatSessionFailureDiagnostic( - state.supervisorSwarmCliSession, - ); - throw codedError('supervisor-swarm-autonomous-chat-closed-before-run'); - } - await sleep(50); - } - throw codedError('supervisor-swarm-autonomous-parent-runtime-timeout'); -} - -export async function captureSupervisorSwarmAutonomousTurnReport() { - const session = requireOpenSupervisorSwarmChatSession(); - const output = await waitForInteractiveCliOutput( - session, - (value) => value.includes('[turn.report] '), - 'supervisor-swarm-autonomous-turn-report-timeout', - 60_000, - ); - const reportLines = output - .split(/\r?\n/u) - .filter((line) => line.startsWith('[turn.report] ')); - assert( - reportLines.length === 1, - 'supervisor-swarm-autonomous-turn-report-count-invalid', - ); - const report = JSON.parse(reportLines[0].slice('[turn.report] '.length)); - assert( - isPlainObject(report) && - JSON.stringify(Object.keys(report).sort()) === - JSON.stringify( - [ - 'schemaVersion', - 'outcome', - 'parentAgentId', - 'sessionId', - 'parentRunId', - 'runtimeCount', - 'busyRuntimeCount', - 'pendingTaskCount', - 'runningTaskCount', - 'waitingForConfirmationCount', - 'waitingForUserInputCount', - 'newAssistantMessageCount', - 'finalReplyChars', - 'reconciliationAgentCount', - ].sort(), - ), - 'supervisor-swarm-autonomous-turn-report-shape-invalid', - ); - state.supervisorSwarm.turnReport = report; -} diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/partial-evidence.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/partial-evidence.mjs index d076cf64d..c459c46a7 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/partial-evidence.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/partial-evidence.mjs @@ -1,5 +1,4 @@ import { - supervisorSwarmMixedSpawnRequestHashesStable, supervisorSwarmParentDeliveries, supervisorSwarmRelevantRunKeys, } from './collaboration-assertions.mjs'; @@ -8,7 +7,6 @@ import { duplicateSupervisorSwarmCollaborationPolicySnapshotCount, inspectSupervisorSwarmCollaborationPolicySnapshot, inspectSupervisorSwarmCollaborationPolicySnapshotBinding, - readSupervisorSwarmCollaborationPolicySnapshotBindingFile, readSupervisorSwarmCollaborationPolicySnapshotFile, supervisorSwarmCollaborationPolicyDriftObservationCount, supervisorSwarmExpectedCollaborationPolicySnapshot, @@ -59,17 +57,9 @@ import { supervisorSwarmQualityAgentId, supervisorSwarmQualityContent, supervisorSwarmQualityPath, - supervisorSwarmSessionId, supervisorSwarmWeakQualityContent, } from './shared.mjs'; -import { - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite, - isSupervisorSwarmFinalReplyTransientRetrySuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, - supervisorSwarmExpectedIsolatedReviewGroups, - supervisorSwarmInitialIsolatedReviewsForSuite, -} from './suite-selection.mjs'; +import { isSupervisorSwarmFinalReplyTransientRetrySuite } from './suite-selection.mjs'; export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { const persistence = await readSupervisorSwarmPersistence({ @@ -125,127 +115,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { record.recordType === 'agent.runtime.provider_request.lifecycle' && relevantRuns.has(`${record.agentId}\0${record.runId}`), ); - const indexedAgentDb = persistence.agentDb.map((record, index) => ({ - record, - index, - })); - const initialBatchActions = - state.supervisorSwarm.initialProviderBatch?.actions ?? []; - const mixedSpawnAction = initialBatchActions.find( - (action) => action.actionId === state.supervisorSwarm.mixedSpawnActionId, - ); - const matchesMixedInitialAction = (record, action) => - action != null && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - record.actionId === action.actionId && - record.actionFingerprint === action.actionFingerprint && - record.tool === action.tool; - const mixedSpawnConfirmationRequirements = indexedAgentDb.filter( - ({ record }) => - record.recordType === - 'agent.runtime.provider_action_batch.confirmation_required' && - record.sessionId === supervisorSwarmSessionId && - record.batchId === state.supervisorSwarm.initialProviderBatch?.batchId && - matchesMixedInitialAction(record, mixedSpawnAction), - ); - const mixedSpawnApprovals = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_confirmation.approved' && - record.sessionId === supervisorSwarmSessionId && - record.confirmedRunId === state.initialRunId && - matchesMixedInitialAction(record, mixedSpawnAction), - ); - const mixedDelegateExecutionIndexes = initialBatchActions - .filter((action) => action.tool === 'agent.delegate') - .flatMap((action) => - indexedAgentDb - .filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_action.executing' && - matchesMixedInitialAction(record, action), - ) - .map(({ index }) => index), - ); - const mixedSpawnReceiptIndexes = indexedAgentDb - .filter( - ({ record }) => - record.recordType === 'agent.runtime.action_receipt' && - record.sessionId === supervisorSwarmSessionId && - record.status === 'ok' && - matchesMixedInitialAction(record, mixedSpawnAction), - ) - .map(({ index }) => index); - const mixedInitialSpawnConfirmationOrderValid = - mixedSpawnConfirmationRequirements.length === 1 && - mixedSpawnApprovals.length === 1 && - mixedDelegateExecutionIndexes.length === 2 && - mixedSpawnReceiptIndexes.length === 1 && - mixedSpawnConfirmationRequirements[0].index < - mixedSpawnApprovals[0].index && - mixedDelegateExecutionIndexes.every( - (index) => mixedSpawnApprovals[0].index < index, - ) && - mixedSpawnApprovals[0].index < mixedSpawnReceiptIndexes[0]; - const matchesMixedFollowupIdentity = (record) => - isNonEmptyString(state.supervisorSwarm.mixedFollowupSpawnActionId) && - record.agentId === projectSupervisorAgentId && - (record.runId === state.initialRunId || - record.confirmedRunId === state.initialRunId) && - record.actionId === state.supervisorSwarm.mixedFollowupSpawnActionId; - const matchesMixedFollowupAction = (record) => - matchesMixedFollowupIdentity(record) && - (record.tool === 'agent.spawn_isolated' || - record.commandId === 'agent.spawn_isolated'); - const mixedFollowupConfirmationRequirements = indexedAgentDb.filter( - ({ record }) => - record.recordType === - 'agent.runtime.provider_action_batch.confirmation_required' && - record.sessionId === supervisorSwarmSessionId && - matchesMixedFollowupAction(record), - ); - const mixedFollowupApprovals = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_confirmation.approved' && - record.sessionId === supervisorSwarmSessionId && - matchesMixedFollowupAction(record), - ); - const mixedFollowupSideEffects = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.agent.spawn_isolated' && - matchesMixedFollowupIdentity(record), - ); - const mixedFollowupReceiptIndexes = indexedAgentDb - .filter( - ({ record }) => - record.recordType === 'agent.runtime.action_receipt' && - record.status === 'ok' && - matchesMixedFollowupAction(record), - ) - .map(({ index }) => index); - const mixedFollowupObservationIndexes = indexedAgentDb - .filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_observation' && - record.status === 'ok' && - matchesMixedFollowupAction(record), - ) - .map(({ index }) => index); - const mixedFollowupSpawnConfirmationOrderValid = - mixedFollowupConfirmationRequirements.length === 1 && - mixedFollowupApprovals.length === 1 && - mixedFollowupSideEffects.length === 1 && - mixedFollowupReceiptIndexes.length === 1 && - mixedFollowupObservationIndexes.length === 1 && - mixedFollowupConfirmationRequirements[0].index < - mixedFollowupApprovals[0].index && - mixedFollowupApprovals[0].index < mixedFollowupSideEffects[0].index && - mixedFollowupSideEffects[0].index < mixedFollowupReceiptIndexes[0] && - mixedFollowupReceiptIndexes[0] < mixedFollowupObservationIndexes[0]; - const mixedSpawnConfirmationOrderValid = - mixedInitialSpawnConfirmationOrderValid && - (!isSupervisorSwarmMultiIsolatedHarnessSuite() || - mixedFollowupSpawnConfirmationOrderValid); const toleratePartialRead = async ( surface, reader, @@ -278,7 +147,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { isolatedEvidenceContents, changedFiles, collaborationPolicySnapshotFile, - collaborationPolicySnapshotBindingFile, collaborationPolicySidecar, finalReplyResponseStream, ] = await Promise.all([ @@ -359,14 +227,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { : null, null, ), - toleratePartialRead( - 'collaboration-policy-snapshot-binding', - () => - supervisorSwarmInitialBatchBindsPolicySnapshot() - ? readSupervisorSwarmCollaborationPolicySnapshotBindingFile() - : null, - null, - ), toleratePartialRead( 'collaboration-policy-sidecar', () => readJson(supervisorSwarmCollaborationPolicyPath()), @@ -469,63 +329,12 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { partialCollaborationPolicySnapshots[0], supervisorSwarmExpectedCollaborationPolicySnapshot(), ); - const partialCollaborationPolicySnapshotBytesStable = - isNonEmptyString( - state.supervisorSwarm.collaborationPolicySnapshotInitialBytes, - ) && - collaborationPolicySnapshotFile?.bytes === - state.supervisorSwarm.collaborationPolicySnapshotInitialBytes && - hashValue(collaborationPolicySnapshotFile.bytes) === - state.supervisorSwarm.collaborationPolicySnapshotInitialBytesSha256; - const partialCollaborationPolicySnapshotFieldsStable = - isPlainObject( - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord, - ) && - isPlainObject(collaborationPolicySnapshotFile?.snapshot) && - JSON.stringify(collaborationPolicySnapshotFile.snapshot) === - JSON.stringify( - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord, - ) && - JSON.stringify(collaborationPolicySnapshotFile.snapshot) === - JSON.stringify(partialCollaborationPolicySnapshots[0]); - const partialCollaborationPolicySnapshotFingerprintStable = - partialCollaborationPolicySnapshotFieldsStable && - collaborationPolicySnapshotFile.snapshot.snapshotFingerprint === - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord - .snapshotFingerprint && - partialCollaborationPolicySnapshotInspection.identityHash === - state.supervisorSwarm.collaborationPolicySnapshotInitialIdentityHash; - const partialCollaborationPolicySnapshotPolicyFingerprintStable = - partialCollaborationPolicySnapshotFieldsStable && - collaborationPolicySnapshotFile.snapshot.policyFingerprint === - state.supervisorSwarm.collaborationPolicySnapshotInitialRecord - .policyFingerprint; const partialCollaborationPolicySnapshotBindingInspection = inspectSupervisorSwarmCollaborationPolicySnapshotBinding( partialCollaborationPolicySnapshotBindings[0], collaborationPolicySnapshotFile?.snapshot ?? partialCollaborationPolicySnapshots[0], ); - const partialCollaborationPolicySnapshotBindingBytesStable = - isNonEmptyString( - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes, - ) && - collaborationPolicySnapshotBindingFile?.bytes === - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes && - hashValue(collaborationPolicySnapshotBindingFile.bytes) === - state.supervisorSwarm - .collaborationPolicySnapshotBindingInitialBytesSha256; - const partialCollaborationPolicySnapshotBindingFieldsStable = - isPlainObject( - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialRecord, - ) && - isPlainObject(collaborationPolicySnapshotBindingFile?.binding) && - JSON.stringify(collaborationPolicySnapshotBindingFile.binding) === - JSON.stringify( - state.supervisorSwarm.collaborationPolicySnapshotBindingInitialRecord, - ) && - JSON.stringify(collaborationPolicySnapshotBindingFile.binding) === - JSON.stringify(partialCollaborationPolicySnapshotBindings[0]); const partialCollaborationPolicyDriftObservationCount = supervisorSwarmCollaborationPolicyDriftObservationCount( persistence.agentDb, @@ -538,62 +347,9 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { duplicateSupervisorSwarmCollaborationPolicySnapshotBindingCount( partialCollaborationPolicySnapshotBindings, ); - const partialChatSessionFailureDiagnostic = - state.supervisorSwarm.chatSessionFailureDiagnostic; - const partialIdentityBefore = - state.supervisorSwarm.initialBatchRecoveryPreKillIdentity; - const partialIdentityAfter = - state.supervisorSwarm.initialBatchRecoveryPostRecoveryIdentity; - const partialSideEffectsBefore = - state.supervisorSwarm.initialBatchRecoveryPreKillSideEffects; - const partialSideEffectsAfter = - state.supervisorSwarm.initialBatchRecoveryPostRecoverySideEffects; - const partialRecoveryRequired = - isSupervisorSwarmCollaborationPolicyMixedRecoverySuite(); - const zeroSideEffectFields = [ - 'deliveryCount', - 'claimCount', - 'isolatedGroupCount', - 'isolatedChildCount', - 'isolatedResultCount', - 'isolatedJoinDeliveryCount', - 'delegatedChildTaskCount', - 'projectRevision', - 'projectModifiedPathCount', - 'projectMutationActionCount', - 'initialActionExecutionCount', - 'initialActionReceiptCount', - 'initialActionSideEffectCount', - 'recoveryFailedCount', - ]; - const partialRecoveryZeroSideEffects = - partialRecoveryRequired && - partialSideEffectsBefore != null && - partialSideEffectsAfter != null && - zeroSideEffectFields.every( - (field) => - partialSideEffectsBefore[field] === 0 && - partialSideEffectsAfter[field] === 0, - ); return buildSupervisorSwarmEvidence({ ...baseEvidence, - scenario: partialRecoveryRequired - ? 'project-supervisor-collaboration-policy-mixed-initial-batch-recovery' - : baseEvidence.scenario, - interactiveCliUsed: state.supervisorSwarm.interactiveCliUsed, - chatSessionUnexpectedlyClosed: Boolean(partialChatSessionFailureDiagnostic), - chatSessionFailureKind: - partialChatSessionFailureDiagnostic?.failureKind ?? 'none', - chatSessionExitCode: - partialChatSessionFailureDiagnostic?.exitCode ?? 'none', - chatSessionCloseSignal: - partialChatSessionFailureDiagnostic?.signal ?? 'none', - chatSessionProcessErrorCode: - partialChatSessionFailureDiagnostic?.processErrorCode ?? 'none', - chatSessionStderrChars: - partialChatSessionFailureDiagnostic?.stderrChars ?? 0, - chatSessionStderrSha256: - partialChatSessionFailureDiagnostic?.stderrSha256 ?? 'none', + scenario: baseEvidence.scenario, providerModel: state.supervisorSwarm.effectiveModel, providerApiKind: state.supervisorSwarm.effectiveApiKind, providerReasoningEffort: state.supervisorSwarm.effectiveReasoningEffort, @@ -701,12 +457,8 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { ?.orchestratorOnlyAfterDelegation ?? false, initialCollaborationContractCountsMatched: partialCollaborationContract?.initialStaticAgentIds?.length === 2 && - partialCollaborationContract?.isolatedSpawnCount === - (isSupervisorSwarmMixedHarnessSuite() ? 1 : 0) && - partialCollaborationContract?.isolatedChildCount === - (isSupervisorSwarmMixedHarnessSuite() - ? supervisorSwarmInitialIsolatedReviewsForSuite().length - : 0), + partialCollaborationContract?.isolatedSpawnCount === 0 && + partialCollaborationContract?.isolatedChildCount === 0, initialCollaborationPolicyFingerprint: partialCollaborationContract?.policyFingerprint ?? null, initialCollaborationContractFingerprint: @@ -750,19 +502,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { partialCollaborationPolicySnapshotInspection.identityHashMatched, collaborationPolicySnapshotBoundAtValid: partialCollaborationPolicySnapshotInspection.boundAtValid, - collaborationPolicySnapshotBytesStable: - partialCollaborationPolicySnapshotBytesStable, - collaborationPolicySnapshotFieldsStable: - partialCollaborationPolicySnapshotFieldsStable, - collaborationPolicySnapshotFingerprintStable: - partialCollaborationPolicySnapshotFingerprintStable, - collaborationPolicySnapshotPolicyFingerprintStable: - partialCollaborationPolicySnapshotPolicyFingerprintStable, - collaborationPolicySnapshotStable: - partialCollaborationPolicySnapshotBytesStable && - partialCollaborationPolicySnapshotFieldsStable && - partialCollaborationPolicySnapshotFingerprintStable && - partialCollaborationPolicySnapshotPolicyFingerprintStable, collaborationPolicySnapshotBindingCaptured: partialCollaborationPolicySnapshotRequired && partialCollaborationPolicySnapshotBindingInspection.exactShape, @@ -781,16 +520,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { partialCollaborationPolicySnapshotBindingInspection.boundAtMatched, collaborationPolicySnapshotBindingSnapshotMatched: partialCollaborationPolicySnapshotBindingInspection.snapshotMatched, - collaborationPolicySnapshotBindingBytesStable: - partialCollaborationPolicySnapshotBindingBytesStable, - collaborationPolicySnapshotBindingFieldsStable: - partialCollaborationPolicySnapshotBindingFieldsStable, - collaborationPolicySnapshotBindingStable: - partialCollaborationPolicySnapshotBindingBytesStable && - partialCollaborationPolicySnapshotBindingFieldsStable && - partialCollaborationPolicySnapshotBindingInspection.snapshotMatched, - collaborationPolicyDriftFixtureWritten: - state.supervisorSwarm.collaborationPolicyDriftFixtureWritten, collaborationPolicySidecarMinIsolatedGroupsBeforeClaim: collaborationPolicySidecar?.minIsolatedGroupsBeforeClaim ?? 0, collaborationPolicyDriftStatusObserved: @@ -805,142 +534,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) { residualSidecars.collaborationPolicySnapshotArtifacts, collaborationPolicySnapshotBindingResidualArtifactCount: residualSidecars.collaborationPolicySnapshotBindingArtifacts, - initialBatchRecoveryRequired: partialRecoveryRequired, - initialBatchRecoveryBoundaryObserved: - state.supervisorSwarm.initialBatchRecoveryBoundaryObserved, - initialBatchRecoveryBatchIdStable: - partialRecoveryRequired && - partialIdentityBefore?.batchId === partialIdentityAfter?.batchId, - initialBatchRecoveryPolicyFingerprintStable: - partialRecoveryRequired && - partialIdentityBefore?.policyFingerprint === - partialIdentityAfter?.policyFingerprint, - initialBatchRecoveryContractFingerprintStable: - partialRecoveryRequired && - partialIdentityBefore?.contractFingerprint === - partialIdentityAfter?.contractFingerprint, - initialBatchRecoveryAllActionIdsStable: - partialRecoveryRequired && - JSON.stringify( - partialIdentityBefore?.actions?.map((action) => action.actionId), - ) === - JSON.stringify( - partialIdentityAfter?.actions?.map((action) => action.actionId), - ), - initialBatchRecoveryProviderStartedIdentitySetStable: - partialRecoveryRequired && - JSON.stringify(partialSideEffectsBefore?.providerStartedIdentities) === - JSON.stringify(partialSideEffectsAfter?.providerStartedIdentities), - initialBatchRecoveryWaitingConfirmationStable: - partialRecoveryRequired && - partialIdentityBefore?.status === 'waiting-confirmation' && - partialIdentityBefore?.nextActionIndex === 0 && - partialIdentityAfter?.status === 'waiting-confirmation' && - partialIdentityAfter?.nextActionIndex === 0, - initialBatchRecoveryZeroSideEffects: partialRecoveryZeroSideEffects, - initialBatchRecoveryPreKillDeliveryCount: - partialSideEffectsBefore?.deliveryCount ?? 0, - initialBatchRecoveryPostRecoveryDeliveryCount: - partialSideEffectsAfter?.deliveryCount ?? 0, - initialBatchRecoveryPreKillGroupCount: - partialSideEffectsBefore?.isolatedGroupCount ?? 0, - initialBatchRecoveryPostRecoveryGroupCount: - partialSideEffectsAfter?.isolatedGroupCount ?? 0, - initialBatchRecoveryPreKillChildCount: - partialSideEffectsBefore?.isolatedChildCount ?? 0, - initialBatchRecoveryPostRecoveryChildCount: - partialSideEffectsAfter?.isolatedChildCount ?? 0, - initialBatchRecoveryPreKillProjectRevision: - partialSideEffectsBefore?.projectRevision ?? 0, - initialBatchRecoveryPostRecoveryProjectRevision: - partialSideEffectsAfter?.projectRevision ?? 0, - initialBatchRecoveryPreKillProjectModificationCount: - partialSideEffectsBefore?.projectModifiedPathCount ?? 0, - initialBatchRecoveryPostRecoveryProjectModificationCount: - partialSideEffectsAfter?.projectModifiedPathCount ?? 0, - initialBatchRecoveryPreKillActionExecutionCount: - partialSideEffectsBefore?.initialActionExecutionCount ?? 0, - initialBatchRecoveryPostRecoveryActionExecutionCount: - partialSideEffectsAfter?.initialActionExecutionCount ?? 0, - initialBatchRecoveryPreKillActionReceiptCount: - partialSideEffectsBefore?.initialActionReceiptCount ?? 0, - initialBatchRecoveryPostRecoveryActionReceiptCount: - partialSideEffectsAfter?.initialActionReceiptCount ?? 0, - initialBatchRecoveryConfirmationRestoredCount: - state.supervisorSwarm.initialBatchRecoveryConfirmationRestoredCount, - initialBatchRecoveryRunnerBootChanged: - partialRecoveryRequired && - isNonEmptyString( - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId, - ) && - isNonEmptyString( - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId, - ) && - state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId !== - state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId, - initialBatchRecoveryPidfdClaimCount: - state.supervisorSwarm.initialBatchRecoveryPidfdClaimCount, - initialBatchRecoveryPidfdSignalCount: - state.supervisorSwarm.initialBatchRecoveryPidfdSignalCount, - mixedModeEnabled: isSupervisorSwarmMixedHarnessSuite(), - mixedSpawnActionCaptured: isNonEmptyString( - state.supervisorSwarm.mixedSpawnActionId, - ), - mixedFollowupSpawnActionCaptured: isNonEmptyString( - state.supervisorSwarm.mixedFollowupSpawnActionId, - ), - mixedSpawnRequestHashStable: supervisorSwarmMixedSpawnRequestHashesStable( - isolatedRecords.groups, - ), - mixedSpawnConfirmationRequiredCount: - mixedSpawnConfirmationRequirements.length + - mixedFollowupConfirmationRequirements.length, - mixedSpawnApprovalCount: - mixedSpawnApprovals.length + mixedFollowupApprovals.length, - mixedSpawnConfirmationOrderValid, - nativeMixedCollaborationPlanCount: persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.tool_plan.protocol' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - Array.isArray(record.functionNames) && - record.functionNames.filter( - (name) => name === 'runtime_tool_agent_delegate', - ).length === 2 && - record.functionNames.filter( - (name) => name === 'runtime_tool_agent_spawn_isolated', - ).length === 1, - ).length, - nativeFollowupIsolatedPlanCount: - isSupervisorSwarmMultiIsolatedHarnessSuite() - ? persistence.agentDb.filter( - (record) => - record.recordType === 'agent.runtime.tool_plan.protocol' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - Array.isArray(record.functionNames) && - record.functionNames.filter( - (name) => name === 'runtime_tool_agent_delegate', - ).length === 0 && - record.functionNames.filter( - (name) => name === 'runtime_tool_agent_spawn_isolated', - ).length === 1, - ).length - : 0, - staticIsolatedProviderOverlapObserved: - state.supervisorSwarm.staticIsolatedProviderOverlapObserved, - staticIsolatedProviderRequestIdentityCount: new Set( - state.supervisorSwarm.staticIsolatedProviderRequestIds, - ).size, - mixedParentIdentityStable: - isolatedRecords.groups.length === - supervisorSwarmExpectedIsolatedReviewGroups().length && - isolatedRecords.groups.every( - (group) => - group.parentAgentId === projectSupervisorAgentId && - group.parentSessionId === supervisorSwarmSessionId && - group.parentRunId === state.initialRunId, - ), initialDeliveryCount: initial.length, repairDeliveryCount: repairs.length, repairDeliveryStatus: observedRepairStatus, diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/protocol-validation.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/protocol-validation.mjs index dbe6a7882..43c080801 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/protocol-validation.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/protocol-validation.mjs @@ -22,8 +22,6 @@ import { } from './shared.mjs'; import { isSupervisorSwarmFinalReplyTransientRetrySuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, isSupervisorSwarmTransientRetrySuite, } from './suite-selection.mjs'; @@ -403,12 +401,10 @@ export function validateSupervisorSwarmNativeProtocol( toolPlanRepairEvidenceHasNoFatalLocalRepair(repairEvidence), 'supervisor-swarm-native-tool-protocol-required', ); - const mixed = isSupervisorSwarmMixedHarnessSuite(); - const expectedActionFunctionCount = mixed ? 3 : 2; + const expectedActionFunctionCount = 2; const allowedInitialFunctionNames = new Set([ 'update_agent_plan', 'runtime_tool_agent_delegate', - ...(mixed ? ['runtime_tool_agent_spawn_isolated'] : []), ]); const initialMultiCall = protocols.filter((record) => { if (!Array.isArray(record.functionNames)) return false; @@ -428,7 +424,7 @@ export function validateSupervisorSwarmNativeProtocol( ).length === 2 && actionFunctionNames.filter( (name) => name === 'runtime_tool_agent_spawn_isolated', - ).length === (mixed ? 1 : 0) && + ).length === 0 && record.functionNames.every((name) => allowedInitialFunctionNames.has(name), ) && @@ -445,40 +441,6 @@ export function validateSupervisorSwarmNativeProtocol( initialMultiCall.length === 1, 'supervisor-swarm-native-collaboration-plan-count-invalid', ); - const followupIsolatedPlans = isSupervisorSwarmMultiIsolatedHarnessSuite() - ? protocols.filter((record) => { - if (!Array.isArray(record.functionNames)) return false; - const actionFunctionNames = record.functionNames.filter((name) => - name.startsWith('runtime_tool_'), - ); - return ( - record.agentId === projectSupervisorAgentId && - record.sessionId === supervisorSwarmSessionId && - record.runId === state.initialRunId && - record.loopIteration > - state.supervisorSwarm.initialProviderBatch.loopIteration && - actionFunctionNames.length === 1 && - actionFunctionNames[0] === 'runtime_tool_agent_spawn_isolated' && - record.functionNames.every((name) => - ['update_agent_plan', 'runtime_tool_agent_spawn_isolated'].includes( - name, - ), - ) && - record.functionNames.filter((name) => name === 'update_agent_plan') - .length <= 1 && - hasValidToolPlanProtocolCallProjection(record) && - record.functionCallCount === record.functionNames.length && - Array.isArray(record.callIdSha256s) && - record.callIdSha256s.length === record.functionCallCount && - new Set(record.callIdSha256s).size === record.callIdSha256s.length - ); - }) - : []; - assert( - !isSupervisorSwarmMultiIsolatedHarnessSuite() || - followupIsolatedPlans.length === 1, - 'supervisor-swarm-native-followup-isolated-plan-count-invalid', - ); return { toolPlanProtocolCount: relevant.filter( (record) => record.recordType === 'agent.runtime.tool_plan.protocol', @@ -496,10 +458,6 @@ export function validateSupervisorSwarmNativeProtocol( (record) => record.protocol === 'text_json', ).length, nativeDualDelegatePlanCount: initialMultiCall.length, - nativeMixedCollaborationPlanCount: isSupervisorSwarmMixedHarnessSuite() - ? initialMultiCall.length - : 0, - nativeFollowupIsolatedPlanCount: followupIsolatedPlans.length, }; } @@ -637,9 +595,7 @@ export function validateSupervisorSwarmActionPersistence( } const initialBatchActions = state.supervisorSwarm.initialProviderBatch?.actions ?? []; - const expectedInitialActionCount = isSupervisorSwarmMixedHarnessSuite() - ? 3 - : 2; + const expectedInitialActionCount = 2; assert( initialBatchActions.length === expectedInitialActionCount, 'supervisor-swarm-initial-batch-action-count-invalid', @@ -651,284 +607,20 @@ export function validateSupervisorSwarmActionPersistence( record.actionId === action.actionId && record.actionFingerprint === action.actionFingerprint && record.tool === action.tool; - let mixedSpawnConfirmationRequiredCount = 0; - let mixedSpawnApprovalCount = 0; - let mixedSpawnConfirmationOrderValid = false; - let mixedInitialSpawnTerminalObservationIndex = -1; - if (isSupervisorSwarmMixedHarnessSuite()) { - const delegateActions = initialBatchActions.filter( - (action) => action.tool === 'agent.delegate', - ); - const spawnActions = initialBatchActions.filter( + assert( + initialBatchActions.every( (action) => - action.tool === 'agent.spawn_isolated' && - action.actionId === state.supervisorSwarm.mixedSpawnActionId, - ); - const spawnAction = spawnActions[0]; - const indexedAgentDb = agentDb.map((record, index) => ({ record, index })); - const initialActionTimeline = (action) => { - const auto = action.tool === 'agent.delegate'; - const executions = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_action.executing' && - record.executionMode === 'auto' && - matchesInitialBatchAction(record, action), - ); - const autoObserved = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_action.observed' && - record.executionMode === 'auto' && - record.observationStatus === 'ok' && - matchesInitialBatchAction(record, action), - ); - const receipts = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.action_receipt' && - record.sessionId === supervisorSwarmSessionId && - record.executionMode === (auto ? 'auto' : 'confirmation') && - record.status === 'ok' && - matchesInitialBatchAction(record, action), - ); - const terminalObservations = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_observation' && - record.status === 'ok' && - record.decision === (auto ? 'auto' : 'approved') && - matchesInitialBatchAction(record, action), - ); - let sideEffects = []; - if (auto) { - const actionDeliveries = deliveries.filter( - (delivery) => - delivery.parentAgentId === projectSupervisorAgentId && - delivery.parentSessionId === supervisorSwarmSessionId && - delivery.parentRunId === state.initialRunId && - delivery.parentActionId === action.actionId && - delivery.repairOfDelegationId == null, - ); - sideEffects = - actionDeliveries.length === 1 - ? indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.agent.delegate' && - record.parentRunId === state.initialRunId && - record.delegationId === actionDeliveries[0].delegationId && - record.targetAgentId === actionDeliveries[0].targetAgentId && - record.runId === actionDeliveries[0].targetRunId, - ) - : []; - } else { - sideEffects = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.agent.spawn_isolated' && - record.agentId === projectSupervisorAgentId && + executing.filter((record) => matchesInitialBatchAction(record, action)) + .length === 1 && + receipts.filter( + (record) => record.sessionId === supervisorSwarmSessionId && - record.runId === state.initialRunId && - record.actionId === action.actionId, - ); - } - return { - action, - auto, - executions, - autoObserved, - receipts, - terminalObservations, - sideEffects, - }; - }; - const actionTimelines = initialBatchActions - .map(initialActionTimeline) - .sort( - (left, right) => left.action.actionIndex - right.action.actionIndex, - ); - const delegateTimelines = actionTimelines.filter(({ auto }) => auto); - const spawnTimeline = actionTimelines.find(({ auto }) => !auto); - const confirmationRequired = spawnAction - ? indexedAgentDb.filter( - ({ record }) => - record.recordType === - 'agent.runtime.provider_action_batch.confirmation_required' && - record.agentId === projectSupervisorAgentId && - record.sessionId === supervisorSwarmSessionId && - record.runId === state.initialRunId && - record.batchId === - state.supervisorSwarm.initialProviderBatch.batchId && - record.actionCount === expectedInitialActionCount && - record.actionIndex === spawnAction.actionIndex && - matchesInitialBatchAction(record, spawnAction), - ) - : []; - const approvals = spawnAction - ? indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_confirmation.approved' && - record.sessionId === supervisorSwarmSessionId && - record.confirmedRunId === state.initialRunId && - record.commandId === 'agent.spawn_isolated' && - matchesInitialBatchAction(record, spawnAction), - ) - : []; - const actionLifecycleOrderValid = actionTimelines.every((timeline) => { - if ( - timeline.sideEffects.length !== 1 || - timeline.receipts.length !== 1 || - timeline.terminalObservations.length !== 1 - ) { - return false; - } - if (timeline.auto) { - return ( - timeline.executions.length === 1 && - timeline.autoObserved.length === 1 && - timeline.executions[0].index < timeline.sideEffects[0].index && - timeline.sideEffects[0].index < timeline.autoObserved[0].index && - timeline.autoObserved[0].index < timeline.receipts[0].index && - timeline.receipts[0].index < timeline.terminalObservations[0].index - ); - } - return ( - timeline.executions.length === 0 && - timeline.autoObserved.length === 0 && - approvals.length === 1 && - approvals[0].index < timeline.sideEffects[0].index && - timeline.sideEffects[0].index < timeline.receipts[0].index && - timeline.receipts[0].index < timeline.terminalObservations[0].index - ); - }); - const batchActionOrderValid = actionTimelines.every((timeline, index) => { - if (timeline.action.actionIndex !== index) return false; - if (index === 0) return true; - const previousEnd = - actionTimelines[index - 1].terminalObservations[0]?.index; - const currentStart = timeline.auto - ? timeline.executions[0]?.index - : timeline.sideEffects[0]?.index; - return ( - Number.isSafeInteger(previousEnd) && - Number.isSafeInteger(currentStart) && - previousEnd < currentStart - ); - }); - mixedSpawnConfirmationRequiredCount = confirmationRequired.length; - mixedSpawnApprovalCount = approvals.length; - mixedInitialSpawnTerminalObservationIndex = - spawnTimeline?.terminalObservations[0]?.index ?? -1; - mixedSpawnConfirmationOrderValid = - confirmationRequired.length === 1 && - approvals.length === 1 && - confirmationRequired[0].index < approvals[0].index && - actionTimelines.every( - (timeline) => - approvals[0].index < - (timeline.auto - ? timeline.executions[0]?.index - : timeline.sideEffects[0]?.index), - ) && - actionLifecycleOrderValid && - batchActionOrderValid; - assert( - delegateActions.length === 2 && - new Set(delegateActions.map((action) => action.actionId)).size === 2 && - spawnActions.length === 1 && - spawnAction.executionMode === 'confirmation' && - delegateActions.every((action) => action.executionMode === 'auto') && - delegateTimelines.length === 2 && - spawnTimeline != null && - mixedSpawnConfirmationRequiredCount === 1 && - mixedSpawnApprovalCount === 1 && - mixedSpawnConfirmationOrderValid, - 'supervisor-swarm-mixed-initial-batch-action-persistence-invalid', - ); - } else { - assert( - initialBatchActions.every( - (action) => - executing.filter((record) => + record.status === 'ok' && matchesInitialBatchAction(record, action), - ).length === 1 && - receipts.filter( - (record) => - record.sessionId === supervisorSwarmSessionId && - record.status === 'ok' && - matchesInitialBatchAction(record, action), - ).length === 1, - ), - 'supervisor-swarm-initial-batch-action-persistence-invalid', - ); - } - if (isSupervisorSwarmMultiIsolatedHarnessSuite()) { - const followupActionId = state.supervisorSwarm.mixedFollowupSpawnActionId; - assert( - isNonEmptyString(followupActionId) && - followupActionId !== state.supervisorSwarm.mixedSpawnActionId, - 'supervisor-swarm-followup-spawn-action-missing', - ); - const indexedAgentDb = agentDb.map((record, index) => ({ record, index })); - const matchesFollowupIdentity = (record) => - record.agentId === projectSupervisorAgentId && - (record.runId === state.initialRunId || - record.confirmedRunId === state.initialRunId) && - record.actionId === followupActionId; - const matchesFollowupToolRecord = (record) => - matchesFollowupIdentity(record) && - (record.tool === 'agent.spawn_isolated' || - record.commandId === 'agent.spawn_isolated'); - const confirmationRequired = indexedAgentDb.filter( - ({ record }) => - record.recordType === - 'agent.runtime.provider_action_batch.confirmation_required' && - record.sessionId === supervisorSwarmSessionId && - matchesFollowupToolRecord(record), - ); - const approvals = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_confirmation.approved' && - record.sessionId === supervisorSwarmSessionId && - matchesFollowupToolRecord(record), - ); - const sideEffects = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.agent.spawn_isolated' && - record.sessionId === supervisorSwarmSessionId && - matchesFollowupIdentity(record), - ); - const followupReceipts = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.action_receipt' && - record.sessionId === supervisorSwarmSessionId && - record.executionMode === 'confirmation' && - record.status === 'ok' && - matchesFollowupToolRecord(record), - ); - const terminalObservations = indexedAgentDb.filter( - ({ record }) => - record.recordType === 'agent.runtime.tool_observation' && - record.status === 'ok' && - record.decision === 'approved' && - matchesFollowupToolRecord(record), - ); - const followupOrderValid = - confirmationRequired.length === 1 && - approvals.length === 1 && - sideEffects.length === 1 && - followupReceipts.length === 1 && - terminalObservations.length === 1 && - mixedInitialSpawnTerminalObservationIndex >= 0 && - mixedInitialSpawnTerminalObservationIndex < - confirmationRequired[0].index && - confirmationRequired[0].index < approvals[0].index && - approvals[0].index < sideEffects[0].index && - sideEffects[0].index < followupReceipts[0].index && - followupReceipts[0].index < terminalObservations[0].index; - assert( - followupOrderValid, - 'supervisor-swarm-followup-spawn-action-persistence-invalid', - ); - mixedSpawnConfirmationRequiredCount += confirmationRequired.length; - mixedSpawnApprovalCount += approvals.length; - mixedSpawnConfirmationOrderValid &&= followupOrderValid; - } + ).length === 1, + ), + 'supervisor-swarm-initial-batch-action-persistence-invalid', + ); const matchesRecoveredRepairPending = (record) => record.agentId === state.supervisorSwarm.repairTargetAgentId && record.runId === state.supervisorSwarm.repairTargetRunId && @@ -957,9 +649,6 @@ export function validateSupervisorSwarmActionPersistence( duplicateReceiptCount, duplicateExecutingActionIdCount, isolatedMutationActionCount: isolatedMutationActions.length, - mixedSpawnConfirmationRequiredCount, - mixedSpawnApprovalCount, - mixedSpawnConfirmationOrderValid, recoveredRepairPendingActionCount: recoveredRepairConfirmationLifecycle.receipts.length, failedRepairActionCount: repairAttempts.failedRepairActions.length, diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/repair-recovery.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/repair-recovery.mjs index 79274d129..e2b644de0 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/repair-recovery.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/repair-recovery.mjs @@ -6,14 +6,10 @@ import { assertSupervisorSwarmRuntimeHealthy, assertSupervisorSwarmWeakQualityDelivery, observeSupervisorSwarmInitialProviderOverlap, - observeSupervisorSwarmStaticIsolatedProviderOverlap, - supervisorSwarmMixedIsolatedClaimsReady, supervisorSwarmParentDeliveries, supervisorSwarmRelevantRunKeys, supervisorSwarmRepairAttemptEvidence, - validateSupervisorSwarmMixedIsolatedPersistence, } from './collaboration-assertions.mjs'; -import { validateSupervisorSwarmIsolatedSpawnInput } from './collaboration-policy.mjs'; import { readSupervisorSwarmResidualSidecarCounts, supervisorSwarmResidualSidecarsEmpty, @@ -33,8 +29,6 @@ import { codedError, confirmPendingActions, findPendingActions, - hashValue, - isNonEmptyString, isPlainObject, killRunnerOnce, path, @@ -45,7 +39,6 @@ import { sleep, state, supervisorSwarmConfirmedTools, - supervisorSwarmIsolatedReviews, supervisorSwarmQualityAgentId, supervisorSwarmQualityPath, supervisorSwarmSessionId, @@ -55,11 +48,7 @@ import { } from './shared.mjs'; import { isSupervisorSwarmInitialTransientRetrySuite, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, isSupervisorSwarmTransientRetrySuite, - supervisorSwarmExpectedIsolatedReviewGroups, } from './suite-selection.mjs'; export async function confirmSupervisorSwarmPendingActions( @@ -68,50 +57,9 @@ export async function confirmSupervisorSwarmPendingActions( ) { const before = state.confirmedActionIds.size; const allowedTools = new Set(supervisorSwarmConfirmedTools); - if (isSupervisorSwarmMixedHarnessSuite()) { - allowedTools.add('agent.spawn_isolated'); - } await confirmPendingActions(allowedTools, (pending) => { const runKey = `${pending.agentId}\0${pending.runId}`; const deferred = deferredRunKeys.has(runKey); - if (pending.tool === 'agent.spawn_isolated') { - const initialSpawn = - pending.actionId === state.supervisorSwarm.mixedSpawnActionId; - if (!initialSpawn) { - assert( - isSupervisorSwarmMultiIsolatedHarnessSuite(), - 'supervisor-swarm-followup-spawn-outside-multi-group-suite', - ); - const requestHash = validateSupervisorSwarmIsolatedSpawnInput( - pending.action?.input, - 1, - 'supervisor-swarm-mixed-followup', - supervisorSwarmExpectedIsolatedReviewGroups(), - ); - assert( - isNonEmptyString(state.supervisorSwarm.mixedSpawnActionId) && - pending.actionId !== state.supervisorSwarm.mixedSpawnActionId && - (state.supervisorSwarm.mixedFollowupSpawnActionId == null || - state.supervisorSwarm.mixedFollowupSpawnActionId === - pending.actionId) && - (state.supervisorSwarm.mixedFollowupSpawnRequestHash == null || - state.supervisorSwarm.mixedFollowupSpawnRequestHash === - requestHash), - 'supervisor-swarm-followup-spawn-identity-invalid', - ); - state.supervisorSwarm.mixedFollowupSpawnActionId = pending.actionId; - state.supervisorSwarm.mixedFollowupSpawnRequestHash = requestHash; - } - assert( - isSupervisorSwarmMixedHarnessSuite() && - pending.agentId === projectSupervisorAgentId && - pending.runId === state.initialRunId && - (initialSpawn || - pending.actionId === - state.supervisorSwarm.mixedFollowupSpawnActionId), - 'supervisor-swarm-unexpected-spawn-confirmation', - ); - } assert( confirmRunKeys.has(runKey) || deferred, 'supervisor-swarm-unexpected-pending-run', @@ -121,14 +69,7 @@ export async function confirmSupervisorSwarmPendingActions( pending.agentId === projectSupervisorAgentId && pending.runId === state.initialRunId; assert( - !isParentRun || - pending.tool === 'project.verify' || - (isSupervisorSwarmMixedHarnessSuite() && - pending.tool === 'agent.spawn_isolated' && - [ - state.supervisorSwarm.mixedSpawnActionId, - state.supervisorSwarm.mixedFollowupSpawnActionId, - ].includes(pending.actionId)), + !isParentRun || pending.tool === 'project.verify', 'supervisor-swarm-parent-pending-tool-invalid', ); return true; @@ -255,9 +196,6 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( repair, repairPending, ) { - const mixedState = isSupervisorSwarmMixedHarnessSuite() - ? validateSupervisorSwarmMixedIsolatedPersistence(persistence) - : null; const initial = supervisorSwarmParentDeliveries( persistence.deliveries, ).filter((delivery) => delivery.repairOfDelegationId == null); @@ -290,11 +228,6 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( (delivery) => task.agentId === delivery.targetAgentId && task.runId === delivery.targetRunId, - ) || - (mixedState?.instances ?? []).some( - (instance) => - task.agentId === instance.instanceId && - task.runId === instance.runId, ), ) .map(supervisorSwarmTaskIdentity) @@ -312,14 +245,10 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( .map(supervisorSwarmClaimIdentity) .sort((left, right) => left.actionId.localeCompare(right.actionId)); assert( - preKillTaskIdentities.length === (mixedState ? 7 : 4) && - preKillClaimIdentities.length === 1, + preKillTaskIdentities.length === 4 && preKillClaimIdentities.length === 1, 'supervisor-swarm-pre-kill-work-or-claim-identity-invalid', ); - const relevantRuns = supervisorSwarmRelevantRunKeys( - parentDeliveries, - mixedState?.instances ?? [], - ); + const relevantRuns = supervisorSwarmRelevantRunKeys(parentDeliveries); const preKillProviderStartedIdentities = supervisorSwarmProviderStartedIdentities(persistence.agentDb, relevantRuns); const preKillParentContext = persistence.contextBundles.find( @@ -353,12 +282,6 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( .sort((left, right) => left.delegationId.localeCompare(right.delegationId)); state.supervisorSwarm.preKillClaimIdentities = preKillClaimIdentities; state.supervisorSwarm.preKillTaskIdentities = preKillTaskIdentities; - state.supervisorSwarm.preKillMixedIdentity = mixedState - ? { - isolated: mixedState.identity, - parentContextSha256: hashValue(JSON.stringify(preKillParentContext)), - } - : null; state.supervisorSwarm.runnerKillBoundaryObserved = true; await killRunnerOnce(); @@ -437,21 +360,11 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( candidate.tool === repairPending.tool, ); if (recoveredPending) { - const currentMixedState = isSupervisorSwarmMixedHarnessSuite() - ? validateSupervisorSwarmMixedIsolatedPersistence(after) - : null; - const currentParentContext = after.contextBundles.find( - (bundle) => - bundle.agentId === projectSupervisorAgentId && - bundle.sessionId === supervisorSwarmSessionId && - bundle.runId === state.initialRunId, - ); const currentProviderStartedIdentities = supervisorSwarmProviderStartedIdentities( after.agentDb, supervisorSwarmRelevantRunKeys( supervisorSwarmParentDeliveries(after.deliveries), - currentMixedState?.instances ?? [], ), ); assert( @@ -468,15 +381,7 @@ export async function restartSupervisorSwarmRunnerAtRepairBoundary( JSON.stringify(currentProviderStartedIdentities) === JSON.stringify( state.supervisorSwarm.preKillProviderStartedIdentities, - ) && - (!currentMixedState || - (JSON.stringify(currentMixedState.identity) === - JSON.stringify( - state.supervisorSwarm.preKillMixedIdentity?.isolated, - ) && - hashValue(JSON.stringify(currentParentContext)) === - state.supervisorSwarm.preKillMixedIdentity - ?.parentContextSha256)), + ), 'supervisor-swarm-recovery-identity-or-provider-replay-invalid', ); state.identityStable = true; @@ -500,84 +405,18 @@ export async function driveSupervisorSwarmToRepairKillBoundary() { (delivery) => delivery.repairOfDelegationId == null, ); assert(initial.length <= 2, 'supervisor-swarm-extra-initial-delivery'); - if ( - isSupervisorSwarmMixedHarnessSuite() && - isNonEmptyString(state.supervisorSwarm.mixedSpawnActionId) && - !state.confirmedActionIds.has(state.supervisorSwarm.mixedSpawnActionId) - ) { - const pending = await findPendingActions(); - if ( - pending.some( - (candidate) => - candidate.agentId === projectSupervisorAgentId && - candidate.runId === state.initialRunId && - candidate.actionId === state.supervisorSwarm.mixedSpawnActionId && - candidate.tool === 'agent.spawn_isolated', - ) - ) { - await confirmSupervisorSwarmPendingActions( - new Set([`${projectSupervisorAgentId}\0${state.initialRunId}`]), - new Set( - initial.map( - (delivery) => - `${delivery.targetAgentId}\0${delivery.targetRunId}`, - ), - ), - ); - } - } - if (isSupervisorSwarmMultiIsolatedHarnessSuite()) { - const pending = await findPendingActions(); - const followupSpawnPending = pending.find( - (candidate) => - candidate.agentId === projectSupervisorAgentId && - candidate.runId === state.initialRunId && - candidate.tool === 'agent.spawn_isolated' && - candidate.actionId !== state.supervisorSwarm.mixedSpawnActionId, - ); - if ( - followupSpawnPending && - !state.confirmedActionIds.has(followupSpawnPending.actionId) - ) { - await confirmSupervisorSwarmPendingActions( - new Set([`${projectSupervisorAgentId}\0${state.initialRunId}`]), - new Set( - parentDeliveries.map( - (delivery) => - `${delivery.targetAgentId}\0${delivery.targetRunId}`, - ), - ), - ); - } - } if (initial.length === 2) { const { quality } = assertSupervisorSwarmInitialDeliveries(initial); observeSupervisorSwarmInitialProviderOverlap( persistence.agentDb, initial, ); - const isolatedInstances = - supervisorSwarmParentIsolatedRecords(persistence).instances; - observeSupervisorSwarmStaticIsolatedProviderOverlap( - persistence.agentDb, - initial, - isolatedInstances, - ); - if ( - state.supervisorSwarm.initialProviderOverlapObserved && - (!isSupervisorSwarmMixedHarnessSuite() || - state.supervisorSwarm.staticIsolatedProviderOverlapObserved) - ) { + if (state.supervisorSwarm.initialProviderOverlapObserved) { const confirmRunKeys = new Set( initial.map( (delivery) => `${delivery.targetAgentId}\0${delivery.targetRunId}`, ), ); - if (isSupervisorSwarmInteractiveChatSuite()) { - confirmRunKeys.add( - `${projectSupervisorAgentId}\0${state.initialRunId}`, - ); - } await confirmSupervisorSwarmPendingActions( confirmRunKeys, new Set( @@ -685,13 +524,6 @@ export async function driveSupervisorSwarmToRepairKillBoundary() { } return; } - if (isSupervisorSwarmMixedHarnessSuite()) { - if (!supervisorSwarmMixedIsolatedClaimsReady(persistence)) { - await sleep(50); - continue; - } - validateSupervisorSwarmMixedIsolatedPersistence(persistence); - } await restartSupervisorSwarmRunnerAtRepairBoundary( persistence, repair, @@ -790,29 +622,6 @@ export async function driveSupervisorSwarmRuntimeToCompletion() { delivery.delegationId === task.delegationId, ), ); - const isolatedRecords = supervisorSwarmParentIsolatedRecords(persistence); - observeSupervisorSwarmStaticIsolatedProviderOverlap( - persistence.agentDb, - deliveries.filter((delivery) => delivery.repairOfDelegationId == null), - isolatedRecords.instances, - ); - const mixedReady = - !isSupervisorSwarmMixedHarnessSuite() || - (isolatedRecords.groups.length === - supervisorSwarmExpectedIsolatedReviewGroups().length && - isolatedRecords.instances.length === - supervisorSwarmIsolatedReviews.length && - isolatedRecords.results.length === - supervisorSwarmIsolatedReviews.length && - isolatedRecords.results.every( - (record) => record.result?.status === 'completed', - ) && - isolatedRecords.joinDeliveries.length === - supervisorSwarmExpectedIsolatedReviewGroups().length && - isolatedRecords.joinDeliveries.every( - (delivery) => delivery.status === 'claimed-by-parent', - ) && - validateSupervisorSwarmMixedIsolatedPersistence(persistence)); const terminalStateObserved = deliveries.length === 3 && deliveries.every((delivery) => delivery.status === 'claimed-by-parent') && @@ -820,7 +629,6 @@ export async function driveSupervisorSwarmRuntimeToCompletion() { childTasks.every( (task) => task.status === 'completed' && task.phase === 'completed', ) && - Boolean(mixedReady) && parentRuntime?.runId === state.initialRunId && parentRuntime?.sessionId === supervisorSwarmSessionId && parentRuntime?.status === 'idle' && diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/setup.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/setup.mjs index 3d4c3d4f6..e39cdb29e 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/setup.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-swarm/setup.mjs @@ -5,8 +5,6 @@ import { commandPassedMarker, configFileName, fs, - hashValue, - mainAgentId, path, randomUUID, readJson, @@ -14,9 +12,6 @@ import { seedDisposableProject, state, supervisorCollaborationPolicySchemaVersion, - supervisorSwarmAutonomousRoutingTerms, - supervisorSwarmAutonomousTask, - supervisorSwarmCollaborationPolicyControlTerms, supervisorSwarmDeniedMutationTools, supervisorSwarmDesignAgentId, supervisorSwarmDesignContent, @@ -29,61 +24,15 @@ import { supervisorSwarmQualityMarker, supervisorSwarmQualityPath, supervisorSwarmWeakQualityContent, + visibleText, } from './shared.mjs'; -import { - isSupervisorSwarmAutonomousChatSuite, - isSupervisorSwarmInteractiveChatSuite, - isSupervisorSwarmMixedHarnessSuite, - isSupervisorSwarmMultiIsolatedHarnessSuite, - isSupervisorSwarmTransientRetrySuite, - supervisorSwarmInitialIsolatedReviewsForSuite, -} from './suite-selection.mjs'; + +export function supervisorSwarmVerificationFixtureSource() { + return `import fs from 'node:fs';\nconst html = fs.readFileSync('game/index.html', 'utf8');\nconst agents = fs.readFileSync('AGENTS.md', 'utf8');\nconst optionalExact = (file, expected) => !fs.existsSync(file) || fs.readFileSync(file, 'utf8') === expected;\nconst passed = html.includes(${JSON.stringify(visibleText)}) && html.includes(' output.includes('Agent Swarm Chat'), - 'user-input-cli-banner-timeout', - 30_000, - ); - writeInteractiveCliLine(state.userInputCliSession, task); - - const pending = await waitForPendingUserInputRequest(); - await waitForInteractiveCliOutput( - state.userInputCliSession, - (output) => - output.includes(`[Needs input] agent=${projectSupervisorAgentId}`) && - output.includes(`request=${pending.requestId}`), - 'user-input-cli-question-timeout', - 120_000, - ); - await claimOwnedRunner(); - const beforeKillRunner = await readRunnerStatus(); - state.userInput.oldRunnerBootId = runnerBootId(beforeKillRunner); - assert( - isNonEmptyString(state.userInput.oldRunnerBootId), - 'user-input-runner-boot-before-kill-missing', - ); - await captureUserInputWaitingBoundary('before-kill'); - await killRunnerOnce(); - await runCli(['--agent-resume', state.projectRoot], { timeoutMs: 120_000 }); - state.resumed = true; - const restarted = await waitForRunnerBootChange( - state.userInput.oldRunnerBootId, - ); - state.userInput.newRunnerBootId = runnerBootId(restarted); - await claimOwnedRunner(restarted); - await captureUserInputWaitingBoundary('after-restart'); - await sleep(750); - await captureUserInputWaitingBoundary('after-stable-window'); - - writeInteractiveCliLine(state.userInputCliSession, userInputAnswerText); - await answerRemainingInteractiveQuestions(state.userInputCliSession); - await waitForInteractiveCliOutput( - state.userInputCliSession, - (output) => output.includes(`[\u5df2\u56de\u7b54] ${pending.requestId}`), - 'user-input-cli-answer-timeout', - 60_000, - ); - await waitForUserInputRuntimeCompletion(); - await waitForInteractiveCliOutput( - state.userInputCliSession, - (output) => - output.includes('\nAgent> ') || - output.includes( - '[\u672c\u8f6e\u7ed3\u675f] 父 Agent 回复已完整流式输出。', - ), - 'user-input-cli-final-reply-timeout', - 120_000, - ); - writeInteractiveCliLine(state.userInputCliSession, '/quit'); - await waitForInteractiveCliExit(state.userInputCliSession, 30_000); - state.userInputCliSession = null; - - state.identityStable = true; - state.evidence = await validateUserInputRuntimeEvidence(); - assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected'); -} - -export async function readUserInputSidecars() { - const files = ( - await listFiles(path.join(state.projectRoot, '.agent/runtime/user-input')) - ) - .filter((file) => file.endsWith('.json')) - .sort(); - return Promise.all( - files.map(async (file) => ({ file, record: await readJson(file) })), - ); -} - -export async function readUserInputPersistence() { - const runtimeStatePath = path.join( - state.projectRoot, - '.agent/runtime/agents', - `${projectSupervisorAgentId}.json`, - ); - const [ - taskSnapshot, - events, - agentDb, - activity, - output, - runtimeState, - sidecars, - ] = await Promise.all([ - readTaskSnapshot(), - readAllRuntimeEvents(), - readOptionalJsonl(path.join(state.projectRoot, '.agent/agent.db')), - readOptionalJsonl(path.join(state.projectRoot, '.agent/activity.jsonl')), - readOptionalJsonl(path.join(state.projectRoot, '.agent/output.jsonl')), - readJson(runtimeStatePath).catch(() => null), - readUserInputSidecars(), - ]); - const sessionId = runtimeState?.sessionId ?? state.initialSessionId; - const conversations = isNonEmptyString(sessionId) - ? await readOptionalJsonl( - agentConversationPath(projectSupervisorAgentId, sessionId), - ) - : []; - return { - taskSnapshot, - events, - agentDb, - activity, - output, - runtimeState, - sidecars, - conversations, - }; -} - -export function userInputProviderLifecycleStarted(agentDb) { - return agentDb.filter( - (record) => - record.recordType === 'agent.runtime.provider_request.lifecycle' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - record.status === 'started', - ); -} - -export async function waitForPendingUserInputRequest() { - const deadline = Date.now() + 8 * 60 * 1000; - while (Date.now() < deadline) { - const persistence = await readUserInputPersistence(); - const { runtimeState, sidecars, conversations, taskSnapshot, agentDb } = - persistence; - const latest = taskSnapshot.latest.find( - (task) => - task.agentId === projectSupervisorAgentId && - task.runId === runtimeState?.runId, - ); - if (latest && isFailedTask(latest)) { - throw codedError('user-input-runtime-failed-before-question'); - } - if ( - runtimeState?.agentId === projectSupervisorAgentId && - runtimeState.status === 'waiting-for-user-input' && - runtimeState.phase === 'waiting-for-user-input' && - sidecars.length === 1 - ) { - const record = sidecars[0].record; - assert( - record.schemaVersion === 'game-creator-runtime-user-input.v1' && - record.agentId === projectSupervisorAgentId && - record.runId === runtimeState.runId && - record.sessionId === runtimeState.sessionId && - record.status === 'pending' && - Array.isArray(record.questions) && - record.questions.length === 1 && - record.responseId == null && - Object.keys(record.answers ?? {}).length === 0, - 'user-input-pending-sidecar-invalid', - ); - const questionMessages = conversations.filter( - (message) => - message.role === 'assistant' && - message.messageId === record.questionMessageId, - ); - assert( - questionMessages.length === 1 && - questionMessages[0].content.includes(record.questions[0].question), - 'user-input-question-conversation-invalid', - ); - state.initialRunId = runtimeState.runId; - state.initialSessionId = runtimeState.sessionId; - state.userInput.requestId = record.requestId; - state.userInput.actionId = record.actionId; - state.userInput.questionMessageId = record.questionMessageId; - state.userInput.questionCount = record.questions.length; - state.userInput.optionCount = record.questions.reduce( - (count, question) => count + question.options.length, - 0, - ); - state.userInput.privateValues = [ - ...record.questions.map((question) => question.question), - ...record.questions.flatMap((question) => - question.options.map((option) => option.description), - ), - ].filter(isNonEmptyString); - state.userInput.providerStartedBeforeKill = - userInputProviderLifecycleStarted(agentDb).length; - state.userInput.conversationCountBeforeKill = conversations.length; - assert( - state.userInput.providerStartedBeforeKill > 0, - 'user-input-provider-planning-lifecycle-missing', - ); - return record; - } - if (runtimeState?.phase === 'needs-reconciliation') { - throw codedError('user-input-runtime-needs-reconciliation'); - } - await sleep(250); - } - throw codedError('user-input-question-timeout'); -} - -export async function captureUserInputWaitingBoundary(stage) { - const persistence = await readUserInputPersistence(); - const { runtimeState, sidecars, conversations, agentDb } = persistence; - assert( - runtimeState?.agentId === projectSupervisorAgentId && - runtimeState.runId === state.initialRunId && - runtimeState.sessionId === state.initialSessionId && - runtimeState.status === 'waiting-for-user-input' && - runtimeState.phase === 'waiting-for-user-input' && - sidecars.length === 1 && - sidecars[0].record.requestId === state.userInput.requestId && - sidecars[0].record.actionId === state.userInput.actionId && - sidecars[0].record.status === 'pending' && - sidecars[0].record.responseId == null && - conversations.length === state.userInput.conversationCountBeforeKill, - `user-input-${stage}-waiting-boundary-invalid`, - ); - const providerStarted = userInputProviderLifecycleStarted(agentDb).length; - assert( - providerStarted === state.userInput.providerStartedBeforeKill, - `user-input-${stage}-provider-called-while-waiting`, - ); - if (stage !== 'before-kill') { - state.userInput.providerStartedAfterRestart = providerStarted; - state.userInput.conversationCountAfterRestart = conversations.length; - } -} - -export async function waitForUserInputRuntimeCompletion() { - const deadline = Date.now() + 8 * 60 * 1000; - while (Date.now() < deadline) { - const persistence = await readUserInputPersistence(); - const { runtimeState, sidecars, taskSnapshot, conversations } = persistence; - if (sidecars.length > 1) { - throw codedError('user-input-unexpected-second-request'); - } - const latest = taskSnapshot.latest.find( - (task) => - task.agentId === projectSupervisorAgentId && - task.runId === state.initialRunId, - ); - if (latest && isFailedTask(latest)) { - throw codedError('user-input-runtime-failed-after-answer'); - } - if ( - runtimeState?.runId === state.initialRunId && - runtimeState.sessionId === state.initialSessionId && - runtimeState.status === 'idle' && - runtimeState.phase === 'completed' && - latest?.status === 'completed' && - latest.phase === 'completed' && - sidecars.length === 1 && - sidecars[0].record.status === 'answered' - ) { - const record = sidecars[0].record; - state.userInput.responseId = record.responseId; - state.userInput.answerMessageId = record.answerMessageId; - const finalAssistants = conversations.filter( - (message) => - message.role === 'assistant' && - message.messageId !== record.questionMessageId, - ); - if (finalAssistants.length === 1) return persistence; - } - if (runtimeState?.phase === 'needs-reconciliation') { - throw codedError('user-input-runtime-needs-reconciliation-after-answer'); - } - await sleep(250); - } - throw codedError('user-input-completion-timeout'); -} - -export function validateUserInputProviderLifecycle(agentDb) { - const lifecycle = agentDb.filter( - (record) => - record.recordType === 'agent.runtime.provider_request.lifecycle' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId, - ); - const byRequest = new Map(); - for (const record of lifecycle) { - assert( - isNonEmptyString(record.requestId) && - isNonEmptyString(record.requestKind) && - isNonEmptyString(record.requestSlot), - 'user-input-provider-lifecycle-identity-invalid', - ); - const records = byRequest.get(record.requestId) ?? []; - records.push(record); - byRequest.set(record.requestId, records); - } - for (const records of byRequest.values()) { - assert( - records.length === 2 && - records[0].status === 'started' && - ['completed', 'failed', 'interrupted'].includes(records[1].status) && - records[0].requestKind === records[1].requestKind && - records[0].requestSlot === records[1].requestSlot && - records[0].runId === records[1].runId, - 'user-input-provider-lifecycle-sequence-invalid', - ); - } - const started = lifecycle.filter((record) => record.status === 'started'); - assert( - started.length >= 2 && - started.length === byRequest.size && - state.userInput.providerStartedBeforeKill === - state.userInput.providerStartedAfterRestart, - 'user-input-provider-lifecycle-count-invalid', - ); - return { - requestIdentityCount: byRequest.size, - startedCount: started.length, - terminalCount: lifecycle.length - started.length, - }; -} - -export async function validateUserInputRuntimeEvidence() { - const persistence = await readUserInputPersistence(); - const { - taskSnapshot, - events, - agentDb, - activity, - output, - runtimeState, - sidecars, - conversations, - } = persistence; - assert(sidecars.length === 1, 'user-input-sidecar-count-invalid'); - const sidecar = sidecars[0].record; - const latest = taskSnapshot.latest.find( - (task) => - task.agentId === projectSupervisorAgentId && - task.runId === state.initialRunId, - ); - assert( - runtimeState?.agentId === projectSupervisorAgentId && - runtimeState.runId === state.initialRunId && - runtimeState.sessionId === state.initialSessionId && - runtimeState.status === 'idle' && - runtimeState.phase === 'completed' && - latest?.status === 'completed' && - latest.phase === 'completed', - 'user-input-final-runtime-identity-invalid', - ); - assert( - sidecar.schemaVersion === 'game-creator-runtime-user-input.v1' && - sidecar.agentId === projectSupervisorAgentId && - sidecar.runId === state.initialRunId && - sidecar.sessionId === state.initialSessionId && - sidecar.requestId === state.userInput.requestId && - sidecar.actionId === state.userInput.actionId && - sidecar.status === 'answered' && - sidecar.responseId === state.userInput.responseId && - sidecar.questionMessageId === state.userInput.questionMessageId && - sidecar.answerMessageId === state.userInput.answerMessageId && - sidecar.questions.length === 1 && - Object.keys(sidecar.answers).length === 1 && - Object.values(sidecar.answers)[0] === userInputAnswerText, - 'user-input-final-sidecar-invalid', - ); - const questionMessages = conversations.filter( - (message) => - message.role === 'assistant' && - message.messageId === sidecar.questionMessageId, - ); - const answerMessages = conversations.filter( - (message) => - message.role === 'user' && message.messageId === sidecar.answerMessageId, - ); - const finalAssistants = conversations.filter( - (message) => - message.role === 'assistant' && - message.messageId !== sidecar.questionMessageId, - ); - assert( - questionMessages.length === 1 && - answerMessages.length === 1 && - answerMessages[0].content.includes(userInputAnswerCanary) && - finalAssistants.length === 1, - 'user-input-conversation-cardinality-invalid', - ); - const duplicateMessageCount = duplicateCount( - conversations.map((message) => message.messageId).filter(Boolean), - ); - assert(duplicateMessageCount === 0, 'user-input-duplicate-message-identity'); - const observations = agentDb.filter( - (record) => - record.recordType === 'agent.runtime.user_input.answered' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId && - record.actionId === sidecar.actionId && - record.requestId === sidecar.requestId, - ); - assert( - observations.length === 1 && - JSON.stringify(observations[0]).includes('answerCount=1'), - 'user-input-public-observation-count-invalid', - ); - const lifecycle = validateUserInputProviderLifecycle(agentDb); - const completedAudits = agentDb.filter( - (record) => - record.recordType === 'agent.runtime.completed' && - record.agentId === projectSupervisorAgentId && - record.runId === state.initialRunId, - ); - assert( - completedAudits.length === 1, - 'user-input-completed-audit-count-invalid', - ); - const finalizationFiles = ( - await listFiles( - path.join(state.projectRoot, '.agent/runtime/finalizations'), - ) - ).filter((file) => file.endsWith('.json')); - assert( - finalizationFiles.length === 0, - 'user-input-finalization-journal-present', - ); - assert( - countExactSecrets( - Buffer.from(finalAssistants.map((message) => message.content).join('\n')), - disposableProjectPathVariants(), - ) === 0, - 'user-input-final-assistant-project-path-leak', - ); - - const publicSurfaces = { - event: events, - agentDb, - activity, - output, - runtimeState, - }; - const privateValues = [ - userInputAnswerCanary, - userInputAnswerText, - ...state.userInput.privateValues, - ].filter(isNonEmptyString); - const taskPrivateValues = privateValues.filter( - (value) => !buildUserInputTaskPrompt().includes(value), - ); - const privateBodyPublicCounts = countSensitiveValuesBySurface( - publicSurfaces, - privateValues, - 'user-input-private-body-public', - ); - const taskPrivateBodyPublicCounts = countSensitiveValuesBySurface( - { task: taskSnapshot.all }, - taskPrivateValues, - 'user-input-private-body-public', - ); - const apiKeyPublicCounts = countSensitiveValuesBySurface( - { task: taskSnapshot.all, ...publicSurfaces }, - state.secrets, - 'user-input-api-key-public', - ); - const projectPathPublicCounts = countSensitiveValuesBySurface( - { task: taskSnapshot.all, ...publicSurfaces }, - disposableProjectPathVariants(), - 'user-input-project-path-public', - ); - const sidecarSecretLeakCount = countExactSecrets( - Buffer.from(JSON.stringify(sidecar)), - state.secrets, - ); - assert( - sidecarSecretLeakCount === 0, - 'user-input-sidecar-secret-leak-detected', - ); - const secretLeakCount = await countSecretsInProject( - state.projectRoot, - state.secrets, - ); - assert(secretLeakCount === 0, 'user-input-project-secret-leak-detected'); - - return { - scenario: 'project-supervisor-needs-input-runner-restart', - targetAgentId: projectSupervisorAgentId, - providerModel: 'gpt-5.5', - isolatedAppDataUsed: true, - formalConfigCliCallCount: state.isolatedRunner.sourceConfigCliCallCount, - sourceRunnerEndpointUnchanged: false, - sourceConfigHardlinkCount: state.isolatedRunner.configLinks.length, - sourceConfigLinksVerified: false, - taskCount: taskSnapshot.all.length, - eventCount: events.length, - agentDbRecordCount: agentDb.length, - conversationMessageCount: conversations.length, - targetRunCount: new Set( - taskSnapshot.all - .filter((task) => task.agentId === projectSupervisorAgentId) - .map((task) => task.runId), - ).size, - stableSessionCount: new Set( - taskSnapshot.all - .filter((task) => task.agentId === projectSupervisorAgentId) - .map((task) => task.sessionId), - ).size, - userInputSidecarCount: sidecars.length, - userInputQuestionCount: sidecar.questions.length, - userInputOptionCount: state.userInput.optionCount, - userInputAnswerCount: Object.keys(sidecar.answers).length, - userInputQuestionMessageCount: questionMessages.length, - userInputAnswerMessageCount: answerMessages.length, - finalAssistantCount: finalAssistants.length, - completedAuditCount: completedAudits.length, - toolObservationCount: observations.length, - providerRequestIdentityCount: lifecycle.requestIdentityCount, - providerLifecycleStartedCount: lifecycle.startedCount, - providerLifecycleTerminalCount: lifecycle.terminalCount, - providerStartedBeforeRunnerKill: state.userInput.providerStartedBeforeKill, - providerStartedAfterRunnerRestart: - state.userInput.providerStartedAfterRestart, - providerCalledWhileWaiting: false, - conversationCountBeforeRunnerKill: - state.userInput.conversationCountBeforeKill, - conversationCountAfterRunnerRestart: - state.userInput.conversationCountAfterRestart, - runnerBootChanged: - state.userInput.oldRunnerBootId !== state.userInput.newRunnerBootId, - duplicateMessageCount, - finalizationJournalCount: finalizationFiles.length, - privateBodyPublicLeakCount: - sumObjectValues(privateBodyPublicCounts) + - sumObjectValues(taskPrivateBodyPublicCounts), - apiKeyPublicLeakCount: sumObjectValues(apiKeyPublicCounts), - projectPathPublicLeakCount: sumObjectValues(projectPathPublicCounts), - projectPathPublicSurfaceCount: Object.keys(projectPathPublicCounts).length, - userInputSidecarSecretLeakCount: sidecarSecretLeakCount, - userInputReportLeakCount: state.userInput.reportLeakCount, - userInputRunnerKillMethod: null, - userInputRunnerPidfdClaimCount: state.isolatedRunner.pidfdClaimCount, - userInputRunnerPidfdSignalCount: state.isolatedRunner.pidfdSignalCount, - userInputRunnerStopped: false, - userInputAppDataCleanupPerformed: false, - secretLeakCount, - lureLeakCount: state.lureLeakCount, - paths: [ - '.agent/runtime/user-input', - '.agent/runtime/tasks', - '.agent/runtime/events', - '.agent/agent.db', - '.agent/conversations', - ], - }; -} - -export async function collectPartialUserInputEvidence() { - const persistence = await readUserInputPersistence(); - return { - taskCount: persistence.taskSnapshot.all.length, - eventCount: persistence.events.length, - agentDbRecordCount: persistence.agentDb.length, - conversationMessageCount: persistence.conversations.length, - userInputSidecarCount: persistence.sidecars.length, - userInputQuestionCount: - persistence.sidecars[0]?.record?.questions?.length ?? 0, - userInputAnswerCount: Object.keys( - persistence.sidecars[0]?.record?.answers ?? {}, - ).length, - finalAssistantCount: persistence.conversations.filter( - (message) => - message.role === 'assistant' && - message.messageId !== - persistence.sidecars[0]?.record?.questionMessageId, - ).length, - }; -} - -export function emptyUserInputEvidence() { - return { - scenario: 'project-supervisor-needs-input-runner-restart', - targetAgentId: projectSupervisorAgentId, - providerModel: 'gpt-5.5', - isolatedAppDataUsed: false, - formalConfigCliCallCount: 0, - sourceRunnerEndpointUnchanged: false, - sourceConfigHardlinkCount: 0, - sourceConfigLinksVerified: false, - taskCount: 0, - eventCount: 0, - agentDbRecordCount: 0, - conversationMessageCount: 0, - targetRunCount: 0, - stableSessionCount: 0, - userInputSidecarCount: 0, - userInputQuestionCount: 0, - userInputOptionCount: 0, - userInputAnswerCount: 0, - userInputQuestionMessageCount: 0, - userInputAnswerMessageCount: 0, - finalAssistantCount: 0, - completedAuditCount: 0, - toolObservationCount: 0, - providerRequestIdentityCount: 0, - providerLifecycleStartedCount: 0, - providerLifecycleTerminalCount: 0, - providerStartedBeforeRunnerKill: 0, - providerStartedAfterRunnerRestart: 0, - providerCalledWhileWaiting: false, - conversationCountBeforeRunnerKill: 0, - conversationCountAfterRunnerRestart: 0, - runnerBootChanged: false, - duplicateMessageCount: 0, - finalizationJournalCount: 0, - privateBodyPublicLeakCount: 0, - apiKeyPublicLeakCount: 0, - projectPathPublicLeakCount: 0, - projectPathPublicSurfaceCount: 0, - userInputSidecarSecretLeakCount: 0, - userInputReportLeakCount: 0, - userInputRunnerKillMethod: null, - userInputRunnerPidfdClaimCount: 0, - userInputRunnerPidfdSignalCount: 0, - userInputRunnerStopped: false, - userInputAppDataCleanupPerformed: false, - secretLeakCount: 0, - lureLeakCount: 0, - paths: [], - }; -} - -export function isUserInputRuntimeSuite() { - return state.suite === userInputRuntimeSuite; -} diff --git a/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs b/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs deleted file mode 100644 index 45656fc67..000000000 --- a/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs +++ /dev/null @@ -1,2149 +0,0 @@ -import { spawn } from 'node:child_process'; -import { randomUUID } from 'node:crypto'; -import { constants as fsConstants } from 'node:fs'; -import { - chmod, - copyFile, - lstat, - mkdir, - mkdtemp, - open, - readdir, - readFile, - realpath, - rm, - writeFile, -} from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { createInterface } from 'node:readline/promises'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { inflateSync } from 'node:zlib'; - -import { AGC_APP_IDENTIFIER } from './channel-identity.mjs'; - -// 联调工具驱动的始终是默认渠道客户端:安装身份取渠道基线,不跟随发布渠道。 -export const appIdentifier = AGC_APP_IDENTIFIER; -export const configFileName = 'game-creator.config.json'; -export const localConfigFileName = 'game-creator.config.local.json'; -export const runnerEndpointFileName = 'agent-runner.endpoint.json'; -export const testProjectPrefix = 'genarrative-agc-swarm-test-'; -export const testProjectSentinelName = '.agc-swarm-test.json'; -export const testProjectSentinelSchema = - 'genarrative-agc-swarm-test-project.v1'; -export const testRuntimeConfigPrefix = 'genarrative-agc-swarm-config-'; -export const testRuntimeConfigSentinelName = '.agc-swarm-config.json'; -export const testRuntimeConfigSentinelSchema = - 'genarrative-agc-swarm-test-config.v1'; -export const ungeneratedGameEntryMarker = - '还没有生成游戏。回到聊天输入创意并确认生成后'; -export const defaultRealSwarmTestTask = - '制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。'; -export const swarmTurnReportPrefix = '[turn.report] '; -export const swarmTurnReportSchema = 'game-creator-swarm-turn-report.v1'; - -const swarmTurnReportKeys = [ - 'schemaVersion', - 'outcome', - 'parentAgentId', - 'sessionId', - 'parentRunId', - 'runtimeCount', - 'busyRuntimeCount', - 'pendingTaskCount', - 'runningTaskCount', - 'waitingForConfirmationCount', - 'waitingForUserInputCount', - 'newAssistantMessageCount', - 'finalReplyChars', - 'reconciliationAgentCount', -].sort(); -const settledZeroCountFields = [ - 'busyRuntimeCount', - 'pendingTaskCount', - 'runningTaskCount', - 'waitingForConfirmationCount', - 'waitingForUserInputCount', - 'reconciliationAgentCount', -]; - -const requiredFormalArtifactSpecs = [ - { path: 'memory/project.md', kind: 'file' }, - { path: 'game/game_design.md', kind: 'file' }, - { path: 'game/balance.json', kind: 'json' }, - { path: 'assets/manifest.art.json', kind: 'json' }, - { path: 'assets/manifest.audio.json', kind: 'json' }, - { path: 'game/index.html', kind: 'file' }, - { path: 'exports/README.md', kind: 'file' }, -]; -const editorImageArtifactSpecs = [ - { path: 'assets/ui-prototype.png', kind: 'image', aspectRatio: 16 / 9 }, - { path: 'assets/art-spritesheet.png', kind: 'image', aspectRatio: 1 }, -]; -export const requiredSwarmManifestTaskIds = 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 appRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url))); -const cargoManifestPath = path.join(appRoot, 'src-tauri', 'Cargo.toml'); -const configWizardPath = path.join( - appRoot, - 'scripts', - 'game-creator-config-wizard.mjs', -); -const cargoCommand = process.platform === 'win32' ? 'cargo.exe' : 'cargo'; -const childTerminationGraceMs = 10_000; -const childForceTerminationWaitMs = 5_000; -const runnerShutdownTimeoutMs = 20_000; -const cleanupDirectoryTimeoutMs = 10_000; -const maximumValidatedPngBytes = 64 * 1024 * 1024; -const maximumValidatedPngPixels = 100_000_000; -const maximumInflatedPngBytes = 256 * 1024 * 1024; -const minimumMarkdownBodyCharacters = 24; -const minimumHtmlCharacters = 120; -const incompleteArtifactTextPattern = - /\b(?:todo|tbd|placeholder|coming[\t ]+soon|lorem[\t ]+ipsum)\b|待补充|待完善|占位|尚未完成|稍后补充|待填写|待验证|待复核|待确认|待定/iu; -const uncheckedMarkdownChecklistPattern = - /^[\t ]*(?:>[\t ]*)*(?:[-+*]|\d+[.)])[\t ]+\[[\t ]\](?:[\t ]|$)/mu; - -export function hasIncompleteArtifactMarker(content) { - return ( - incompleteArtifactTextPattern.test(content) || - uncheckedMarkdownChecklistPattern.test(content) - ); -} - -export const usage = `用法: - npm run agc:test:chat - npm run agc:test:chat:manual -- [选项] - -自动读取客户端 AppData 配置并复制到隔离目录,创建一次性项目后进入 Project Supervisor 自主测试。 -带 --task 时完成正式产物验收后自动退出;手工聊天模式完成后启动持续预览。 - -选项: - --config-dir <绝对路径> 显式指定客户端 AppData 配置来源目录 - --project-dir <绝对路径> 使用已有项目或空目录,不自动删除 - --keep-project 保留自动创建的一次性项目 - --no-open 手工模式启动预览但不自动打开浏览器 - --task <需求> 通过 manual 入口非交互提交自定义需求 - --timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,手工模式默认不限时 - --dry-run 只检查目录发现和项目准备,不启动 LLM - -h, --help 显示帮助 -`; - -function readOptionValue(args, index, option) { - const value = args[index + 1]?.trim(); - if (!value || value.startsWith('--')) { - throw new Error(`${option} 缺少路径`); - } - return value; -} - -function readTimeoutMinutes(args, index, option) { - const value = readOptionValue(args, index, option); - if (!/^[1-9]\d*$/u.test(value)) { - throw new Error(`${option} 必须是 1-1440 的整数分钟`); - } - const minutes = Number(value); - if (!Number.isSafeInteger(minutes) || minutes > 1_440) { - throw new Error(`${option} 必须是 1-1440 的整数分钟`); - } - return minutes; -} - -export function parseSwarmTestArguments(args) { - const options = { - configDir: null, - projectDir: null, - keepProject: false, - openBrowser: true, - task: null, - timeoutMinutes: null, - dryRun: false, - help: false, - }; - for (let index = 0; index < args.length; index += 1) { - const argument = args[index]; - if (argument === '--config-dir') { - if (options.configDir) throw new Error('--config-dir 只能指定一次'); - options.configDir = readOptionValue(args, index, argument); - index += 1; - } else if (argument === '--project-dir') { - if (options.projectDir) throw new Error('--project-dir 只能指定一次'); - options.projectDir = readOptionValue(args, index, argument); - index += 1; - } else if (argument === '--keep-project') { - options.keepProject = true; - } else if (argument === '--no-open') { - options.openBrowser = false; - } else if (argument === '--task') { - if (options.task) throw new Error('--task 只能指定一次'); - const task = readOptionValue(args, index, argument); - if (task.length > 4_000) throw new Error('--task 不能超过 4000 字符'); - options.task = task; - index += 1; - } else if (argument === '--timeout-minutes') { - if (options.timeoutMinutes !== null) { - throw new Error('--timeout-minutes 只能指定一次'); - } - options.timeoutMinutes = readTimeoutMinutes(args, index, argument); - index += 1; - } else if (argument === '--dry-run') { - options.dryRun = true; - } else if (argument === '--help' || argument === '-h') { - options.help = true; - } else { - throw new Error(`未知选项:${argument}`); - } - } - return options; -} - -export function shouldStartPersistentPreview(options) { - return !options.task; -} - -export function resolveSwarmTestTimeoutMs(options) { - const minutes = options.timeoutMinutes ?? (options.task ? 50 : null); - return minutes === null ? null : minutes * 60_000; -} - -function pushUnique(values, value) { - if (value && !values.includes(value)) values.push(value); -} - -export function defaultRuntimeConfigDirCandidates({ - platform = process.platform, - environment = process.env, - homeDirectory = os.homedir(), -} = {}) { - const candidates = []; - if (platform === 'win32') { - pushUnique( - candidates, - environment.APPDATA - ? path.win32.join(environment.APPDATA, appIdentifier) - : path.win32.join(homeDirectory, 'AppData', 'Roaming', appIdentifier), - ); - if (environment.LOCALAPPDATA) { - pushUnique( - candidates, - path.win32.join(environment.LOCALAPPDATA, appIdentifier), - ); - } - } else if (platform === 'darwin') { - pushUnique( - candidates, - path.posix.join( - homeDirectory, - 'Library', - 'Application Support', - appIdentifier, - ), - ); - } else { - const configuredRoot = environment.XDG_CONFIG_HOME; - const posixAbsoluteConfiguredRoot = - configuredRoot && path.posix.isAbsolute(configuredRoot); - const hostAbsoluteConfiguredRoot = - configuredRoot && - !posixAbsoluteConfiguredRoot && - path.isAbsolute(configuredRoot); - const configRoot = - configuredRoot && - (posixAbsoluteConfiguredRoot || hostAbsoluteConfiguredRoot) - ? configuredRoot - : path.posix.join(homeDirectory, '.config'); - pushUnique( - candidates, - hostAbsoluteConfiguredRoot - ? path.join(configRoot, appIdentifier) - : path.posix.join(configRoot, appIdentifier), - ); - } - return candidates; -} - -async function isRegularFileWithoutSymlink(filePath) { - const metadata = await lstat(filePath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - return Boolean(metadata?.isFile() && !metadata.isSymbolicLink()); -} - -export async function discoverRuntimeConfigDir( - explicitConfigDir, - platformContext, -) { - if (explicitConfigDir && !path.isAbsolute(explicitConfigDir)) { - throw new Error('--config-dir 必须是绝对路径'); - } - const candidates = explicitConfigDir - ? [explicitConfigDir] - : defaultRuntimeConfigDirCandidates(platformContext); - for (const candidate of candidates) { - const resolved = path.resolve(candidate); - const metadata = await lstat(resolved).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (!metadata?.isDirectory() || metadata.isSymbolicLink()) continue; - if ( - !(await isRegularFileWithoutSymlink(path.join(resolved, configFileName))) - ) { - continue; - } - return realpath(resolved); - } - if (explicitConfigDir) { - throw new Error( - `指定目录中没有可用的 ${configFileName}:${explicitConfigDir}`, - ); - } - throw new Error( - `未找到客户端 AppData 配置。请运行 npm run agc:config,或启动 npm run agc 后在“运行时配置”中保存 LLM Provider。`, - ); -} - -export function canPromptForMissingRuntimeConfig( - stdinIsTty = process.stdin.isTTY, - stdoutIsTty = process.stdout.isTTY, -) { - return Boolean(stdinIsTty && stdoutIsTty); -} - -async function askToConfigureMissingRuntime() { - if (!canPromptForMissingRuntimeConfig()) return false; - const readline = createInterface({ - input: process.stdin, - output: process.stdout, - }); - try { - const answer = ( - await readline.question( - '未找到客户端 AppData 配置,是否现在进入安全配置向导? [Y/n]: ', - ) - ) - .trim() - .toLowerCase(); - return !answer || ['y', 'yes', '是'].includes(answer); - } finally { - readline.close(); - } -} - -export function buildMissingConfigWizardArguments(explicitConfigDir) { - return [ - configWizardPath, - '--configure-only', - ...(explicitConfigDir ? ['--config-dir', explicitConfigDir] : []), - ]; -} - -async function runMissingConfigWizard(setActiveChild, explicitConfigDir) { - const child = spawnChild( - process.execPath, - buildMissingConfigWizardArguments(explicitConfigDir), - { - stdio: 'inherit', - }, - ); - setActiveChild(child); - const result = await childExit(child); - setActiveChild(null); - if (result.code !== 0 || result.signal) { - throw new Error( - `配置向导未正常完成:code=${result.code ?? ''} signal=${result.signal ?? ''}`, - ); - } -} - -async function secureWindowsPrivateRuntimePath( - targetPath, - options, - secureWindowsPath = null, -) { - if (secureWindowsPath) { - await secureWindowsPath(targetPath, options); - return; - } - const { secureWindowsGameCreatorPathForCurrentUser } = await import( - './game-creator-config-wizard.mjs' - ); - await secureWindowsGameCreatorPathForCurrentUser(targetPath, options); -} - -async function copyPrivateRuntimeConfigEntry( - sourceConfigDir, - runtimeConfigDir, - fileName, - required, - secureWindowsPath = null, -) { - const sourcePath = path.join(sourceConfigDir, fileName); - const sourceMetadata = await lstat(sourcePath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (!sourceMetadata) { - if (required) throw new Error(`配置来源缺少 ${fileName}`); - return false; - } - if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink()) { - throw new Error(`配置来源必须是无符号链接普通文件:${fileName}`); - } - - const destinationPath = path.join(runtimeConfigDir, fileName); - if (process.platform === 'win32') { - const sourceBytes = await readFile(sourcePath); - const destinationFile = await open(destinationPath, 'wx', 0o600); - try { - await secureWindowsPrivateRuntimePath( - destinationPath, - { isDirectory: false }, - secureWindowsPath, - ); - await destinationFile.writeFile(sourceBytes); - await destinationFile.sync(); - } finally { - await destinationFile.close(); - } - } else { - await copyFile(sourcePath, destinationPath, fsConstants.COPYFILE_EXCL); - await chmod(destinationPath, 0o600); - } - const [sourceMetadataAfterCopy, destinationMetadata] = await Promise.all([ - lstat(sourcePath), - lstat(destinationPath), - ]); - if ( - !sourceMetadataAfterCopy.isFile() || - sourceMetadataAfterCopy.isSymbolicLink() || - sourceMetadataAfterCopy.dev !== sourceMetadata.dev || - sourceMetadataAfterCopy.ino !== sourceMetadata.ino || - sourceMetadataAfterCopy.size !== sourceMetadata.size || - sourceMetadataAfterCopy.mtimeMs !== sourceMetadata.mtimeMs || - !destinationMetadata.isFile() || - destinationMetadata.isSymbolicLink() || - (process.platform !== 'win32' && - ((destinationMetadata.mode & 0o077) !== 0 || - (destinationMetadata.dev === sourceMetadata.dev && - destinationMetadata.ino === sourceMetadata.ino))) - ) { - throw new Error(`隔离配置副本身份或权限无效:${fileName}`); - } - return true; -} - -export async function prepareSwarmTestRuntimeConfig( - sourceConfigDir, - tempRoot, - { secureWindowsPath = null } = {}, -) { - if (!path.isAbsolute(sourceConfigDir)) { - throw new Error('配置来源目录必须是绝对路径'); - } - const sourceMetadata = await lstat(sourceConfigDir).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (!sourceMetadata?.isDirectory() || sourceMetadata.isSymbolicLink()) { - throw new Error(`配置来源必须是无符号链接普通目录:${sourceConfigDir}`); - } - const canonicalSourceConfigDir = await realpath(sourceConfigDir); - const canonicalTempRoot = await realpath( - path.resolve(tempRoot ?? os.tmpdir()), - ); - const runtimeConfigDir = await mkdtemp( - path.join(canonicalTempRoot, testRuntimeConfigPrefix), - ); - try { - if (process.platform === 'win32') { - await secureWindowsPrivateRuntimePath( - runtimeConfigDir, - { isDirectory: true }, - secureWindowsPath, - ); - } else { - await chmod(runtimeConfigDir, 0o700); - } - const sentinelToken = randomUUID(); - await writeFile( - path.join(runtimeConfigDir, testRuntimeConfigSentinelName), - `${JSON.stringify({ - schemaVersion: testRuntimeConfigSentinelSchema, - token: sentinelToken, - })}\n`, - { flag: 'wx', mode: 0o600 }, - ); - await copyPrivateRuntimeConfigEntry( - canonicalSourceConfigDir, - runtimeConfigDir, - configFileName, - true, - secureWindowsPath, - ); - await copyPrivateRuntimeConfigEntry( - canonicalSourceConfigDir, - runtimeConfigDir, - localConfigFileName, - false, - secureWindowsPath, - ); - return { - path: await realpath(runtimeConfigDir), - sourcePath: canonicalSourceConfigDir, - owned: true, - sentinelToken, - }; - } catch (error) { - await rm(runtimeConfigDir, { recursive: true, force: true }); - throw error; - } -} - -const cleanupDirectoryChildProgram = String.raw` -const { rm } = require('node:fs/promises'); -const target = process.argv[1]; -rm(target, { recursive: true, force: false }).catch((error) => { - process.stderr.write(String(error && error.message || error)); - process.exitCode = 1; -}); -`; - -export async function removeDirectoryWithTimeout( - directoryPath, - { - timeoutMs = cleanupDirectoryTimeoutMs, - childProgram = cleanupDirectoryChildProgram, - } = {}, -) { - const child = spawnChild( - process.execPath, - ['-e', childProgram, directoryPath], - { - stdio: ['ignore', 'ignore', 'ignore'], - }, - ); - const result = await childExitWithTimeout( - child, - timeoutMs, - `清理目录 ${path.basename(directoryPath)}`, - { graceMs: 1_000, forceWaitMs: 2_000 }, - ); - if (result.code !== 0 || result.signal) { - throw new Error( - `清理目录失败:${path.basename(directoryPath)} code=${result.code ?? ''} signal=${result.signal ?? ''}`, - ); - } -} - -export async function cleanupSwarmTestRuntimeConfig(runtimeConfig) { - if (!runtimeConfig?.owned) return false; - const sentinelPath = path.join( - runtimeConfig.path, - testRuntimeConfigSentinelName, - ); - if (!(await isRegularFileWithoutSymlink(sentinelPath))) { - throw new Error('拒绝清理:隔离配置哨兵缺失或类型无效'); - } - let sentinel; - try { - sentinel = JSON.parse(await readFile(sentinelPath, 'utf8')); - } catch (error) { - throw new Error(`拒绝清理:隔离配置哨兵无效:${error.message}`); - } - if ( - sentinel.schemaVersion !== testRuntimeConfigSentinelSchema || - sentinel.token !== runtimeConfig.sentinelToken - ) { - throw new Error('拒绝清理:隔离配置哨兵身份不匹配'); - } - const canonical = await realpath(runtimeConfig.path); - if ( - canonical !== runtimeConfig.path || - canonical === runtimeConfig.sourcePath || - !path.basename(canonical).startsWith(testRuntimeConfigPrefix) - ) { - throw new Error('拒绝清理:隔离配置目录身份不匹配'); - } - const endpointMetadata = await lstat( - path.join(canonical, runnerEndpointFileName), - ).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (endpointMetadata) { - throw new Error('拒绝清理:隔离 Agent Runner 尚未退出'); - } - await removeDirectoryWithTimeout(canonical); - return true; -} - -async function ensureExplicitProject(projectDir) { - if (!path.isAbsolute(projectDir)) { - throw new Error('--project-dir 必须是绝对路径'); - } - const resolved = path.resolve(projectDir); - const metadata = await lstat(resolved).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (!metadata) { - await mkdir(resolved, { recursive: true, mode: 0o700 }); - } else if (!metadata.isDirectory() || metadata.isSymbolicLink()) { - throw new Error(`测试项目路径必须是普通目录:${resolved}`); - } - const canonical = await realpath(resolved); - const entries = await readdir(canonical); - const initialized = await isRegularFileWithoutSymlink( - path.join(canonical, '.agent', 'manifest.json'), - ); - if (entries.length > 0 && !initialized) { - throw new Error(`--project-dir 只能指向空目录或已初始化项目:${canonical}`); - } - return { - path: canonical, - owned: false, - sentinelToken: null, - }; -} - -export async function prepareSwarmTestProject(explicitProjectDir, tempRoot) { - if (explicitProjectDir) return ensureExplicitProject(explicitProjectDir); - const root = path.resolve(tempRoot ?? os.tmpdir()); - const projectPath = await mkdtemp(path.join(root, testProjectPrefix)); - try { - if (process.platform !== 'win32') await chmod(projectPath, 0o700); - const sentinelToken = randomUUID(); - await writeFile( - path.join(projectPath, testProjectSentinelName), - `${JSON.stringify({ - schemaVersion: testProjectSentinelSchema, - token: sentinelToken, - })}\n`, - { flag: 'wx', mode: 0o600 }, - ); - return { - path: await realpath(projectPath), - owned: true, - sentinelToken, - }; - } catch (error) { - await rm(projectPath, { recursive: true, force: true }); - throw error; - } -} - -export async function cleanupSwarmTestProject(project) { - if (!project?.owned) return false; - const sentinelPath = path.join(project.path, testProjectSentinelName); - if (!(await isRegularFileWithoutSymlink(sentinelPath))) { - throw new Error('拒绝清理:一次性项目哨兵缺失或类型无效'); - } - let sentinel; - try { - sentinel = JSON.parse(await readFile(sentinelPath, 'utf8')); - } catch (error) { - throw new Error(`拒绝清理:一次性项目哨兵无效:${error.message}`); - } - if ( - sentinel.schemaVersion !== testProjectSentinelSchema || - sentinel.token !== project.sentinelToken - ) { - throw new Error('拒绝清理:一次性项目哨兵身份不匹配'); - } - const canonical = await realpath(project.path); - if ( - canonical !== project.path || - !path.basename(canonical).startsWith(testProjectPrefix) - ) { - throw new Error('拒绝清理:一次性项目目录身份不匹配'); - } - await removeDirectoryWithTimeout(canonical); - return true; -} - -export function buildCargoCliArguments(cliArguments) { - // `--quiet` only silences cargo's own build chatter; compiler errors and the - // CLI's stdout still come through. Without it the crate's several hundred - // dead-code warnings are reprinted on every spawn and bury the run output - // this script exists to show. - return [ - 'run', - '--quiet', - '--manifest-path', - cargoManifestPath, - '--', - ...cliArguments, - ]; -} - -function spawnChild(command, args, options = {}) { - return spawn(command, args, { - cwd: appRoot, - env: process.env, - detached: process.platform !== 'win32', - ...options, - }); -} - -const childExitPromises = new WeakMap(); -const closedChildren = new WeakSet(); - -function childExit(child) { - const existing = childExitPromises.get(child); - if (existing) return existing; - const exitPromise = new Promise((resolve, reject) => { - child.once('error', reject); - child.once('close', (code, signal) => { - closedChildren.add(child); - resolve({ code, signal }); - }); - }); - childExitPromises.set(child, exitPromise); - return exitPromise; -} - -async function childExitWithin(exitPromise, timeoutMs) { - let timeoutHandle; - const timeoutPromise = new Promise((resolve) => { - timeoutHandle = setTimeout(() => resolve(null), timeoutMs); - }); - try { - return await Promise.race([exitPromise, timeoutPromise]); - } finally { - clearTimeout(timeoutHandle); - } -} - -export async function terminateChildTree( - child, - signal = 'SIGTERM', - force = false, -) { - if (!child || closedChildren.has(child) || !Number.isInteger(child.pid)) { - return; - } - if (process.platform === 'win32') { - const taskkill = spawn( - 'taskkill.exe', - ['/PID', String(child.pid), '/T', ...(force ? ['/F'] : [])], - { - stdio: 'ignore', - windowsHide: true, - }, - ); - const taskkillExit = childExit(taskkill).catch(() => null); - if (!(await childExitWithin(taskkillExit, childForceTerminationWaitMs))) { - try { - taskkill.kill('SIGKILL'); - } catch { - // The taskkill helper may have exited at the timeout boundary. - } - await childExitWithin(taskkillExit, childForceTerminationWaitMs); - } - return; - } - try { - process.kill(-child.pid, force ? 'SIGKILL' : signal); - } catch (error) { - try { - child.kill(force ? 'SIGKILL' : signal); - } catch { - if (error?.code !== 'ESRCH') throw error; - } - } -} - -async function terminateChildTreeAndWait( - child, - exitPromise, - signal, - label, - { - graceMs = childTerminationGraceMs, - forceWaitMs = childForceTerminationWaitMs, - } = {}, -) { - await terminateChildTree(child, signal, false); - const gracefulResult = await childExitWithin(exitPromise, graceMs); - if (gracefulResult) return gracefulResult; - - await terminateChildTree(child, 'SIGKILL', true); - const forcedResult = await childExitWithin(exitPromise, forceWaitMs); - if (forcedResult) return forcedResult; - throw new Error(`${label} 无法在强制终止进程树后关闭 stdio`); -} - -export async function childExitWithTimeout( - child, - timeoutMs, - label, - terminationOptions, -) { - const exitPromise = childExit(child); - if (timeoutMs === null) return exitPromise; - let timeoutHandle; - const timeoutPromise = new Promise((resolve) => { - timeoutHandle = setTimeout(() => resolve({ kind: 'timeout' }), timeoutMs); - }); - try { - const first = await Promise.race([ - exitPromise.then((result) => ({ kind: 'exit', result })), - timeoutPromise, - ]); - if (first.kind === 'exit') return first.result; - try { - await terminateChildTreeAndWait( - child, - exitPromise, - 'SIGTERM', - label, - terminationOptions, - ); - } catch (error) { - error.code = 'AGC_CHILD_TIMEOUT'; - throw error; - } - throw Object.assign( - new Error(`${label} 超过 ${Math.ceil(timeoutMs / 1000)} 秒期限`), - { code: 'AGC_CHILD_TIMEOUT' }, - ); - } finally { - clearTimeout(timeoutHandle); - } -} - -function isPlainObject(value) { - return ( - value !== null && - typeof value === 'object' && - !Array.isArray(value) && - Object.getPrototypeOf(value) === Object.prototype - ); -} - -function isNonNegativeSafeInteger(value) { - return Number.isSafeInteger(value) && value >= 0; -} - -export function parseSettledSwarmTurnReport(output) { - const reportLines = String(output) - .split(/\r?\n/u) - .filter((line) => line.startsWith(swarmTurnReportPrefix)); - if (reportLines.length !== 1) { - throw new Error( - `Agent Swarm 终态报告数量无效:expected=1 actual=${reportLines.length}`, - ); - } - - let report; - try { - report = JSON.parse(reportLines[0].slice(swarmTurnReportPrefix.length)); - } catch { - throw new Error('Agent Swarm 终态报告不是有效 JSON'); - } - if ( - !isPlainObject(report) || - JSON.stringify(Object.keys(report).sort()) !== - JSON.stringify(swarmTurnReportKeys) - ) { - throw new Error('Agent Swarm 终态报告结构无效'); - } - if (report.schemaVersion !== swarmTurnReportSchema) { - throw new Error('Agent Swarm 终态报告 schema 无效'); - } - if ( - typeof report.parentAgentId !== 'string' || - !report.parentAgentId.trim() || - typeof report.sessionId !== 'string' || - !report.sessionId.trim() || - typeof report.parentRunId !== 'string' || - !report.parentRunId.trim() - ) { - throw new Error('Agent Swarm 终态报告运行身份无效'); - } - const countFields = [ - 'runtimeCount', - ...settledZeroCountFields, - 'newAssistantMessageCount', - 'finalReplyChars', - ]; - if (countFields.some((field) => !isNonNegativeSafeInteger(report[field]))) { - throw new Error('Agent Swarm 终态报告计数无效'); - } - if ( - !['settled', 'failed', 'incomplete', 'needs-reconciliation'].includes( - report.outcome, - ) - ) { - throw new Error('Agent Swarm 终态报告 outcome 无效'); - } - if (report.outcome !== 'settled') { - throw new Error(`Agent Swarm 本轮未收束:outcome=${report.outcome}`); - } - const unsettledField = settledZeroCountFields.find( - (field) => report[field] !== 0, - ); - if (unsettledField) { - throw new Error( - `Agent Swarm 本轮仍有未收束工作:${unsettledField}=${report[unsettledField]}`, - ); - } - if (report.runtimeCount < 1) { - throw new Error('Agent Swarm 终态报告没有 Runtime'); - } - if ( - report.newAssistantMessageCount !== 1 || - report.finalReplyChars < 1 || - report.finalReplyChars > 1_000_000 - ) { - throw new Error('Agent Swarm 最终回复无效'); - } - return report; -} - -async function runCapturedCargo( - cliArguments, - setActiveChild, - { timeoutMs = null, label = 'Cargo 子命令', stdin = null } = {}, -) { - const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { - stdio: [stdin === null ? 'ignore' : 'pipe', 'pipe', 'pipe'], - }); - setActiveChild(child); - if (stdin !== null) { - child.stdin.end(stdin); - } - let stdout = ''; - let stderr = ''; - child.stdout.setEncoding('utf8'); - child.stderr.setEncoding('utf8'); - child.stdout.on('data', (chunk) => { - stdout += chunk; - }); - child.stderr.on('data', (chunk) => { - stderr += chunk; - }); - try { - const result = await childExitWithTimeout(child, timeoutMs, label); - return { ...result, stdout, stderr }; - } finally { - setActiveChild(null); - } -} - -async function runInteractiveCargo(cliArguments, setActiveChild) { - const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { - stdio: 'inherit', - }); - setActiveChild(child); - const result = await childExit(child); - setActiveChild(null); - return result; -} - -const swarmConfirmationPromptPattern = /输入 approve 或 reject:$/u; -const swarmUserInputPromptPattern = /请选择 1-\d+,或直接输入其他答案:$/u; - -export function nextSwarmAutoPilotReply(output) { - if (swarmConfirmationPromptPattern.test(output)) return 'approve'; - if (swarmUserInputPromptPattern.test(output)) return '1'; - return null; -} - -// CLI 的 REPL 是「先打印提示符再读行」,所以第一个「你>」出现时本轮还没开始跑: -// 它就是用来读我们这条任务的。收 stdin 必须等到投递之后的下一个提示符——那才是 -// 本轮结束、CLI 回到待输入状态。绝大多数情况下此前已经打印过 turn 回执,但总控也 -// 可能判定直接回复而不起持久 Run,那条路径没有回执,只等回执会一直干等到超时。 -const swarmChatPromptPattern = /(^|\n)你> $/u; - -export function swarmAutoPilotSitsAtPrompt(output) { - return swarmChatPromptPattern.test(output); -} - -export function swarmAutoPilotShouldCloseInput(output, promptsAfterSubmit) { - return swarmAutoPilotSitsAtPrompt(output) && promptsAfterSubmit >= 1; -} - -async function runTaskCargo( - cliArguments, - task, - setActiveChild, - timeoutMs, - autoPilot = false, -) { - const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { - stdio: ['pipe', 'pipe', 'inherit'], - }); - setActiveChild(child); - const reportLines = []; - let pendingLine = ''; - let settled = false; - let taskSubmitted = false; - let promptsSeen = 0; - let sittingAtPrompt = false; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (chunk) => { - process.stdout.write(chunk); - pendingLine += chunk; - const lines = pendingLine.split('\n'); - pendingLine = lines.pop() ?? ''; - for (const line of lines) { - const normalizedLine = line.endsWith('\r') ? line.slice(0, -1) : line; - if (normalizedLine.startsWith(swarmTurnReportPrefix)) { - reportLines.push(normalizedLine); - settled = true; - } - } - if (!autoPilot || child.stdin.writableEnded) return; - const atPrompt = swarmAutoPilotSitsAtPrompt(pendingLine); - if (atPrompt && !sittingAtPrompt) promptsSeen += 1; - sittingAtPrompt = atPrompt; - // turn 已给出回执、或 CLI 回到了投递之后的下一个提示符,都说明本轮结束。 - if ( - settled || - (taskSubmitted && - swarmAutoPilotShouldCloseInput(pendingLine, promptsSeen - 1)) - ) { - child.stdin.end(); - return; - } - const reply = nextSwarmAutoPilotReply(pendingLine); - if (reply === null) return; - console.log(`[自动应答] ${reply}`); - pendingLine = ''; - child.stdin.write(`${reply}\n`); - }); - if (autoPilot) { - child.stdin.write(`${task}\n`); - taskSubmitted = true; - } else { - child.stdin.end(`${task}\n`); - } - try { - const result = await childExitWithTimeout( - child, - timeoutMs, - 'Agent Swarm 自动任务', - ); - const normalizedPendingLine = pendingLine.endsWith('\r') - ? pendingLine.slice(0, -1) - : pendingLine; - if (normalizedPendingLine.startsWith(swarmTurnReportPrefix)) { - reportLines.push(normalizedPendingLine); - } - return { - ...result, - turnReportOutput: reportLines.join('\n'), - }; - } finally { - setActiveChild(null); - } -} - -export function parseRunnerShutdownOutput(output) { - const match = output.match(/^runner\.stopped=(true|false)$/m); - if (!match) throw new Error('Runner 收束命令缺少 stopped 状态'); - return match[1] === 'true'; -} - -async function shutdownSwarmTestRunner(runtimeConfig, setActiveChild) { - const result = await runCapturedCargo( - ['--config-dir', runtimeConfig.path, '--runner-shutdown-if-idle'], - setActiveChild, - { - timeoutMs: runnerShutdownTimeoutMs, - label: '隔离 Agent Runner 收束命令', - }, - ); - if (result.code !== 0 || result.signal) { - throw new Error( - `隔离 Agent Runner 收束失败:${result.stderr.trim() || result.stdout.trim() || `code=${result.code ?? ''} signal=${result.signal ?? ''}`}`, - ); - } - return parseRunnerShutdownOutput(result.stdout); -} - -export function validatePreviewUrl(value) { - const url = new URL(value); - if ( - url.protocol !== 'http:' || - url.hostname !== '127.0.0.1' || - !url.port || - url.username || - url.password || - url.pathname !== '/' || - url.search || - url.hash - ) { - throw new Error('预览命令返回了非 loopback URL'); - } - return url.toString(); -} - -export async function hasGeneratedGameEntry(projectPath) { - const gameEntryPath = path.join(projectPath, 'game', 'index.html'); - if (!(await isRegularFileWithoutSymlink(gameEntryPath))) return false; - const html = await readFile(gameEntryPath, 'utf8'); - return html.trim().length > 0 && !html.includes(ungeneratedGameEntryMarker); -} - -const pngSignature = Buffer.from([ - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, -]); -const pngCrcTable = Uint32Array.from({ length: 256 }, (_unused, index) => { - let value = index; - for (let bit = 0; bit < 8; bit += 1) { - value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; - } - return value >>> 0; -}); - -function pngCrc32(bytes) { - let value = 0xffffffff; - for (const byte of bytes) { - value = pngCrcTable[(value ^ byte) & 0xff] ^ (value >>> 8); - } - return (value ^ 0xffffffff) >>> 0; -} - -export function validatePngBytes(bytes) { - if ( - !Buffer.isBuffer(bytes) || - bytes.length < 57 || - bytes.length > maximumValidatedPngBytes || - !bytes.subarray(0, pngSignature.length).equals(pngSignature) - ) { - throw new Error('PNG 签名或文件大小无效'); - } - - let offset = pngSignature.length; - let ihdr = null; - let ihdrCount = 0; - let idatSeen = false; - let idatEnded = false; - let iendSeen = false; - let plteCount = 0; - let plteEntries = 0; - const idatChunks = []; - while (offset < bytes.length) { - if (bytes.length - offset < 12) throw new Error('PNG chunk 被截断'); - const length = bytes.readUInt32BE(offset); - const typeOffset = offset + 4; - const dataOffset = typeOffset + 4; - const dataEnd = dataOffset + length; - const chunkEnd = dataEnd + 4; - if (dataEnd > bytes.length - 4 || chunkEnd > bytes.length) { - throw new Error('PNG chunk 长度越界'); - } - const type = bytes.subarray(typeOffset, dataOffset).toString('ascii'); - if (!/^[A-Za-z]{4}$/u.test(type)) throw new Error('PNG chunk 类型无效'); - const expectedCrc = bytes.readUInt32BE(dataEnd); - const actualCrc = pngCrc32(bytes.subarray(typeOffset, dataEnd)); - if (expectedCrc !== actualCrc) throw new Error(`${type} CRC 无效`); - - const data = bytes.subarray(dataOffset, dataEnd); - if (offset === pngSignature.length && type !== 'IHDR') { - throw new Error('IHDR 必须是第一个 chunk'); - } - if (type === 'IHDR') { - ihdrCount += 1; - if (ihdrCount !== 1 || length !== 13) { - throw new Error('IHDR 数量或长度无效'); - } - ihdr = { - width: data.readUInt32BE(0), - height: data.readUInt32BE(4), - bitDepth: data[8], - colorType: data[9], - compression: data[10], - filter: data[11], - interlace: data[12], - }; - } else if (type === 'PLTE') { - plteCount += 1; - if ( - !ihdr || - idatSeen || - iendSeen || - plteCount !== 1 || - length < 3 || - length > 768 || - length % 3 !== 0 - ) { - throw new Error('PLTE 数量、长度或顺序无效'); - } - plteEntries = length / 3; - } else if (type === 'IDAT') { - if (!ihdr || idatEnded || iendSeen) { - throw new Error('IDAT 顺序无效'); - } - idatSeen = true; - idatChunks.push(data); - } else if (type === 'IEND') { - if (!ihdr || !idatSeen || iendSeen || length !== 0) { - throw new Error('IEND 数量、长度或顺序无效'); - } - iendSeen = true; - if (chunkEnd !== bytes.length) throw new Error('IEND 后存在额外数据'); - } else { - if ((type.charCodeAt(0) & 0x20) === 0) { - throw new Error(`不支持的 PNG critical chunk:${type}`); - } - if (idatSeen) idatEnded = true; - } - offset = chunkEnd; - } - - if (!ihdr || ihdrCount !== 1 || !idatSeen || !iendSeen) { - throw new Error('PNG 缺少唯一 IHDR、IDAT 或 IEND'); - } - const validBitDepths = { - 0: [1, 2, 4, 8, 16], - 2: [8, 16], - 3: [1, 2, 4, 8], - 4: [8, 16], - 6: [8, 16], - }; - if ( - ihdr.width < 1 || - ihdr.height < 1 || - ihdr.width * ihdr.height > maximumValidatedPngPixels || - !validBitDepths[ihdr.colorType]?.includes(ihdr.bitDepth) || - ihdr.compression !== 0 || - ihdr.filter !== 0 || - ihdr.interlace !== 0 - ) { - throw new Error('IHDR 参数无效或不支持交错 PNG'); - } - if ( - (ihdr.colorType === 3 && - (plteCount !== 1 || plteEntries > 2 ** ihdr.bitDepth)) || - ([0, 4].includes(ihdr.colorType) && plteCount !== 0) - ) { - throw new Error('PLTE 与 PNG color type 或 bit depth 不匹配'); - } - - const channels = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }[ihdr.colorType]; - const rowBytes = Math.ceil((ihdr.width * channels * ihdr.bitDepth) / 8); - const expectedInflatedBytes = ihdr.height * (rowBytes + 1); - if ( - !Number.isSafeInteger(expectedInflatedBytes) || - expectedInflatedBytes > maximumInflatedPngBytes - ) { - throw new Error('PNG scanline 大小无效'); - } - let inflated; - try { - const compressed = Buffer.concat(idatChunks); - const result = inflateSync(compressed, { - maxOutputLength: expectedInflatedBytes + 1, - info: true, - }); - if (result.engine.bytesWritten !== compressed.length) { - throw new Error('trailing compressed bytes'); - } - inflated = result.buffer; - } catch { - throw new Error('IDAT zlib 数据无法完整解压'); - } - if (inflated.length !== expectedInflatedBytes) { - throw new Error('非交错 PNG scanline 长度无效'); - } - for (let row = 0; row < ihdr.height; row += 1) { - if (inflated[row * (rowBytes + 1)] > 4) { - throw new Error(`第 ${row + 1} 行 filter byte 无效`); - } - } - return { width: ihdr.width, height: ihdr.height }; -} - -async function validatePngFile(filePath) { - const metadata = await lstat(filePath); - if ( - !metadata.isFile() || - metadata.isSymbolicLink() || - metadata.size < 1_024 || - metadata.size > maximumValidatedPngBytes - ) { - throw new Error('PNG 文件缺失、类型无效或大小超限'); - } - const noFollowFlag = - process.platform === 'win32' ? 0 : (fsConstants.O_NOFOLLOW ?? 0); - const file = await open(filePath, fsConstants.O_RDONLY | noFollowFlag); - try { - return validatePngBytes(await file.readFile()); - } finally { - await file.close(); - } -} - -async function inspectFormalArtifact(projectPath, spec) { - const artifactPath = path.join(projectPath, ...spec.path.split('/')); - let metadata; - try { - metadata = await lstat(artifactPath); - } catch (error) { - return { - path: spec.path, - reason: - error?.code === 'ENOENT' || error?.code === 'ENOTDIR' - ? '缺失' - : '无法安全读取', - }; - } - if (!metadata.isFile() || metadata.isSymbolicLink()) { - return { path: spec.path, reason: '不是无符号链接普通文件' }; - } - if (metadata.size === 0) return { path: spec.path, reason: '空文件' }; - - const noFollowFlag = - process.platform === 'win32' ? 0 : (fsConstants.O_NOFOLLOW ?? 0); - let file; - try { - file = await open(artifactPath, fsConstants.O_RDONLY | noFollowFlag); - } catch { - return { path: spec.path, reason: '无法安全读取' }; - } - try { - const openedMetadata = await file.stat(); - if (!openedMetadata.isFile()) { - return { path: spec.path, reason: '不是无符号链接普通文件' }; - } - if (openedMetadata.size === 0) { - return { path: spec.path, reason: '空文件' }; - } - - if (spec.kind === 'json') { - let parsed; - try { - parsed = JSON.parse(await file.readFile('utf8')); - } catch { - return { path: spec.path, reason: 'JSON 无法解析' }; - } - if (!isPlainObject(parsed) || Object.keys(parsed).length === 0) { - return { path: spec.path, reason: 'JSON 必须是非空对象' }; - } - } else if (spec.kind === 'file' || spec.kind === 'game-entry') { - const content = await file.readFile('utf8'); - if (content.trim().length === 0) { - return { path: spec.path, reason: '空文件' }; - } - if (hasIncompleteArtifactMarker(content)) { - return { path: spec.path, reason: '仍包含占位标记' }; - } - const compactContent = content.replace(/\s/gu, ''); - if ( - spec.kind === 'file' && - compactContent.length < minimumMarkdownBodyCharacters - ) { - return { path: spec.path, reason: 'Markdown 正文过短' }; - } - if ( - spec.kind === 'game-entry' && - content.includes(ungeneratedGameEntryMarker) - ) { - return { path: spec.path, reason: '仍是初始化占位页' }; - } - if ( - spec.kind === 'game-entry' && - (compactContent.length < minimumHtmlCharacters || - !/]*>[\s\S]*<\/html>/iu.test(content) || - !/ maximumValidatedPngBytes) { - return { path: spec.path, reason: '图片文件大小超限' }; - } - let dimensions; - try { - dimensions = validatePngBytes(await file.readFile()); - } catch (error) { - return { - path: spec.path, - reason: `PNG 文件无效:${error.message}`, - }; - } - const ratio = dimensions.width / dimensions.height; - if (Math.abs(ratio - spec.aspectRatio) > 0.03) { - return { - path: spec.path, - reason: `图片比例无效:${dimensions.width}x${dimensions.height}`, - }; - } - } - } catch { - return { path: spec.path, reason: '无法安全读取' }; - } finally { - await file.close(); - } - return null; -} - -function resolveProjectEvidencePath(projectPath, relativePath) { - if ( - typeof relativePath !== 'string' || - !relativePath || - path.isAbsolute(relativePath) || - relativePath.includes('\\') - ) { - throw new Error('证据路径无效'); - } - const root = path.resolve(projectPath); - const targetPath = path.resolve(root, ...relativePath.split('/')); - if (!targetPath.startsWith(`${root}${path.sep}`)) { - throw new Error('证据路径越界'); - } - return targetPath; -} - -async function readSafeJson(projectPath, relativePath, maximumBytes) { - const targetPath = resolveProjectEvidencePath(projectPath, relativePath); - const metadata = await lstat(targetPath).catch(() => null); - if ( - !metadata?.isFile() || - metadata.isSymbolicLink() || - metadata.size < 2 || - metadata.size > maximumBytes - ) { - throw new Error('文件缺失、类型无效或大小超限'); - } - const noFollowFlag = - process.platform === 'win32' ? 0 : (fsConstants.O_NOFOLLOW ?? 0); - const file = await open(targetPath, fsConstants.O_RDONLY | noFollowFlag); - try { - return JSON.parse(await file.readFile('utf8')); - } finally { - await file.close(); - } -} - -async function readSafeJsonLines(projectPath, relativePath, maximumBytes) { - const targetPath = resolveProjectEvidencePath(projectPath, relativePath); - const metadata = await lstat(targetPath); - if ( - !metadata.isFile() || - metadata.isSymbolicLink() || - metadata.size < 2 || - metadata.size > maximumBytes - ) { - throw new Error('JSONL 文件缺失、类型无效或大小超限'); - } - const noFollowFlag = - process.platform === 'win32' ? 0 : (fsConstants.O_NOFOLLOW ?? 0); - const file = await open(targetPath, fsConstants.O_RDONLY | noFollowFlag); - try { - const content = await file.readFile('utf8'); - if (!content.endsWith('\n')) throw new Error('JSONL 尾记录不完整'); - return content - .split(/\r?\n/u) - .filter(Boolean) - .map((line) => JSON.parse(line)); - } finally { - await file.close(); - } -} - -async function inspectReadyTaskExactlyOnce( - projectPath, - parentRunId, - agentDbRecords, -) { - const issues = []; - for (const taskId of requiredSwarmManifestTaskIds) { - const journalPath = `.agent/runtime/tasks/${taskId}.jsonl`; - try { - const journal = await readSafeJsonLines( - projectPath, - journalPath, - 8 * 1024 * 1024, - ); - const currentRunRecords = journal.filter( - (record) => - record?.agentId === taskId && - record?.taskId === taskId && - record?.source === 'agent-ready-task-scheduler' && - record?.parentAgentId === 'project-supervisor' && - record?.parentRunId === parentRunId, - ); - const runIds = [ - ...new Set(currentRunRecords.map((record) => record?.runId)), - ].filter((runId) => typeof runId === 'string' && runId.length > 0); - if (runIds.length !== 1) throw new Error('logical-run-count'); - const runId = runIds[0]; - const latest = currentRunRecords - .filter((record) => record?.runId === runId) - .at(-1); - if ( - latest?.status !== 'completed' || - latest?.phase !== 'completed' || - latest?.runProfile !== 'autonomous-game-build' - ) { - throw new Error('logical-run-terminal'); - } - const count = (recordType) => - agentDbRecords.filter( - (record) => - record?.recordType === recordType && - record?.agentId === taskId && - record?.taskId === taskId && - record?.runId === runId && - record?.source === 'agent-ready-task-scheduler', - ).length; - const projections = agentDbRecords.filter( - (record) => - record?.recordType === - 'agent.runtime.autonomous_ready_task.manifest_projected' && - record?.agentId === taskId && - record?.taskId === taskId && - record?.runId === runId && - record?.source === 'agent-ready-task-scheduler' && - record?.parentAgentId === 'project-supervisor' && - record?.parentRunId === parentRunId && - record?.terminalPhase === 'completed' && - record?.manifestStatus === 'completed', - ); - if ( - count('agent.runtime.background_task') !== 1 || - count('agent.runtime.background_task.completed') !== 1 || - count('agent.runtime.background_task.failed') !== 0 || - count('agent.runtime.background_task.cancelled') !== 0 || - projections.length !== 1 - ) { - throw new Error('lifecycle-count'); - } - } catch { - issues.push({ - path: journalPath, - reason: `正式任务 ${taskId} 未在当前父 Run 中恰好启动并完成一次`, - }); - } - } - return issues; -} - -async function inspectSwarmRuntimeAcceptance(projectPath, parentRunId = null) { - const issues = []; - let manifest; - try { - manifest = await readSafeJson( - projectPath, - '.agent/manifest.json', - 2 * 1024 * 1024, - ); - } catch { - issues.push({ - path: '.agent/manifest.json', - reason: '无法读取正式任务图', - }); - } - if (manifest) { - const tasks = Array.isArray(manifest.tasks) ? manifest.tasks : []; - const taskIds = tasks.map((task) => task?.id); - const expectedIds = [...requiredSwarmManifestTaskIds].sort(); - const actualIds = [...taskIds].sort(); - if ( - tasks.length !== requiredSwarmManifestTaskIds.length || - new Set(taskIds).size !== taskIds.length || - JSON.stringify(actualIds) !== JSON.stringify(expectedIds) || - tasks.some((task) => task?.status !== 'completed') - ) { - issues.push({ - path: '.agent/manifest.json', - reason: '固定 16 个正式任务未全部且仅完成一次', - }); - } - } - - let revision; - try { - const revisionRecord = await readSafeJson( - projectPath, - '.agent/runtime/project-revision.json', - 64 * 1024, - ); - revision = revisionRecord?.revision; - if (!Number.isSafeInteger(revision) || revision < 1) throw new Error(); - } catch { - issues.push({ - path: '.agent/runtime/project-revision.json', - reason: '当前项目 revision 无效', - }); - } - - let records = []; - let recordsValid = true; - try { - const databasePath = path.join(projectPath, '.agent', 'agent.db'); - const metadata = await lstat(databasePath); - if ( - !metadata.isFile() || - metadata.isSymbolicLink() || - metadata.size < 2 || - metadata.size > 64 * 1024 * 1024 - ) { - throw new Error(); - } - records = (await readFile(databasePath, 'utf8')) - .split(/\r?\n/u) - .filter(Boolean) - .map((line) => JSON.parse(line)); - } catch { - recordsValid = false; - issues.push({ path: '.agent/agent.db', reason: 'Runtime 证据库无效' }); - } - if (!recordsValid) return issues; - if (parentRunId) { - issues.push( - ...(await inspectReadyTaskExactlyOnce(projectPath, parentRunId, records)), - ); - } - const staticSmoke = records.some( - (record) => - record?.recordType === 'agent.runtime.command.run_limited' && - record?.commandId === 'game.static_smoke' && - record?.status === 'completed' && - record?.revision === revision, - ); - if (!staticSmoke) { - issues.push({ - path: '.agent/agent.db', - reason: '缺少当前 revision 的静态检查通过凭证', - }); - } - const browserEvidence = records - .filter( - (record) => - record?.recordType === 'agent.runtime.preview.validation' && - record?.passed === true && - record?.playtestPassed === true && - record?.revision === revision, - ) - .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0]; - if (!browserEvidence) { - issues.push({ - path: '.agent/agent.db', - reason: '缺少当前 revision 的桌面与移动试玩通过凭证', - }); - return issues; - } - const screenshots = Array.isArray(browserEvidence.screenshots) - ? browserEvidence.screenshots - : []; - for (const viewport of ['desktop', 'mobile']) { - const screenshot = screenshots.find((item) => - String(item).endsWith(`/${viewport}.png`), - ); - let screenshotValid = false; - try { - if (screenshot) { - await validatePngFile( - resolveProjectEvidencePath(projectPath, screenshot), - ); - screenshotValid = true; - } - } catch { - screenshotValid = false; - } - if (!screenshotValid) { - issues.push({ - path: '.agent/agent.db', - reason: `缺少 ${viewport} 试玩截图`, - }); - } - } - try { - const report = await readSafeJson( - projectPath, - browserEvidence.reportPath, - 2 * 1024 * 1024, - ); - const viewportResults = Array.isArray(report?.viewportResults) - ? report.viewportResults - : []; - if ( - report?.passed !== true || - report?.playtest?.passed !== true || - !['desktop', 'mobile'].every((viewport) => - viewportResults.some( - (result) => result?.viewport === viewport && result?.passed === true, - ), - ) - ) { - throw new Error(); - } - } catch { - issues.push({ - path: String( - browserEvidence.reportPath ?? '.agent/runtime/browser-validations', - ), - reason: '双视口试玩报告无效或已过期', - }); - } - return issues; -} - -export async function inspectSwarmProjectArtifacts( - projectPath, - { requireEditorImages = false, parentRunId = null } = {}, -) { - const specs = requireEditorImages - ? [...requiredFormalArtifactSpecs, ...editorImageArtifactSpecs] - : requiredFormalArtifactSpecs; - const issues = ( - await Promise.all( - specs.map((spec) => inspectFormalArtifact(projectPath, spec)), - ) - ).filter(Boolean); - issues.push( - ...(await inspectSwarmRuntimeAcceptance(projectPath, parentRunId)), - ); - return { - valid: issues.length === 0, - requireEditorImages, - invalidPaths: issues.map((issue) => issue.path), - issues, - }; -} - -export async function validateSwarmProjectArtifacts(projectPath, options) { - const inspection = await inspectSwarmProjectArtifacts(projectPath, options); - if (!inspection.valid) { - throw new Error( - `Agent Swarm 已退出,但最小正式产物检查失败:\n${inspection.issues - .map((issue) => `- ${issue.path}:${issue.reason}`) - .join('\n')}`, - ); - } - return inspection; -} - -export async function hasConfiguredEditorApiKey(configDir) { - let configured = false; - for (const fileName of [configFileName, localConfigFileName]) { - const configPath = path.join(configDir, fileName); - const metadata = await lstat(configPath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - if (!metadata) continue; - if (!metadata.isFile() || metadata.isSymbolicLink()) { - throw new Error(`运行配置必须是无符号链接普通文件:${fileName}`); - } - let config; - try { - config = JSON.parse(await readFile(configPath, 'utf8')); - } catch (error) { - throw new Error(`解析运行配置失败:${fileName}:${error.message}`); - } - if ( - config?.editorApi && - Object.prototype.hasOwnProperty.call(config.editorApi, 'apiKey') && - typeof config.editorApi.apiKey === 'string' - ) { - configured = config.editorApi.apiKey.trim().length > 0; - } - } - return configured; -} - -export async function openPreviewUrl(url, platform = process.platform) { - const validated = validatePreviewUrl(url); - const command = - platform === 'win32' - ? 'cmd.exe' - : platform === 'darwin' - ? 'open' - : 'xdg-open'; - const args = - platform === 'win32' - ? ['/d', '/s', '/c', 'start', '', validated] - : [validated]; - await new Promise((resolve, reject) => { - const child = spawn(command, args, { - detached: true, - stdio: 'ignore', - windowsHide: true, - }); - child.once('error', reject); - child.once('spawn', () => { - child.unref(); - resolve(); - }); - }); -} - -async function runPreview( - projectPath, - openBrowser, - setActiveChild, - stopRequested, -) { - const child = spawnChild( - cargoCommand, - buildCargoCliArguments(['--preview-serve', projectPath]), - { stdio: ['ignore', 'pipe', 'pipe'] }, - ); - setActiveChild(child); - child.stdout.setEncoding('utf8'); - child.stderr.setEncoding('utf8'); - child.stdout.pipe(process.stdout); - child.stderr.pipe(process.stderr); - - let pending = ''; - let previewUrl = null; - let resolvePreviewUrl; - let rejectPreviewUrl; - const previewUrlReady = new Promise((resolve, reject) => { - resolvePreviewUrl = resolve; - rejectPreviewUrl = reject; - }); - child.stdout.on('data', (chunk) => { - pending += chunk; - const lines = pending.split(/\r?\n/); - pending = lines.pop() ?? ''; - for (const line of lines) { - if (!line.startsWith('previewUrl=')) continue; - try { - previewUrl = validatePreviewUrl( - line.slice('previewUrl='.length).trim(), - ); - resolvePreviewUrl(previewUrl); - } catch (error) { - rejectPreviewUrl(error); - } - } - }); - - const exitPromise = childExit(child); - try { - exitPromise.then(({ code, signal }) => { - if (!previewUrl) { - rejectPreviewUrl( - new Error( - `预览进程提前退出:code=${code ?? ''} signal=${signal ?? ''}`, - ), - ); - } - }); - const url = await previewUrlReady; - console.log(`\n试玩地址:${url}`); - console.log('按 Ctrl+C 结束预览。'); - if (openBrowser) { - await openPreviewUrl(url).catch((error) => { - console.warn( - `无法自动打开浏览器,请手动访问上面的地址:${error.message}`, - ); - }); - } - const result = await exitPromise; - if (!stopRequested() && (result.code !== 0 || result.signal)) { - throw new Error( - `预览进程异常退出:code=${result.code ?? ''} signal=${result.signal ?? ''}`, - ); - } - } finally { - if (!closedChildren.has(child)) { - await terminateChildTreeAndWait( - child, - exitPromise, - 'SIGINT', - '持续预览进程', - ); - } - setActiveChild(null); - } -} - -export async function runSwarmTestChat(options) { - let sourceConfigDir = null; - let runtimeConfig = null; - let project = null; - let activeChild = null; - let receivedSignal = null; - let timedOut = false; - let phase = 'setup'; - let runnerMayHaveStarted = false; - let turnReport = null; - let primaryError = null; - let forceTerminationHandle = null; - const setActiveChild = (child) => { - activeChild = child; - }; - const concurrentChildren = new Set(); - const stopRequested = () => receivedSignal !== null; - const handleSignal = (signal) => { - const repeatedSignal = receivedSignal !== null; - receivedSignal ??= signal; - const targets = [activeChild, ...concurrentChildren].filter(Boolean); - if (targets.length === 0) return; - for (const target of targets) { - void terminateChildTree(target, signal, repeatedSignal).catch(() => {}); - } - if (repeatedSignal) return; - forceTerminationHandle = setTimeout(() => { - for (const target of [activeChild, ...concurrentChildren].filter( - Boolean, - )) { - void terminateChildTree(target, 'SIGKILL', true).catch(() => {}); - } - }, childTerminationGraceMs); - forceTerminationHandle.unref(); - }; - const clearForceTermination = () => { - if (forceTerminationHandle) { - clearTimeout(forceTerminationHandle); - forceTerminationHandle = null; - } - }; - const timeoutMs = resolveSwarmTestTimeoutMs(options); - const timeoutDeadline = timeoutMs === null ? null : Date.now() + timeoutMs; - const timeoutHandle = - timeoutMs === null - ? null - : setTimeout(() => { - timedOut = true; - console.error( - `agc:test:chat 已达到 ${options.timeoutMinutes ?? 50} 分钟执行期限,正在安全收束。`, - ); - handleSignal('SIGTERM'); - }, timeoutMs); - timeoutHandle?.unref(); - process.on('SIGINT', handleSignal); - process.on('SIGTERM', handleSignal); - - try { - session: { - try { - sourceConfigDir = await discoverRuntimeConfigDir(options.configDir); - } catch (error) { - if (!(await askToConfigureMissingRuntime())) throw error; - await runMissingConfigWizard(setActiveChild, options.configDir); - sourceConfigDir = await discoverRuntimeConfigDir(options.configDir); - } - runtimeConfig = await prepareSwarmTestRuntimeConfig(sourceConfigDir); - if (receivedSignal) break session; - project = await prepareSwarmTestProject(options.projectDir); - if (receivedSignal) break session; - - console.log(`配置来源:${path.join(sourceConfigDir, configFileName)}`); - console.log( - `隔离运行配置:${path.join(runtimeConfig.path, configFileName)}`, - ); - console.log(`测试项目:${project.path}`); - if (options.dryRun) { - console.log('测试环境检查通过;未启动 LLM。'); - break session; - } - - console.log('\n正在检查 LLM 配置...'); - const llmStatus = await runCapturedCargo( - ['--config-dir', runtimeConfig.path, '--llm-status'], - setActiveChild, - ); - if (receivedSignal) break session; - if (llmStatus.code !== 0 || llmStatus.signal) { - throw new Error( - `LLM 配置未就绪:${llmStatus.stderr.trim() || llmStatus.stdout.trim()}`, - ); - } - console.log('LLM 配置已就绪。'); - console.log( - options.task - ? '已提交一条非交互游戏需求,正在等待 Swarm 自主完成。\n' - : '输入一条游戏需求并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n', - ); - - phase = 'chat'; - runnerMayHaveStarted = true; - const chatArguments = [ - '--config-dir', - runtimeConfig.path, - '--swarm-chat', - '--init', - '--autonomous-game-build', - project.path, - ]; - let chat; - try { - chat = options.task - ? await runTaskCargo( - chatArguments, - options.task, - setActiveChild, - timeoutDeadline === null - ? null - : Math.max(1, timeoutDeadline - Date.now()), - ) - : await runInteractiveCargo(chatArguments, setActiveChild); - } catch (error) { - if (error?.code === 'AGC_CHILD_TIMEOUT') timedOut = true; - throw error; - } - if (receivedSignal) break session; - if (chat.code !== 0 || chat.signal) { - throw new Error( - `Agent Swarm 未正常收束:code=${chat.code ?? ''} signal=${chat.signal ?? ''}`, - ); - } - if (options.task) { - turnReport = parseSettledSwarmTurnReport(chat.turnReportOutput); - } - const requireEditorImages = await hasConfiguredEditorApiKey( - runtimeConfig.path, - ); - await validateSwarmProjectArtifacts(project.path, { - requireEditorImages, - parentRunId: turnReport?.parentRunId ?? null, - }); - - if (!shouldStartPersistentPreview(options)) { - phase = 'complete'; - console.log( - `\n真实 Swarm 测试通过:正式产物${ - requireEditorImages ? '及画布图片' : '' - }已验收,Runtime 已完成静态检查和双视口试玩。`, - ); - break session; - } - - phase = 'preview'; - console.log('\nAgent Swarm 已收束,正在启动试玩...'); - await runPreview( - project.path, - options.openBrowser, - setActiveChild, - stopRequested, - ); - phase = 'complete'; - } - } catch (error) { - primaryError = error; - } finally { - if (timeoutHandle) clearTimeout(timeoutHandle); - clearForceTermination(); - let cleanupError = null; - let runnerStopped = true; - if (runtimeConfig && runnerMayHaveStarted) { - try { - runnerStopped = await shutdownSwarmTestRunner( - runtimeConfig, - setActiveChild, - ); - if (!runnerStopped) { - cleanupError = new Error( - '隔离 Agent Runner 仍有任务,已保留测试项目和隔离运行配置', - ); - } - } catch (error) { - runnerStopped = false; - cleanupError = error; - } - } - const preserveFailedRun = - project?.owned && - (phase === 'chat' || - phase === 'artifact-validation' || - (phase === 'preview' && !receivedSignal)); - const preserveForRunner = project?.owned && !runnerStopped; - if ( - project?.owned && - (options.keepProject || preserveFailedRun || preserveForRunner) - ) { - if (preserveFailedRun && !options.keepProject) { - console.warn( - '测试尚未正常结束,为避免删除后台任务或失败证据,测试项目不会自动清理。', - ); - } - console.log(`已保留测试项目:${project.path}`); - } else if (project?.owned) { - try { - await cleanupSwarmTestProject(project); - console.log('已清理一次性测试项目。'); - } catch (error) { - cleanupError ??= error; - } - } - if (runtimeConfig) { - if (runnerStopped) { - try { - await cleanupSwarmTestRuntimeConfig(runtimeConfig); - console.log('已清理隔离运行配置。'); - } catch (error) { - cleanupError ??= error; - } - } else { - console.warn(`已保留隔离运行配置:${runtimeConfig.path}`); - } - } - if (cleanupError) { - if (primaryError) { - console.warn(`测试现场清理未完成:${cleanupError.message}`); - } else { - primaryError = cleanupError; - } - } - process.off('SIGINT', handleSignal); - process.off('SIGTERM', handleSignal); - } - if (timedOut) { - if (primaryError) { - console.warn(`超时收束附带错误:${primaryError.message}`); - } - primaryError = Object.assign(new Error('真实 Swarm 测试超过内部执行期限'), { - exitCode: 124, - }); - } else if (receivedSignal) { - if (primaryError) { - console.warn(`信号收束附带错误:${primaryError.message}`); - } - primaryError = Object.assign( - new Error(`真实 Swarm 测试收到 ${receivedSignal}`), - { exitCode: receivedSignal === 'SIGINT' ? 130 : 143 }, - ); - } - if (primaryError) throw primaryError; -} - -async function main() { - const options = parseSwarmTestArguments(process.argv.slice(2)); - if (options.help) { - console.log(usage); - return; - } - await runSwarmTestChat(options); -} - -const entryPath = process.argv[1] - ? pathToFileURL(path.resolve(process.argv[1])).href - : ''; -if (entryPath === import.meta.url) { - main().catch((error) => { - console.error(`agc:test:chat 失败:${error.message}`); - process.exitCode = Number.isInteger(error.exitCode) ? error.exitCode : 1; - }); -} diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index eaba4e582..159e50782 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -23,10 +23,6 @@ execFileSync( { stdio: 'inherit' }, ); -import { - appIdentifier, - defaultRealSwarmTestTask, -} from './agent-swarm-test-chat.mjs'; import { AGC_APP_IDENTIFIER, AGC_PRODUCT_NAME, @@ -83,10 +79,6 @@ const defaultAppConfig = JSON.parse( const rootPackageConfig = JSON.parse( fs.readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'), ); -const swarmTestChatSource = fs.readFileSync( - new URL('../scripts/agent-swarm-test-chat.mjs', import.meta.url), - 'utf8', -); const viteConfigSource = fs.readFileSync( new URL('../vite.config.ts', import.meta.url), 'utf8', @@ -132,8 +124,8 @@ const rustSharedContractSource = fs.readFileSync( const allowedUncalledTauriCommands = [ 'append_direct_project_conversation_message', // Supervisor 调试窗口、开发者面板、专业 Agent 对话与旧命令聊天的前端调用方已随 - // Supervisor 前端链路整体删除;命令本身仍注册在 Rust 侧并由 native Runtime、CLI - // swarm 与 Rust 测试使用,保留 present,仅不再出现在 App 前端源码里。 + // Supervisor 前端链路整体删除,终端 swarm CLI 也已退役;命令本身仍注册在 Rust 侧 + // 供 native Runtime 与 Rust 测试使用,保留 present,仅不出现在 App 前端源码里。 'answer_game_creator_agent_runtime_user_input', 'cancel_game_creator_agent_runtime_task', 'chat_with_game_creator_role_agent', @@ -141,12 +133,11 @@ const allowedUncalledTauriCommands = [ 'check_game_creator_llm_config', 'confirm_game_creator_agent_runtime_task', 'diff_local_project_checkpoint', - 'get_game_creation_agent_capabilities', - 'get_limited_local_commands', 'list_local_project_export_packages', 'read_game_creator_agent_runtime', 'read_local_agent_memory', 'read_local_game_memory', + 'read_local_project_file', 'reject_game_creator_agent_runtime_task', 'retry_game_creator_agent_runtime_task', 'schedule_game_creator_agent_ready_tasks', @@ -155,7 +146,7 @@ const allowedUncalledTauriCommands = [ 'write_local_agent_memory', 'write_local_game_memory', 'write_local_project_file', - // Agent 运行时会话 / 目标 / 协作命令由 native 侧与 CLI swarm 驱动,前端没有调用方。 + // Agent 运行时会话 / 目标 / 协作命令由 native 侧驱动,前端没有调用方。 'archive_game_creator_agent_session', 'clear_game_creator_agent_goal', 'compact_game_creator_agent_runtime_context', @@ -795,14 +786,14 @@ async function runConfigWizardRegressionChecks() { assertSafeGameCreatorConfigDestination(outsideConfigDir, { requireDedicatedLeaf: true, }), - new RegExp(appIdentifier.replaceAll('.', '\\.')), + new RegExp(AGC_APP_IDENTIFIER.replaceAll('.', '\\.')), ); - const dedicatedConfigDir = path.join(testRoot, appIdentifier); + const dedicatedConfigDir = path.join(testRoot, AGC_APP_IDENTIFIER); assert.equal( await assertSafeGameCreatorConfigDestination(dedicatedConfigDir, { requireDedicatedLeaf: true, }), - path.join(canonicalTestRoot, appIdentifier), + path.join(canonicalTestRoot, AGC_APP_IDENTIFIER), ); const injectedNonGitConfigDir = path.join(testRoot, 'injected-non-git'); @@ -1297,15 +1288,6 @@ if ( ); } -if ( - packageConfig.scripts?.swarm !== - 'node scripts/run-cli-with-config.mjs --swarm-chat' -) { - throw new Error( - 'AI game creator shell swarm must use client config before starting the interactive Agent runtime', - ); -} - if ( packageConfig.scripts?.config !== 'node scripts/game-creator-config-wizard.mjs' @@ -1315,31 +1297,6 @@ if ( ); } -if ( - packageConfig.scripts?.['test:chat'] !== - `node scripts/agent-swarm-test-chat.mjs --task ${JSON.stringify(defaultRealSwarmTestTask)} --no-open` -) { - throw new Error( - 'AI game creator shell test:chat must use the one-click Swarm test entry', - ); -} - -if ( - packageConfig.scripts?.['test:chat:manual'] !== - 'node scripts/agent-swarm-test-chat.mjs' -) { - throw new Error( - 'AI game creator shell test:chat:manual must keep the interactive Swarm test entry', - ); -} - -if ( - rootPackageConfig.scripts?.['agc:test'] !== - 'npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e --' -) { - throw new Error('agc:test must delegate to the deterministic playable E2E'); -} - if ( rootPackageConfig.scripts?.['agc:config'] !== 'npm --prefix apps/ai-game-creator-shell run config --' @@ -1349,37 +1306,6 @@ if ( ); } -if ( - rootPackageConfig.scripts?.['agc:test:chat'] !== - 'npm --prefix apps/ai-game-creator-shell run test:chat --' -) { - throw new Error( - 'agc:test:chat must delegate to the one-click Swarm test entry', - ); -} - -if ( - rootPackageConfig.scripts?.['agc:test:chat:manual'] !== - 'npm --prefix apps/ai-game-creator-shell run test:chat:manual --' -) { - throw new Error( - 'agc:test:chat:manual must delegate to the interactive Swarm test entry', - ); -} - -for (const requiredSource of [ - "import { AGC_APP_IDENTIFIER } from './channel-identity.mjs'", - 'export const appIdentifier = AGC_APP_IDENTIFIER', - "'--swarm-chat'", - "'--autonomous-game-build'", - "'--preview-serve'", - 'cleanupSwarmTestProject(project)', -]) { - if (!swarmTestChatSource.includes(requiredSource)) { - throw new Error(`Swarm test entry contract drifted: ${requiredSource}`); - } -} - // 基线配置必须等于默认渠道的安装身份:默认渠道不能改身份,否则已发布客户端 // 的升级链路与既有安装目录都会断开。 const defaultChannelIdentity = resolveChannelInstallIdentity('dev'); @@ -1939,18 +1865,12 @@ for (const snippet of [ '陶泥儿智能创作(固定)', '官方账号服务(固定)', 'runtime_config.save', - "'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'", 'async function executeRunLocal', 'function needsInitializedChatProject', - "'/remember [short|long|blackboard] 内容:追加短期、长期或黑板记忆'", - "'/memory-set [short|long|blackboard] 内容:覆盖保存对应记忆'", - 'function parseRememberInput', - "'/trace 或 /loop:查看最近一次 Agent loop trace'", "'permission.pending'", "'permission.confirm'", "'permission.cancel'", "'command.auto'", - 'function summarizeAgentRunTrace', ]) { if (!appSource.includes(snippet)) { throw new Error( @@ -1964,7 +1884,6 @@ for (const script of [ 'ai-game-creator-shell:dev-server', 'ai-game-creator-shell:build', 'ai-game-creator-shell:agent-task', - 'agc:swarm', 'ai-game-creator-shell:typecheck', 'ai-game-creator-shell:agent-run:smoke', 'ai-game-creator-shell:check', @@ -1974,13 +1893,6 @@ for (const script of [ } } -if ( - rootPackageConfig.scripts?.['agc:swarm'] !== - 'npm --prefix apps/ai-game-creator-shell run swarm --' -) { - throw new Error('root agc:swarm script must forward CLI args'); -} - if ( rootPackageConfig.scripts?.['ai-game-creator-shell:build'] !== 'npm --prefix apps/ai-game-creator-shell run build --' diff --git a/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs b/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs deleted file mode 100644 index 9c8318c57..000000000 --- a/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs +++ /dev/null @@ -1,3040 +0,0 @@ -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 ` - - - -灵露花园 - - -
Garden defenders -

灵露花园

GENARRATIVE_REAL_E2E_VISIBLE

Goal: defend the garden and win every wave.

-
-
-
Level 1 ready
- -
-`; -} - -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( - '
', - '
', - ) - .replace( - '\n