fix: preserve Pi projection continuity
This commit is contained in:
@@ -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: [] } },
|
||||
|
||||
@@ -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 } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user