fix(pi): keep active runs alive during background sleep

This commit is contained in:
2026-08-25 20:56:17 +08:00
parent 92f4c91088
commit 621ebb1781
22 changed files with 1061 additions and 68 deletions

View File

@@ -22,7 +22,7 @@ import {
clearWorksSquareAIGatewayCredential,
seedWorksSquareAIGatewayCredential,
} from '../../services/works-square-ai-gateway';
import type { PrepareConversationInput } from '../contracts';
import type { ConversationPatchEnvelope, PrepareConversationInput } from '../contracts';
import { PiManagedExtensionHost } from './extension-host';
import { PiManagedInputRevisionCoordinator } from './managed-input-revision';
import { runPiReleasePressureCleanup } from './release-proof-cleanup';
@@ -273,6 +273,23 @@ export interface PiReleaseResilienceProof extends PiReleaseResilienceStatus {
realTurnVerified: false;
}
export interface PiReleaseResilienceIdleStatus {
workers: ReturnType<CodingProductComposition['runtime']['getDiagnostics']>['workers'];
resources: ReturnType<PiConversationRuntime['getResilienceProofDiagnostics']>;
processes: { supported: boolean; parent: number[]; child: number[] };
backgroundSleepReasoned: boolean;
realTurnVerified: false;
}
export interface PiReleaseIntentionalDisposeProof {
terminal: { observed: boolean; errorCode: string | null; recoverable: boolean };
other: { runStatus: string; bindingPreserved: boolean };
targetBindingPreserved: boolean;
providerRequestsUnchanged: boolean;
resources: ReturnType<PiConversationRuntime['getResilienceProofDiagnostics']>;
realTurnVerified: false;
}
const PROOF_ACCOUNT_ID = 'release-proof-account';
const PROOF_AGENT_ID = 'release-proof-agent';
const PROOF_MODEL_ID = 'release-proof-model';
@@ -1843,6 +1860,90 @@ export function releaseFinalAsarResilienceParents(): void {
run.provider.releaseParents();
}
export async function restartFinalAsarResilienceOther(): Promise<PiReleaseResilienceStatus> {
const run = resilienceCompositionRun;
if (!run) throw new Error('PI resilience proof is not running');
const accepted = await run.composition.conversations.acceptPrompt({
conversationId: run.otherConversationId,
clientRequestId: 'release-proof-other-active-restarted',
mode: 'prompt',
text: 'RESILIENCE_OTHER_ACTIVE_RESTARTED',
attachments: [],
});
if (!accepted.accepted) throw new Error('Resilience isolation restart was not accepted');
return await waitForResilienceStatus(
(status) => status.other.runStatus === 'running'
&& status.activeProviderRequests.parent === 1,
'Resilience isolation restart did not become active',
);
}
export async function getFinalAsarResilienceIdleStatus(): Promise<PiReleaseResilienceIdleStatus> {
const run = resilienceCompositionRun;
if (!run) throw new Error('PI resilience proof is not running');
const runtime = resilienceRuntime(run);
const processInspection = await inspectWindowsPiProcesses(run.hostToken);
const lifecycleLogs = getRecentLogs().filter((line) => (
line.includes('[PiWorkerLifecycle]')
&& (line.includes(run.targetConversationId) || line.includes(run.otherConversationId))
));
return {
workers: runtime.getDiagnostics().workers,
resources: runtime.getResilienceProofDiagnostics(),
processes: {
supported: processInspection.supported,
parent: processInspection.processes
.filter(({ role }) => role === 'parent')
.map(({ processId }) => processId)
.sort((left, right) => left - right),
child: processInspection.processes
.filter(({ role }) => role === 'child')
.map(({ processId }) => processId)
.sort((left, right) => left - right),
},
backgroundSleepReasoned: lifecycleLogs.some((line) => (
line.includes('"classification": "intentional_stop"')
&& line.includes('"reason": "background_sleep"')
)),
realTurnVerified: false,
};
}
export async function disposeFinalAsarResilienceTarget(): Promise<PiReleaseIntentionalDisposeProof> {
const run = resilienceCompositionRun;
if (!run) throw new Error('PI resilience proof is not running');
const runtime = resilienceRuntime(run);
const beforeRequests = providerRequestCounts(run.provider);
let errorCode: string | null = null;
let recoverable = false;
const unsubscribe = runtime.subscribe((envelope: ConversationPatchEnvelope) => {
if (envelope.conversationId !== run.targetConversationId
|| envelope.patch.op !== 'run.state'
|| envelope.patch.run.status !== 'error') return;
errorCode = envelope.patch.run.error?.code ?? null;
recoverable = envelope.patch.run.error?.recoverable ?? false;
});
try {
await runtime.dispose(run.targetConversationId, 'test_injection');
} finally {
unsubscribe();
}
const [other, targetBindingPreserved, otherBindingPreserved] = await Promise.all([
run.composition.conversations.getSnapshot(run.otherConversationId),
resilienceBindingPreserved(run, run.targetConversationId, run.targetBinding),
resilienceBindingPreserved(run, run.otherConversationId, run.otherBinding),
]);
return {
terminal: { observed: errorCode !== null, errorCode, recoverable },
other: { runStatus: other.run.status, bindingPreserved: otherBindingPreserved },
targetBindingPreserved,
providerRequestsUnchanged: JSON.stringify(beforeRequests)
=== JSON.stringify(providerRequestCounts(run.provider)),
resources: runtime.getResilienceProofDiagnostics(),
realTurnVerified: false,
};
}
export async function finishFinalAsarResilienceProof(): Promise<PiReleaseResilienceProof> {
const run = resilienceCompositionRun;
if (!run) throw new Error('PI resilience proof is not running');
@@ -1883,6 +1984,7 @@ export async function finishFinalAsarResilienceProof(): Promise<PiReleaseResilie
|| released.resources.pool.processBudget.waiting !== 0
|| released.resources.pool.runs.active !== 0
|| released.resources.pool.runs.waiting !== 0
|| released.resources.backgroundLeases.active !== 0
|| released.resources.subagents?.activeChildPermits !== 0
|| released.resources.subagents?.waitingChildPermits !== 0
|| released.resources.subagents?.activeDispatches !== 0