mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 08:02:21 +00:00
* fix(claude): settle a queued send the CLI withdrew from its own cancelled frame Claude reports each uuid-stamped command's lifecycle (queued, started, completed, cancelled). A send it withdraws from its queue gets `cancelled` before the interrupt or cancel_async_message answer, so a lost or failed answer no longer leaves that send pending: it settles as withdrawn, with the same reason and words as the receipt path. A command the CLI already started also ends `cancelled` when its turn is interrupted or fails, so `cancelled` after `started` is not a withdrawal; an echoed send has left the waiter lists and is never reached. Tests replay real 2.1.280 captures, scrubbed. * fix(claude): release a doubted send when the CLI reports its session idle A Claude send whose write ended in doubt is recorded `unknown`, and a live `unknown` reads as work still owed, so the chat showed Working until the child exited. Claude sends `session_state_changed idle` only once its whole queue has drained, so it can no longer be holding that send. The runtime now routes that report to the host's existing release, the same one Codex's thread-stopped report uses; it retires `unknown` only, never `pending`. * fix(claude): keep a command's started mark when a redelivery re-emits queued; fixtures name msg_lifecycle_v1 * fix(claude): settle every terminal lifecycle state of a send the CLI never echoed A send the CLI started, then cancelled before any echo, stayed pending: it may already be in the conversation, so it is released as doubt (unknown, recovered), never withdrawn and never re-sent. A late echo still accepts it. The 2.1.280 schema has two more terminal states. `discarded` (the CLI ended its session with the send still queued) settles as not delivered; `refused` (declined before it queued) settles as not accepted by the provider. After `started`, either one is doubt, as `cancelled` is. The late-settlement path gains an `unknown` outcome, which the host records as released doubt. * fix(claude): release a send the CLI took but left unanswered when it goes idle `session_state_changed idle` comes only once the CLI's queue has drained, so a send it took that is still unanswered there got no echo and never will: a turn that throws can leave `started` with no terminal state. Idle releases it as doubt. What proves the CLI took a send is its lifecycle frame. On a CLI that reports no lifecycle, it is the send's place on stdin: one whose write finished before an interrupt went out was read before the interrupt was, so the first idle after that interrupt releases it too. A send armed ahead of the interrupt but written after it is left alone, since the CLI may still run it. * fix(native-chat): keep the idle sweep off a Claude child that holds a send A Claude retrying a rate-limited request has taken the send but echoes nothing, so no turn row exists yet and the sweep rested the child after the idle window, turning the send into doubt. The adapter now reports whether the CLI holds a send (lifecycle `queued` or `started`, not yet echoed or ended), derived from the live waiters, and owed work counts it. Nothing is stored: every held send leaves the live set on its echo, its terminal lifecycle state, the CLI's idle, or the child's exit, so the hold ends with the send. * fix(claude): count only a started send at idle and as a held send 2.1.280's end-of-turn cleanup can report idle before it re-reads its queue, so a send read in that window goes queued, idle, started. Releasing every taken send at idle doubted that live send and dropped Working. Only a `started` send is released at idle or keeps the child from the idle sweep; a `queued` one ends by starting and echoing, by a terminal lifecycle frame, or with the child. The stdin-order path for CLIs without lifecycle frames is removed: a doubted send retired there disables content matching on CLIs that mint their own echo ids, and no Orca failure called for it. Those CLIs keep the earlier behaviour. Comments that said only a failed write or child exit ends a waiter, or that idle comes only once the queue has drained, now say what ends one. * docs(claude): say only what the CLI's lifecycle frames and idle actually prove * fix(claude): hold the idle sweep while Claude has a send queued, not only started The sweep rested a child whose CLI had queued a follow-up behind a turn, dropping the send it had already taken. The hold now spans the CLI reporting it took the send until its echo, a terminal lifecycle state, or the child's exit. The idle release still covers only started sends: 2.1.280 can report idle before it re-reads its queue. * refactor(native-chat): give provider-proven late dispatch settlement its own module * test(claude): pin a steer a Stop interrupts after it started as doubt, not withdrawn * fix(native-chat): one ordered journal writer Every journal write, streamed or direct, lands in the chat's one write queue in the order it is issued, and has landed in the fold when its call returns (except while an owed import is paid, when it lands in queue order). The event sink stops being a queue ahead of it; journal-write coalescing, which moved a replaced write to the tail, is removed; closing a sink no longer loses writes already handed over. The ten flushStreamedEvents patches go. A streamed text item takes its place at its first delta, so a direct write inside the coalescing window never lands above text already streamed. A Stop interrupts whatever its bookkeeping writes do. * fix(native-chat): a failed Stop holds the lane until its queue pause lands A Stop whose note write or stop call failed skipped the wait for its withdrawal and pause, so the lane freed first; with writes queued behind owed work, the drain could hand the waiting card to the agent after Stop. * fix(native-chat): a journal write body is typed synchronous The queue's ordering rule needs every write body to finish before it returns; serialize still took a promise-returning body, so an await inside one would let a later write land first. The body type now refuses a promise. * fix(native-chat): a stream's first window snapshot always lands The first-delta write counted as the growth checkpoint, so the window's snapshot was skipped until 32 more characters arrived: a stream showed only its first token, and a Codex reasoning row could sit as an empty aside. The coalescer now marks the row-creating emit and the first snapshot after it, and both providers' growth throttles write those. * test(native-chat): streamed text rewritten in place above a Stop note Covers a stream whose later deltas wait in the window across a Stop, for Codex and Claude, and a Codex item completed inside the window. * chore(native-chat): drop comments that still describe coalesced journal writes * fix(native-chat): type the draft-table write body synchronous too * fix(native-chat): an empty delta owes no streamed-text emit The opening snapshot is forced past the growth rule, so an empty second Codex delta rewrote the row with identical text. The coalescer now marks a stream dirty only when a delta adds text. * fix(native-chat): a mutation's open pays an owed import first With the flushes gone, a Stop naming no turn, a goal set or a /clear could read the fold while provider rows still waited behind a restore's owed import: the Stop answered cancelled:false and interrupted nothing. The open every mutation shares now waits for the import, as a reader's does; a failed import is reported and never refuses the mutation. * chore(native-chat): whenImported says mutations await it too * test(native-chat): a Stop interrupts before its withdrawal or pause settles Pins the order for a write that is held and then fails, for a Stop naming its turn and one naming none. * test(native-chat): a Stop ends a starting child before its withdrawal or pause settles * test(native-chat): Stop order tests wait for the interrupt, not a 50 ms timer * test(native-chat): settlement-order test reads rows through the journal row parser Replaces a Reflect.get field walk, which the low-evidence audit rejects, with parseJournalRow and the named row types. * fix(native-chat): an empty delta still owes its emit, just not a forced one The previous fix stopped an empty delta from marking the stream dirty, which broke the pinned contract that an empty stream is snapshotted and flushed. The coalescer now marks a stream dirty on every delta and tracks separately whether its text changed since the last emit; only a changed snapshot is the opening one that skips the growth throttle. * revert(native-chat): drop the first-text row write from the delta coalescer The immediate first-delta emit (and the opening flag and counters it needed) had no Orca-observed failure behind it and added a journal commit per streamed item. The coalescer, the Claude checkpoints and the tests that pinned the first-text row go back to main's behaviour; the one ordered writer, the sink hand-off and the Stop rules stay.