Files
Genarrative/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e/suites/supervisor-autonomous-playable.mjs
T
kdletters c09a511aaa
Project CI / Repository checks (push) Successful in 2m24s
Project CI / Frontend tests (push) Successful in 2m54s
Project CI / Backend tests (push) Successful in 4m33s
Project CI / Native shell tests (push) Successful in 14m29s
Codex/fix pr188 191 ci (#194)
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/194
Co-authored-by: kdletters <kdletters@qq.com>
Co-committed-by: kdletters <kdletters@qq.com>
2026-08-25 15:41:55 +08:00

1886 lines
67 KiB
JavaScript

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,
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;
}