690 lines
24 KiB
JavaScript
690 lines
24 KiB
JavaScript
import {
|
|
assert,
|
|
codedError,
|
|
hashValue,
|
|
isFailedTask,
|
|
sleep,
|
|
} from '../assertions/core.mjs';
|
|
import {
|
|
actionAuditIdentity,
|
|
collectNativeRuntimeToolPlanProtocolEvidence,
|
|
disposableProjectPathVariants,
|
|
duplicateCount,
|
|
emptyToolPlanRepairCountsByProtocolErrorKind,
|
|
isNonEmptyString,
|
|
receiptAuditIdentity,
|
|
sumObjectValues,
|
|
validateNativeRuntimeToolPlanProtocolEvidence,
|
|
} from '../assertions/runtime.mjs';
|
|
import { fs, path } from '../dependencies.mjs';
|
|
import {
|
|
claimOwnedRunner,
|
|
ensureOwnedRunnerStableKillSupport,
|
|
prepareIsolatedSuiteAppData,
|
|
} from '../harness/app-data.mjs';
|
|
import { listFiles, readOptionalJsonl } from '../harness/io.mjs';
|
|
import { prepareCliBinary, runCli } from '../harness/process.mjs';
|
|
import { seedDisposableProject } from '../harness/project.mjs';
|
|
import {
|
|
agentConversationPath,
|
|
countLureLeaks,
|
|
countSecretsInProject,
|
|
countSensitiveValuesBySurface,
|
|
findPendingActions,
|
|
readRuntime,
|
|
readTaskSnapshot,
|
|
waitForResponseRuntimeIdentity,
|
|
} from '../harness/runtime.mjs';
|
|
import {
|
|
configFileName,
|
|
goalSessionId,
|
|
mainAgentId,
|
|
parallelReadAlphaQuery,
|
|
parallelReadBetaQuery,
|
|
parallelReadCorpusFileCount,
|
|
parallelReadCorpusPath,
|
|
parallelReadSuite,
|
|
providerRequestLifecycleSchemaVersion,
|
|
requestedRunId,
|
|
runTimeoutMs,
|
|
state,
|
|
} from '../runtime-state.mjs';
|
|
import { readScopedAgentsPersistence } from './scoped-agents.mjs';
|
|
|
|
export async function seedParallelReadDisposableProject() {
|
|
await seedDisposableProject();
|
|
const corpusRoot = path.join(state.projectRoot, parallelReadCorpusPath);
|
|
await fs.mkdir(corpusRoot, { recursive: true });
|
|
const fillerBody = `${'parallel-read-filler '.repeat(820)}\n`;
|
|
const writes = [];
|
|
for (let index = 0; index < parallelReadCorpusFileCount; index += 1) {
|
|
writes.push(
|
|
fs.writeFile(
|
|
path.join(corpusRoot, `${String(index).padStart(4, '0')}.txt`),
|
|
fillerBody,
|
|
),
|
|
);
|
|
}
|
|
const evidenceBody = [
|
|
`alpha evidence ${parallelReadAlphaQuery}`,
|
|
`beta evidence ${parallelReadBetaQuery}`,
|
|
'',
|
|
].join('\n');
|
|
const repositoryInstructions = `# Parallel read real E2E\n\n- This project is read-only for the current audit. Do not write files, run commands, use Git, or delegate.\n- Verify ${parallelReadAlphaQuery} and ${parallelReadBetaQuery} with two separate project-wide text searches scoped to ${parallelReadCorpusPath}.\n- Submit both independent searches together in one planning turn before drawing a conclusion.\n- After both observations arrive, answer briefly without quoting repository instructions.\n- Never read or expose .env, ${configFileName}, or .agent/private-secret.txt.\n`;
|
|
await Promise.all([
|
|
...writes,
|
|
fs.writeFile(path.join(corpusRoot, 'zzzz-evidence.txt'), evidenceBody),
|
|
fs.writeFile(
|
|
path.join(state.projectRoot, 'AGENTS.md'),
|
|
repositoryInstructions,
|
|
),
|
|
]);
|
|
state.parallelRead.privateValues = [repositoryInstructions, evidenceBody];
|
|
}
|
|
|
|
export function buildParallelReadTaskPrompt() {
|
|
return `对当前项目做一次严格只读的双证据核验:同时确认 ${parallelReadAlphaQuery} 与 ${parallelReadBetaQuery} 是否分别存在于 ${parallelReadCorpusPath}。必须实际执行两项彼此独立的项目全文搜索,并在同一个 planning 轮次一起提交;收到两项 observation 后再简短回答各自是否找到。不要修改项目,不要执行命令、Git 或委派。`;
|
|
}
|
|
|
|
export function assertParallelReadTaskPrompt(task) {
|
|
assert(
|
|
task.includes(parallelReadAlphaQuery) &&
|
|
task.includes(parallelReadBetaQuery) &&
|
|
task.includes(parallelReadCorpusPath) &&
|
|
task.includes('同一个 planning 轮次') &&
|
|
task.includes('严格只读'),
|
|
'parallel-read-task-boundary-missing',
|
|
);
|
|
for (const forbidden of [
|
|
'project.search',
|
|
'file.read',
|
|
'submit_agent_tool_plan',
|
|
state.projectRoot,
|
|
]) {
|
|
assert(!task.includes(forbidden), 'parallel-read-task-runtime-recipe-leak');
|
|
}
|
|
}
|
|
|
|
export async function runParallelReadE2e() {
|
|
await ensureOwnedRunnerStableKillSupport();
|
|
await seedParallelReadDisposableProject();
|
|
state.cliBinary = await prepareCliBinary();
|
|
await prepareIsolatedSuiteAppData();
|
|
|
|
const task = buildParallelReadTaskPrompt();
|
|
assertParallelReadTaskPrompt(task);
|
|
state.initialTask = {
|
|
chars: [...task].length,
|
|
sha256: hashValue(task),
|
|
};
|
|
state.initialRunId = requestedRunId;
|
|
state.initialSessionId = goalSessionId;
|
|
state.isolatedRunner.launchAttempted = true;
|
|
await runCli(
|
|
[
|
|
'--agent-enqueue',
|
|
'--init',
|
|
state.projectRoot,
|
|
mainAgentId,
|
|
state.initialRunId,
|
|
task,
|
|
],
|
|
{ timeoutMs: 120_000 },
|
|
);
|
|
await claimOwnedRunner();
|
|
const runtime = await waitForResponseRuntimeIdentity();
|
|
assert(
|
|
runtime.agentId === mainAgentId &&
|
|
runtime.runId === state.initialRunId &&
|
|
runtime.sessionId === state.initialSessionId,
|
|
'parallel-read-runtime-identity-invalid',
|
|
);
|
|
state.identityStable = true;
|
|
|
|
await driveParallelReadRuntimeToCompletion();
|
|
state.evidence = await validateParallelReadEvidence();
|
|
assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected');
|
|
}
|
|
|
|
export async function driveParallelReadRuntimeToCompletion() {
|
|
const deadline = Date.now() + runTimeoutMs;
|
|
let quietPolls = 0;
|
|
while (Date.now() < deadline) {
|
|
const pending = await findPendingActions();
|
|
assert(pending.length === 0, 'parallel-read-unexpected-confirmation');
|
|
const [runtime, tasks, conversations] = await Promise.all([
|
|
readRuntime(mainAgentId).catch(() => null),
|
|
readTaskSnapshot(),
|
|
readOptionalJsonl(
|
|
agentConversationPath(mainAgentId, state.initialSessionId),
|
|
),
|
|
]);
|
|
const task = tasks.latest.find(
|
|
(candidate) =>
|
|
candidate.agentId === mainAgentId &&
|
|
candidate.runId === state.initialRunId,
|
|
);
|
|
if (task && isFailedTask(task)) {
|
|
throw codedError('parallel-read-runtime-failed');
|
|
}
|
|
if (runtime?.phase === 'needs-reconciliation') {
|
|
throw codedError('parallel-read-runtime-needs-reconciliation');
|
|
}
|
|
const completed =
|
|
runtime?.runId === state.initialRunId &&
|
|
runtime?.sessionId === state.initialSessionId &&
|
|
runtime?.status === 'idle' &&
|
|
runtime?.phase === 'completed' &&
|
|
task?.status === 'completed' &&
|
|
task?.phase === 'completed' &&
|
|
conversations.filter((message) => message.role === 'assistant').length ===
|
|
1;
|
|
if (completed) {
|
|
quietPolls += 1;
|
|
if (quietPolls >= 3) return;
|
|
} else {
|
|
quietPolls = 0;
|
|
}
|
|
await sleep(250);
|
|
}
|
|
throw codedError('parallel-read-runtime-timeout');
|
|
}
|
|
|
|
export function validateParallelReadProviderLifecycle(agentDb) {
|
|
const lifecycle = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.provider_request.lifecycle' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
const byRequest = new Map();
|
|
for (const record of lifecycle) {
|
|
assert(
|
|
record.auditSchemaVersion === providerRequestLifecycleSchemaVersion &&
|
|
isNonEmptyString(record.requestId) &&
|
|
isNonEmptyString(record.requestSlot) &&
|
|
['tool-plan', 'final-reply'].includes(record.requestKind) &&
|
|
record.webSearchEnabled === false,
|
|
'parallel-read-provider-lifecycle-record-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].requestKind === records[1].requestKind &&
|
|
records[0].requestSlot === records[1].requestSlot,
|
|
'parallel-read-provider-lifecycle-sequence-invalid',
|
|
);
|
|
}
|
|
const started = lifecycle.filter((record) => record.status === 'started');
|
|
assert(
|
|
byRequest.size > 0 &&
|
|
started.length === byRequest.size &&
|
|
started.some((record) => record.requestKind === 'tool-plan'),
|
|
'parallel-read-provider-request-count-invalid',
|
|
);
|
|
return {
|
|
requestIdentityCount: byRequest.size,
|
|
startedCount: started.length,
|
|
terminalCount: lifecycle.filter((record) => record.status === 'completed')
|
|
.length,
|
|
toolPlanCount: started.filter(
|
|
(record) => record.requestKind === 'tool-plan',
|
|
).length,
|
|
finalReplyCount: started.filter(
|
|
(record) => record.requestKind === 'final-reply',
|
|
).length,
|
|
duplicateCount: duplicateCount(
|
|
lifecycle.map((record) => `${record.requestId}:${record.status}`),
|
|
),
|
|
};
|
|
}
|
|
|
|
export async function validateParallelReadEvidence() {
|
|
const persistence = await readScopedAgentsPersistence();
|
|
const {
|
|
taskSnapshot,
|
|
events,
|
|
agentDb,
|
|
conversations,
|
|
activity,
|
|
output,
|
|
runtimeState,
|
|
contextBundle,
|
|
} = persistence;
|
|
const latest = taskSnapshot.latest.find(
|
|
(task) => task.agentId === mainAgentId && task.runId === state.initialRunId,
|
|
);
|
|
assert(
|
|
runtimeState?.agentId === mainAgentId &&
|
|
runtimeState.runId === state.initialRunId &&
|
|
runtimeState.sessionId === state.initialSessionId &&
|
|
runtimeState.status === 'idle' &&
|
|
runtimeState.phase === 'completed' &&
|
|
latest?.status === 'completed' &&
|
|
latest.phase === 'completed',
|
|
'parallel-read-final-runtime-invalid',
|
|
);
|
|
|
|
const batchAudits = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.parallel_read_batch.completed' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
assert(batchAudits.length === 1, 'parallel-read-batch-audit-count-invalid');
|
|
const batch = batchAudits[0];
|
|
assert(
|
|
isNonEmptyString(batch.batchId) &&
|
|
batch.actionCount === 2 &&
|
|
Array.isArray(batch.actionIds) &&
|
|
batch.actionIds.length === 2 &&
|
|
new Set(batch.actionIds).size === 2 &&
|
|
Array.isArray(batch.tools) &&
|
|
batch.tools.length === 2 &&
|
|
batch.tools.every((tool) => tool === 'project.search') &&
|
|
batch.overlapped === true &&
|
|
Number.isFinite(batch.overlapNanos) &&
|
|
batch.overlapNanos > 0,
|
|
'parallel-read-batch-overlap-evidence-invalid',
|
|
);
|
|
const batchActionIds = batch.actionIds;
|
|
const receipts = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.action_receipt' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId &&
|
|
batchActionIds.includes(record.actionId),
|
|
);
|
|
assert(
|
|
receipts.length === 2 &&
|
|
receipts.every(
|
|
(record) => record.tool === 'project.search' && record.status === 'ok',
|
|
) &&
|
|
JSON.stringify(receipts.map((record) => record.actionId)) ===
|
|
JSON.stringify(batchActionIds),
|
|
'parallel-read-receipt-order-invalid',
|
|
);
|
|
for (const actionId of batchActionIds) {
|
|
for (const recordType of [
|
|
'agent.runtime.tool_action.executing',
|
|
'agent.runtime.tool_action.observed',
|
|
'agent.runtime.action_receipt',
|
|
'agent.runtime.tool_observation',
|
|
]) {
|
|
const matching = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === recordType &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId &&
|
|
record.actionId === actionId,
|
|
);
|
|
assert(
|
|
matching.length === 1,
|
|
`parallel-read-action-lifecycle-invalid:${recordType}`,
|
|
);
|
|
if (recordType === 'agent.runtime.tool_observation') {
|
|
assert(
|
|
matching[0].parallelBatchId === batch.batchId &&
|
|
matching[0].status === 'ok',
|
|
'parallel-read-observation-batch-identity-invalid',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
const projectedCalls = (runtimeState.recentToolCalls ?? []).filter((call) =>
|
|
batchActionIds.includes(call.actionId),
|
|
);
|
|
assert(
|
|
projectedCalls.length === 2 &&
|
|
JSON.stringify(projectedCalls.map((call) => call.actionId)) ===
|
|
JSON.stringify(batchActionIds) &&
|
|
projectedCalls.every(
|
|
(call) => call.tool === 'project.search' && call.status === 'ok',
|
|
),
|
|
'parallel-read-runtime-projection-order-invalid',
|
|
);
|
|
const projectedDetails = projectedCalls.map((call) => call.detail ?? '');
|
|
assert(
|
|
projectedDetails.filter((detail) => detail.includes(parallelReadAlphaQuery))
|
|
.length === 1 &&
|
|
projectedDetails.filter((detail) =>
|
|
detail.includes(parallelReadBetaQuery),
|
|
).length === 1,
|
|
'parallel-read-independent-search-observation-missing',
|
|
);
|
|
const receiptActionIds = receipts.map((record) => record.actionId);
|
|
const observationActionIds = agentDb
|
|
.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.tool_observation' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId &&
|
|
batchActionIds.includes(record.actionId),
|
|
)
|
|
.map((record) => record.actionId);
|
|
assert(
|
|
JSON.stringify(receiptActionIds) === JSON.stringify(batchActionIds) &&
|
|
JSON.stringify(observationActionIds) === JSON.stringify(batchActionIds),
|
|
'parallel-read-provider-order-projection-invalid',
|
|
);
|
|
const batchEvents = events.filter(
|
|
(event) =>
|
|
event.agentId === mainAgentId &&
|
|
event.runId === state.initialRunId &&
|
|
event.eventType === 'parallel_read_batch.completed',
|
|
);
|
|
assert(batchEvents.length === 1, 'parallel-read-completed-event-invalid');
|
|
|
|
const protocolEvidence =
|
|
validateNativeRuntimeToolPlanProtocolEvidence(agentDb);
|
|
const nativeProtocols = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.tool_plan.protocol' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId &&
|
|
record.protocol === 'native_runtime_tools',
|
|
);
|
|
assert(
|
|
nativeProtocols.some(
|
|
(record) =>
|
|
Number.isSafeInteger(record.functionCallCount) &&
|
|
record.functionCallCount >= 2,
|
|
),
|
|
'parallel-read-native-multi-call-plan-missing',
|
|
);
|
|
const providerLifecycle = validateParallelReadProviderLifecycle(agentDb);
|
|
assert(
|
|
providerLifecycle.duplicateCount === 0,
|
|
'parallel-read-duplicate-provider-lifecycle',
|
|
);
|
|
|
|
const userMessages = conversations.filter(
|
|
(message) => message.role === 'user',
|
|
);
|
|
const assistantMessages = conversations.filter(
|
|
(message) => message.role === 'assistant',
|
|
);
|
|
const completedAudits = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.completed' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
const backgroundCompletedAudits = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.background_task.completed' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
assert(
|
|
userMessages.length === 1 &&
|
|
assistantMessages.length === 1 &&
|
|
completedAudits.length === 1 &&
|
|
backgroundCompletedAudits.length === 1,
|
|
'parallel-read-conversation-completion-cardinality-invalid',
|
|
);
|
|
const duplicateMessageCount = duplicateCount(
|
|
conversations.map((message) => message.messageId).filter(Boolean),
|
|
);
|
|
const duplicateReceiptCount = duplicateCount(
|
|
receipts.map(receiptAuditIdentity),
|
|
);
|
|
const actionLifecycleRecords = agentDb.filter(
|
|
(record) =>
|
|
batchActionIds.includes(record.actionId) &&
|
|
[
|
|
'agent.runtime.tool_action.executing',
|
|
'agent.runtime.tool_action.observed',
|
|
'agent.runtime.action_receipt',
|
|
'agent.runtime.tool_observation',
|
|
].includes(record.recordType),
|
|
);
|
|
const duplicateActionLifecycleCount = duplicateCount(
|
|
actionLifecycleRecords.map(actionAuditIdentity),
|
|
);
|
|
assert(
|
|
duplicateMessageCount === 0 &&
|
|
duplicateReceiptCount === 0 &&
|
|
duplicateActionLifecycleCount === 0,
|
|
'parallel-read-duplicate-persistence-identity',
|
|
);
|
|
const finalizationFiles = (
|
|
await listFiles(
|
|
path.join(state.projectRoot, '.agent/runtime/finalizations'),
|
|
)
|
|
).filter((file) => file.endsWith('.json'));
|
|
const pendingBatchFiles = (
|
|
await listFiles(
|
|
path.join(state.projectRoot, '.agent/runtime/parallel-read-batches'),
|
|
)
|
|
).filter((file) => file.endsWith('.json'));
|
|
assert(
|
|
finalizationFiles.length === 0 && pendingBatchFiles.length === 0,
|
|
'parallel-read-terminal-sidecar-present',
|
|
);
|
|
|
|
const publicSurfaces = {
|
|
task: taskSnapshot.all,
|
|
event: events,
|
|
agentDb,
|
|
activity,
|
|
output,
|
|
runtimeState,
|
|
contextBundle,
|
|
};
|
|
const privateBodyPublicCounts = countSensitiveValuesBySurface(
|
|
{ task: taskSnapshot.all, event: events, agentDb, activity, output },
|
|
state.parallelRead.privateValues,
|
|
'parallel-read-private-body-public',
|
|
);
|
|
const apiKeyPublicCounts = countSensitiveValuesBySurface(
|
|
publicSurfaces,
|
|
state.secrets,
|
|
'parallel-read-api-key-public',
|
|
);
|
|
const projectPathPublicCounts = countSensitiveValuesBySurface(
|
|
publicSurfaces,
|
|
disposableProjectPathVariants(),
|
|
'parallel-read-project-path-public',
|
|
);
|
|
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: 'native-tool-plan-persistent-parallel-read-batch',
|
|
targetAgentId: mainAgentId,
|
|
providerModel: state.parallelRead.effectiveModel,
|
|
providerApiKind: state.parallelRead.effectiveApiKind,
|
|
isolatedAppDataUsed: true,
|
|
formalConfigCliCallCount: state.isolatedRunner.sourceConfigCliCallCount,
|
|
sourceRunnerEndpointUnchanged: false,
|
|
sourceConfigReplicaCount: state.isolatedRunner.configLinks.length,
|
|
sourceConfigReplicasVerified: false,
|
|
taskCount: taskSnapshot.all.length,
|
|
eventCount: events.length,
|
|
agentDbRecordCount: agentDb.length,
|
|
conversationMessageCount: conversations.length,
|
|
targetRunCount: new Set(
|
|
taskSnapshot.all
|
|
.filter((task) => task.agentId === mainAgentId)
|
|
.map((task) => task.runId),
|
|
).size,
|
|
stableSessionCount: new Set(
|
|
taskSnapshot.all
|
|
.filter((task) => task.agentId === mainAgentId)
|
|
.map((task) => task.sessionId),
|
|
).size,
|
|
corpusFileCount: parallelReadCorpusFileCount + 1,
|
|
successfulSearchCount: receipts.length,
|
|
parallelBatchCount: batchAudits.length,
|
|
parallelBatchActionCount: batch.actionCount,
|
|
parallelBatchOverlapNanos: batch.overlapNanos,
|
|
parallelBatchOverlapped: batch.overlapped,
|
|
providerOrderedProjection: true,
|
|
...protocolEvidence,
|
|
providerRequestIdentityCount: providerLifecycle.requestIdentityCount,
|
|
providerLifecycleStartedCount: providerLifecycle.startedCount,
|
|
providerLifecycleTerminalCount: providerLifecycle.terminalCount,
|
|
toolPlanProviderRequestCount: providerLifecycle.toolPlanCount,
|
|
finalReplyProviderRequestCount: providerLifecycle.finalReplyCount,
|
|
finalAssistantCount: assistantMessages.length,
|
|
completedAuditCount: completedAudits.length,
|
|
duplicateActionLifecycleCount,
|
|
duplicateReceiptCount,
|
|
duplicateProviderLifecycleCount: providerLifecycle.duplicateCount,
|
|
finalizationJournalCount: finalizationFiles.length,
|
|
privateBodyPublicLeakCount: sumObjectValues(privateBodyPublicCounts),
|
|
apiKeyPublicLeakCount: sumObjectValues(apiKeyPublicCounts),
|
|
projectPathPublicLeakCount: sumObjectValues(projectPathPublicCounts),
|
|
projectPathPublicSurfaceCount: Object.keys(projectPathPublicCounts).length,
|
|
parallelReadReportLeakCount: state.parallelRead.reportLeakCount,
|
|
parallelReadRunnerKillMethod: null,
|
|
parallelReadRunnerPidfdClaimCount: state.isolatedRunner.pidfdClaimCount,
|
|
parallelReadRunnerPidfdSignalCount: state.isolatedRunner.pidfdSignalCount,
|
|
parallelReadRunnerStopped: false,
|
|
parallelReadAppDataCleanupPerformed: false,
|
|
secretLeakCount,
|
|
lureLeakCount: state.lureLeakCount,
|
|
paths: [
|
|
'.agent/runtime/context-bundles',
|
|
'.agent/runtime/tasks',
|
|
'.agent/runtime/events',
|
|
'.agent/agent.db',
|
|
'.agent/conversations',
|
|
],
|
|
};
|
|
}
|
|
|
|
export async function collectPartialParallelReadEvidence() {
|
|
const persistence = await readScopedAgentsPersistence();
|
|
const { taskSnapshot, agentDb, conversations } = persistence;
|
|
const batches = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.parallel_read_batch.completed' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
const batchActionIds = batches.flatMap((record) => record.actionIds ?? []);
|
|
const receipts = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.action_receipt' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId &&
|
|
batchActionIds.includes(record.actionId),
|
|
);
|
|
const lifecycle = agentDb.filter(
|
|
(record) =>
|
|
record.recordType === 'agent.runtime.provider_request.lifecycle' &&
|
|
record.agentId === mainAgentId &&
|
|
record.runId === state.initialRunId,
|
|
);
|
|
const protocols = collectNativeRuntimeToolPlanProtocolEvidence(agentDb);
|
|
return {
|
|
providerModel: state.parallelRead.effectiveModel,
|
|
providerApiKind: state.parallelRead.effectiveApiKind,
|
|
taskCount: taskSnapshot.all.length,
|
|
eventCount: persistence.events.length,
|
|
agentDbRecordCount: agentDb.length,
|
|
conversationMessageCount: conversations.length,
|
|
successfulSearchCount: receipts.filter(
|
|
(record) => record.tool === 'project.search' && record.status === 'ok',
|
|
).length,
|
|
parallelBatchCount: batches.length,
|
|
parallelBatchActionCount: batches[0]?.actionCount ?? 0,
|
|
parallelBatchOverlapNanos: batches[0]?.overlapNanos ?? 0,
|
|
parallelBatchOverlapped: batches[0]?.overlapped === true,
|
|
...protocols,
|
|
providerRequestIdentityCount: new Set(
|
|
lifecycle.map((record) => record.requestId).filter(Boolean),
|
|
).size,
|
|
providerLifecycleStartedCount: lifecycle.filter(
|
|
(record) => record.status === 'started',
|
|
).length,
|
|
providerLifecycleTerminalCount: lifecycle.filter(
|
|
(record) => record.status === 'completed',
|
|
).length,
|
|
finalAssistantCount: conversations.filter(
|
|
(message) => message.role === 'assistant',
|
|
).length,
|
|
};
|
|
}
|
|
|
|
export function emptyParallelReadEvidence() {
|
|
return {
|
|
scenario: 'native-tool-plan-persistent-parallel-read-batch',
|
|
targetAgentId: mainAgentId,
|
|
providerModel: null,
|
|
providerApiKind: null,
|
|
isolatedAppDataUsed: false,
|
|
formalConfigCliCallCount: 0,
|
|
sourceRunnerEndpointUnchanged: false,
|
|
sourceConfigReplicaCount: 0,
|
|
sourceConfigReplicasVerified: false,
|
|
taskCount: 0,
|
|
eventCount: 0,
|
|
agentDbRecordCount: 0,
|
|
conversationMessageCount: 0,
|
|
targetRunCount: 0,
|
|
stableSessionCount: 0,
|
|
corpusFileCount: parallelReadCorpusFileCount + 1,
|
|
successfulSearchCount: 0,
|
|
parallelBatchCount: 0,
|
|
parallelBatchActionCount: 0,
|
|
parallelBatchOverlapNanos: 0,
|
|
parallelBatchOverlapped: false,
|
|
providerOrderedProjection: false,
|
|
toolPlanProtocolCount: 0,
|
|
nativeRuntimeToolPlanCount: 0,
|
|
toolPlanRepairCount: 0,
|
|
nativeRuntimeToolPlanRepairCount: 0,
|
|
toolPlanRepairedLoopCount: 0,
|
|
toolPlanSecondRepairCount: 0,
|
|
toolPlanRepairCountsByProtocolErrorKind:
|
|
emptyToolPlanRepairCountsByProtocolErrorKind(),
|
|
wrapperToolPlanFallbackCount: 0,
|
|
textJsonToolPlanFallbackCount: 0,
|
|
toolPlanAuditPayloadLeakCount: 0,
|
|
providerRequestIdentityCount: 0,
|
|
providerLifecycleStartedCount: 0,
|
|
providerLifecycleTerminalCount: 0,
|
|
toolPlanProviderRequestCount: 0,
|
|
finalReplyProviderRequestCount: 0,
|
|
finalAssistantCount: 0,
|
|
completedAuditCount: 0,
|
|
duplicateActionLifecycleCount: 0,
|
|
duplicateReceiptCount: 0,
|
|
duplicateProviderLifecycleCount: 0,
|
|
finalizationJournalCount: 0,
|
|
privateBodyPublicLeakCount: 0,
|
|
apiKeyPublicLeakCount: 0,
|
|
projectPathPublicLeakCount: 0,
|
|
projectPathPublicSurfaceCount: 0,
|
|
parallelReadReportLeakCount: 0,
|
|
parallelReadRunnerKillMethod: null,
|
|
parallelReadRunnerPidfdClaimCount: 0,
|
|
parallelReadRunnerPidfdSignalCount: 0,
|
|
parallelReadRunnerStopped: false,
|
|
parallelReadAppDataCleanupPerformed: false,
|
|
secretLeakCount: 0,
|
|
lureLeakCount: 0,
|
|
paths: [],
|
|
};
|
|
}
|
|
|
|
export function isParallelReadSuite() {
|
|
return state.suite === parallelReadSuite;
|
|
}
|