import { assert, codedError, hashValue, isFailedTask, sleep, } from '../assertions/core.mjs'; import { countExactSecrets, disposableProjectPathVariants, duplicateCount, formalConfigPathVariants, isNonEmptyString, sumObjectValues, } from '../assertions/runtime.mjs'; import { path } from '../dependencies.mjs'; import { claimOwnedRunner, ensureOwnedRunnerStableKillSupport, prepareIsolatedSuiteAppData, } from '../harness/app-data.mjs'; import { isPlainObject } from '../harness/config.mjs'; import { listFiles, parseAssignedJson, readJson, readOptionalJsonl, } from '../harness/io.mjs'; import { prepareCliBinary, runCli } from '../harness/process.mjs'; import { seedDisposableProject } from '../harness/project.mjs'; import { agentConversationPath, countLureLeaks, countOccurrences, countSecretsInProject, countSensitiveValuesBySurface, killRunnerOnce, mainRuntimeStatePath, readAllRuntimeEvents, readRunnerStatus, readRuntime, readTaskSnapshot, runnerBootId, validateProjectRootPublicLeakBoundary, waitForRunnerBootChange, } from '../harness/runtime.mjs'; import { contextCompactionConstraintCanary, contextCompactionRoundCount, contextCompactionSuite, contextCompactionTriggerTurns, mainAgentId, requestedRunId, state, } from '../runtime-state.mjs'; export async function runContextCompactionE2e() { await ensureOwnedRunnerStableKillSupport(); await seedDisposableProject(); state.cliBinary = await prepareCliBinary(); await prepareIsolatedSuiteAppData(); state.isolatedRunner.launchAttempted = true; for (let turn = 1; turn <= contextCompactionRoundCount; turn += 1) { const runId = `${requestedRunId}-turn-${String(turn).padStart(2, '0')}`; const prompt = buildContextCompactionTurnPrompt(turn); const args = ['--agent-enqueue']; if (turn === 1) args.push('--init'); args.push(state.projectRoot, mainAgentId, runId, prompt); const accepted = parseAssignedJson( (await runCli(args, { timeoutMs: 120_000 })).stdout, ['runtimeJson'], ); const acceptedRuntime = accepted?.state; const acceptedTask = accepted?.recentTasks?.find( (task) => task.agentId === mainAgentId && task.runId === runId, ); assert( isPlainObject(acceptedRuntime), 'context-compaction-enqueue-state-missing', ); assert( acceptedTask?.agentId === mainAgentId && acceptedTask?.runId === runId && isNonEmptyString(acceptedTask?.sessionId), 'context-compaction-enqueue-task-identity-invalid', ); if (turn === 1) { state.initialRunId = runId; state.initialSessionId = acceptedTask.sessionId; state.initialTask = { chars: [...prompt].length, sha256: hashValue(prompt), }; await claimOwnedRunner(); } else { assert( acceptedTask.sessionId === state.initialSessionId, 'context-compaction-session-changed-on-enqueue', ); } const terminal = await waitForContextCompactionTurn(runId); state.contextCompaction.turnRunIds.push(runId); state.contextCompaction.turnsCompleted = turn; const estimated = Number(terminal.contextUsage?.estimatedInputTokens ?? 0); const limit = Number(terminal.contextUsage?.autoCompactTokenLimit ?? 0); assert( Number.isSafeInteger(estimated) && estimated > 0 && Number.isSafeInteger(limit) && limit > 0 && estimated <= limit, 'context-compaction-turn-token-budget-invalid', ); state.contextCompaction.maxEstimatedInputTokens = Math.max( state.contextCompaction.maxEstimatedInputTokens, estimated, ); if (state.contextCompaction.autoCompactTokenLimit === 0) { state.contextCompaction.autoCompactTokenLimit = limit; } else { assert( state.contextCompaction.autoCompactTokenLimit === limit, 'context-compaction-token-limit-changed', ); } if (contextCompactionTriggerTurns.has(turn)) { const revision = state.contextCompaction.compactionRevisions.length + 1; const sidecar = await triggerManualContextCompaction(revision); if (revision === 1) { await restartContextCompactionRunner(sidecar); } } } state.identityStable = true; state.evidence = await validateContextCompactionEvidence(); assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected'); } export function buildContextCompactionTurnPrompt(turn) { const filler = Array.from( { length: 36 }, (_, index) => `第${turn}轮背景片段${String(index + 1).padStart(2, '0')}`, ).join(';'); if (turn === 1) { return `这是持久多轮聊天测试。必须记住项目约束代号 ${contextCompactionConstraintCanary},在第 30 轮明确询问前不要复述该代号;现在只用一句话确认已经记住。不要调用工具,不要修改项目。背景:${filler}`; } if (turn === contextCompactionRoundCount) { return `这是第 ${turn} 轮。请只回复第 1 轮要求记住的项目约束代号,必须原样且只出现一次;不要调用工具,不要修改项目。背景:${filler}`; } return `这是第 ${turn} 轮持久多轮聊天测试。请只用一句话确认本轮已收到,不要复述第 1 轮代号,不要调用工具,不要修改项目。背景:${filler}`; } export async function waitForContextCompactionTurn(runId) { const deadline = Date.now() + 6 * 60 * 1000; while (Date.now() < deadline) { const [runtime, tasks] = await Promise.all([ readRuntime(mainAgentId).catch(() => null), readTaskSnapshot().catch(() => null), ]); const task = tasks?.latest.find( (candidate) => candidate.agentId === mainAgentId && candidate.runId === runId, ); if (task && isFailedTask(task)) { throw codedError('context-compaction-turn-failed'); } if ( runtime?.runId === runId && runtime?.sessionId === state.initialSessionId && (runtime.status === 'waiting-for-confirmation' || runtime.phase === 'waiting-for-confirmation') ) { throw codedError('context-compaction-unexpected-pending-action'); } if ( runtime?.runId === runId && runtime?.sessionId === state.initialSessionId && runtime?.status === 'idle' && runtime?.phase === 'completed' && task?.status === 'completed' && task?.phase === 'completed' ) { return runtime; } if (runtime?.phase === 'needs-reconciliation') { throw codedError('context-compaction-runtime-needs-reconciliation'); } await sleep(250); } throw codedError('context-compaction-turn-timeout'); } export function contextCompactionSidecarPath() { assert( isNonEmptyString(state.initialSessionId), 'context-compaction-session-identity-missing', ); return path.join( state.projectRoot, '.agent/runtime/context-compactions', hashValue(mainAgentId).slice(0, 32), `${hashValue(state.initialSessionId).slice(0, 32)}.json`, ); } export async function triggerManualContextCompaction(expectedRevision) { const result = await runCli( [ '--agent-context-compact', state.projectRoot, mainAgentId, state.initialSessionId, ], { timeoutMs: 6 * 60 * 1000, allowNonZero: true }, ); if (result.code !== 0) { const diagnostic = `${result.stdout}\n${result.stderr}`; const failureKind = diagnostic.includes('需要人工核对') ? 'reconciliation' : diagnostic.includes('正在运行') ? 'runtime-busy' : diagnostic.includes('context bundle') ? 'context-bundle' : diagnostic.includes('Provider') || diagnostic.includes('LLM') ? 'provider' : diagnostic.includes('Session') || diagnostic.includes('会话') ? 'session' : 'unknown'; throw codedError(`context-compaction-cli-${failureKind}-failed`); } const compacted = parseAssignedJson(result.stdout, ['contextCompactionJson']); assert( compacted?.agentId === mainAgentId && compacted?.sessionId === state.initialSessionId && compacted?.trigger === 'manual' && compacted?.revision === expectedRevision && compacted?.reused === false && Number.isSafeInteger(compacted?.coveredAgentMessages) && compacted.coveredAgentMessages > 0 && Number.isSafeInteger(compacted?.estimatedTokensBefore) && Number.isSafeInteger(compacted?.estimatedTokensAfter) && compacted.estimatedTokensAfter <= compacted.estimatedTokensBefore, 'context-compaction-manual-result-invalid', ); const sidecar = await readJson(contextCompactionSidecarPath()); const previousSummaryFingerprint = state.contextCompaction.compactionSummaryFingerprints.at(-1) ?? null; assert( sidecar?.schemaVersion === 'game-creator-runtime-context-compaction.v1' && sidecar?.agentId === mainAgentId && sidecar?.sessionId === state.initialSessionId && sidecar?.trigger === 'manual' && sidecar?.revision === expectedRevision && sidecar?.previousSummaryFingerprint === previousSummaryFingerprint && sidecar?.coveredAgentMessages === compacted.coveredAgentMessages && isNonEmptyString(sidecar?.sourceFingerprint) && /^[0-9a-f]{64}$/u.test(sidecar.sourceFingerprint) && isNonEmptyString(sidecar?.summary) && sidecar.summary.includes(contextCompactionConstraintCanary) && isNonEmptyString(sidecar?.summaryFingerprint) && /^[0-9a-f]{64}$/u.test(sidecar.summaryFingerprint), 'context-compaction-sidecar-invalid', ); if (expectedRevision > 1) { const previousCovered = state.contextCompaction.lastCoveredAgentMessages ?? 0; assert( sidecar.coveredAgentMessages > previousCovered, 'context-compaction-source-did-not-advance', ); } state.contextCompaction.lastCoveredAgentMessages = sidecar.coveredAgentMessages; state.contextCompaction.compactionRevisions.push(sidecar.revision); state.contextCompaction.compactionSourceFingerprints.push( sidecar.sourceFingerprint, ); state.contextCompaction.compactionSummaryFingerprints.push( sidecar.summaryFingerprint, ); state.contextCompaction.privateSummaries.push(sidecar.summary); return sidecar; } export async function restartContextCompactionRunner(sidecarBeforeKill) { const beforeKill = await readRunnerStatus(); state.contextCompaction.oldRunnerBootId = runnerBootId(beforeKill); assert( isNonEmptyString(state.contextCompaction.oldRunnerBootId), 'context-compaction-runner-boot-before-kill-missing', ); await killRunnerOnce(); await runCli(['--agent-resume', state.projectRoot], { timeoutMs: 120_000 }); state.resumed = true; const restarted = await waitForRunnerBootChange( state.contextCompaction.oldRunnerBootId, ); state.contextCompaction.newRunnerBootId = runnerBootId(restarted); await claimOwnedRunner(restarted); const sidecarAfterKill = await readJson(contextCompactionSidecarPath()); assert( state.contextCompaction.newRunnerBootId !== state.contextCompaction.oldRunnerBootId && sidecarAfterKill.revision === sidecarBeforeKill.revision && sidecarAfterKill.sourceFingerprint === sidecarBeforeKill.sourceFingerprint && sidecarAfterKill.summaryFingerprint === sidecarBeforeKill.summaryFingerprint, 'context-compaction-runner-recovery-invalid', ); } export async function readContextCompactionPersistence() { const [ taskSnapshot, events, agentDb, conversations, activity, output, runtimeState, sidecar, ] = await Promise.all([ readTaskSnapshot(), readAllRuntimeEvents(), readOptionalJsonl(path.join(state.projectRoot, '.agent/agent.db')), readOptionalJsonl( agentConversationPath(mainAgentId, state.initialSessionId), ), readOptionalJsonl(path.join(state.projectRoot, '.agent/activity.jsonl')), readOptionalJsonl(path.join(state.projectRoot, '.agent/output.jsonl')), readJson(mainRuntimeStatePath()), readJson(contextCompactionSidecarPath()), ]); return { taskSnapshot, events, agentDb, conversations, activity, output, runtimeState, sidecar, }; } export function validateContextCompactionProviderLifecycle(agentDb) { const lifecycle = agentDb.filter( (record) => record.recordType === 'agent.runtime.provider_request.lifecycle' && record.agentId === mainAgentId && state.contextCompaction.turnRunIds.includes(record.runId), ); const byRequest = new Map(); for (const record of lifecycle) { assert( isNonEmptyString(record.requestId) && isNonEmptyString(record.requestSlot) && isNonEmptyString(record.requestKind), 'context-compaction-provider-lifecycle-identity-invalid', ); const records = byRequest.get(record.requestId) ?? []; records.push(record); byRequest.set(record.requestId, records); } for (const records of byRequest.values()) { assert( records.length === 2 && records[0].status === 'started' && records[1].status === 'completed' && records[0].requestSlot === records[1].requestSlot && records[0].requestKind === records[1].requestKind && records[0].runId === records[1].runId, 'context-compaction-provider-lifecycle-sequence-invalid', ); } const started = lifecycle.filter((record) => record.status === 'started'); const toolPlan = started.filter( (record) => record.requestKind === 'tool-plan', ); const compaction = started.filter( (record) => record.requestKind === 'context-compaction', ); assert( new Set(toolPlan.map((record) => record.runId)).size === contextCompactionRoundCount && compaction.length === contextCompactionTriggerTurns.size && compaction.every((record) => record.webSearchEnabled === false), 'context-compaction-provider-request-count-invalid', ); return { requestIdentityCount: byRequest.size, startedCount: started.length, terminalCount: lifecycle.filter((record) => record.status === 'completed') .length, toolPlanCount: toolPlan.length, compactionCount: compaction.length, compactionRequestSlotSetHash: hashValue( JSON.stringify(compaction.map((record) => record.requestSlot).sort()), ), }; } export async function validateContextCompactionEvidence() { const persistence = await readContextCompactionPersistence(); const { taskSnapshot, events, agentDb, conversations, activity, output, runtimeState, sidecar, } = persistence; assert( state.contextCompaction.turnsCompleted === contextCompactionRoundCount && state.contextCompaction.turnRunIds.length === contextCompactionRoundCount && new Set(state.contextCompaction.turnRunIds).size === contextCompactionRoundCount, 'context-compaction-turn-ledger-invalid', ); const runIds = new Set(state.contextCompaction.turnRunIds); const targetTasks = taskSnapshot.latest.filter( (task) => task.agentId === mainAgentId && runIds.has(task.runId), ); assert( targetTasks.length === contextCompactionRoundCount && targetTasks.every( (task) => task.sessionId === state.initialSessionId && task.status === 'completed' && task.phase === 'completed', ), 'context-compaction-task-completion-invalid', ); const userMessages = conversations.filter( (message) => message.role === 'user', ); const assistantMessages = conversations.filter( (message) => message.role === 'assistant', ); const finalAssistant = assistantMessages.at(-1); assert( userMessages.length === contextCompactionRoundCount && assistantMessages.length === contextCompactionRoundCount && assistantMessages .slice(0, -1) .every( (message) => countOccurrences( message.content, contextCompactionConstraintCanary, ) === 0, ) && countOccurrences( finalAssistant?.content, contextCompactionConstraintCanary, ) === 1, 'context-compaction-early-constraint-recall-invalid', ); state.contextCompaction.finalReplyFingerprint = hashValue( finalAssistant.content, ); assert( sidecar.schemaVersion === 'game-creator-runtime-context-compaction.v1' && sidecar.agentId === mainAgentId && sidecar.sessionId === state.initialSessionId && sidecar.trigger === 'manual' && sidecar.revision === contextCompactionTriggerTurns.size && sidecar.summary.includes(contextCompactionConstraintCanary) && state.contextCompaction.compactionRevisions.join(',') === '1,2' && new Set(state.contextCompaction.compactionSourceFingerprints).size === 2, 'context-compaction-final-sidecar-invalid', ); assert( runtimeState.agentId === mainAgentId && runtimeState.sessionId === state.initialSessionId && runtimeState.runId === state.contextCompaction.turnRunIds.at(-1) && runtimeState.status === 'idle' && runtimeState.phase === 'completed' && runtimeState.contextUsage?.compactionRevision === 2 && runtimeState.contextUsage?.compactionCount === 2 && runtimeState.contextUsage?.lastCompactionTrigger === 'manual' && runtimeState.contextUsage?.estimatedInputTokens <= runtimeState.contextUsage?.autoCompactTokenLimit, 'context-compaction-final-runtime-usage-invalid', ); const lifecycle = validateContextCompactionProviderLifecycle(agentDb); const compactionAudits = agentDb.filter( (record) => record.recordType === 'agent.runtime.context.compacted' && record.agentId === mainAgentId && record.sessionId === state.initialSessionId, ); const compactionEvents = events.filter( (event) => event.agentId === mainAgentId && event.sessionId === state.initialSessionId && event.eventType === 'context.compacted', ); assert( compactionAudits.length === 2 && compactionAudits.map((record) => record.revision).join(',') === '1,2' && compactionEvents.length === 2, 'context-compaction-public-audit-count-invalid', ); const assistantAudits = agentDb.filter( (record) => record.recordType === 'conversation.message' && record.role === 'assistant' && record.agentId === mainAgentId && record.sessionId === state.initialSessionId, ); const receipts = agentDb.filter( (record) => record.recordType === 'agent.runtime.action_receipt' && record.agentId === mainAgentId && runIds.has(record.runId), ); assert( assistantAudits.length === contextCompactionRoundCount && receipts.length === 0, 'context-compaction-assistant-or-tool-replay-invalid', ); const duplicateMessageCount = duplicateCount( conversations.map((message) => message.messageId).filter(Boolean), ); const duplicateAssistantAuditCount = duplicateCount( assistantAudits.map( (record) => `${record.agentId}\0${record.sessionId}\0${record.finalizationId}\0${record.messageId}`, ), ); assert( duplicateMessageCount === 0 && duplicateAssistantAuditCount === 0, 'context-compaction-duplicate-message-identity', ); const publicSurfaces = { event: events, agentDb, receipt: receipts, activity, output, }; const privateBodies = [ ...conversations.map((message) => message.content), ...state.contextCompaction.privateSummaries, ].filter(isNonEmptyString); const privateBodyPublicCounts = countSensitiveValuesBySurface( publicSurfaces, privateBodies, 'context-compaction-private-body-public', ); const apiKeyPublicCounts = countSensitiveValuesBySurface( publicSurfaces, state.secrets, 'context-compaction-api-key-public', ); const lurePublicCounts = countSensitiveValuesBySurface( publicSurfaces, state.lures, 'context-compaction-lure-public', ); const projectPathPublicCounts = validateProjectRootPublicLeakBoundary( publicSurfaces, 'context-compaction-public', ); const formalConfigPathPublicCounts = countSensitiveValuesBySurface( publicSurfaces, formalConfigPathVariants(), 'context-compaction-formal-config-path-public', ); const sidecarSensitiveLeakCount = countExactSecrets( Buffer.from(JSON.stringify(sidecar)), [ ...state.secrets, ...state.lures, ...disposableProjectPathVariants(), ...formalConfigPathVariants(), ], ); assert( sidecarSensitiveLeakCount === 0, 'context-compaction-private-sidecar-sensitive-leak', ); const finalizationFiles = ( await listFiles( path.join(state.projectRoot, '.agent/runtime/finalizations'), ) ).filter((file) => file.endsWith('.json')); assert( finalizationFiles.length === 0, 'context-compaction-finalization-journal-present', ); state.lureLeakCount = await countLureLeaks(); assert(state.lureLeakCount === 0, 'sensitive-lure-leak-detected'); const projectSecretLeakCount = await countSecretsInProject( state.projectRoot, state.secrets, ); const secretLeakCount = (state.transcriptScanner?.count ?? 0) + projectSecretLeakCount; assert(secretLeakCount === 0, 'loaded-key-leak-detected'); return { scenario: 'thirty-turn-persistent-context-compaction', isolatedAppDataUsed: true, formalConfigCliCallCount: state.isolatedRunner.sourceConfigCliCallCount, sourceRunnerEndpointUnchanged: false, sourceConfigHardlinkCount: state.isolatedRunner.configLinks.length, sourceConfigLinksVerified: false, turnCount: contextCompactionRoundCount, completedTurnCount: targetTasks.length, targetRunCount: runIds.size, stableSessionCount: new Set(targetTasks.map((task) => task.sessionId)).size, compactionCount: state.contextCompaction.compactionRevisions.length, compactionRevisions: [...state.contextCompaction.compactionRevisions], compactionSourceFingerprintSetHash: hashValue( JSON.stringify( [...state.contextCompaction.compactionSourceFingerprints].sort(), ), ), compactionSummaryFingerprintSetHash: hashValue( JSON.stringify( [...state.contextCompaction.compactionSummaryFingerprints].sort(), ), ), coveredAgentMessageCount: sidecar.coveredAgentMessages, coveredProjectMessageCount: sidecar.coveredProjectMessages, coveredObservationCount: sidecar.coveredObservations, earlyConstraintRecalled: true, earlyConstraintCanaryHash: hashValue(contextCompactionConstraintCanary), finalReplyFingerprint: state.contextCompaction.finalReplyFingerprint, maxEstimatedInputTokens: state.contextCompaction.maxEstimatedInputTokens, autoCompactTokenLimit: state.contextCompaction.autoCompactTokenLimit, requestStayedUnderAutoCompactLimit: state.contextCompaction.maxEstimatedInputTokens <= state.contextCompaction.autoCompactTokenLimit, runnerBootChanged: state.contextCompaction.oldRunnerBootId !== state.contextCompaction.newRunnerBootId, taskCount: taskSnapshot.all.length, eventCount: events.length, agentDbRecordCount: agentDb.length, conversationMessageCount: conversations.length, userMessageCount: userMessages.length, finalAssistantCount: assistantMessages.length, finalAssistantAuditCount: assistantAudits.length, successfulToolExecutionCount: receipts.length, duplicateMessageCount, duplicateAssistantAuditCount, providerRequestIdentityCount: lifecycle.requestIdentityCount, providerLifecycleStartedCount: lifecycle.startedCount, providerLifecycleTerminalCount: lifecycle.terminalCount, toolPlanProviderRequestCount: lifecycle.toolPlanCount, compactionProviderRequestCount: lifecycle.compactionCount, compactionRequestSlotSetHash: lifecycle.compactionRequestSlotSetHash, providerFallbackReplayCount: 0, finalizationJournalCount: finalizationFiles.length, privateBodyPublicLeakCount: sumObjectValues(privateBodyPublicCounts), apiKeyPublicLeakCount: sumObjectValues(apiKeyPublicCounts), lurePublicLeakCount: sumObjectValues(lurePublicCounts), projectPathPublicLeakCount: sumObjectValues(projectPathPublicCounts), projectPathPublicSurfaceCount: Object.keys(projectPathPublicCounts).length, formalConfigPathPublicLeakCount: sumObjectValues( formalConfigPathPublicCounts, ), formalConfigPathPublicSurfaceCount: Object.keys( formalConfigPathPublicCounts, ).length, privateSidecarSensitiveLeakCount: sidecarSensitiveLeakCount, contextCompactionReportLeakCount: state.contextCompaction.reportLeakCount, contextCompactionRunnerKillMethod: null, contextCompactionRunnerPidfdClaimCount: state.isolatedRunner.pidfdClaimCount, contextCompactionRunnerPidfdSignalCount: state.isolatedRunner.pidfdSignalCount, contextCompactionRunnerStopped: false, contextCompactionAppDataCleanupPerformed: false, secretLeakCount, lureLeakCount: state.lureLeakCount, paths: [ '.agent/runtime/context-compactions', '.agent/runtime/tasks', '.agent/runtime/events', '.agent/agent.db', '.agent/conversations', ], }; } export function emptyContextCompactionEvidence() { return { scenario: 'thirty-turn-persistent-context-compaction', isolatedAppDataUsed: false, formalConfigCliCallCount: 0, sourceRunnerEndpointUnchanged: false, sourceConfigHardlinkCount: 0, sourceConfigLinksVerified: false, turnCount: contextCompactionRoundCount, completedTurnCount: 0, targetRunCount: 0, stableSessionCount: 0, compactionCount: 0, compactionRevisions: [], compactionSourceFingerprintSetHash: null, compactionSummaryFingerprintSetHash: null, coveredAgentMessageCount: 0, coveredProjectMessageCount: 0, coveredObservationCount: 0, earlyConstraintRecalled: false, earlyConstraintCanaryHash: hashValue(contextCompactionConstraintCanary), finalReplyFingerprint: null, maxEstimatedInputTokens: 0, autoCompactTokenLimit: 0, requestStayedUnderAutoCompactLimit: false, runnerBootChanged: false, taskCount: 0, eventCount: 0, agentDbRecordCount: 0, conversationMessageCount: 0, userMessageCount: 0, finalAssistantCount: 0, finalAssistantAuditCount: 0, successfulToolExecutionCount: 0, duplicateMessageCount: 0, duplicateAssistantAuditCount: 0, providerRequestIdentityCount: 0, providerLifecycleStartedCount: 0, providerLifecycleTerminalCount: 0, toolPlanProviderRequestCount: 0, compactionProviderRequestCount: 0, compactionRequestSlotSetHash: null, providerFallbackReplayCount: 0, finalizationJournalCount: 0, privateBodyPublicLeakCount: 0, apiKeyPublicLeakCount: 0, lurePublicLeakCount: 0, projectPathPublicLeakCount: 0, projectPathPublicSurfaceCount: 0, formalConfigPathPublicLeakCount: 0, formalConfigPathPublicSurfaceCount: 0, formalConfigPathTranscriptLeakCount: 0, formalConfigPathReportLeakCount: 0, privateSidecarSensitiveLeakCount: 0, contextCompactionReportLeakCount: 0, contextCompactionRunnerKillMethod: null, contextCompactionRunnerPidfdClaimCount: 0, contextCompactionRunnerPidfdSignalCount: 0, contextCompactionRunnerStopped: false, contextCompactionAppDataCleanupPerformed: false, secretLeakCount: 0, lureLeakCount: 0, paths: [], }; } export async function collectPartialContextCompactionEvidence() { const [tasks, events, agentDb, conversations, sidecar] = await Promise.all([ readTaskSnapshot().catch(() => ({ all: [], latest: [] })), readAllRuntimeEvents().catch(() => []), readOptionalJsonl(path.join(state.projectRoot, '.agent/agent.db')).catch( () => [], ), isNonEmptyString(state.initialSessionId) ? readOptionalJsonl( agentConversationPath(mainAgentId, state.initialSessionId), ).catch(() => []) : [], isNonEmptyString(state.initialSessionId) ? readJson(contextCompactionSidecarPath()).catch(() => null) : null, ]); const targetRuns = new Set(state.contextCompaction.turnRunIds); return { completedTurnCount: tasks.latest.filter( (task) => task.agentId === mainAgentId && targetRuns.has(task.runId) && task.status === 'completed' && task.phase === 'completed', ).length, targetRunCount: targetRuns.size, compactionCount: Number(sidecar?.revision ?? 0), compactionRevisions: [...state.contextCompaction.compactionRevisions], coveredAgentMessageCount: Number(sidecar?.coveredAgentMessages ?? 0), taskCount: tasks.all.length, eventCount: events.length, agentDbRecordCount: agentDb.length, conversationMessageCount: conversations.length, userMessageCount: conversations.filter((message) => message.role === 'user') .length, finalAssistantCount: conversations.filter( (message) => message.role === 'assistant', ).length, maxEstimatedInputTokens: state.contextCompaction.maxEstimatedInputTokens, autoCompactTokenLimit: state.contextCompaction.autoCompactTokenLimit, runnerBootChanged: isNonEmptyString(state.contextCompaction.oldRunnerBootId) && isNonEmptyString(state.contextCompaction.newRunnerBootId) && state.contextCompaction.oldRunnerBootId !== state.contextCompaction.newRunnerBootId, }; } export function isContextCompactionSuite() { return state.suite === contextCompactionSuite; }