Files
AIGameCreator App be89296492 拆分 AI 游戏创作客户端大型模块
拆分 App 认证、壳层、运行配置与项目摘要模块
拆分 Tauri 项目能力与 Rust 测试领域模块
拆分界面测试与 Agent Runtime 真实 E2E 套件
补充源码扫描和客户端模块化文档约定
2026-07-21 22:53:29 +08:00

821 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}