mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* fix(agent-hooks): give resumed Claude sessions a sidebar row at SessionStart (STA-3386) Claude's hook set never registered SessionStart and normalizeClaudeEvent dropped it at ingest, so a resumed session that idled produced zero hook traffic and earned no sidebar agent row until the first prompt. - Register SessionStart in CLAUDE_EVENTS (local + remote installs). - Map lead SessionStart (startup/resume/clear) to an idle 'done' row, resetting stale roster/task/cron/tool/prompt state like the Codex path; compact restarts and child-attributed SessionStart stay dropped. - Thread hookEventName through the agent-status IPC payload so the completion coordinator can tell a session connect from a turn result; a SessionStart 'done' no longer raises agent-task-complete. * fix(agent-hooks): mark SessionStart rows as session boundaries, not completions (STA-3386) Review follow-up: represent the idle connect as a first-class sessionBoundary flag on the status payload instead of gating one renderer consumer on hookEventName. - sessionBoundary rides AgentStatusPayload/AgentStatusEntry (done-only, clamped like interrupted); drops the hookEventName IPC threading. - Completion-reactive consumers ignore session boundaries: the completion coordinator (task-complete notifications), automation dispatch observers (a connecting agent no longer completes the run and closes its tab), activity unread counts, and the dashboard finished timestamp; the status slice keeps boundaries out of stateHistory and preserves the flag across done->done repaints. - SessionStart sources are allowlisted (startup/resume/clear) so compact restarts or unknown sources fail closed mid-turn. - A live SessionStart now un-retires a reusable pane like a fresh prompt, so resume-in-reused-pane earns its row too. * fix(agent-hooks): keep session-boundary dones out of teardown and completion history (STA-3386) Review round 2: - A boundary done no longer deletes the pane's launch-config registry entry, so a resumed idle TUI keeps its registered-launch-agent identity evidence. - A boundary landing on a REAL done pushes that completion into stateHistory so the finished timestamp and unread badge survive a resume//clear right after a finish. - The done->done flag carry yields to turn evidence (assistant message or changed prompt) so a genuine completion can never be suppressed. - Star-nag value-moment observer and the server's OSC-equivalence dedupe now discriminate the flag. * fix(agent-hooks): keep a displaced completion unread in the sidebar badge (STA-3386) Review round 3: sidebar-badge mode counts only the live entry, so a session boundary landing on an unacknowledged completion silently dropped the sidebar badge while the agent-events count kept it. Count the displaced completion from history for boundary rows, and pin the behavior with countActivityUnread tests. * fix(agent-hooks): prevent SessionStart completion side effects (STA-3386) * fix(agent-hooks): preserve SessionStart through renderer IPC (STA-3386)
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { parseAuthenticatedFrame, parseReadyFrame } from './remote-runtime-request-frames'
|
|
import type { RemoteRuntimeClientError } from './remote-runtime-client-error'
|
|
import {
|
|
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
|
|
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY
|
|
} from './protocol-version'
|
|
import { dispatchSharedControlFrame } from './remote-runtime-shared-control-frame-dispatch'
|
|
import { parseSharedControlFrame } from './remote-runtime-shared-control-protocol'
|
|
import type { SharedControlRetiredRequestIds } from './remote-runtime-shared-control-retired-request-ids'
|
|
import { resolveSharedControlReadyWaiters } from './remote-runtime-shared-control-state'
|
|
import type {
|
|
SharedControlConnectionState,
|
|
SharedControlLogicalSubscription,
|
|
SharedControlPendingRequest,
|
|
SharedControlReadyWaiter
|
|
} from './remote-runtime-shared-control-types'
|
|
|
|
export function handleSharedControlTextFrame(args: {
|
|
frame: string
|
|
state: SharedControlConnectionState
|
|
sharedKey: Uint8Array | null
|
|
deviceToken: string
|
|
environmentId?: string
|
|
pendingRequests: Map<string, SharedControlPendingRequest<unknown>>
|
|
subscriptions: Map<string, SharedControlLogicalSubscription<unknown>>
|
|
retiredRequestIds: SharedControlRetiredRequestIds
|
|
readyWaiters: SharedControlReadyWaiter[]
|
|
setState: (state: SharedControlConnectionState) => void
|
|
handleSocketClosed: (error: RemoteRuntimeClientError) => void
|
|
sendEncrypted: (payload: unknown) => boolean
|
|
markReady: () => void
|
|
replaySubscriptions: () => void
|
|
}): void {
|
|
if (args.state === 'awaiting_ready') {
|
|
const error = parseReadyFrame(args.frame)
|
|
if (error) {
|
|
args.handleSocketClosed(error)
|
|
return
|
|
}
|
|
args.setState('awaiting_authenticated')
|
|
args.sendEncrypted({
|
|
type: 'e2ee_auth',
|
|
deviceToken: args.deviceToken,
|
|
clientCapabilities: [
|
|
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY,
|
|
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY
|
|
]
|
|
})
|
|
return
|
|
}
|
|
|
|
const parsed = parseSharedControlFrame(args.frame, args.sharedKey, args.state)
|
|
if (parsed.type === 'auth') {
|
|
const error = parseAuthenticatedFrame(parsed.plaintext)
|
|
if (error) {
|
|
args.handleSocketClosed(error)
|
|
return
|
|
}
|
|
args.setState('ready')
|
|
args.markReady()
|
|
resolveSharedControlReadyWaiters(args.readyWaiters)
|
|
args.replaySubscriptions()
|
|
return
|
|
}
|
|
|
|
if (parsed.type === 'error') {
|
|
args.handleSocketClosed(parsed.error)
|
|
return
|
|
}
|
|
|
|
dispatchSharedControlFrame({
|
|
environmentId: args.environmentId,
|
|
frame: parsed.frame,
|
|
pendingRequests: args.pendingRequests,
|
|
subscriptions: args.subscriptions,
|
|
retiredRequestIds: args.retiredRequestIds,
|
|
deviceToken: args.deviceToken,
|
|
send: args.sendEncrypted
|
|
})
|
|
}
|