fix(pi): keep active runs alive during background sleep
This commit is contained in:
@@ -215,6 +215,26 @@ async function waitForResilienceProof(electronApplication, predicate, message) {
|
||||
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',
|
||||
@@ -543,7 +563,7 @@ export async function runPackagedProductProof(options) {
|
||||
await resilienceTarget.click();
|
||||
const resilienceComposer = page.getByRole('textbox');
|
||||
await resilienceComposer.waitFor({ state: 'visible', timeout: 30_000 });
|
||||
await resilienceComposer.fill('RESILIENCE_TARGET_ACTIVE');
|
||||
await resilienceComposer.fill('BACKGROUND_ACTIVE');
|
||||
try {
|
||||
await page.getByRole('button', { name: '发送' }).click({ timeout: 30_000 });
|
||||
} catch (error) {
|
||||
@@ -557,6 +577,109 @@ export async function runPackagedProductProof(options) {
|
||||
);
|
||||
}
|
||||
|
||||
const backgroundActive = await waitForResilienceProof(
|
||||
electronApplication,
|
||||
(status) => status?.target?.runStatus === 'running'
|
||||
&& 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',
|
||||
);
|
||||
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 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 setMainWindowVisible(electronApplication, true);
|
||||
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 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'
|
||||
@@ -565,6 +688,7 @@ export async function runPackagedProductProof(options) {
|
||||
&& 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',
|
||||
@@ -721,6 +845,21 @@ export async function runPackagedProductProof(options) {
|
||||
},
|
||||
resilience: {
|
||||
setup: resilienceStart.resilience,
|
||||
backgroundLifecycle: {
|
||||
active: backgroundActive.resilience,
|
||||
hiddenPastGrace: backgroundHidden.resilience,
|
||||
idleEvicted: backgroundIdle.resilience,
|
||||
uiPromptAccepted: true,
|
||||
answerVisibleAfterShow: true,
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user