mirror of
https://github.com/stablyai/orca.git
synced 2026-10-04 16:02:08 +00:00
* fix(native-chat): keep a Codex ask's questions in the order it asked them Codex journals every question of one ask in a single write, so the questions share a timestamp. The transcript list sorted rows by timestamp and broke ties by row id, and a question's id ends in the question id the model chose, so answered, cancelled and still-pending rows of one ask came out in the alphabetical order of those ids. The transcript projection now breaks timestamp ties by the order its source gave: the journal's order for structured sessions. The session assembler keeps its id tie-break, since the sources it merges share no order of their own. * refactor(native-chat): name the id tie-break comparator for what it does Two shared comparators differed only in whether they break timestamp ties by id, under near-identical names; spell the id tie-break in the name. * fix(native-chat): order structured chat rows by their journal position The desktop list and the host's conversation outline sorted structured rows by timestamp. The journal's contract is that the sequence orders the timeline and the timestamp is the provider's clock: a Codex ask writes all of its questions in one write (one sequence, one timestamp), and a row recovered after a crash carries an earlier clock at a later sequence. The host now records each item's place within the write that created it, keeps it across revisions like the sequence, and sends it as an optional field. Rows projected from the journal carry that position, and both the desktop list and the outline order journal rows by it. Rows outside the journal keep their rules: rank for the streaming and pending tail, outbox sends after every journal row, and terminal-backed chats keep time then id. * fix(native-chat): keep a refused send at its journal place, and keep list positions off worker reads A send the host journalled before the provider refused it is shown from the outbox, and it sorted after every journal row, so it dropped below whatever the agent wrote after it. It now takes the journal position of the submission the host recorded. Structured worker reads and their archives projected journal rows through the same projection, so they returned the list-only journal position on every message. The worker payload bound now drops it. * test(native-chat): a failed restart's row draws below the message it failed Since a message is accepted before its delivery starts the agent, a restart that fails is journalled after the message, and the refused message keeps that journal place in the chat. Both host paths now pin it through the chat's own projection: a start the host could not make, and a restarted child that exits before proving its start. Folds the journal reducer's batch item write onto fewer lines, which the merge of main pushed past the file's line limit.
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
// The transcript's projection of a conversation's messages into the rows it draws:
|
|
// ordered, tool runs folded into the turn that made them, harness turns dropped.
|
|
// Shared so the host's conversation outline asks "which user messages draw a row?"
|
|
// of exactly what the renderer's transcript runs, not a second reading of it.
|
|
|
|
import type { NativeChatMessage } from './native-chat-types'
|
|
import { compareAgentJournalPositions } from './agent-session-journal-position'
|
|
import { stripNoiseMessages } from './native-chat-noise'
|
|
import { foldToolMessages } from './native-chat-tool-fold'
|
|
|
|
/** Timestamp, then id. A null timestamp sorts first so a source that cannot supply
|
|
* one stays in place rather than jumping to the end. */
|
|
export function compareNativeChatMessagesByTime(
|
|
a: NativeChatMessage,
|
|
b: NativeChatMessage
|
|
): number {
|
|
const at = a.timestamp ?? Number.NEGATIVE_INFINITY
|
|
const bt = b.timestamp ?? Number.NEGATIVE_INFINITY
|
|
if (at !== bt) {
|
|
return at - bt
|
|
}
|
|
if (a.id < b.id) {
|
|
return -1
|
|
}
|
|
if (a.id > b.id) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
/** Rows the journal holds read in the journal's own order, never its clock: a
|
|
* batch shares one timestamp, and a row recovered after a crash carries an
|
|
* earlier one. A row not in the journal yet — a send still in the outbox — was
|
|
* made after everything the journal holds, so it follows them; only such rows,
|
|
* and terminal-backed transcripts, which have no journal, order by time. */
|
|
export function compareNativeChatTranscriptMessages(
|
|
a: NativeChatMessage,
|
|
b: NativeChatMessage
|
|
): number {
|
|
if (a.journalPosition && b.journalPosition) {
|
|
return compareAgentJournalPositions(a.journalPosition, b.journalPosition)
|
|
}
|
|
if (a.journalPosition || b.journalPosition) {
|
|
return a.journalPosition ? -1 : 1
|
|
}
|
|
return compareNativeChatMessagesByTime(a, b)
|
|
}
|
|
|
|
/** `compare` lets the renderer order its own tail rows (streaming, optimistic
|
|
* sends), which never exist on the host. */
|
|
export function projectNativeChatTranscriptMessages(
|
|
messages: readonly NativeChatMessage[],
|
|
compare: (
|
|
a: NativeChatMessage,
|
|
b: NativeChatMessage
|
|
) => number = compareNativeChatTranscriptMessages
|
|
): NativeChatMessage[] {
|
|
// Not `toSorted`: mobile's Hermes lacks it, and src/shared must stay loadable there.
|
|
return stripNoiseMessages(foldToolMessages(Array.from(messages).sort(compare)))
|
|
}
|