fix(pi): settle delayed compact ownership

This commit is contained in:
2026-08-26 09:41:25 +08:00
parent f5e6a04c7d
commit 9f05e2d7e1
12 changed files with 334 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
import { execFile } from 'node:child_process';
import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';
@@ -26,6 +26,7 @@ import type { ConversationPatchEnvelope, PrepareConversationInput } from '../con
import { PiManagedExtensionHost } from './extension-host';
import { PiManagedInputRevisionCoordinator } from './managed-input-revision';
import { runPiReleasePressureCleanup } from './release-proof-cleanup';
import { ensurePiManagedPaths } from './resource-loader';
import type { PiRpcEvent } from './rpc-client';
import { createPiManagedWorkerOpener, PiConversationRuntime } from './runtime';
import { PiSessionRegistry } from './session-registry';
@@ -510,7 +511,9 @@ async function startLocalProofProvider(mode: ProofProviderMode): Promise<LocalPr
return;
}
const toolNames = toolNamesFrom(body);
const role: ProofWorkerRole = toolNames.includes('subagent') ? 'parent' : 'child';
const role: ProofWorkerRole = mode === 'resilience' && delayedCompactionArmed
? 'parent'
: toolNames.includes('subagent') ? 'parent' : 'child';
const toolResult = hasToolResult(body);
const providerRequest: ProviderRequest = {
role,
@@ -552,7 +555,7 @@ async function startLocalProofProvider(mode: ProofProviderMode): Promise<LocalPr
respondWithSubagentCall(response, model, 'coding');
return;
}
if (role === 'parent' && delayedCompactionArmed) {
if (delayedCompactionArmed) {
delayedCompactionArmed = false;
const entry = { role, response } satisfies HeldProviderResponse;
held.add(entry);
@@ -1743,6 +1746,7 @@ async function waitForResilienceStatus(
export async function startFinalAsarResilienceProof(input: {
composition: CodingProductComposition;
projectPath: string;
userDataDir: string;
hostProxyBaseUrl: string;
hostToken: string;
}): Promise<{
@@ -1766,6 +1770,13 @@ export async function startFinalAsarResilienceProof(input: {
const providerService = getProviderService();
await providerService.createAccount(proxyProviderAccount(input.hostProxyBaseUrl));
await providerService.setDefaultAccount(PROOF_ACCOUNT_ID);
const managedPaths = await ensurePiManagedPaths(input.userDataDir);
await writeFile(path.join(managedPaths.configDir, 'settings.json'), JSON.stringify({
compaction: {
reserveTokens: 128,
keepRecentTokens: 1,
},
}, null, 2), 'utf8');
const project = await input.composition.projects.createProject({ projectPath: input.projectPath });
projectId = project.project.id;
await createCodingProjectAgent(input.projectPath, {

View File

@@ -1398,6 +1398,41 @@ export class PiConversationRuntime implements CodingConversationRuntime {
});
return;
}
if (event.type === 'top-level.settled') {
void this.enqueueProjection(event.conversationId, async () => {
const snapshot = this.states.get(event.conversationId)?.snapshot;
const current = snapshot?.run;
if (!snapshot
|| snapshot.cursor.workerGeneration !== event.generation
|| current?.runId !== event.runId
|| runIsTerminal(current.status)) return;
this.emit(event.conversationId, {
op: 'run.state',
run: {
status: 'idle',
runId: event.runId,
settledAt: this.now(),
terminalReason: 'completed',
},
}, event.runId);
this.extensionUi.endRun(event.conversationId, event.runId);
try {
await Promise.allSettled([
this.interactions.cancelRun(event.conversationId, event.runId, true),
this.extensionHost?.clearRun(
event.conversationId,
event.generation,
event.runId,
),
]);
} finally {
this.releaseRunBackgroundLease(event.conversationId, event.runId);
}
}).catch((error) => {
this.recordProjectionFailure(event.conversationId, event.generation, error);
});
return;
}
if (event.type === 'top-level.failed') {
void this.enqueueProjection(event.conversationId, async () => {
await this.failRun(
@@ -1525,8 +1560,11 @@ export class PiConversationRuntime implements CodingConversationRuntime {
const current = this.states.get(conversationId)?.snapshot?.run;
const snapshotGeneration = this.states.get(conversationId)?.snapshot?.cursor.workerGeneration;
if (current?.runId !== runId
|| runIsTerminal(current.status)
|| (generation !== undefined && snapshotGeneration !== generation)) return;
if (runIsTerminal(current.status)) {
if (releaseBackgroundLease) this.releaseRunBackgroundLease(conversationId, runId);
return;
}
const publicError = runtimeFailure(error);
this.emit(conversationId, {
op: 'run.state',

View File

@@ -117,6 +117,12 @@ export type PiWorkerPoolEvent =
generation: number;
runId: string;
}
| {
type: 'top-level.settled';
conversationId: string;
generation: number;
runId: string;
}
| {
type: 'top-level.failed';
conversationId: string;
@@ -756,6 +762,15 @@ export class PiWorkerPool {
onLateResult: (result) => this.handleLateTopLevelResult(current!, run, result),
});
this.confirmTopLevel(current, run);
if (run.command.type === 'compact') {
this.settleTopLevel(current);
this.emit({
type: 'top-level.settled',
conversationId: run.conversationId,
generation: current.generation,
runId: run.runId,
});
}
run.resolve(response);
} catch (error) {
const active = this.activeRuns.get(run.conversationId);
@@ -823,6 +838,15 @@ export class PiWorkerPool {
generation: record.generation,
runId: run.runId,
});
if (run.command.type === 'compact') {
this.settleTopLevel(record);
this.emit({
type: 'top-level.settled',
conversationId: run.conversationId,
generation: record.generation,
runId: run.runId,
});
}
return;
}
if (result.error.code === 'PI_RPC_EXITED'