mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 16:02:45 +00:00
fix(mobile): remove a stream error callback that could never fire
The native chat subscribe declared an onError the adapter never invoked, since the transport has none: a stream error arrives as an error frame through the listener, which is where the caller already handles it. The hook's second callback was unreachable, so it goes with the parameter. The terminal adapter's acknowledge was a no-op with no caller here or on main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -36,11 +36,12 @@ export type HostSessionNativeChatImageAttachResult =
|
||||
export type HostSessionNativeChatOperations = {
|
||||
/** Whether the serving host can read this workspace's agent transcripts. */
|
||||
readability(workspaceId: string): Promise<boolean>
|
||||
/** No error callback: the transport has none, and a stream error arrives as an `error` frame
|
||||
* through `onEvent`, which is where the caller already handles it. */
|
||||
subscribe(
|
||||
target: HostSessionNativeChatTarget,
|
||||
limit: number,
|
||||
onEvent: (event: MobileNativeChatStreamFrame) => void,
|
||||
onError: () => void
|
||||
onEvent: (event: MobileNativeChatStreamFrame) => void
|
||||
): () => void
|
||||
read(
|
||||
target: HostSessionNativeChatTarget,
|
||||
|
||||
@@ -57,7 +57,6 @@ export type HostSessionTerminalOperations = {
|
||||
onEvent: (event: HostSessionTerminalStreamEvent) => void,
|
||||
onError: () => void
|
||||
): () => void
|
||||
acknowledge(terminalId: string, throughSequence: number): void
|
||||
sendInput(
|
||||
terminalId: string,
|
||||
text: string,
|
||||
|
||||
@@ -22,7 +22,6 @@ export function nativeHostSessionTerminalOperations(
|
||||
)
|
||||
return unsubscribe
|
||||
},
|
||||
acknowledge() {},
|
||||
async sendInput(terminalId, text, enter, clientId) {
|
||||
return client
|
||||
.sendRequest(
|
||||
|
||||
@@ -156,71 +156,61 @@ export function useMobileNativeChatSession(args: {
|
||||
sessionId,
|
||||
transcriptPath
|
||||
})
|
||||
const unsubscribe = operations.subscribe(
|
||||
target,
|
||||
limitRef.current,
|
||||
(raw) => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
const frame = raw as MobileNativeChatStreamFrame
|
||||
const applied = applyMobileNativeChatStreamFrame({
|
||||
merger: mergerRef.current,
|
||||
frame,
|
||||
limit: limitRef.current,
|
||||
replaceSnapshot: !snapshotSeenRef.current
|
||||
})
|
||||
if (applied.kind === 'ignored') {
|
||||
return
|
||||
}
|
||||
if (applied.kind === 'error') {
|
||||
setRead({ operations, identity, status: 'error' })
|
||||
setError(applied.error)
|
||||
return
|
||||
}
|
||||
if (frame.type === 'snapshot' && !applied.pending) {
|
||||
// A pending window has no transcript behind it, so the snapshot that
|
||||
// follows is still this subscription's base, not a reconnect replay.
|
||||
snapshotSeenRef.current = true
|
||||
}
|
||||
if (applied.windowReplaced || frame.type === 'snapshot') {
|
||||
// Why: any authoritative window (and any replay merge) invalidates an
|
||||
// in-flight older-page request; stale results must not land on it.
|
||||
streamGenerationRef.current += 1
|
||||
loadingEarlierRef.current = false
|
||||
setLoadingEarlier(false)
|
||||
}
|
||||
if (applied.windowReplaced) {
|
||||
// Only a genuinely fresh window resets the grown read window — an
|
||||
// overlapping reconnect replay keeps the paged-in history and limit.
|
||||
limitRef.current = INITIAL_LIMIT
|
||||
beforeOffsetRef.current = applied.beforeOffset ?? null
|
||||
setHasMore(applied.hasMore ?? applied.messages.length >= INITIAL_LIMIT)
|
||||
}
|
||||
setMessages(applied.messages)
|
||||
if (!applied.windowReplaced && applied.hasMore != null) {
|
||||
setHasMore(applied.hasMore)
|
||||
}
|
||||
if (!applied.windowReplaced && applied.beforeOffset != null) {
|
||||
beforeOffsetRef.current = applied.beforeOffset
|
||||
}
|
||||
if (applied.cursorInvalidated) {
|
||||
// Fall back to a growing-tail read so history trimmed by live appends
|
||||
// cannot leave a gap between the retained window and the old cursor.
|
||||
streamGenerationRef.current += 1
|
||||
loadingEarlierRef.current = false
|
||||
setLoadingEarlier(false)
|
||||
beforeOffsetRef.current = null
|
||||
}
|
||||
setRead({ operations, identity, status: applied.pending ? 'awaiting-transcript' : 'ready' })
|
||||
},
|
||||
() => {
|
||||
if (!cancelled) {
|
||||
setRead({ operations, identity, status: 'error' })
|
||||
setError('Transcript stream failed')
|
||||
}
|
||||
const unsubscribe = operations.subscribe(target, limitRef.current, (raw) => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
)
|
||||
const frame = raw as MobileNativeChatStreamFrame
|
||||
const applied = applyMobileNativeChatStreamFrame({
|
||||
merger: mergerRef.current,
|
||||
frame,
|
||||
limit: limitRef.current,
|
||||
replaceSnapshot: !snapshotSeenRef.current
|
||||
})
|
||||
if (applied.kind === 'ignored') {
|
||||
return
|
||||
}
|
||||
if (applied.kind === 'error') {
|
||||
setRead({ operations, identity, status: 'error' })
|
||||
setError(applied.error)
|
||||
return
|
||||
}
|
||||
if (frame.type === 'snapshot' && !applied.pending) {
|
||||
// A pending window has no transcript behind it, so the snapshot that
|
||||
// follows is still this subscription's base, not a reconnect replay.
|
||||
snapshotSeenRef.current = true
|
||||
}
|
||||
if (applied.windowReplaced || frame.type === 'snapshot') {
|
||||
// Why: any authoritative window (and any replay merge) invalidates an
|
||||
// in-flight older-page request; stale results must not land on it.
|
||||
streamGenerationRef.current += 1
|
||||
loadingEarlierRef.current = false
|
||||
setLoadingEarlier(false)
|
||||
}
|
||||
if (applied.windowReplaced) {
|
||||
// Only a genuinely fresh window resets the grown read window — an
|
||||
// overlapping reconnect replay keeps the paged-in history and limit.
|
||||
limitRef.current = INITIAL_LIMIT
|
||||
beforeOffsetRef.current = applied.beforeOffset ?? null
|
||||
setHasMore(applied.hasMore ?? applied.messages.length >= INITIAL_LIMIT)
|
||||
}
|
||||
setMessages(applied.messages)
|
||||
if (!applied.windowReplaced && applied.hasMore != null) {
|
||||
setHasMore(applied.hasMore)
|
||||
}
|
||||
if (!applied.windowReplaced && applied.beforeOffset != null) {
|
||||
beforeOffsetRef.current = applied.beforeOffset
|
||||
}
|
||||
if (applied.cursorInvalidated) {
|
||||
// Fall back to a growing-tail read so history trimmed by live appends
|
||||
// cannot leave a gap between the retained window and the old cursor.
|
||||
streamGenerationRef.current += 1
|
||||
loadingEarlierRef.current = false
|
||||
setLoadingEarlier(false)
|
||||
beforeOffsetRef.current = null
|
||||
}
|
||||
setRead({ operations, identity, status: applied.pending ? 'awaiting-transcript' : 'ready' })
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
|
||||
Reference in New Issue
Block a user