import { isPendingActivationViable, selectPlanDecisionForTurn, selectPlanTurnDismissed, type AgentActivityDisplayStatus, type AgentActivityMessage, type AgentActivitySessionGoalSyncState, type AgentActivityTurn, type CanonicalAgentSession, type PendingActivationIntentRecord, type SessionGoalControlPresentation, type SessionRuntimeAvailability, type AgentSessionEngine } from "@tutti-os/agent-activity-core"; import { useEffect, useMemo } from "react"; import type { AgentGUIRuntime } from "../../../agentActivityRuntime"; import { translate } from "../../../i18n/index"; import type { AgentConversationVM } from "../../../shared/agentConversation/contracts/agentConversationVM"; import type { AgentApprovalItemVM } from "../../../shared/agentConversation/contracts/agentApprovalItemVM"; import { latestPlanTurnId, planImplementationPromptFromPlanTurn } from "../../../shared/agentConversation/planImplementationPresentation"; import type { AgentSessionState } from "../../../shared/agentSessionTypes"; import type { AppErrorCode } from "../../../shared/contracts/dto"; import type { AgentGUIObservationGapSource, AgentGUIInteractionReadinessReason, AgentGUIInteractionReadinessSource, AgentGUITargetConnectionSource } from "../../../types"; import { useEngineSelector } from "../../../shared/engine/useEngineSelector"; import type { AgentGUIConversationSummary, AgentGUIInteractivePrompt } from "../model/agentGuiConversationModel"; import { isDifferentKnownConversationOwner, resolveAgentGUIComposerGate } from "../model/agentGuiComposerGate"; import type { AgentGUISessionChrome } from "../model/agentGuiNodeTypes"; import { composerSettingsSupportFromOptions } from "../model/composerSettingsSupport"; import { agentActivityDisplayStatusBusy, conversationBusyStatus } from "./agentGuiController.draftMessageHelpers"; import { isNonRetryableResumeErrorCode } from "./agentGuiController.errors"; import { projectAgentGUIMessagesToTimelineItems } from "./agentGuiController.promptHelpers"; import { promptRequestId } from "./agentGuiController.diagnostics"; import { reportAgentGUIRenderStateDiagnostic } from "./agentGuiController.reporting"; import { useAgentGUITargetConnectionState } from "./useAgentGUITargetConnectionState"; import { useAgentObservationGap } from "../../../shared/agentConversation/AgentObservationGapContext"; import { resolveAgentGUIInteractionReadinessIdentity } from "./agentGuiController.interactionHelpers"; import { useAgentGUIInteractionReadiness } from "./useAgentGUIInteractionReadiness"; interface CurrentValue { current: T; } interface ExactInteractionPromptIdentity { agentSessionId?: string | null; requestId?: string | null; turnId?: string | null; } function useExactInteractionReadiness(input: { prompt: ExactInteractionPromptIdentity | null; source?: AgentGUIInteractionReadinessSource | null; workspaceId: string; }) { const identity = useMemo( () => resolveAgentGUIInteractionReadinessIdentity({ agentSessionId: input.prompt?.agentSessionId, requestId: input.prompt?.requestId, turnId: input.prompt?.turnId, workspaceId: input.workspaceId }), [ input.prompt?.agentSessionId, input.prompt?.requestId, input.prompt?.turnId, input.workspaceId ] ); const readiness = useAgentGUIInteractionReadiness({ identity, required: input.prompt !== null, source: input.source }); return { block: readiness?.status === "blocked" ? readiness : null, ownsInteraction: input.source != null && input.prompt !== null }; } export function resolveAgentGUISharingRevokedRecovery(input: { activeConversationId: string | null; selectedAgentTargetOwnerLabel: string | null; selectedAgentTargetUnavailable: boolean; selectedAgentTargetUnavailableReason: string | null; sessionRuntimeBlock: Extract< SessionRuntimeAvailability, { state: "blocked" } > | null; }): AgentGUISessionChrome["recovery"] { const selectedTargetSharingRevoked = input.activeConversationId === null && input.selectedAgentTargetUnavailable && input.selectedAgentTargetUnavailableReason === "agent_sharing_revoked"; if ( input.sessionRuntimeBlock?.reason !== "agent_sharing_revoked" && !selectedTargetSharingRevoked ) { return null; } return { kind: "agent-sharing-revoked", message: translate("agentHost.agentGui.agentSharingRevoked", { owner: (input.sessionRuntimeBlock?.reason === "agent_sharing_revoked" ? input.sessionRuntimeBlock.ownerLabel : null) ?? input.selectedAgentTargetOwnerLabel ?? translate("agentHost.agentGui.sharedDeviceLabel") }), canRetry: false }; } interface UseAgentGUISessionPresentationInput { activeConversation: AgentGUIConversationSummary | null; activeConversationId: string | null; activeEngineActiveTurn: AgentActivityTurn | null; activeEngineAvailability: "available" | "blocked" | "missing"; activeEngineHasPendingInteractions: boolean; activeHasPendingSubmitStopTarget: boolean; activeEngineLatestTurn: AgentActivityTurn | null; activeEngineRuntimeAvailability: SessionRuntimeAvailability | null; activeEngineRuntimeActivity: "idle" | "running"; activeEngineSession: CanonicalAgentSession | null; /** In-flight / waiting session settings update; blocks submit until settled. */ activeEngineSettingsUpdate: { status: string; } | null; activeGoalControlPresentation: SessionGoalControlPresentation; activeLatestPendingSubmitTurnId: string | null; activeLiveState: "inactive" | "activating" | "active" | "failed"; activeMessages: readonly AgentActivityMessage[]; activePendingActivation: PendingActivationIntentRecord | null; activeSessionState: AgentSessionState | null; activeTimelineItems: ReturnType< typeof projectAgentGUIMessagesToTimelineItems >; activationError: string | null; activationErrorCode: AppErrorCode | null; activationState: "inactive" | "activating" | "active" | "failed" | null; activityDisplayStatus: AgentActivityDisplayStatus | null; agentActivityRuntime: AgentGUIRuntime; composerSupport: ReturnType; conversation: AgentConversationVM | null; currentUserId?: string | null; hasUnconfirmedSubmit: boolean; isCreatingConversation: boolean; isInterrupting: boolean; isLoadingMessages: boolean; isRespondingToInteraction: boolean; isSubmitting: boolean; lastRenderStateDiagnosticKeyRef: CurrentValue; pendingApproval: AgentApprovalItemVM | null; planImplementationTurnIdRef: CurrentValue; providerReadinessGate: | import("../../../types").AgentGUIProviderReadinessGate | null; selectedAgentTargetUnavailable: boolean; selectedAgentTargetUnavailableReason: string | null; selectedAgentTargetOwnerLabel: string | null; agentTargetsLoading: boolean; ownerDeviceLabel?: string | null; serverInteractivePrompt: AgentGUIInteractivePrompt | null; sessionEngine: AgentSessionEngine; targetConnectionAgentTargetId?: string | null; targetConnectionSource?: AgentGUITargetConnectionSource | null; interactionReadinessSource?: AgentGUIInteractionReadinessSource | null; observationGapSource?: AgentGUIObservationGapSource | null; workspaceId: string; } export function useAgentGUISessionPresentation( input: UseAgentGUISessionPresentationInput ) { const latestTimelinePlanTurnId = latestPlanTurnId(input.activeTimelineItems); const planImplementationTurnId = input.activeConversationId !== null && input.composerSupport.planImplementation && input.composerSupport.plan && input.activeEngineLatestTurn?.phase === "settled" && input.activeEngineLatestTurn.outcome === "completed" && input.activeEngineLatestTurn.turnId === latestTimelinePlanTurnId ? latestTimelinePlanTurnId : null; const activePlanDecision = useEngineSelector(input.sessionEngine, (state) => selectPlanDecisionForTurn( state, input.activeConversationId, planImplementationTurnId ) ); const activePlanTurnDismissed = useEngineSelector( input.sessionEngine, (state) => selectPlanTurnDismissed( state, input.activeConversationId, planImplementationTurnId ) ); input.planImplementationTurnIdRef.current = planImplementationTurnId; const planImplementationPrompt = planImplementationTurnId !== null && input.activeConversationId !== null && !activePlanTurnDismissed ? planImplementationPromptFromPlanTurn( planImplementationTurnId, input.activeConversation?.title ?? "" ) : null; const unresolvedInteractivePrompt = input.serverInteractivePrompt ?? planImplementationPrompt; const interactiveReadinessPrompt = unresolvedInteractivePrompt?.kind === "plan-implementation" ? null : unresolvedInteractivePrompt; const interactiveReadiness = useExactInteractionReadiness({ prompt: interactiveReadinessPrompt, source: input.interactionReadinessSource, workspaceId: input.workspaceId }); const approvalReadiness = useExactInteractionReadiness({ prompt: input.pendingApproval, source: input.interactionReadinessSource, workspaceId: input.workspaceId }); const pendingInteractivePrompt = interactiveReadiness.block?.reason === "binding_revoked" ? null : unresolvedInteractivePrompt; const pendingApproval = approvalReadiness.block?.reason === "binding_revoked" ? null : input.pendingApproval; const activeInteractionReadiness = pendingInteractivePrompt !== null ? interactiveReadiness : pendingApproval !== null ? approvalReadiness : unresolvedInteractivePrompt !== null ? interactiveReadiness : approvalReadiness; const interactionReadinessBlock = activeInteractionReadiness.block; const interactionReadinessOwnsInteraction = activeInteractionReadiness.ownsInteraction; const interactivePromptResponsePending = input.isRespondingToInteraction || interactiveReadiness.block !== null || activePlanDecision?.status === "requested" || activePlanDecision?.status === "unknown"; const approvalResponsePending = input.isRespondingToInteraction || approvalReadiness.block !== null; const activeActivationAwaitsInitialTurn = Boolean( input.activeConversationId && input.activePendingActivation?.mode === "new" && input.activePendingActivation.agentSessionId === input.activeConversationId && input.activePendingActivation.initialTurnExpected && isPendingActivationViable(input.activePendingActivation) && !input.activeEngineLatestTurn ); const activeInitialGoalSetHasPendingOperation = Boolean( input.activeConversationId && input.activePendingActivation?.mode === "new" && input.activePendingActivation.agentSessionId === input.activeConversationId && input.activePendingActivation.initialGoalControl?.action === "set" && isPendingActivationViable(input.activePendingActivation) && input.activeGoalControlPresentation.goal?.status === "active" && initialGoalHasPendingOperationEvidence({ goalIsOptimistic: input.activeGoalControlPresentation.optimistic, syncState: input.activeEngineSession?.goalSyncState ?? null }) && !input.activeEngineLatestTurn ); const hasPendingTurnStartEvidence = Boolean( input.activeConversationId && (activeActivationAwaitsInitialTurn || activeInitialGoalSetHasPendingOperation || input.hasUnconfirmedSubmit || input.isSubmitting || (!input.activeEngineSession && input.activeLatestPendingSubmitTurnId)) ); const activeSubmitBlocked = input.activeEngineAvailability === "blocked"; const sessionRuntimeBlock = input.activeEngineRuntimeAvailability?.state === "blocked" ? input.activeEngineRuntimeAvailability : null; const sessionRuntimeBlockedReason = sessionRuntimeBlock?.reason ?? null; const targetConnection = useAgentGUITargetConnectionState({ agentTargetId: input.targetConnectionAgentTargetId, source: input.targetConnectionSource }); const observationGap = useAgentObservationGap( input.activeEngineSession?.agentSessionId ?? input.activeConversationId, input.activeEngineActiveTurn?.turnId, input.observationGapSource ); const hasCanonicalTerminalActivity = input.activityDisplayStatus === "completed" || input.activityDisplayStatus === "failed" || input.activityDisplayStatus === "canceled"; const executing = !hasCanonicalTerminalActivity && (agentActivityDisplayStatusBusy(input.activityDisplayStatus) || (input.activeEngineActiveTurn !== null && input.activeEngineActiveTurn.phase !== "settled") || (!input.activeEngineSession && input.activeEngineRuntimeActivity === "running")); const isAwaitingTurnStart = hasPendingTurnStartEvidence && !executing; const activeConversationBusy = !hasCanonicalTerminalActivity && (executing || (!input.activeEngineSession && conversationBusyStatus(input.activeConversation?.status ?? null))); const activeSessionResumable = input.activeEngineSession?.resumable ?? input.activeConversation?.resumable ?? input.activeSessionState?.resumable; const activeConversationRequiresResume = input.activeConversationId !== null && input.activationState !== "active"; const activeConversationResumeUnavailable = activeConversationRequiresResume && activeSessionResumable === false; const sessionChromeRawState = useMemo< AgentGUISessionChrome["rawState"] >(() => { const agentSessionId = input.activeEngineSession?.agentSessionId ?? input.activeGoalControlPresentation.agentSessionId ?? input.activeConversationId; if (!agentSessionId) { return null; } return { agentSessionId, goal: input.activeGoalControlPresentation.goal, goalControlStatus: input.activeGoalControlPresentation.status, goalIsOptimistic: input.activeGoalControlPresentation.optimistic }; }, [ input.activeConversationId, input.activeEngineSession?.agentSessionId, input.activeGoalControlPresentation ]); const sessionChrome = useMemo(() => { if (interactionReadinessBlock?.reason === "binding_revoked") { return { auth: null, approval: null, recovery: { kind: "agent-sharing-revoked", message: translate("agentHost.agentGui.agentSharingRevoked", { owner: input.selectedAgentTargetOwnerLabel ?? translate("agentHost.agentGui.sharedDeviceLabel") }), canRetry: false }, rawState: sessionChromeRawState }; } if (interactionReadinessBlock !== null) { const synchronizing = interactionReadinessBlock.reason === "synchronizing"; const device = input.ownerDeviceLabel?.trim() || translate("agentHost.agentGui.sharedDeviceLabel"); return { auth: null, approval: pendingApproval, recovery: { kind: synchronizing ? "transport-connecting" : "transport-unavailable", message: translate( synchronizing ? "agentHost.agentGui.runtimeSynchronizingProgress" : input.activeEngineActiveTurn !== null ? "agentHost.agentGui.runtimeUnavailableActive" : "agentHost.agentGui.runtimeUnavailable", { device } ), canRetry: false, interactionScoped: true }, rawState: sessionChromeRawState }; } if (!interactionReadinessOwnsInteraction) { const sharingRevokedRecovery = resolveAgentGUISharingRevokedRecovery({ activeConversationId: input.activeConversationId, selectedAgentTargetOwnerLabel: input.selectedAgentTargetOwnerLabel, selectedAgentTargetUnavailable: input.selectedAgentTargetUnavailable, selectedAgentTargetUnavailableReason: input.selectedAgentTargetUnavailableReason, sessionRuntimeBlock }); if (sharingRevokedRecovery) { return { auth: null, approval: null, recovery: sharingRevokedRecovery, rawState: sessionChromeRawState }; } } if ( !interactionReadinessOwnsInteraction && (targetConnection.visibleState?.status === "connecting" || targetConnection.visibleState?.status === "unavailable") ) { const device = input.ownerDeviceLabel?.trim() || translate("agentHost.agentGui.sharedDeviceLabel"); const reconnecting = targetConnection.visibleState.status === "connecting"; const retryAttempt = targetConnection.visibleState.retryAttempt; return { auth: null, approval: null, recovery: { kind: reconnecting ? "transport-connecting" : "transport-unavailable", message: translate( reconnecting ? retryAttempt > 0 ? "agentHost.agentGui.runtimeReconnectingAttempt" : "agentHost.agentGui.runtimeConnecting" : input.activeEngineActiveTurn !== null ? "agentHost.agentGui.runtimeUnavailableActive" : "agentHost.agentGui.runtimeUnavailable", { attempt: retryAttempt, device } ), canRetry: false }, rawState: sessionChromeRawState }; } if (!interactionReadinessOwnsInteraction && observationGap !== null) { return { auth: null, approval: null, recovery: { kind: "transport-connecting", message: translate("agentHost.agentGui.runtimeSynchronizingProgress"), canRetry: false }, rawState: sessionChromeRawState }; } const normalizedError = input.activationError?.trim() ?? ""; const authState = input.activeSessionState?.authState?.trim() ?? ""; const providerSessionMissing = isNonRetryableResumeErrorCode( input.activationErrorCode ); const isAuthError = !providerSessionMissing && (authState !== "" || (normalizedError !== "" && /auth|sign in|log in|login|unauthorized|authenticated/i.test( normalizedError ))); const isResumeNotLocalRecovery = providerSessionMissing || activeConversationResumeUnavailable; const recoveryMessage = isResumeNotLocalRecovery ? translate( input.activeConversation?.isImported === true ? "messages.agentImportedSessionResumeUnavailable" : "messages.agentResumeSessionNotLocal" ) : normalizedError; return { auth: providerSessionMissing ? null : authState !== "" ? { message: authState } : isAuthError ? { message: normalizedError } : null, approval: pendingApproval, recovery: input.activeLiveState === "activating" && input.activePendingActivation?.mode !== "new" ? { kind: "activating", message: translate("messages.agentSessionReconnecting") } : !isAuthError && recoveryMessage ? isResumeNotLocalRecovery ? { kind: "resume-unavailable", message: recoveryMessage, followupAction: "continue-in-new-conversation" as const } : { kind: "failed", message: recoveryMessage, canRetry: false } : null, rawState: sessionChromeRawState }; }, [ activeConversationResumeUnavailable, input.activeConversation, input.activationError, input.activationErrorCode, input.activeConversationId, input.activeEngineActiveTurn, input.activeLiveState, input.activeSessionState, input.activePendingActivation?.mode, input.ownerDeviceLabel, input.selectedAgentTargetOwnerLabel, input.selectedAgentTargetUnavailable, input.selectedAgentTargetUnavailableReason, interactionReadinessBlock, interactionReadinessOwnsInteraction, pendingApproval, sessionRuntimeBlock, targetConnection.visibleState, observationGap, sessionChromeRawState ]); const hasNonRetryableRecoveryFailure = (sessionChrome.recovery?.kind === "failed" && sessionChrome.recovery.canRetry === false) || sessionChrome.recovery?.kind === "resume-unavailable"; const authBlocked = sessionChrome.auth !== null; const isCollaboratorConversation = isDifferentKnownConversationOwner({ conversationUserId: input.activeConversation?.userId, currentUserId: input.currentUserId }); const hasPendingApproval = pendingApproval !== null; const hasPendingInteractivePrompt = pendingInteractivePrompt !== null; const settingsUpdateStatus = input.activeEngineSettingsUpdate?.status; const settingsUpdatePending = settingsUpdateStatus === "inFlight" || settingsUpdateStatus === "waitingForRuntime" || settingsUpdateStatus === "unknown" || settingsUpdateStatus === "failed"; const composerGate = useMemo( () => resolveAgentGUIComposerGate({ activeConversationBusy, activeConversationId: input.activeConversationId, activeEngineHasPendingInteractions: input.activeEngineHasPendingInteractions, activeLiveState: input.activeLiveState, activeConversationResumeUnavailable, agentTargetsLoading: input.agentTargetsLoading, authBlocked, hasNonRetryableRecoveryFailure, isCollaboratorConversation, isCreatingConversation: input.isCreatingConversation, isInterrupting: input.isInterrupting, isAwaitingTurnStart, isSubmitting: input.isSubmitting, pendingApproval: hasPendingApproval, pendingInteractivePrompt: hasPendingInteractivePrompt, providerReadinessGate: input.providerReadinessGate, selectedAgentTargetUnavailable: input.selectedAgentTargetUnavailable, settingsUpdatePending, sessionRuntimeBlockedReason, targetConnectionBlocked: targetConnection.blocked || observationGap !== null || interactionReadinessBlock !== null }), [ activeConversationBusy, activeConversationResumeUnavailable, authBlocked, hasNonRetryableRecoveryFailure, hasPendingInteractivePrompt, input.activeConversationId, input.activeEngineHasPendingInteractions, input.activeLiveState, input.agentTargetsLoading, input.isCreatingConversation, input.isInterrupting, input.isSubmitting, input.providerReadinessGate, input.selectedAgentTargetUnavailable, isCollaboratorConversation, isAwaitingTurnStart, hasPendingApproval, sessionRuntimeBlockedReason, settingsUpdatePending, targetConnection.blocked, observationGap, interactionReadinessBlock ] ); const isRespondingApproval = approvalResponsePending || (!approvalReadiness.ownsInteraction && composerGate.runtime.status === "blocked"); const isRespondingInteractivePrompt = interactivePromptResponsePending || (!interactiveReadiness.ownsInteraction && composerGate.runtime.status === "blocked"); const canSubmit = composerGate.submission.status === "ready"; const canQueueWhileBusy = composerGate.submission.status === "queue"; const hasSentUserMessage = input.activeTimelineItems.some( (item) => item.role === "user" ); useEffect(() => { const diagnosticKey = [ input.activeConversationId ?? "", input.activeConversation?.status ?? "", input.activityDisplayStatus ?? "", input.activeLiveState, input.activeEngineActiveTurn?.phase ?? input.activeEngineLatestTurn?.phase ?? "", input.activeEngineActiveTurn?.outcome ?? input.activeEngineLatestTurn?.outcome ?? "", input.activeEngineActiveTurn?.turnId ?? "", input.activeEngineAvailability, input.activeEngineRuntimeActivity, activeConversationBusy ? "busy" : "ready", isAwaitingTurnStart ? "awaiting-turn-start" : "turn-start-observed", activeSubmitBlocked ? "submit-blocked" : "submit-open", pendingApproval?.requestId ?? "", promptRequestId(pendingInteractivePrompt) ?? "", input.conversation?.activity.status ?? "", input.conversation?.sourceDetail.showProcessingIndicator ? "show-processing" : "hide-processing", input.conversation?.rows .filter((row) => row.kind === "processing") .map((row) => `${row.id}:${row.turnId ?? ""}`) .join(",") ?? "", input.isCreatingConversation ? "creating" : "", input.isLoadingMessages ? "loading-messages" : "", input.isSubmitting ? "submitting" : "", canSubmit ? "can-submit" : "cannot-submit", canQueueWhileBusy ? "can-queue" : "cannot-queue", composerGate.editor.status, composerGate.editor.reason ?? "", composerGate.submission.reason ?? "", composerGate.runtime.reason ?? "" ].join(":"); if (input.lastRenderStateDiagnosticKeyRef.current === diagnosticKey) return; input.lastRenderStateDiagnosticKeyRef.current = diagnosticKey; reportAgentGUIRenderStateDiagnostic({ activeActivityDisplayStatus: input.activityDisplayStatus, activeConversation: input.activeConversation, activeConversationBusy, activeConversationId: input.activeConversationId, activeEngineActiveTurn: input.activeEngineActiveTurn, activeEngineAvailability: input.activeEngineAvailability, activeEngineLatestTurn: input.activeEngineLatestTurn, activeEngineRuntimeActivity: input.activeEngineRuntimeActivity, isAwaitingTurnStart, activeLiveState: input.activeLiveState, activeRuntimeSession: input.activeEngineSession, activeSessionState: input.activeSessionState, activeSubmitBlocked, canQueueWhileBusy, canSubmit, conversation: input.conversation, isCreatingConversation: input.isCreatingConversation, isLoadingMessages: input.isLoadingMessages, isSubmitting: input.isSubmitting, pendingApproval, pendingInteractivePrompt, runtime: input.agentActivityRuntime, workspaceId: input.workspaceId }); }, [ activeConversationBusy, isAwaitingTurnStart, activeSubmitBlocked, canQueueWhileBusy, canSubmit, composerGate, input.activeConversation, input.activeConversationId, input.activeEngineActiveTurn, input.activeEngineAvailability, input.activeEngineLatestTurn, input.activeEngineRuntimeActivity, input.activeEngineSession, input.activeLiveState, input.activeSessionState, input.activityDisplayStatus, input.agentActivityRuntime, input.conversation, input.isCreatingConversation, input.isLoadingMessages, input.isSubmitting, input.lastRenderStateDiagnosticKeyRef, input.workspaceId, pendingApproval, pendingInteractivePrompt ]); return { approvalDisabledReason: approvalReadiness.block ? interactionReadinessReasonMessage(approvalReadiness.block.reason) : null, activeConversationBusy, composerGate, hasPendingSubmitStopTarget: input.activeHasPendingSubmitStopTarget, hasSentUserMessage, interactivePromptDisabledReason: interactiveReadiness.block ? interactionReadinessReasonMessage(interactiveReadiness.block.reason) : null, isRespondingApproval, isRespondingInteractivePrompt, isAwaitingTurnStart, pendingApproval, pendingInteractivePrompt, sessionChrome }; } function initialGoalHasPendingOperationEvidence(input: { goalIsOptimistic: boolean; syncState: AgentActivitySessionGoalSyncState | null; }): boolean { if (input.goalIsOptimistic) return true; switch (input.syncState?.syncStatus) { case "pending": case "applying": case "unknown": return Boolean(input.syncState.pendingOperationId?.trim()); case "synced": return input.syncState.executionPending === true; default: return false; } } function interactionReadinessReasonMessage( reason: AgentGUIInteractionReadinessReason ): string { switch (reason) { case "owner_offline": return translate("agentHost.agentGui.interactionOwnerOffline"); case "binding_revoked": return translate("agentHost.agentGui.interactionBindingRevoked"); case "synchronizing": default: return translate("agentHost.agentGui.interactionSynchronizing"); } }