be89296492
拆分 App 认证、壳层、运行配置与项目摘要模块 拆分 Tauri 项目能力与 Rust 测试领域模块 拆分界面测试与 Agent Runtime 真实 E2E 套件 补充源码扫描和客户端模块化文档约定
389 lines
12 KiB
JavaScript
389 lines
12 KiB
JavaScript
import {
|
|
assert,
|
|
codedError,
|
|
hashValue,
|
|
recordError,
|
|
summarizeRecordedError,
|
|
} from '../assertions/core.mjs';
|
|
import { fs, os, path, TextDecoder } from '../dependencies.mjs';
|
|
import {
|
|
mainAgentId,
|
|
projectSupervisorAgentId,
|
|
sentinelFileName,
|
|
sentinelSchema,
|
|
state,
|
|
} from '../runtime-state.mjs';
|
|
import { isContextCompactionSuite } from '../suites/context-compaction.mjs';
|
|
import { isGoalRuntimeSuite } from '../suites/goal.mjs';
|
|
import { isMcpRuntimeSuite } from '../suites/mcp.mjs';
|
|
import { isParallelReadSuite } from '../suites/parallel-read.mjs';
|
|
import { isProjectSkillSuite } from '../suites/project-skill.mjs';
|
|
import { isResponseStreamSuite } from '../suites/response-stream.mjs';
|
|
import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs';
|
|
import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs';
|
|
import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs';
|
|
import { isSupervisorSwarmSuite } from '../suites/supervisor-swarm.mjs';
|
|
import { isUserInputRuntimeSuite } from '../suites/user-input.mjs';
|
|
import { isWebSearchSuite } from '../suites/web-search.mjs';
|
|
import { isPathInside, readJson } from './io.mjs';
|
|
|
|
export async function removeDisposableProject() {
|
|
const [realTemp, realProject] = await Promise.all([
|
|
fs.realpath(os.tmpdir()),
|
|
fs.realpath(state.projectRoot),
|
|
]);
|
|
if (!isPathInside(realTemp, realProject)) return false;
|
|
const sentinelPath = path.join(realProject, sentinelFileName);
|
|
const metadata = await fs.lstat(sentinelPath).catch(() => null);
|
|
if (!metadata?.isFile() || metadata.isSymbolicLink()) return false;
|
|
const sentinel = await readJson(sentinelPath).catch(() => null);
|
|
if (
|
|
sentinel?.schemaVersion !== sentinelSchema ||
|
|
sentinel?.token !== state.sentinelToken
|
|
) {
|
|
return false;
|
|
}
|
|
await fs.rm(realProject, { recursive: true, force: false });
|
|
return true;
|
|
}
|
|
|
|
export function buildSummary() {
|
|
const secretLeakCount =
|
|
state.transcriptLeakCount + state.projectLeakCount + state.reportLeakCount;
|
|
const base = {
|
|
status: state.status,
|
|
suite: state.suite,
|
|
config: state.config,
|
|
blocked: state.blocked,
|
|
run: {
|
|
agentId:
|
|
isUserInputRuntimeSuite() ||
|
|
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
|
isSupervisorSwarmSuite()
|
|
? projectSupervisorAgentId
|
|
: mainAgentId,
|
|
runIdHash: hashValue(state.initialRunId),
|
|
sessionIdHash: hashValue(state.initialSessionId),
|
|
runnerKilled: isSupervisorAutonomousPlayableLaneDefenseSuite()
|
|
? false
|
|
: state.runnerKilled,
|
|
resumed: state.resumed,
|
|
identityStable: state.identityStable,
|
|
},
|
|
evidence: {
|
|
...state.evidence,
|
|
secretLeakCount,
|
|
lureLeakCount: state.lureLeakCount,
|
|
projectPathTranscriptLeakCount: state.projectPathTranscriptLeakCount,
|
|
projectPathReportLeakCount: state.projectPathReportLeakCount,
|
|
...(isWebSearchSuite() ||
|
|
isContextCompactionSuite() ||
|
|
isMcpRuntimeSuite() ||
|
|
isUserInputRuntimeSuite() ||
|
|
isScopedAgentsSuite() ||
|
|
isProjectSkillSuite() ||
|
|
isParallelReadSuite() ||
|
|
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
|
isSupervisorSwarmSuite() ||
|
|
isSteerRunnerKillSuite()
|
|
? {
|
|
formalConfigPathTranscriptLeakCount:
|
|
state.formalConfigPathTranscriptLeakCount,
|
|
formalConfigPathReportLeakCount:
|
|
state.formalConfigPathReportLeakCount,
|
|
}
|
|
: {}),
|
|
},
|
|
cleanup: {
|
|
performed: state.cleanupPerformed,
|
|
kept: Boolean(state.options?.keepProject),
|
|
},
|
|
errorCount: state.errors.length,
|
|
errorHashes: state.errors.map(summarizeRecordedError),
|
|
};
|
|
base.summaryHash = hashValue(JSON.stringify(base));
|
|
return base;
|
|
}
|
|
|
|
export function emptyEvidence() {
|
|
return {
|
|
taskCount: 0,
|
|
eventCount: 0,
|
|
agentDbRecordCount: 0,
|
|
successfulToolExecutionCount: 0,
|
|
toolPlanProtocolCount: 0,
|
|
structuredPlanUpdateCount: 0,
|
|
structuredPlanRevision: 0,
|
|
structuredPlanCompletedStepCount: 0,
|
|
structuredPlanRegressionCount: 0,
|
|
structuredPlanPreKillRevision: 0,
|
|
structuredPlanPreKillCompletedStepCount: 0,
|
|
structuredPlanPreKillIncompleteStepCount: 0,
|
|
structuredPlanPreKillTerminalStepHash: null,
|
|
structuredPlanRecoveredRevision: 0,
|
|
structuredPlanRecoveredCompletedStepCount: 0,
|
|
structuredPlanRecoveredTerminalStepHash: null,
|
|
structuredPlanTimelineUpdateCount: 0,
|
|
structuredPlanTimelineAnchoredUpdateCount: 0,
|
|
structuredPlanCompletionTransitionCount: 0,
|
|
structuredPlanCompletionObservationCount: 0,
|
|
structuredPlanPrematureCompletionCount: 0,
|
|
structuredPlanTimelineHash: null,
|
|
steerAcceptedPlanRevision: 0,
|
|
steerSequence: 0,
|
|
steerIdHash: null,
|
|
steerMessageIdHash: null,
|
|
steerInstructionSha256: null,
|
|
steerProviderInterrupted: false,
|
|
steerProviderPlanningWaitMatched: false,
|
|
steerCompletedStepCountAtAcceptance: 0,
|
|
steerIncompleteStepCountAtAcceptance: 0,
|
|
steerFirstPostPlanRevision: 0,
|
|
steerFirstPostIncompleteStepCount: 0,
|
|
steerIncompletePlanReordered: false,
|
|
steerOldPendingActionCount: 0,
|
|
steerOldPendingActionSetHash: null,
|
|
steerOldPendingExecutionCount: 0,
|
|
steerOldPlanMaterializedActionCount: 0,
|
|
steerAcceptanceWindowExecutionCount: 0,
|
|
steerSideEffectReceiptCountAtAcceptance: 0,
|
|
steerSideEffectSnapshotHash: null,
|
|
steerPreSideEffectReplayCount: 0,
|
|
steerLedgerRecordCount: 0,
|
|
steerAppliedCount: 0,
|
|
steerClosedCount: 0,
|
|
steerAuditCount: 0,
|
|
steerTaskRunCountBefore: 0,
|
|
steerTaskRunCountAfter: 0,
|
|
steerTaskRunSetHash: null,
|
|
finalTargetMainRunCount: 0,
|
|
finalLegalMainLineageRunCount: 0,
|
|
finalUnexpectedMainRunCount: 0,
|
|
finalTargetMainRunSetHash: null,
|
|
steerPublicInstructionLeakCount: 0,
|
|
steerInstructionReportLeakCount: 0,
|
|
confirmedActionLifecycleCount: 0,
|
|
sideEffectActionCount: 0,
|
|
sideEffectReplayCount: 0,
|
|
idempotentReplayActionCount: 0,
|
|
actionReceiptReplayRecordCount: 0,
|
|
completedProjectionCount: 0,
|
|
finalAssistantAuditCount: 0,
|
|
projectRevision: 0,
|
|
projectIndexExecutionCount: 0,
|
|
gitInspectExecutionCount: 0,
|
|
gitInspectChangedFileCount: 0,
|
|
gitInspectRevisionNeutral: false,
|
|
gitInspectPostCommitSelectedPathsClean: false,
|
|
gitCommitExecutionCount: 0,
|
|
gitCommitPathCount: 0,
|
|
gitCommitAuditCount: 0,
|
|
gitCommitReceiptCount: 0,
|
|
gitCommitParentMatched: false,
|
|
gitCommitTreeMatched: false,
|
|
gitCommitReflogMatched: false,
|
|
gitCommitPostInspectSelectedPathsClean: false,
|
|
repositoryContextSourceCount: 0,
|
|
checkpointFileCount: 0,
|
|
patchsetExecutionCount: 0,
|
|
patchsetPreparedAuditCount: 0,
|
|
patchsetCompletedAuditCount: 0,
|
|
patchsetChangeCount: 0,
|
|
patchsetRevisionDelta: 0,
|
|
patchsetContentDiffFileCount: 0,
|
|
patchsetCheckpointBound: false,
|
|
patchsetExpectedSha256Matched: false,
|
|
halfCompletedFileCount: 0,
|
|
commandExecRunCount: 0,
|
|
commandExecFailedCount: 0,
|
|
commandExecSucceededCount: 0,
|
|
commandOutputReadExecutionCount: 0,
|
|
commandOutputPageCount: 0,
|
|
commandOutputMarkerSidecarCount: 0,
|
|
commandOutputMarkerContextCount: 0,
|
|
commandOutputMarkerTaskLeakCount: 0,
|
|
commandOutputMarkerEventLeakCount: 0,
|
|
commandOutputMarkerAgentDbLeakCount: 0,
|
|
commandOutputMarkerConversationLeakCount: 0,
|
|
commandOutputMarkerActivityLeakCount: 0,
|
|
commandOutputMarkerOutputLeakCount: 0,
|
|
commandOutputMarkerRuntimeStateLeakCount: 0,
|
|
commandOutputMarkerReceiptLeakCount: 0,
|
|
commandOutputReadReceiptCount: 0,
|
|
commandOutputMarkerReportLeakCount: 0,
|
|
editorApiAssetCount: 0,
|
|
verificationPassed: false,
|
|
browserValidationCount: 0,
|
|
imageInspectExecutionCount: 0,
|
|
imageInspectImageCount: 0,
|
|
imageInspectDedicatedAuditCount: 0,
|
|
imageInspectReceiptCount: 0,
|
|
imageInspectResponseIdPresent: false,
|
|
persistedImagePayloadLeakCount: 0,
|
|
isolatedInstanceCount: 0,
|
|
isolatedTemplateCount: 0,
|
|
isolatedJoinCount: 0,
|
|
isolatedJoinDeliveryTarget: null,
|
|
isolatedParentWakeDispatchCount: 0,
|
|
actionHistoryExecutionCount: 0,
|
|
actionHistoryResultCount: 0,
|
|
actionHistoryRecursiveResultCount: 0,
|
|
actionReceiptCount: 0,
|
|
mainRunActionReceiptCount: 0,
|
|
actionReceiptRequiredToolCount: 0,
|
|
actionReceiptDuplicateIdentityCount: 0,
|
|
actionReceiptSecretLeakCount: 0,
|
|
actionReceiptLureLeakCount: 0,
|
|
conversationMessageCount: 0,
|
|
targetSessionMessageCount: 0,
|
|
targetSessionUserMessageCount: 0,
|
|
targetSessionAssistantMessageCount: 0,
|
|
targetSessionConversationAuditCount: 0,
|
|
finalAssistantCount: 0,
|
|
duplicateActionCount: 0,
|
|
duplicateMessageCount: 0,
|
|
duplicateReceiptCount: 0,
|
|
confirmedActionCount: 0,
|
|
projectPathPublicLeakCount: 0,
|
|
projectPathPublicSurfaceCount: 0,
|
|
projectPathTranscriptLeakCount: 0,
|
|
projectPathReportLeakCount: 0,
|
|
secretLeakCount: 0,
|
|
lureLeakCount: 0,
|
|
paths: [],
|
|
};
|
|
}
|
|
|
|
export async function collectPartialRuntimeJsonlSurface(
|
|
suitePrefix,
|
|
surface,
|
|
resolveFiles,
|
|
) {
|
|
let files;
|
|
try {
|
|
files = await resolveFiles();
|
|
} catch {
|
|
const errors = ['surface-list-failed'];
|
|
recordError(
|
|
`${suitePrefix}-partial-${surface}-read-failed`,
|
|
codedError(errors[0]),
|
|
);
|
|
return { records: [], errors };
|
|
}
|
|
const result = await readJsonlFilesPreservingValidRecords(files);
|
|
if (result.errors.length > 0) {
|
|
recordError(
|
|
`${suitePrefix}-partial-${surface}-read-failed`,
|
|
codedError([...new Set(result.errors)].join(',')),
|
|
);
|
|
}
|
|
return {
|
|
records: result.records,
|
|
errors: [...new Set(result.errors)],
|
|
};
|
|
}
|
|
|
|
export async function readJsonlFilesPreservingValidRecords(files) {
|
|
const records = [];
|
|
const errors = [];
|
|
for (const file of files) {
|
|
const result = await readJsonlPreservingValidRecords(file);
|
|
records.push(...result.records);
|
|
errors.push(...result.errors);
|
|
}
|
|
return { records, errors };
|
|
}
|
|
|
|
export async function readJsonlPreservingValidRecords(file) {
|
|
let content;
|
|
try {
|
|
content = await fs.readFile(file);
|
|
} catch (error) {
|
|
return {
|
|
records: [],
|
|
errors: [
|
|
error?.code === 'ENOENT' ? 'file-disappeared' : 'file-read-failed',
|
|
],
|
|
};
|
|
}
|
|
const records = [];
|
|
const errors = [];
|
|
const lines = splitJsonlBufferLines(content);
|
|
for (const lineRecord of lines) {
|
|
let line;
|
|
try {
|
|
line = decodeUtf8Fatal(lineRecord.bytes, 'jsonl-invalid-utf8');
|
|
} catch {
|
|
errors.push('invalid-utf8');
|
|
continue;
|
|
}
|
|
if (line.trim().length === 0) continue;
|
|
try {
|
|
const record = JSON.parse(line);
|
|
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
errors.push('invalid-record');
|
|
continue;
|
|
}
|
|
records.push(record);
|
|
} catch {
|
|
errors.push(lineRecord.terminated ? 'invalid-record' : 'truncated-tail');
|
|
}
|
|
}
|
|
return { records, errors };
|
|
}
|
|
|
|
export function splitJsonlBufferLines(content) {
|
|
assert(Buffer.isBuffer(content), 'jsonl-content-not-buffer');
|
|
const lines = [];
|
|
let start = 0;
|
|
for (let index = 0; index < content.length; index += 1) {
|
|
if (content[index] !== 0x0a) continue;
|
|
let end = index;
|
|
if (end > start && content[end - 1] === 0x0d) end -= 1;
|
|
lines.push({ bytes: content.subarray(start, end), terminated: true });
|
|
start = index + 1;
|
|
}
|
|
if (start < content.length) {
|
|
lines.push({ bytes: content.subarray(start), terminated: false });
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
export function decodeUtf8Fatal(content, code = 'invalid-utf8') {
|
|
try {
|
|
return new TextDecoder('utf-8', { fatal: true }).decode(content);
|
|
} catch (error) {
|
|
throw codedError(code, error);
|
|
}
|
|
}
|
|
|
|
export async function readLatestJsonFile(files) {
|
|
const candidates = [];
|
|
for (const file of files.filter((entry) => entry.endsWith('.json'))) {
|
|
const metadata = await fs.stat(file).catch(() => null);
|
|
if (metadata?.isFile())
|
|
candidates.push({ file, mtimeMs: metadata.mtimeMs });
|
|
}
|
|
candidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
return candidates.length > 0
|
|
? readJson(candidates[0].file).catch(() => null)
|
|
: null;
|
|
}
|
|
|
|
export function isIsolatedRunnerSuite() {
|
|
return (
|
|
isSteerRunnerKillSuite() ||
|
|
isGoalRuntimeSuite() ||
|
|
isResponseStreamSuite() ||
|
|
isWebSearchSuite() ||
|
|
isContextCompactionSuite() ||
|
|
isMcpRuntimeSuite() ||
|
|
isUserInputRuntimeSuite() ||
|
|
isScopedAgentsSuite() ||
|
|
isProjectSkillSuite() ||
|
|
isParallelReadSuite() ||
|
|
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
|
isSupervisorSwarmSuite()
|
|
);
|
|
}
|