mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* fix(sidebar): keep agent rows ordered by their session start once history wraps Sidebar agent rows are ordered by the session's first reported state, read as `stateHistory[0].startedAt`. `stateHistory` is a rolling window capped at AGENT_STATE_HISTORY_MAX that trims oldest-first, so past ~20 transitions that row is a recent turn: the row's identity clock silently becomes an activity clock and busy agents re-sort themselves on every turn. Latch the session origin in `firstStateStartedAt` when the entry is built and read it through `agentEntrySessionStartedAt`, keeping the history lookup as a fallback for entries that predate the field. * test(sidebar): cover row order through activity and retention * fix(sidebar): preserve session row order through activity and retention * test: complete paired terminal status fixtures * fix: carry accepted session origins across paired rows * test: cover paired origin work and mobile action parity * test(mobile): expect sessionLive in the phone chat identity after main's start-a-chat change
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import type { AgentStatusEntry } from './agent-status-types'
|
|
import { agentProviderSessionsEqual } from './agent-session-resume'
|
|
import { sameAgentStatusTerminalOwner } from './agent-status-terminal-owner'
|
|
|
|
type SessionIdentity = Pick<
|
|
AgentStatusEntry,
|
|
'agentType' | 'providerSession' | 'connectionId' | 'worktreeId'
|
|
>
|
|
|
|
function finite(value: number | undefined): number | undefined {
|
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
}
|
|
|
|
export function agentEntrySessionStartedAt(
|
|
entry: Pick<AgentStatusEntry, 'firstStateStartedAt' | 'stateHistory' | 'stateStartedAt'>
|
|
): number {
|
|
return (
|
|
finite(entry.firstStateStartedAt) ??
|
|
finite(entry.stateHistory[0]?.startedAt) ??
|
|
entry.stateStartedAt
|
|
)
|
|
}
|
|
|
|
/** Missing identity enriches a row; only accepted contradictory identity starts another session. */
|
|
export function resolveAgentSessionStartedAt(args: {
|
|
entry: AgentStatusEntry
|
|
previous?: AgentStatusEntry
|
|
previousStartedAt?: number
|
|
identity?: SessionIdentity
|
|
}): number {
|
|
const { entry, previous, previousStartedAt, identity = entry } = args
|
|
if (!previous) {
|
|
return agentEntrySessionStartedAt(entry)
|
|
}
|
|
const agentChanged =
|
|
previous.agentType &&
|
|
previous.agentType !== 'unknown' &&
|
|
identity.agentType &&
|
|
identity.agentType !== 'unknown' &&
|
|
previous.agentType !== identity.agentType
|
|
const sessionChanged =
|
|
previous.providerSession &&
|
|
identity.providerSession &&
|
|
!agentProviderSessionsEqual(
|
|
identity.agentType,
|
|
{
|
|
...previous.providerSession,
|
|
transcriptPath:
|
|
previous.providerSession.transcriptPath ?? identity.providerSession.transcriptPath
|
|
},
|
|
{
|
|
...identity.providerSession,
|
|
transcriptPath:
|
|
identity.providerSession.transcriptPath ?? previous.providerSession.transcriptPath
|
|
}
|
|
)
|
|
const worktreeChanged =
|
|
previous.worktreeId &&
|
|
identity.worktreeId &&
|
|
!sameAgentStatusTerminalOwner(
|
|
{ worktreeId: previous.worktreeId, connectionId: null },
|
|
{ worktreeId: identity.worktreeId, connectionId: null }
|
|
)
|
|
const connectionChanged =
|
|
previous.connectionId !== undefined &&
|
|
identity.connectionId !== undefined &&
|
|
!sameAgentStatusTerminalOwner(previous, identity)
|
|
if (agentChanged || sessionChanged || worktreeChanged || connectionChanged) {
|
|
// State clocks can span sessions; only the accepted source dates a replacement.
|
|
return finite(entry.firstStateStartedAt) ?? finite(entry.evidenceObservedAt) ?? entry.updatedAt
|
|
}
|
|
return (
|
|
finite(previous.firstStateStartedAt) ??
|
|
finite(previousStartedAt) ??
|
|
agentEntrySessionStartedAt(previous)
|
|
)
|
|
}
|