refactor(agent): simplify inbox-driven turn admission
This commit is contained in:
104 files changed
+550
-511
No files matched your search
@@ -122,15 +122,15 @@ export interface ContextMessageNode {
|
||||
source: unknown
|
||||
}
|
||||
|
||||
/** Durable notice that a closed failed step is waiting for a model-request retry. */
|
||||
/** Durable notice that a failed model request is waiting for another attempt. */
|
||||
export type ModelRetryNode = LlmRetryEventData & {
|
||||
kind: 'model-retry'
|
||||
seq: number
|
||||
/** Unix epoch ms from the llm/retry session event. */
|
||||
time: number
|
||||
/**
|
||||
* Client-derived lifecycle: scheduled until a retry turn starts, started
|
||||
* once it does, or cancelled when the failed turn aborts first.
|
||||
* Client-derived lifecycle: scheduled until another attempt emits retry or
|
||||
* chunk evidence, started once it does, or cancelled if the turn aborts first.
|
||||
*/
|
||||
retryState: 'scheduled' | 'started' | 'cancelled'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
/**
|
||||
* Convert a durable failure into copy that is safe to expose in the GUI.
|
||||
* @param failure - Structured failure preserved by the session event.
|
||||
* @param failure - Failure value preserved by the session event.
|
||||
* @returns Display-safe copy for client projections.
|
||||
*/
|
||||
export function displayFailureMessage(failure: { code?: string; message: string }): string {
|
||||
export function displayFailureMessage(failure: unknown): string {
|
||||
if (failure === null || typeof failure !== 'object') return String(failure)
|
||||
const record = failure as { code?: unknown; message?: unknown }
|
||||
// Provider AUTH messages may echo a masked or partially preserved credential.
|
||||
// Keep the raw diagnostic in the session log, but never project it into UI state.
|
||||
return failure.code === 'AUTH' ? 'API key is invalid' : failure.message
|
||||
if (record.code === 'AUTH') return 'API key is invalid'
|
||||
return typeof record.message === 'string' ? record.message : JSON.stringify(failure)
|
||||
}
|
||||
@@ -360,9 +360,9 @@ function deriveRequests(events: readonly SessionEvent[]): readonly RequestView[]
|
||||
}
|
||||
if (sourceEvent.type === 'turn/end' && sourceEvent.data.reason.kind === 'error') {
|
||||
const reason = sourceEvent.data.reason
|
||||
updateAssistant(ordinaryByStep.get(requestKey(sourceEvent.data.turn, reason.step)), {
|
||||
updateAssistant(ordinaryByStep.get(requestKey(sourceEvent.data.turn, sourceEvent.data.step)), {
|
||||
status: 'error',
|
||||
error: displayFailureMessage('failure' in reason ? reason.failure : reason),
|
||||
error: displayFailureMessage(reason.error),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -646,6 +646,7 @@ export class Session implements SessionFace {
|
||||
if (this.partial !== null && this.partial.turn === data.turn && this.partial.step === data.step) {
|
||||
this.partial = null
|
||||
}
|
||||
this.settleScheduledRetry('started', data.turn)
|
||||
this.derivedNodes.push({
|
||||
kind: 'model-retry',
|
||||
seq: event.seq,
|
||||
@@ -716,11 +717,10 @@ export class Session implements SessionFace {
|
||||
return
|
||||
}
|
||||
switch (event.type) {
|
||||
case 'turn/start': {
|
||||
if (event.data.trigger.kind === 'retry') this.settleScheduledRetry('started')
|
||||
case 'turn/start':
|
||||
return
|
||||
}
|
||||
case 'assistant/chunk': {
|
||||
this.settleScheduledRetry('started', event.data.turn)
|
||||
const { turn, step, chunk } = event.data
|
||||
if (this.partial === null || this.partial.turn !== turn || this.partial.step !== step) {
|
||||
this.partial = new PartialAccumulator(turn, step)
|
||||
@@ -748,22 +748,29 @@ export class Session implements SessionFace {
|
||||
return
|
||||
}
|
||||
case 'turn/end': {
|
||||
if (event.data.reason.kind === 'aborted' || event.data.reason.kind === 'disposed') {
|
||||
if (event.data.reason.kind === 'error') {
|
||||
this.settleScheduledRetry('started', event.data.turn)
|
||||
} else if (event.data.reason.kind === 'aborted' || event.data.reason.kind === 'interrupted') {
|
||||
this.settleScheduledRetry('cancelled', event.data.turn)
|
||||
}
|
||||
if (
|
||||
event.data.reason.kind === 'error'
|
||||
&& !this.derivedNodes.some(node => node.kind === 'model-retry' && node.turn === event.data.turn)
|
||||
) {
|
||||
const failure = 'failure' in event.data.reason ? event.data.reason.failure : event.data.reason
|
||||
const failure = event.data.reason.error
|
||||
const failedTurn = event.data.turn
|
||||
const code = failure !== null && typeof failure === 'object'
|
||||
&& typeof (failure as { code?: unknown }).code === 'string'
|
||||
? (failure as { code: string }).code
|
||||
: undefined
|
||||
this.derivedNodes.push({
|
||||
kind: 'turn-error',
|
||||
seq: event.seq,
|
||||
time: event.time,
|
||||
turn: event.data.turn,
|
||||
step: event.data.reason.step,
|
||||
turn: failedTurn,
|
||||
step: event.data.step,
|
||||
message: displayFailureMessage(failure),
|
||||
...(failure.code === undefined ? {} : { code: failure.code }),
|
||||
...code === undefined ? {} : { code },
|
||||
})
|
||||
this.derivedRev++
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user