mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
A structured-chat turn used to end by tombstoning its running lifecycle item, which threw away the only durable record of when the turn ended. Completed "Worked for" labels therefore depended on the renderer having observed the turn finish, and vanished on reopen. The lifecycle item is now revised in place, never tombstoned: - running, with startedAt, at the provider's turn start - completed or interrupted, with completedAt, at the provider's terminal frame, a user stop, or a child exit the host observed - unverifiable, with no end, when a cold acquire finds a running row from a generation whose exit nobody observed Both timestamps are the execution host's clock at receipt, captured before the deferred sink, so the completed value is identical on every client and needs no client clock. Codex history restore uses the provider's own second-granular endpoints for turns that predate this change. Desktop and mobile read settled durations off the journal through one shared selector, and anchor the live counter on the host start with the client's local receipt so a skewed client clock never leaks into the label. Locally observed durations remain the fallback for hosts that still tombstone. Timestamps live inside the existing turnLifecycle field, which old clients strip, and every working-state consumer keys on state === 'running', so no capability negotiation is needed.
81 lines
4.0 KiB
TypeScript
81 lines
4.0 KiB
TypeScript
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'
|
|
import type {
|
|
AskAnswerSelection,
|
|
AskPrompt,
|
|
parseAskFromStatus
|
|
} from '../../../src/shared/native-chat-ask'
|
|
import type { detectAgentPermission } from './mobile-native-chat-permission'
|
|
import type { parseAgentQuestion } from './mobile-native-chat-question'
|
|
import type { NativeChatSettledTurn } from '../../../src/shared/native-chat-turn-status'
|
|
import type { MobileNativeChatSendOutcome } from './mobile-native-chat-send'
|
|
import type { MobileNativeChatPendingMessage } from './use-mobile-native-chat-drafts'
|
|
import type { useMobileNativeChatSession } from './use-mobile-native-chat-session'
|
|
import type { MobileNativeChatSessionOptionPickersProps } from './MobileNativeChatSessionOptionPickers'
|
|
|
|
export type MobileNativeChatController = {
|
|
/** Whether a tab's effective view is chat (per-tab override, else the default). */
|
|
isTabChatView: (tabId: string) => boolean
|
|
toggleTabChatView: (tabId: string) => void
|
|
showNativeChat: boolean
|
|
showNativeChatRef: MutableRefObject<boolean>
|
|
/** Resolved agent for the active chat tab (names the empty-state copy). */
|
|
nativeChatAgent: string | null
|
|
chatComposerText: string
|
|
setChatComposerText: Dispatch<SetStateAction<string>>
|
|
getChatComposerEditGeneration: () => number
|
|
chatPending: MobileNativeChatPendingMessage[]
|
|
chatImagePreviewsByMessageId: Record<string, string[]>
|
|
nativeChatSession: ReturnType<typeof useMobileNativeChatSession>
|
|
/** Structured lane: drives the per-turn status row and live tool progress. */
|
|
nativeChatStructured: boolean
|
|
nativeChatAgentWorking: boolean
|
|
/** Structured lane: host-recorded turn timing for the per-turn status rows. */
|
|
nativeChatWorkingStartedAt: number | null
|
|
nativeChatSettledTurns: ReadonlyMap<string, NativeChatSettledTurn> | null
|
|
nativeChatStreamingText?: string
|
|
/** Agent mid-turn, regardless of whether chat is the visible view. */
|
|
nativeChatStreamLive: boolean
|
|
/** Host/workspace/tab/session scope for stateful streaming suppression. */
|
|
nativeChatStreamScopeKey: string
|
|
nativeChatPermission: ReturnType<typeof detectAgentPermission>
|
|
nativeChatQuestion: ReturnType<typeof parseAgentQuestion>
|
|
/** The pending ask, already null while dismissed (dismissal lives here so it
|
|
* survives the chat-view subtree unmounting on a view toggle). */
|
|
nativeChatAsk: ReturnType<typeof parseAskFromStatus>
|
|
/** Stable key for the current ask card (keys the card component). */
|
|
nativeChatAskKey: string | null
|
|
/** Hide the current ask until a genuinely different question arrives. */
|
|
dismissNativeChatAsk: () => void
|
|
handleNativeChatAnswerAsk: (
|
|
prompt: AskPrompt,
|
|
selections: AskAnswerSelection[]
|
|
) => Promise<boolean>
|
|
handleNativeChatCancelAsk: () => Promise<boolean>
|
|
handleNativeChatRespondPermission: (text: string) => Promise<boolean>
|
|
handleNativeChatStop: () => void
|
|
nativeChatFilePaths: string[]
|
|
loadNativeChatFiles: (query: string) => void
|
|
handleNativeChatQuestionAnswer: (text: string) => Promise<boolean>
|
|
handleNativeChatSend: (text: string, images?: string[]) => Promise<boolean>
|
|
/** Outcome-preserving send: callers that pasted terminal input beforehand
|
|
* (image sends) must see 'unknown' to heal a possibly-orphaned paste. Such a
|
|
* caller passes its own `deadline` so the paste it already spent and this text
|
|
* body share one budget instead of holding the composer for two. */
|
|
handleNativeChatSendWithOutcome: (
|
|
text: string,
|
|
images?: string[],
|
|
deadline?: number,
|
|
attachments?: readonly {
|
|
id?: string
|
|
path: string
|
|
previewUri: string
|
|
}[]
|
|
) => Promise<MobileNativeChatSendOutcome>
|
|
/** Launch-context text still parked on the agent's TUI input line, or null.
|
|
* Image sends read it to size their leading clear (one Ctrl+U per line). */
|
|
readSeededLaunchDraft: () => string | null
|
|
/** Model/session-option pickers for the composer, or null when the active
|
|
* agent has no session-option catalog. */
|
|
nativeChatSessionOptions: MobileNativeChatSessionOptionPickersProps | null
|
|
}
|