fix: preserve Pi projection continuity

This commit is contained in:
2026-08-23 08:40:58 +08:00
parent 3599064bc4
commit 4bbe3f8b1e
7 changed files with 248 additions and 9 deletions

View File

@@ -31,6 +31,14 @@ export interface PiProjectionDiagnostic {
reason: 'unsupported-event';
}
function retryFailure(message: string) {
return {
code: 'CODING_RUNTIME_START_FAILED' as const,
message,
recoverable: true,
};
}
function asRecord(value: unknown): Record<string, unknown> | null {
return value !== null && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
@@ -142,6 +150,7 @@ export class PiEventProjector {
private assistantMessageId: string | null = null;
private readonly toolDrafts = new Map<number, { id: string; argumentsText: string }>();
private compactionId: string | null = null;
private summarizationRetryBaseRun: ConversationSnapshot['run'] | null = null;
private readonly diagnostics: PiProjectionDiagnostic[] = [];
constructor(options: PiEventProjectorOptions) {
@@ -211,9 +220,77 @@ export class PiEventProjector {
];
}
if (event.type === 'auto_retry_end' && event.success === true) {
if (event.type === 'auto_retry_end' && typeof event.success === 'boolean') {
const { retry: _retry, ...run } = snapshot.run;
return [{ op: 'run.state', run: { ...run, status: 'running' } }];
if (event.success) {
return [{ op: 'run.state', run: { ...run, status: 'running' } }];
}
return [{
op: 'run.state',
run: {
...run,
status: 'error',
terminalReason: 'failed',
error: retryFailure('The local Agent retry failed'),
},
}];
}
if (event.type === 'summarization_retry_scheduled'
&& typeof event.attempt === 'number'
&& typeof event.delayMs === 'number') {
const runId = snapshot.run.runId ?? this.createId();
this.summarizationRetryBaseRun ??= { ...snapshot.run, runId };
return [
{
op: 'boundary.upsert',
node: {
kind: 'boundary',
id: this.createId(),
runId,
boundary: 'retry',
attempt: event.attempt,
delayMs: event.delayMs,
},
},
{
op: 'run.state',
run: {
...snapshot.run,
status: 'retrying',
runId,
retry: { attempt: event.attempt, delayMs: event.delayMs },
},
},
];
}
if (event.type === 'summarization_retry_attempt_start'
&& (event.source === 'branchSummary' || event.source === 'compaction')) {
const base = this.summarizationRetryBaseRun ?? snapshot.run;
const { retry: _retry, ...run } = base;
return [
...(event.source === 'compaction'
? [{
op: 'context.replace' as const,
context: { ...snapshot.context, compaction: 'running' as const },
}]
: []),
{
op: 'run.state',
run: {
...run,
status: event.source === 'compaction' ? 'compacting' : run.status,
},
},
];
}
if (event.type === 'summarization_retry_finished') {
const base = this.summarizationRetryBaseRun ?? snapshot.run;
this.summarizationRetryBaseRun = null;
const { retry: _retry, ...run } = base;
return [{ op: 'run.state', run }];
}
if (event.type === 'compaction_start') {
@@ -244,6 +321,8 @@ export class PiEventProjector {
const id = this.compactionId ?? this.createId();
const runId = snapshot.run.runId ?? this.createId();
this.compactionId = null;
const compactionFailed = typeof event.errorMessage === 'string';
const { retry: _retry, ...run } = snapshot.run;
return [
{
op: 'compaction.upsert',
@@ -265,9 +344,17 @@ export class PiEventProjector {
{
op: 'run.state',
run: {
...snapshot.run,
status: event.willRetry === true ? 'running' : snapshot.run.status,
...run,
status: event.willRetry === true
? 'running'
: compactionFailed ? 'error' : snapshot.run.status,
runId,
...(compactionFailed
? {
terminalReason: 'failed' as const,
error: retryFailure('The local Agent summarization failed'),
}
: {}),
},
},
];
@@ -298,6 +385,12 @@ export class PiEventProjector {
}
if (event.type === 'agent_settled') {
const terminalReason = snapshot.run.status === 'aborting'
? 'aborted' as const
: snapshot.run.status === 'error' || snapshot.run.terminalReason === 'failed'
? 'failed' as const
: 'completed' as const;
this.summarizationRetryBaseRun = null;
return [
{
op: 'run.state',
@@ -305,7 +398,10 @@ export class PiEventProjector {
status: 'idle',
...(snapshot.run.runId ? { runId: snapshot.run.runId } : {}),
settledAt: this.now(),
terminalReason: snapshot.run.status === 'aborting' ? 'aborted' : 'completed',
terminalReason,
...(terminalReason === 'failed' && snapshot.run.error
? { error: snapshot.run.error }
: {}),
},
},
{ op: 'queue.replace', queue: { items: [] } },

View File

@@ -361,7 +361,9 @@ export async function projectPiSessionSnapshot(
worker: { status: 'ready', generation: input.workerGeneration },
cursor: {
workerGeneration: input.workerGeneration,
seq: 0,
seq: input.snapshot.cursor.workerGeneration === input.workerGeneration
? input.snapshot.cursor.seq
: 0,
...(typeof response.leafId === 'string' ? { leafEntryId: response.leafId } : {}),
},
};