c09a511aaa
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>
1886 lines
67 KiB
JavaScript
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;
|
|
}
|