Files
orca/src/shared/structured-agent-session-dispatch-rejection.ts
T
Brennan BensonandMerge Sim f55b7ba680 fix(native-chat): cancel pending prompts precisely (#20601)
* fix(native-chat): hide activity while awaiting input

* fix(native-chat): keep approval turns cancellable

* test(native-chat): satisfy split PR quality gate

* fix(native-chat): catalog approval cancellation label

* fix(native-chat): include approval cancellation runtime label

* fix(codex): settle prompts when cancelled turns complete

* fix(codex): settle prompt registry fallbacks

* test(native-chat): cover pending interaction fallbacks

* test(native-chat): split prompt state coverage

* test(native-chat): keep prompt state isolated

* fix(native-chat): bound prompt turn backfill

* refactor(codex): centralize prompt registry bounds

* fix(native-chat): cancel pending prompts precisely

* fix(native-chat): consolidate capability imports

* fix(native-chat): harden precise prompt cancellation

* fix claude cancellation teardown races

* retry claude prompt lifecycle admission

* bound claude prompt cancellation retry work

* fix(codex): bound prompt turn identity on registration

* fix(native-chat): route rejected late dispatch settlements

* fix(codex): retain exact cancellable prompt turn ids

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-14 14:59:03 -07:00

60 lines
2.8 KiB
TypeScript

// Why a submission is `rejected`: the message provably did not happen.
//
// Two different facts reach this state, and the reason string is what tells them
// apart. A CONTENT rejection is permanent — the provider looked at this payload
// and refused it, and it would refuse the same payload again; its reason is the
// provider's own explanation ("Claude messages support at most 20 images") and is
// meant to be read. A TRANSPORT rejection is transient — the frame was never
// handed to the provider at all, so the same text under a fresh id is a first
// delivery and the next attempt may well succeed; its reason names an internal
// cause and must NOT be put in front of a person.
//
// Both are `rejected` because both make the single claim that state exists to
// make: this message did not reach the provider. Neither is ever re-delivered
// under its own id — `rejected` is terminal in the reducer — so a retry rotates
// the client message id, which is a new message and cannot duplicate.
//
// Shared rather than main-only because both sides need it: the host writes the
// reason, and a client has to know which kind it is holding before it can decide
// whether the string is showable.
export const DISPATCH_REJECTED_WRITE_FAILED = 'provider_write_failed'
/** Local admission refused the frame before any transport was involved. Two
* strings rather than one provider-neutral marker because both are already
* durable journal reasons; rewording either would relabel rows on disk. */
export const DISPATCH_REJECTED_QUEUE_FULL = 'claude structured dispatch queue is full'
export const DISPATCH_REJECTED_CODEX_QUEUE_FULL = 'codex structured dispatch queue is full'
/** The provider confirmed a queued frame was withdrawn before execution. */
export const DISPATCH_REJECTED_CANCELLED = 'provider_cancelled_before_start'
export function dispatchWriteFailureReason(error: unknown): string {
const detail = error instanceof Error ? error.message : String(error)
return `${DISPATCH_REJECTED_WRITE_FAILED}: ${detail}`
}
/** True for the internal transport marker, false for a provider's own words. */
export function dispatchRejectionWasTransportWriteFailure(
reason: string | null | undefined
): boolean {
return (
reason === DISPATCH_REJECTED_WRITE_FAILED ||
reason?.startsWith(`${DISPATCH_REJECTED_WRITE_FAILED}: `) === true
)
}
/**
* True when the reason is ours rather than the provider's, so it must not be
* shown verbatim. A content rejection carries the provider's own explanation and
* is the only kind a person should read.
*/
export function dispatchRejectionReasonIsInternal(reason: string | null | undefined): boolean {
return (
dispatchRejectionWasTransportWriteFailure(reason) ||
reason === DISPATCH_REJECTED_QUEUE_FULL ||
reason === DISPATCH_REJECTED_CODEX_QUEUE_FULL ||
reason === DISPATCH_REJECTED_CANCELLED
)
}