989 lines
46 KiB
JavaScript
989 lines
46 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { _electron as electron } from '@playwright/test';
|
|
import { execFile } from 'node:child_process';
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { createServer } from 'node:net';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { performance } from 'node:perf_hooks';
|
|
import { promisify } from 'node:util';
|
|
|
|
import { defaultProductExecutable } from './lib/pi-product-artifact.mjs';
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
function parseArgs(argv, projectRoot = process.cwd()) {
|
|
const options = {
|
|
projectRoot: resolve(projectRoot),
|
|
runtimeRoot: undefined,
|
|
electronExecutable: undefined,
|
|
reportPath: undefined,
|
|
verifyCleanupFailure: true,
|
|
};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const argument = argv[index];
|
|
if (argument === '--') continue;
|
|
const value = argv[index + 1];
|
|
if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value`);
|
|
if (argument === '--runtime-root') options.runtimeRoot = resolve(value);
|
|
else if (argument === '--electron-executable') options.electronExecutable = resolve(value);
|
|
else if (argument === '--report') options.reportPath = resolve(value);
|
|
else throw new Error(`Unknown argument: ${argument}`);
|
|
index += 1;
|
|
}
|
|
options.electronExecutable ??= defaultProductExecutable(options.projectRoot);
|
|
return options;
|
|
}
|
|
|
|
async function allocatePort() {
|
|
return await new Promise((resolvePort, reject) => {
|
|
const server = createServer();
|
|
server.once('error', reject);
|
|
server.listen(0, '127.0.0.1', () => {
|
|
const address = server.address();
|
|
if (!address || typeof address === 'string') {
|
|
server.close(() => reject(new Error('Failed to allocate PI proof Host API port')));
|
|
return;
|
|
}
|
|
server.close((error) => error ? reject(error) : resolvePort(address.port));
|
|
});
|
|
});
|
|
}
|
|
|
|
function assertPackagedMain(result) {
|
|
if (result?.packagedMain?.isPackaged !== true || result?.packagedMain?.appPathUsesAsar !== true) {
|
|
throw new Error(`PI proof did not execute from packaged app.asar Main: ${JSON.stringify(result?.packagedMain)}`);
|
|
}
|
|
}
|
|
|
|
function assertActivePressure(pressure) {
|
|
const expected = {
|
|
parentWorkers: 4,
|
|
childWorkers: 4,
|
|
parentProcesses: 1,
|
|
childProcesses: 4,
|
|
liveProcesses: 5,
|
|
parentProviderRequests: 4,
|
|
childProviderRequests: 4,
|
|
processBudget: 4,
|
|
childPermits: 4,
|
|
dispatches: 4,
|
|
writeLeases: 4,
|
|
};
|
|
const actual = {
|
|
parentWorkers: pressure?.parentWorkers,
|
|
childWorkers: pressure?.childWorkers,
|
|
parentProcesses: pressure?.parentProcessIds?.length,
|
|
childProcesses: pressure?.childProcessIds?.length,
|
|
liveProcesses: pressure?.liveProcessIds?.length,
|
|
parentProviderRequests: pressure?.providerRequests?.parent,
|
|
childProviderRequests: pressure?.providerRequests?.child,
|
|
processBudget: pressure?.processBudget?.active,
|
|
childPermits: pressure?.childPermits?.active,
|
|
dispatches: pressure?.dispatches?.active,
|
|
writeLeases: pressure?.writeLeases?.active,
|
|
};
|
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
throw new Error(`PI pressure did not reach 4 parent threads + 4 child process state: ${JSON.stringify(actual)}`);
|
|
}
|
|
}
|
|
|
|
function assertReleasedPressure(pressure) {
|
|
const counts = [
|
|
pressure?.parentWorkers,
|
|
pressure?.childWorkers,
|
|
pressure?.parentProcessIds?.length,
|
|
pressure?.childProcessIds?.length,
|
|
pressure?.liveProcessIds?.length,
|
|
pressure?.providerRequests?.parent,
|
|
pressure?.providerRequests?.child,
|
|
pressure?.processBudget?.active,
|
|
pressure?.processBudget?.waiting,
|
|
pressure?.childPermits?.active,
|
|
pressure?.childPermits?.waiting,
|
|
pressure?.dispatches?.active,
|
|
pressure?.dispatches?.parents,
|
|
pressure?.writeLeases?.active,
|
|
pressure?.writeLeases?.waiting,
|
|
];
|
|
if (counts.some((value) => value !== 0)) {
|
|
throw new Error(`PI pressure resources were not fully released: ${JSON.stringify(pressure)}`);
|
|
}
|
|
}
|
|
|
|
function assertManagedTurns(extension) {
|
|
const expectedMilestones = 'worker.queue_wait,resources.ready,worker.spawn,rpc.ready,session.open,prompt.accepted,agent.start,provider.first_event,agent.settled';
|
|
const expectedDelayMs = extension?.providerFirstEventDelayMs;
|
|
if (!Number.isFinite(expectedDelayMs) || expectedDelayMs < 1) {
|
|
throw new Error(`PI proof did not expose a controlled Provider first-event delay: ${expectedDelayMs}`);
|
|
}
|
|
for (const turn of extension?.managedTurns ?? []) {
|
|
if (turn.milestones.map(({ milestone }) => milestone).join(',') !== expectedMilestones) {
|
|
throw new Error(`PI managed turn timeline is incomplete: ${JSON.stringify(turn)}`);
|
|
}
|
|
const agentStart = turn.milestones.find(({ milestone }) => milestone === 'agent.start');
|
|
const providerFirstEvent = turn.milestones.find(({ milestone }) => milestone === 'provider.first_event');
|
|
if (agentStart?.source !== 'pi.agent_start'
|
|
|| providerFirstEvent?.source !== 'pi.assistant_message_start'
|
|
|| providerFirstEvent.at <= agentStart.at
|
|
|| providerFirstEvent.durationMs < Math.floor(expectedDelayMs * 0.75)) {
|
|
throw new Error(`PI Provider first-event milestone is not Provider-response-backed: ${JSON.stringify(turn)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertProxyStatus(status) {
|
|
if (status?.currentHostTokenAvailable !== true
|
|
|| status?.realTurnVerified !== false
|
|
|| status?.activeProviderRequests?.child !== 1
|
|
|| status?.providerRequests?.parent < 1
|
|
|| status?.providerRequests?.child !== 1
|
|
|| status?.processes?.supported !== true
|
|
|| status?.processes?.parent?.length !== 1
|
|
|| status?.processes?.child?.length !== 1
|
|
|| status?.providerCompatibility?.roleContract?.developerRejected !== true
|
|
|| status?.providerCompatibility?.roleContract?.systemAccepted !== true
|
|
|| status?.providerCompatibility?.developerRoleRequests !== 0
|
|
|| status?.providerCompatibility?.systemRoleRequests < 2
|
|
|| status?.providerCompatibility?.controlledDefiniteRejections !== 1
|
|
|| Object.values(status?.tokenSafety ?? {}).some((safe) => safe !== true)) {
|
|
throw new Error(`Packaged Main proxy status is incomplete: ${JSON.stringify(status)}`);
|
|
}
|
|
}
|
|
|
|
function assertProxyProof(proof) {
|
|
if (proof?.providerMode !== 'loopback-through-authenticated-host-proxy'
|
|
|| proof?.realTurnVerified !== false
|
|
|| proof?.firstConversation?.count !== 1
|
|
|| proof?.firstConversation?.bindingEstablished !== true
|
|
|| proof?.firstConversation?.workerStatus !== 'ready'
|
|
|| proof?.firstConversation?.runStatus !== 'idle'
|
|
|| proof?.firstConversation?.userInputProjected !== true
|
|
|| proof?.firstConversation?.parentResponseProjected !== true
|
|
|| proof?.firstConversation?.definiteRejectionObserved !== true
|
|
|| proof?.firstConversation?.retryAccepted !== true
|
|
|| proof?.fork?.conversationCount !== 2
|
|
|| proof?.fork?.sameAgent !== true
|
|
|| proof?.fork?.targetBindingEstablished !== true
|
|
|| proof?.fork?.distinctSessionBinding !== true
|
|
|| proof?.fork?.workerStatus !== 'ready'
|
|
|| proof?.fork?.runStatus !== 'idle'
|
|
|| proof?.fork?.sourceEntryRole !== 'user'
|
|
|| proof?.fork?.targetHydratedBeforeSourceEntry !== true
|
|
|| proof?.fork?.sourceUnchanged !== true
|
|
|| proof?.fork?.promptReplayObserved !== false
|
|
|| proof?.providerCompatibility?.developerRoleRequests !== 0
|
|
|| proof?.providerCompatibility?.controlledDefiniteRejections !== 1
|
|
|| proof?.currentHostTokenUsedBy?.join(',') !== 'parent,child'
|
|
|| proof?.released?.workers !== 0
|
|
|| Object.values(proof?.tokenSafety ?? {}).some((safe) => safe !== true)) {
|
|
throw new Error(`Packaged Main proxy proof failed: ${JSON.stringify(proof)}`);
|
|
}
|
|
}
|
|
|
|
async function waitForProxyProof(electronApplication, predicate, message) {
|
|
const deadline = Date.now() + 30_000;
|
|
let latest;
|
|
while (Date.now() < deadline) {
|
|
latest = await evaluateProof(electronApplication, 'proxy.status');
|
|
if (predicate(latest?.proxy)) return latest;
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 200));
|
|
}
|
|
throw new Error(`${message}: ${JSON.stringify(latest?.proxy)}`);
|
|
}
|
|
|
|
async function waitForActiveProxyProof(electronApplication) {
|
|
return await waitForProxyProof(
|
|
electronApplication,
|
|
(status) => status?.activeProviderRequests?.child === 1
|
|
&& status?.processes?.parent?.length === 1
|
|
&& status?.processes?.child?.length === 1,
|
|
'Packaged Main proxy child did not become active',
|
|
);
|
|
}
|
|
|
|
async function waitForResilienceProof(electronApplication, predicate, message) {
|
|
const deadline = Date.now() + 30_000;
|
|
let latest;
|
|
while (Date.now() < deadline) {
|
|
latest = await evaluateProof(electronApplication, 'resilience.status');
|
|
if (predicate(latest?.resilience)) return latest;
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
}
|
|
throw new Error(`${message}: ${JSON.stringify(latest?.resilience)}`);
|
|
}
|
|
|
|
async function waitForResilienceIdleProof(electronApplication, predicate, message) {
|
|
const deadline = Date.now() + 30_000;
|
|
let latest;
|
|
while (Date.now() < deadline) {
|
|
latest = await evaluateProof(electronApplication, 'resilience.idle-status');
|
|
if (predicate(latest?.resilience)) return latest;
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
}
|
|
throw new Error(`${message}: ${JSON.stringify(latest?.resilience)}`);
|
|
}
|
|
|
|
async function setMainWindowVisible(electronApplication, visible) {
|
|
await electronApplication.evaluate(({ BrowserWindow }, shouldShow) => {
|
|
const window = BrowserWindow.getAllWindows()[0];
|
|
if (!window) throw new Error('Packaged Main window is unavailable');
|
|
if (shouldShow) window.show();
|
|
else window.hide();
|
|
}, visible);
|
|
}
|
|
|
|
function assertResilienceTerminal(
|
|
status,
|
|
expectedRunStatus = 'error',
|
|
expectedErrorCode = 'CODING_RUNTIME_START_FAILED',
|
|
) {
|
|
if (status?.target?.workerStatus !== 'error'
|
|
|| status?.target?.runStatus !== expectedRunStatus
|
|
|| status?.target?.errorCode !== expectedErrorCode
|
|
|| status?.target?.recoverable !== true
|
|
|| status?.target?.bindingPreserved !== true
|
|
|| status?.other?.bindingPreserved !== true) {
|
|
throw new Error(`Packaged resilience target did not converge: ${JSON.stringify(status)}`);
|
|
}
|
|
}
|
|
|
|
async function recoverSelectedConversation(page, electronApplication) {
|
|
await page.getByRole('button', { name: '恢复' }).first().click({ timeout: 10_000 });
|
|
const recovered = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.workerStatus === 'ready' && status?.target?.runStatus === 'idle',
|
|
'Packaged resilience target did not recover',
|
|
);
|
|
const composer = page.getByRole('textbox');
|
|
await composer.waitFor({ state: 'visible', timeout: 10_000 });
|
|
if (!await composer.isEnabled()) throw new Error('Recovered resilience composer is disabled');
|
|
return recovered;
|
|
}
|
|
|
|
async function evaluateProof(electronApplication, action) {
|
|
return await electronApplication.evaluate(async (_electron, requestedAction) => {
|
|
const proof = globalThis.__niancodeRunPiReleaseProofE2E;
|
|
if (typeof proof !== 'function') throw new Error('Packaged Main has no PI release proof entry');
|
|
return await proof(requestedAction);
|
|
}, action);
|
|
}
|
|
|
|
async function closeApplication(electronApplication) {
|
|
await Promise.race([
|
|
electronApplication.close().catch(() => undefined),
|
|
new Promise((resolveTimeout) => setTimeout(resolveTimeout, 5_000)),
|
|
]);
|
|
}
|
|
|
|
async function windowsDescendantProcessIds(rootPid) {
|
|
if (process.platform !== 'win32') return [];
|
|
const script = 'Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId | ConvertTo-Json -Compress';
|
|
const { stdout } = await execFileAsync(
|
|
'powershell.exe',
|
|
['-NoProfile', '-NonInteractive', '-Command', script],
|
|
{ windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
|
|
);
|
|
const parsed = stdout.trim() ? JSON.parse(stdout) : [];
|
|
const processes = (Array.isArray(parsed) ? parsed : [parsed]).flatMap((entry) => {
|
|
const pid = Number(entry?.ProcessId);
|
|
const parentPid = Number(entry?.ParentProcessId);
|
|
return Number.isSafeInteger(pid) && Number.isSafeInteger(parentPid) ? [{ pid, parentPid }] : [];
|
|
});
|
|
const descendants = new Set();
|
|
const frontier = [rootPid];
|
|
while (frontier.length > 0) {
|
|
const parentPid = frontier.shift();
|
|
for (const processEntry of processes) {
|
|
if (processEntry.parentPid !== parentPid || descendants.has(processEntry.pid)) continue;
|
|
descendants.add(processEntry.pid);
|
|
frontier.push(processEntry.pid);
|
|
}
|
|
}
|
|
return [...descendants];
|
|
}
|
|
|
|
function processIsAlive(pid) {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function runPackagedProductProof(options) {
|
|
const scratchRoot = await mkdtemp(join(tmpdir(), 'makelore-pi-final-product-proof-'));
|
|
const homeDir = join(scratchRoot, 'home');
|
|
const userDataDir = join(scratchRoot, 'user-data');
|
|
await Promise.all([
|
|
mkdir(join(homeDir, '.config'), { recursive: true }),
|
|
mkdir(join(homeDir, 'AppData', 'Local'), { recursive: true }),
|
|
mkdir(join(homeDir, 'AppData', 'Roaming'), { recursive: true }),
|
|
mkdir(userDataDir, { recursive: true }),
|
|
]);
|
|
const hostApiPort = await allocatePort();
|
|
let electronApplication;
|
|
let pressureActive = false;
|
|
let proxyActive = false;
|
|
let resilienceActive = false;
|
|
try {
|
|
electronApplication = await electron.launch({
|
|
executablePath: options.electronExecutable,
|
|
env: {
|
|
...process.env,
|
|
HOME: homeDir,
|
|
USERPROFILE: homeDir,
|
|
APPDATA: join(homeDir, 'AppData', 'Roaming'),
|
|
LOCALAPPDATA: join(homeDir, 'AppData', 'Local'),
|
|
XDG_CONFIG_HOME: join(homeDir, '.config'),
|
|
NIANCODE_E2E: '1',
|
|
NIANCODE_E2E_SKIP_SETUP: '1',
|
|
NIANCODE_USER_DATA_DIR: userDataDir,
|
|
NIANCODE_PORT_NIANCODE_HOST_API: String(hostApiPort),
|
|
...(process.platform === 'linux' ? { ELECTRON_DISABLE_SANDBOX: '1' } : {}),
|
|
},
|
|
timeout: 90_000,
|
|
});
|
|
const page = await electronApplication.firstWindow();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
const extension = await evaluateProof(electronApplication, 'extension');
|
|
assertPackagedMain(extension);
|
|
assertManagedTurns(extension.extension);
|
|
if (extension.extension?.subagentStatus !== 'complete'
|
|
|| extension.extension?.childToolNames?.join(',') !== 'find,grep,ls,read'
|
|
|| !extension.extension?.parentToolNames?.includes('subagent')
|
|
|| extension.extension?.parentProcessIds?.length !== 1
|
|
|| extension.extension?.childProcessIds?.length !== 1
|
|
|| extension.extension?.providerRequests?.child !== 1
|
|
|| extension.extension?.managedTurns?.length !== 2) {
|
|
throw new Error(`Final ASAR extension/subagent proof failed: ${JSON.stringify(extension.extension)}`);
|
|
}
|
|
|
|
const pressureStart = await evaluateProof(electronApplication, 'pressure.start');
|
|
pressureActive = true;
|
|
assertPackagedMain(pressureStart);
|
|
assertActivePressure(pressureStart.pressure);
|
|
|
|
const uiStartedAt = performance.now();
|
|
await page.getByTestId('ai-module-option-programming').click();
|
|
await page.getByTestId('main-layout').waitFor({ state: 'visible', timeout: 10_000 });
|
|
const uiInteractiveMs = Math.round(performance.now() - uiStartedAt);
|
|
|
|
const pressureFinish = await evaluateProof(electronApplication, 'pressure.finish');
|
|
pressureActive = false;
|
|
assertPackagedMain(pressureFinish);
|
|
assertReleasedPressure(pressureFinish.pressure);
|
|
|
|
let failureCleanup;
|
|
if (options.verifyCleanupFailure === true) {
|
|
const failureStart = await evaluateProof(electronApplication, 'pressure.start');
|
|
pressureActive = true;
|
|
assertPackagedMain(failureStart);
|
|
assertActivePressure(failureStart.pressure);
|
|
let injectedFailure;
|
|
try {
|
|
await evaluateProof(electronApplication, 'pressure.finish.inject-failure');
|
|
} catch (error) {
|
|
injectedFailure = error;
|
|
}
|
|
if (!injectedFailure
|
|
|| !String(injectedFailure).includes('PI release pressure cleanup failed: parents.settle')) {
|
|
throw new Error(`PI pressure cleanup failure injection did not fail as expected: ${String(injectedFailure)}`);
|
|
}
|
|
const retryFinish = await evaluateProof(electronApplication, 'pressure.finish');
|
|
pressureActive = false;
|
|
assertPackagedMain(retryFinish);
|
|
assertReleasedPressure(retryFinish.pressure);
|
|
failureCleanup = {
|
|
injectedAt: 'parents.settle',
|
|
firstFinish: 'failed-as-injected',
|
|
retry: 'pass',
|
|
released: retryFinish.pressure,
|
|
};
|
|
}
|
|
|
|
const proxyStart = await evaluateProof(electronApplication, 'proxy.start');
|
|
proxyActive = true;
|
|
assertPackagedMain(proxyStart);
|
|
if (proxyStart?.proxy?.providerMode !== 'works_square_ai_gateway_proxy'
|
|
|| proxyStart?.proxy?.currentHostTokenAvailable !== true
|
|
|| proxyStart?.proxy?.realTurnVerified !== false) {
|
|
throw new Error(`Packaged Main proxy setup failed: ${JSON.stringify(proxyStart?.proxy)}`);
|
|
}
|
|
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
const moduleSelection = page.getByTestId('ai-module-selection-page');
|
|
if (await moduleSelection.count()) {
|
|
await page.getByTestId('ai-module-option-programming').click();
|
|
await page.getByTestId('main-layout').waitFor({ state: 'visible', timeout: 10_000 });
|
|
}
|
|
await page.evaluate(() => { window.location.hash = '/chat'; });
|
|
const composer = page.getByRole('textbox');
|
|
await composer.waitFor({ state: 'visible', timeout: 30_000 });
|
|
const rejectedPrompt = 'Exercise the packaged Main proxy rejection';
|
|
const retryPrompt = 'Retry the packaged Main proxy Conversation';
|
|
await composer.fill(rejectedPrompt);
|
|
if (!await composer.isEnabled()) throw new Error('Packaged proxy first Conversation composer is disabled');
|
|
const beforeSubmitText = await page.locator('body').innerText();
|
|
if (beforeSubmitText.includes('本地编程运行时暂时不可用')
|
|
|| beforeSubmitText.includes('正在重新连接本地 Agent')) {
|
|
throw new Error(`Packaged proxy first Conversation exposed a runtime banner: ${beforeSubmitText}`);
|
|
}
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
|
|
const rejectedProxy = await waitForProxyProof(
|
|
electronApplication,
|
|
(status) => status?.providerCompatibility?.controlledDefiniteRejections === 1,
|
|
'Packaged Main did not observe the controlled definite rejection',
|
|
);
|
|
const draftDeadline = Date.now() + 10_000;
|
|
while (Date.now() < draftDeadline && await composer.inputValue() !== rejectedPrompt) {
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
}
|
|
const draftRestored = await composer.inputValue() === rejectedPrompt;
|
|
const rejectionText = await page.locator('body').innerText();
|
|
const submissionErrorSafe = !rejectionText.includes('controlled definite model rejection')
|
|
&& !rejectionText.includes('developer role is unsupported')
|
|
&& !rejectionText.includes('X-Works-Square-AI-Token')
|
|
&& !rejectionText.includes('Authorization');
|
|
if (!draftRestored || !submissionErrorSafe) {
|
|
throw new Error(
|
|
`Packaged definite rejection did not restore a safe draft: ${JSON.stringify({ draftRestored, submissionErrorSafe })}`,
|
|
);
|
|
}
|
|
if (await page.getByRole('button', { name: '重试' }).count()) {
|
|
throw new Error('Packaged definite submission rejection was exposed as a runtime recovery error');
|
|
}
|
|
await composer.fill(retryPrompt);
|
|
const sendButton = page.getByRole('button', { name: '发送' });
|
|
const retryDeadline = Date.now() + 10_000;
|
|
while (Date.now() < retryDeadline && !await sendButton.isEnabled()) {
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
}
|
|
const retryAfterEditEnabled = await sendButton.isEnabled();
|
|
if (!retryAfterEditEnabled) {
|
|
const bodyText = await page.locator('body').innerText();
|
|
throw new Error(`Packaged proxy retry stayed disabled after editing: ${bodyText}`);
|
|
}
|
|
await sendButton.click({ timeout: 30_000 });
|
|
|
|
let proxyStatus;
|
|
try {
|
|
proxyStatus = await waitForActiveProxyProof(electronApplication);
|
|
} catch (error) {
|
|
const bodyText = await page.locator('body').innerText().catch(() => '<unavailable>');
|
|
throw new Error(`${error instanceof Error ? error.message : String(error)}; UI=${bodyText}`);
|
|
}
|
|
assertPackagedMain(proxyStatus);
|
|
assertProxyStatus(proxyStatus.proxy);
|
|
await evaluateProof(electronApplication, 'proxy.release-child');
|
|
await page.getByText('REAL_PARENT_COMPLETE').waitFor({ state: 'visible', timeout: 30_000 });
|
|
const afterTurnText = await page.locator('body').innerText();
|
|
if (afterTurnText.includes('本地编程运行时暂时不可用')
|
|
|| afterTurnText.includes('正在重新连接本地 Agent')) {
|
|
throw new Error(`Packaged proxy Conversation did not leave recovery cleanly: ${afterTurnText}`);
|
|
}
|
|
if (!await composer.isEnabled()) throw new Error('Packaged proxy composer is not editable after the turn');
|
|
|
|
const settledProxy = await evaluateProof(electronApplication, 'proxy.status');
|
|
if (settledProxy?.proxy?.conversationCount !== 1
|
|
|| settledProxy?.proxy?.activeProviderRequests?.parent !== 0
|
|
|| settledProxy?.proxy?.activeProviderRequests?.child !== 0) {
|
|
throw new Error(`Packaged proxy source did not settle before fork: ${JSON.stringify(settledProxy?.proxy)}`);
|
|
}
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
if (await page.getByTestId('ai-module-selection-page').count()) {
|
|
await page.getByTestId('ai-module-option-programming').click();
|
|
await page.getByTestId('main-layout').waitFor({ state: 'visible', timeout: 10_000 });
|
|
}
|
|
await page.evaluate(() => { window.location.hash = '/chat'; });
|
|
await page.getByText('REAL_PARENT_COMPLETE').waitFor({ state: 'visible', timeout: 30_000 });
|
|
const timeline = page.getByTestId('coding-conversation-timeline');
|
|
const forkActions = timeline.getByRole('button', { name: '从这里创建新对话分支' });
|
|
await forkActions.waitFor({ state: 'visible', timeout: 30_000 });
|
|
if (await forkActions.count() !== 1) {
|
|
throw new Error(`Packaged proxy timeline exposed ${await forkActions.count()} fork actions for one user and one assistant entry`);
|
|
}
|
|
const sourceHeader = await page.getByTestId('coding-conversation-header').innerText();
|
|
await forkActions.click({ timeout: 30_000 });
|
|
await page.waitForFunction((previous) => {
|
|
const header = document.querySelector('[data-testid="coding-conversation-header"]');
|
|
return Boolean(header?.textContent?.includes('(fork)') && header.textContent !== previous);
|
|
}, sourceHeader, { timeout: 30_000 });
|
|
const forkedHeader = await page.getByTestId('coding-conversation-header').innerText();
|
|
const forkedTitle = forkedHeader.split('\n', 1)[0]?.trim();
|
|
if (!forkedTitle?.includes('(fork)')) {
|
|
throw new Error(`Packaged proxy fork target was not selected: ${forkedHeader}`);
|
|
}
|
|
const agentConversations = page.getByRole('group', { name: 'Packaged proxy proof agent 的对话' });
|
|
await agentConversations.getByRole('button', { name: forkedTitle, exact: true })
|
|
.waitFor({ state: 'visible', timeout: 30_000 });
|
|
const forkedBody = await page.locator('body').innerText();
|
|
if (forkedBody.includes('本地编程运行时暂时不可用')
|
|
|| forkedBody.includes('正在重新连接本地 Agent')) {
|
|
throw new Error(`Packaged user-entry fork exposed a runtime banner: ${forkedBody}`);
|
|
}
|
|
if (await timeline.getByText(retryPrompt, { exact: true }).count() !== 0
|
|
|| await timeline.getByText('REAL_PARENT_COMPLETE', { exact: true }).count() !== 0
|
|
|| await timeline.getByRole('button', { name: '从这里创建新对话分支' }).count() !== 0) {
|
|
throw new Error('Packaged fork target did not hydrate at the path before the selected user entry');
|
|
}
|
|
|
|
const proxyFinish = await evaluateProof(electronApplication, 'proxy.finish');
|
|
proxyActive = false;
|
|
assertPackagedMain(proxyFinish);
|
|
assertProxyProof(proxyFinish.proxy);
|
|
|
|
const resilienceStart = await evaluateProof(electronApplication, 'resilience.start');
|
|
resilienceActive = true;
|
|
assertPackagedMain(resilienceStart);
|
|
if (resilienceStart?.resilience?.bindingsEstablished !== true
|
|
|| resilienceStart?.resilience?.otherRunAccepted !== true
|
|
|| resilienceStart?.resilience?.realTurnVerified !== false) {
|
|
throw new Error(`Packaged resilience setup failed: ${JSON.stringify(resilienceStart?.resilience)}`);
|
|
}
|
|
const promptDelay = await evaluateProof(electronApplication, 'resilience.arm-prompt-delay');
|
|
assertPackagedMain(promptDelay);
|
|
if (!(promptDelay?.resilience?.delayMs > 10_000)) {
|
|
throw new Error(`Packaged prompt proof did not exceed the former 10s threshold: ${JSON.stringify(promptDelay?.resilience)}`);
|
|
}
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
if (await page.getByTestId('ai-module-selection-page').count()) {
|
|
await page.getByTestId('ai-module-option-programming').click();
|
|
await page.getByTestId('main-layout').waitFor({ state: 'visible', timeout: 10_000 });
|
|
}
|
|
await page.evaluate(() => { window.location.hash = '/chat'; });
|
|
const resilienceTarget = page.locator('aside').getByRole('button', {
|
|
name: 'Resilience fault target',
|
|
exact: true,
|
|
});
|
|
await resilienceTarget.waitFor({ state: 'visible', timeout: 30_000 });
|
|
await resilienceTarget.click();
|
|
const resilienceComposer = page.getByRole('textbox');
|
|
await resilienceComposer.waitFor({ state: 'visible', timeout: 30_000 });
|
|
await resilienceComposer.fill('BACKGROUND_ACTIVE');
|
|
try {
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
} catch (error) {
|
|
const [bodyText, status] = await Promise.all([
|
|
page.locator('body').innerText().catch(() => '<unavailable>'),
|
|
evaluateProof(electronApplication, 'resilience.status').catch(() => null),
|
|
]);
|
|
throw new Error(
|
|
`${error instanceof Error ? error.message : String(error)}; `
|
|
+ `UI=${bodyText}; resilience=${JSON.stringify(status?.resilience ?? status)}`,
|
|
);
|
|
}
|
|
|
|
const backgroundActive = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'running'
|
|
&& status?.target?.errorCode === 'CODING_REQUEST_UNCERTAIN'
|
|
&& status?.other?.runStatus === 'running'
|
|
&& status?.activeProviderRequests?.parent === 2
|
|
&& status?.resources?.backgroundLeases?.active === 2
|
|
&& status?.processes?.parent?.length === 2,
|
|
'Packaged background lease proof did not reach two active Main-owned runs',
|
|
);
|
|
const uncertaintyMessage = page.getByText(/请求确认延迟,可能仍在执行/).last();
|
|
await uncertaintyMessage.waitFor({ state: 'visible', timeout: 15_000 });
|
|
const compactButton = page.getByRole('button', { name: '整理上下文' });
|
|
if (await compactButton.isEnabled()) {
|
|
throw new Error('Packaged UI allowed compact to overlap a confirmation-uncertain prompt');
|
|
}
|
|
if (await page.getByText(/本地编程运行时暂时不可用/).count()) {
|
|
throw new Error('Confirmation uncertainty was presented as a local runtime outage');
|
|
}
|
|
await setMainWindowVisible(electronApplication, false);
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 600));
|
|
const backgroundHidden = await evaluateProof(electronApplication, 'resilience.status');
|
|
if (backgroundHidden?.resilience?.target?.runStatus !== 'running'
|
|
|| backgroundHidden?.resilience?.other?.runStatus !== 'running'
|
|
|| backgroundHidden?.resilience?.activeProviderRequests?.parent !== 2
|
|
|| backgroundHidden?.resilience?.resources?.backgroundLeases?.active !== 2
|
|
|| backgroundHidden?.resilience?.processes?.parent?.length !== 2) {
|
|
throw new Error(`Hidden packaged Pi run was stopped despite active leases: ${JSON.stringify(backgroundHidden?.resilience)}`);
|
|
}
|
|
await evaluateProof(electronApplication, 'resilience.release-parents');
|
|
await setMainWindowVisible(electronApplication, true);
|
|
await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'idle'
|
|
&& status?.other?.runStatus === 'idle'
|
|
&& status?.resources?.backgroundLeases?.active === 0,
|
|
'Hidden packaged Pi runs did not settle and release their background leases',
|
|
);
|
|
await page.getByText('RESILIENCE_ACTIVE').last().waitFor({ state: 'visible', timeout: 30_000 });
|
|
if (!await resilienceComposer.isEnabled()) {
|
|
throw new Error('Packaged composer stayed disabled after the hidden run settled');
|
|
}
|
|
await uncertaintyMessage.waitFor({ state: 'hidden', timeout: 10_000 });
|
|
|
|
const compactDelay = await evaluateProof(electronApplication, 'resilience.arm-compact-delay');
|
|
assertPackagedMain(compactDelay);
|
|
if (!(compactDelay?.resilience?.delayMs > 10_000)) {
|
|
throw new Error(`Packaged compact proof did not exceed the former 10s threshold: ${JSON.stringify(compactDelay?.resilience)}`);
|
|
}
|
|
await compactButton.click();
|
|
await uncertaintyMessage.waitFor({ state: 'visible', timeout: 15_000 });
|
|
const compactUncertain = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'compacting'
|
|
&& status?.target?.errorCode === 'CODING_REQUEST_UNCERTAIN'
|
|
&& status?.target?.contextCompaction === 'running'
|
|
&& status?.activeProviderRequests?.parent === 1
|
|
&& status?.resources?.backgroundLeases?.active === 1,
|
|
'Packaged manual compact did not retain ownership after the 10s confirmation threshold',
|
|
);
|
|
if (await compactButton.isEnabled()) {
|
|
throw new Error('Packaged UI enabled another compact while the first compact was uncertain');
|
|
}
|
|
if (await page.getByText(/本地编程运行时暂时不可用/).count()) {
|
|
throw new Error('Packaged manual compact uncertainty was presented as a local runtime outage');
|
|
}
|
|
const compactSettled = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'idle'
|
|
&& status?.target?.errorCode === null
|
|
&& status?.target?.contextCompaction === 'idle'
|
|
&& status?.target?.completedCompactions >= 1
|
|
&& status?.activeProviderRequests?.parent === 0
|
|
&& status?.resources?.backgroundLeases?.active === 0,
|
|
'Packaged manual compact did not settle and release its Main-owned lease',
|
|
);
|
|
await uncertaintyMessage.waitFor({ state: 'hidden', timeout: 10_000 });
|
|
if (!await resilienceComposer.isEnabled()) {
|
|
throw new Error('Packaged composer stayed disabled after manual compact settled');
|
|
}
|
|
await setMainWindowVisible(electronApplication, false);
|
|
const backgroundIdle = await waitForResilienceIdleProof(
|
|
electronApplication,
|
|
(status) => status?.workers?.length === 0
|
|
&& status?.resources?.backgroundLeases?.active === 0
|
|
&& status?.resources?.pool?.processBudget?.active === 0
|
|
&& status?.processes?.parent?.length === 0
|
|
&& status?.processes?.child?.length === 0
|
|
&& status?.backgroundSleepReasoned === true,
|
|
'Settled hidden packaged Pi workers were not reasoned background-sleep evicted',
|
|
);
|
|
await setMainWindowVisible(electronApplication, true);
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
if (await page.getByTestId('ai-module-selection-page').count()) {
|
|
await page.getByTestId('ai-module-option-programming').click();
|
|
await page.getByTestId('main-layout').waitFor({ state: 'visible', timeout: 10_000 });
|
|
}
|
|
await page.evaluate(() => { window.location.hash = '/chat'; });
|
|
await resilienceTarget.waitFor({ state: 'visible', timeout: 30_000 });
|
|
await resilienceTarget.click();
|
|
await resilienceComposer.waitFor({ state: 'visible', timeout: 30_000 });
|
|
|
|
const restartedOther = await evaluateProof(electronApplication, 'resilience.restart-other');
|
|
assertPackagedMain(restartedOther);
|
|
if (restartedOther?.resilience?.other?.runStatus !== 'running'
|
|
|| restartedOther?.resilience?.resources?.backgroundLeases?.active !== 1) {
|
|
throw new Error(`Packaged resilience sibling did not restart: ${JSON.stringify(restartedOther?.resilience)}`);
|
|
}
|
|
await resilienceComposer.fill('RESILIENCE_TARGET_ACTIVE');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
const intentionalActive = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'running'
|
|
&& status?.other?.runStatus === 'running'
|
|
&& status?.resources?.subagents?.activeChildPermits === 1
|
|
&& status?.resources?.subagents?.activeDispatches === 1
|
|
&& status?.resources?.extension?.registrations?.child === 1
|
|
&& status?.resources?.extension?.writeLeases?.active === 1
|
|
&& status?.resources?.backgroundLeases?.active === 2
|
|
&& status?.processes?.parent?.length === 2
|
|
&& status?.processes?.child?.length === 1,
|
|
'Packaged intentional-dispose parent/child state did not become active',
|
|
);
|
|
const intentionalDispose = await evaluateProof(electronApplication, 'resilience.dispose-target');
|
|
assertPackagedMain(intentionalDispose);
|
|
if (intentionalDispose?.resilience?.terminal?.observed !== true
|
|
|| intentionalDispose?.resilience?.terminal?.errorCode !== 'CODING_RUNTIME_START_FAILED'
|
|
|| intentionalDispose?.resilience?.terminal?.recoverable !== true
|
|
|| intentionalDispose?.resilience?.other?.runStatus !== 'running'
|
|
|| intentionalDispose?.resilience?.other?.bindingPreserved !== true
|
|
|| intentionalDispose?.resilience?.targetBindingPreserved !== true
|
|
|| intentionalDispose?.resilience?.providerRequestsUnchanged !== true
|
|
|| intentionalDispose?.resilience?.resources?.backgroundLeases?.active !== 1) {
|
|
throw new Error(`Packaged intentional dispose did not converge safely: ${JSON.stringify(intentionalDispose?.resilience)}`);
|
|
}
|
|
await page.getByText('本地 Agent 已中断,原请求未自动重发。').waitFor({ state: 'visible', timeout: 10_000 });
|
|
if (!await resilienceComposer.isEnabled()) {
|
|
throw new Error('Packaged composer stayed disabled after intentional dispose');
|
|
}
|
|
const recoveredAfterDispose = await recoverSelectedConversation(page, electronApplication);
|
|
if (JSON.stringify(recoveredAfterDispose.resilience.providerRequests)
|
|
!== JSON.stringify(intentionalActive.resilience.providerRequests)) {
|
|
throw new Error('Recover replayed the uncertain prompt after intentional dispose');
|
|
}
|
|
await resilienceComposer.fill('RESILIENCE_RECOVERED_TURN');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
await page.getByText('RESILIENCE_RECOVERED_COMPLETE').waitFor({ state: 'visible', timeout: 30_000 });
|
|
|
|
await resilienceComposer.fill('RESILIENCE_TARGET_ACTIVE');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
const activeResilience = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'running'
|
|
&& status?.other?.runStatus === 'running'
|
|
&& status?.resources?.subagents?.activeChildPermits === 1
|
|
&& status?.resources?.subagents?.activeDispatches === 1
|
|
&& status?.resources?.extension?.registrations?.child === 1
|
|
&& status?.resources?.extension?.writeLeases?.active === 1
|
|
&& status?.resources?.backgroundLeases?.active === 2
|
|
&& status?.processes?.parent?.length === 2
|
|
&& status?.processes?.child?.length === 1,
|
|
'Packaged resilience parent/child/write-lease fault state did not become active',
|
|
);
|
|
const requestsBeforeExit = activeResilience.resilience.providerRequests;
|
|
const exitFailure = await evaluateProof(electronApplication, 'resilience.inject-exit');
|
|
assertPackagedMain(exitFailure);
|
|
assertResilienceTerminal(exitFailure.resilience.status);
|
|
if (exitFailure.resilience.terminalizationMs > 2_000) {
|
|
throw new Error(`Packaged worker exit convergence exceeded 2s: ${exitFailure.resilience.terminalizationMs}`);
|
|
}
|
|
const releasedAfterExit = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.other?.runStatus === 'running'
|
|
&& status?.resources?.subagents?.activeChildPermits === 0
|
|
&& status?.resources?.subagents?.activeDispatches === 0
|
|
&& status?.resources?.extension?.registrations?.child === 0
|
|
&& status?.resources?.extension?.writeLeases?.active === 0
|
|
&& status?.processes?.child?.length === 0,
|
|
'Target crash did not release child and write-lease resources or preserve the other run',
|
|
);
|
|
await page.getByText('本地 Agent 已中断,原请求未自动重发。').waitFor({ state: 'visible', timeout: 10_000 });
|
|
const composerAfterExit = await page.getByTestId('coding-message-composer').innerText();
|
|
if (composerAfterExit.includes('当前对话正在处理') || composerAfterExit.includes('生成中')) {
|
|
throw new Error(`Packaged resilience UI remained busy after worker exit: ${composerAfterExit}`);
|
|
}
|
|
if (!await resilienceComposer.isEnabled()) {
|
|
throw new Error('Packaged resilience composer stayed disabled after worker exit');
|
|
}
|
|
const abortAfterExit = await evaluateProof(electronApplication, 'resilience.abort-after-exit');
|
|
assertPackagedMain(abortAfterExit);
|
|
assertResilienceTerminal(abortAfterExit.resilience);
|
|
|
|
const recoveredAfterExit = await recoverSelectedConversation(page, electronApplication);
|
|
if (JSON.stringify(recoveredAfterExit.resilience.providerRequests) !== JSON.stringify(requestsBeforeExit)) {
|
|
throw new Error('Recover replayed the uncertain accepted prompt after worker exit');
|
|
}
|
|
await resilienceComposer.fill('RESILIENCE_RECOVERED_TURN');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
await page.getByText('RESILIENCE_RECOVERED_COMPLETE').waitFor({ state: 'visible', timeout: 30_000 });
|
|
|
|
await evaluateProof(electronApplication, 'resilience.release-parents');
|
|
await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.other?.runStatus === 'idle' && status?.target?.runStatus === 'idle',
|
|
'Isolation control Conversation did not settle after release',
|
|
);
|
|
|
|
await resilienceComposer.fill('RESILIENCE_PROTOCOL_ACTIVE');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
const protocolActive = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'running'
|
|
&& status?.activeProviderRequests?.parent === 1,
|
|
'Protocol invalidation target did not become active',
|
|
);
|
|
const protocolFailure = await evaluateProof(electronApplication, 'resilience.inject-protocol');
|
|
assertPackagedMain(protocolFailure);
|
|
assertResilienceTerminal(
|
|
protocolFailure.resilience.status,
|
|
'error',
|
|
'CODING_RUNTIME_PROTOCOL_ERROR',
|
|
);
|
|
if (protocolFailure.resilience.terminalizationMs > 2_000) {
|
|
throw new Error(`Packaged protocol convergence exceeded 2s: ${protocolFailure.resilience.terminalizationMs}`);
|
|
}
|
|
await page.getByText('本地 Agent 通信异常,当前对话已停止。').waitFor({ state: 'visible', timeout: 10_000 });
|
|
const recoveredAfterProtocol = await recoverSelectedConversation(page, electronApplication);
|
|
if (JSON.stringify(recoveredAfterProtocol.resilience.providerRequests)
|
|
!== JSON.stringify(protocolFailure.resilience.status.providerRequests)) {
|
|
throw new Error('Recover replayed the uncertain prompt after protocol invalidation');
|
|
}
|
|
|
|
await resilienceComposer.fill('RESILIENCE_SETTLED_BEFORE_CLOSE');
|
|
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
|
await page.getByText('RESILIENCE_SETTLED_COMPLETE').waitFor({ state: 'visible', timeout: 30_000 });
|
|
const settledBeforeClose = await waitForResilienceProof(
|
|
electronApplication,
|
|
(status) => status?.target?.runStatus === 'idle' && status?.target?.workerStatus === 'ready',
|
|
'Settled-before-close target did not reach its authoritative terminal event',
|
|
);
|
|
const settledClose = await evaluateProof(electronApplication, 'resilience.inject-exit');
|
|
assertPackagedMain(settledClose);
|
|
assertResilienceTerminal(settledClose.resilience.status, 'idle');
|
|
await recoverSelectedConversation(page, electronApplication);
|
|
|
|
const resilienceFinish = await evaluateProof(electronApplication, 'resilience.finish');
|
|
resilienceActive = false;
|
|
assertPackagedMain(resilienceFinish);
|
|
if (resilienceFinish?.resilience?.released?.workers !== 0
|
|
|| resilienceFinish?.resilience?.released?.processes !== 0
|
|
|| Object.values(resilienceFinish?.resilience?.lifecycle ?? {}).some((value) => value !== true)
|
|
|| resilienceFinish?.resilience?.realTurnVerified !== false) {
|
|
throw new Error(`Packaged resilience proof failed: ${JSON.stringify(resilienceFinish?.resilience)}`);
|
|
}
|
|
|
|
const mainProcessId = electronApplication.process().pid;
|
|
const proofProcessIds = [
|
|
mainProcessId,
|
|
...await windowsDescendantProcessIds(mainProcessId),
|
|
];
|
|
await closeApplication(electronApplication);
|
|
electronApplication = undefined;
|
|
const exitDeadline = Date.now() + 5_000;
|
|
let lingeringProcessIds = proofProcessIds.filter(processIsAlive);
|
|
while (lingeringProcessIds.length > 0 && Date.now() < exitDeadline) {
|
|
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
lingeringProcessIds = proofProcessIds.filter(processIsAlive);
|
|
}
|
|
if (lingeringProcessIds.length > 0) {
|
|
throw new Error(`Packaged proof left Electron/Pi processes alive: ${lingeringProcessIds.join(',')}`);
|
|
}
|
|
|
|
const report = {
|
|
schemaVersion: 1,
|
|
generatedAt: new Date().toISOString(),
|
|
executable: options.electronExecutable,
|
|
runtimeRoot: options.runtimeRoot ?? null,
|
|
packagedMain: extension.packagedMain,
|
|
extension: extension.extension,
|
|
pressure: {
|
|
processKind: 'final-product-executable-with-ELECTRON_RUN_AS_NODE',
|
|
active: pressureStart.pressure,
|
|
ui: {
|
|
action: 'select Makelore Code and render main layout',
|
|
interactive: true,
|
|
durationMs: uiInteractiveMs,
|
|
},
|
|
released: pressureFinish.pressure,
|
|
...(failureCleanup ? { failureCleanup } : {}),
|
|
},
|
|
proxy: {
|
|
setup: proxyStart.proxy,
|
|
rejected: rejectedProxy.proxy,
|
|
active: proxyStatus.proxy,
|
|
completed: proxyFinish.proxy,
|
|
ui: {
|
|
firstConversationEditable: true,
|
|
definiteRejectionObserved: true,
|
|
draftRestored,
|
|
submissionErrorSafe,
|
|
retryAfterEditEnabled,
|
|
retrySubmitted: true,
|
|
assistantForkActionHidden: true,
|
|
userForkActionAvailable: true,
|
|
forkTargetSelected: true,
|
|
sameAgentOwnership: true,
|
|
sourceUnchanged: proxyFinish.proxy?.fork?.sourceUnchanged === true,
|
|
uncertainPromptReplayed: proxyFinish.proxy?.fork?.promptReplayObserved === true,
|
|
runtimeUnavailableBanner: false,
|
|
permanentRecovering: false,
|
|
},
|
|
realTurnVerified: false,
|
|
},
|
|
resilience: {
|
|
setup: resilienceStart.resilience,
|
|
backgroundLifecycle: {
|
|
configured: promptDelay.resilience,
|
|
active: backgroundActive.resilience,
|
|
hiddenPastGrace: backgroundHidden.resilience,
|
|
idleEvicted: backgroundIdle.resilience,
|
|
uiPromptSubmitted: true,
|
|
hostConfirmationUncertain: true,
|
|
answerVisibleAfterShow: true,
|
|
composerUnlocked: true,
|
|
},
|
|
delayedCompact: {
|
|
configured: compactDelay.resilience,
|
|
uncertain: compactUncertain.resilience,
|
|
settled: compactSettled.resilience,
|
|
overlappingMutationBlocked: true,
|
|
runtimeUnavailableBanner: false,
|
|
composerUnlocked: true,
|
|
},
|
|
intentionalDispose: {
|
|
active: intentionalActive.resilience,
|
|
disposed: intentionalDispose.resilience,
|
|
recovered: recoveredAfterDispose.resilience,
|
|
siblingContinued: true,
|
|
uncertainPromptReplayed: false,
|
|
},
|
|
active: activeResilience.resilience,
|
|
exit: exitFailure.resilience,
|
|
abortAfterExit: abortAfterExit.resilience,
|
|
releasedAfterExit: releasedAfterExit.resilience,
|
|
protocol: {
|
|
active: protocolActive.resilience,
|
|
failure: protocolFailure.resilience,
|
|
},
|
|
settledBeforeClose: {
|
|
settled: settledBeforeClose.resilience,
|
|
close: settledClose.resilience,
|
|
},
|
|
completed: resilienceFinish.resilience,
|
|
ui: {
|
|
terminalErrorVisible: true,
|
|
composerUnlocked: true,
|
|
recoverAvailable: true,
|
|
uncertainPromptReplayed: false,
|
|
},
|
|
realTurnVerified: false,
|
|
},
|
|
cleanExit: {
|
|
trackedProcessIds: proofProcessIds,
|
|
lingeringProcessIds,
|
|
},
|
|
result: 'pass',
|
|
};
|
|
if (options.reportPath) {
|
|
await mkdir(dirname(options.reportPath), { recursive: true });
|
|
await writeFile(options.reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
}
|
|
return report;
|
|
} finally {
|
|
if (electronApplication && pressureActive) {
|
|
await evaluateProof(electronApplication, 'pressure.finish').catch(() => undefined);
|
|
}
|
|
if (electronApplication && proxyActive) {
|
|
await evaluateProof(electronApplication, 'proxy.release-child').catch(() => undefined);
|
|
await evaluateProof(electronApplication, 'proxy.finish').catch(() => undefined);
|
|
}
|
|
if (electronApplication && resilienceActive) {
|
|
await evaluateProof(electronApplication, 'resilience.release-parents').catch(() => undefined);
|
|
await evaluateProof(electronApplication, 'resilience.finish').catch(() => undefined);
|
|
}
|
|
if (electronApplication) await closeApplication(electronApplication);
|
|
await rm(scratchRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
const report = await runPackagedProductProof(options);
|
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
}
|
|
|
|
const isMain = process.argv[1]
|
|
&& pathToFileURL(resolve(process.argv[1])).href === import.meta.url;
|
|
if (isMain) {
|
|
main().catch((error) => {
|
|
process.stderr.write(`${error.stack ?? error.message}\n`);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|