Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
783 lines
25 KiB
TypeScript
783 lines
25 KiB
TypeScript
import {
|
||
createAgentSessionFamilySnapshotSelector,
|
||
selectEngineSessionIsRespondingToInteraction,
|
||
selectWorkspaceAgentConsumerSession,
|
||
selectWorkspaceAgentConsumerSessions
|
||
} from "@tutti-os/agent-activity-core";
|
||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||
import { useAgentHostApi } from "../../../agentActivityHost";
|
||
import { useAgentGUIRuntime } from "../../../agentActivityRuntime";
|
||
import { useAccountStore } from "../../../host/agentHostAccountStore";
|
||
import type { AgentHostUserProject } from "../../../host/agentHostApi";
|
||
import type { AgentSessionComposerSettings } from "../../../shared/agentSessionTypes";
|
||
import { useEngineSelector } from "../../../shared/engine/useEngineSelector";
|
||
import type { AgentGUIDetailViewModel } from "../model/agentGuiNodeTypes";
|
||
import {
|
||
AGENT_GUI_RUNTIME_SESSION_ORIGIN,
|
||
conversationSummaryFromAgentSession,
|
||
type AgentGUIConversationSummary
|
||
} from "../model/agentGuiConversationModel";
|
||
import {
|
||
areAgentComposerProjectPathsEqual,
|
||
normalizeAgentComposerDraftProjectPath
|
||
} from "../model/agentComposerDraftScope";
|
||
import { mergeVisibleConversations } from "./agentGuiController.conversationHelpers";
|
||
import { reuseAgentActivityDisplayStatusesIfUnchanged } from "./agentGuiController.draftMessageHelpers";
|
||
import {
|
||
getAgentGUIErrorCode,
|
||
getAgentGUIErrorMessage
|
||
} from "./agentGuiController.errors";
|
||
import {
|
||
areAgentGUIUserProjectsEqual,
|
||
readAgentGUIUserProjectMutationPending,
|
||
readAgentGUIUserProjectSnapshot,
|
||
upsertAgentGUIUserProject
|
||
} from "./agentGuiController.interactiveHelpers";
|
||
import {
|
||
EMPTY_AGENT_GUI_MESSAGES,
|
||
composerTargetDataFromProviderTarget,
|
||
isExplicitAgentGUIAgentTarget
|
||
} from "./agentGuiController.providerHelpers";
|
||
import type { UseAgentGUINodeControllerInput } from "./agentGuiController.types";
|
||
import { reportAgentGUIActiveConversationCleared } from "./agentGuiController.reporting";
|
||
import { useAgentGUIActivation } from "./useAgentGUIActivation";
|
||
import { useAgentGUIActiveMessages } from "./useAgentGUIActiveMessages";
|
||
import { useAgentGUIConversationRouting } from "./useAgentGUIConversationRouting";
|
||
import { useAgentGUIConversationSelectionController } from "./useAgentGUIConversationSelectionController";
|
||
import { useAgentGUIConversationListState } from "./useAgentGUIConversationListState";
|
||
import { useAgentGUIComposerCapabilities } from "./useAgentGUIComposerCapabilities";
|
||
import { useAgentGUIComposerOptionsSync } from "./useAgentGUIComposerOptionsSync";
|
||
import { useAgentGUIControllerRefs } from "./useAgentGUIControllerRefs";
|
||
import { useAgentGUIOperationActions } from "./useAgentGUIOperationActions";
|
||
import { useAgentGUIViewAssembly } from "./useAgentGUIViewAssembly";
|
||
import { useAgentGUIProviderCatalogSelection } from "./useAgentGUIProviderCatalogSelection";
|
||
import { useAgentGUISessionEngineState } from "./useAgentGUISessionEngineState";
|
||
import { useAgentGUISessionDetailTransport } from "./useAgentGUISessionDetailTransport";
|
||
import { useAgentGUILocalState } from "./useAgentGUILocalState";
|
||
import {
|
||
resolveAgentGUITuttiModeDraftKey,
|
||
useAgentGUITuttiModeActivation
|
||
} from "./useAgentGUITuttiModeActivation";
|
||
export {
|
||
normalizePermissionModeSemantic,
|
||
permissionConfigFromComposerOptions,
|
||
permissionModeDescription,
|
||
permissionModeLabel,
|
||
permissionModeOptions
|
||
} from "./agentGuiController.composerHelpers";
|
||
import { trackAgentGUISettingsProjectChange } from "./agentGuiProjectAnalytics";
|
||
import type { OptimisticComposerTarget } from "./agentGuiController.composerPresentation";
|
||
export * from "./agentGuiController.conversationHelpers";
|
||
export {
|
||
agentGUIConversationDiagnosticDetails,
|
||
agentGUIRuntimeSessionDiagnosticDetails,
|
||
agentGUISessionStateDiagnosticDetails,
|
||
agentGUIToolCallStatusIsWaiting,
|
||
promptRequestId
|
||
} from "./agentGuiController.diagnostics";
|
||
export * from "./agentGuiController.draftMessageHelpers";
|
||
export * from "./agentGuiController.errors";
|
||
export {
|
||
createAgentGUIConversationId,
|
||
normalizeOptionalPrompt,
|
||
normalizeOptionalText,
|
||
projectAgentGUIMessagesToTimelineItems,
|
||
recordValue,
|
||
stringPayloadValue
|
||
} from "./agentGuiController.promptHelpers";
|
||
export * from "./agentGuiController.providerHelpers";
|
||
export * from "./agentGuiController.reporting";
|
||
export {
|
||
messageFromMessageUpdate,
|
||
normalizeTimelineStatus,
|
||
normalizedPositiveNumber,
|
||
timelineItemTime
|
||
} from "./agentGuiController.sessionHelpers";
|
||
export * from "./agentGuiController.stableHelpers";
|
||
export {
|
||
filterMessagesForDetailWindowOverlay,
|
||
maxFiniteMessageVersion,
|
||
minFiniteMessageVersion,
|
||
sessionHasRenderableMessages,
|
||
windowHasTurnMissingUserPrompt
|
||
} from "./useAgentConversationMessagePaging";
|
||
export { resolveConversationSummaryById } from "./useAgentConversationSelection";
|
||
export type { ConversationIntent } from "./useAgentConversationSelection";
|
||
|
||
export type { AgentGUIOpenSessionRequest } from "./agentGuiController.draftMessageHelpers";
|
||
export type { AgentGUIPrefillPromptRequest } from "./useAgentGUIConversationHome";
|
||
export type { AgentGUIComposerAppendRequest } from "./useAgentGUIComposerAppendRequest";
|
||
export type {
|
||
AgentGUIRememberComposerDefaultsInput,
|
||
AgentGUIRememberComposerDefaultsResult
|
||
} from "./agentGuiController.providerHelpers";
|
||
|
||
export function useAgentGUINodeController({
|
||
nodeId,
|
||
isSurfaceActive,
|
||
isSurfaceVisible,
|
||
workspaceId,
|
||
currentUserId,
|
||
workspacePath,
|
||
avoidGroupingEdits,
|
||
data,
|
||
agentTargets,
|
||
agentTargetsLoading = false,
|
||
handoffAgentTargets,
|
||
handoffAgentTargetsLoading = false,
|
||
providerRailMode = "catalog",
|
||
comingSoonProviders,
|
||
providerReadinessGates = null,
|
||
targetConnectionSource = null,
|
||
interactionReadinessSource = null,
|
||
observationGapSource = null,
|
||
defaultAgentTargetId = null,
|
||
composerAppendRequest = null,
|
||
openSessionRequest = null,
|
||
prefillPromptRequest = null,
|
||
codexSaverModeEntryEnabled = false,
|
||
rtkSaverModeEntryEnabled = false,
|
||
onDataChange,
|
||
onComposerAppendHandled,
|
||
onRememberComposerDefaults,
|
||
onShowMessage
|
||
}: UseAgentGUINodeControllerInput) {
|
||
const agentActivityRuntime = useAgentGUIRuntime();
|
||
const agentActivityRuntimeOrigin =
|
||
agentActivityRuntime.origin?.trim() || AGENT_GUI_RUNTIME_SESSION_ORIGIN;
|
||
const sessionEngine = useMemo(() => {
|
||
const engine = agentActivityRuntime.getSessionEngine(workspaceId);
|
||
if (
|
||
engine.identity.workspaceId !== workspaceId ||
|
||
engine.identity.origin !== agentActivityRuntimeOrigin
|
||
) {
|
||
throw new Error(
|
||
"Agent activity runtime returned a session engine for a different identity."
|
||
);
|
||
}
|
||
return engine;
|
||
}, [agentActivityRuntime, agentActivityRuntimeOrigin, workspaceId]);
|
||
// Stable runtime identity isolates conversation queries and session-view refs.
|
||
const agentHostApi = useAgentHostApi();
|
||
const providerCatalogSelection = useAgentGUIProviderCatalogSelection({
|
||
comingSoonProviders,
|
||
data,
|
||
defaultAgentTargetId,
|
||
providerRailMode,
|
||
providerReadinessGates,
|
||
agentTargets,
|
||
agentTargetsLoading,
|
||
handoffAgentTargets,
|
||
handoffAgentTargetsLoading
|
||
});
|
||
const {
|
||
effectiveSelectedProviderTarget,
|
||
homeComposerTargetOverride,
|
||
normalizedComingSoonProviders,
|
||
normalizedExplicitProviderTargets,
|
||
normalizedProviderTargets,
|
||
selectedComposerTargetData,
|
||
selectedAgentTargetIsExplicit,
|
||
setHomeComposerTargetOverride
|
||
} = providerCatalogSelection;
|
||
const localState = useAgentGUILocalState({
|
||
data,
|
||
userProjectsApi: agentHostApi.userProjects
|
||
});
|
||
const {
|
||
activeConversationId,
|
||
clearRailRevealRequest,
|
||
draftByScopeKey,
|
||
draftSettingsBySessionId,
|
||
intent,
|
||
isComposerHome,
|
||
selectedProjectPath,
|
||
requestRailReveal,
|
||
setActiveConversationId,
|
||
setDetailError,
|
||
setIntent,
|
||
setIsComposerHome,
|
||
setIsLoadingMessages,
|
||
setIsUserProjectMutationPending,
|
||
setSelectedProjectPath,
|
||
setUserProjects,
|
||
userProjects
|
||
} = localState;
|
||
const agentActivityDisplayStatuses = useEngineSelector(
|
||
sessionEngine,
|
||
(state) => {
|
||
const statuses = new Map(
|
||
selectWorkspaceAgentConsumerSessions(state).map((item) => [
|
||
item.session.agentSessionId,
|
||
item.displayStatus
|
||
])
|
||
);
|
||
if (activeConversationId && !statuses.has(activeConversationId)) {
|
||
const activeConsumer = selectWorkspaceAgentConsumerSession(
|
||
state,
|
||
activeConversationId
|
||
);
|
||
if (activeConsumer) {
|
||
statuses.set(activeConversationId, activeConsumer.displayStatus);
|
||
}
|
||
}
|
||
return statuses;
|
||
},
|
||
(left, right) =>
|
||
reuseAgentActivityDisplayStatusesIfUnchanged(left, right) === left
|
||
);
|
||
const activeAgentActivityDisplayStatus = activeConversationId
|
||
? (agentActivityDisplayStatuses.get(activeConversationId) ?? null)
|
||
: null;
|
||
const tuttiModeDraftKey = useMemo(
|
||
() => resolveAgentGUITuttiModeDraftKey(nodeId),
|
||
[nodeId]
|
||
);
|
||
const tuttiModeActivation = useAgentGUITuttiModeActivation({
|
||
activeConversationId,
|
||
draftKey: tuttiModeDraftKey,
|
||
engine: sessionEngine,
|
||
workspaceId
|
||
});
|
||
const conversationList = useAgentGUIConversationListState({
|
||
agentActivityRuntimeOrigin,
|
||
currentUserId,
|
||
data,
|
||
normalizedProviderTargets,
|
||
sessionEngine,
|
||
workspaceId
|
||
});
|
||
const {
|
||
attentionReadState,
|
||
conversationFilter,
|
||
conversationListQuery,
|
||
conversationListState,
|
||
conversations
|
||
} = conversationList;
|
||
const hasLoadedConversations = conversationListState?.initialized ?? false;
|
||
const isLoadingConversations = conversationListState?.isLoading ?? false;
|
||
const sessionEngineState = useAgentGUISessionEngineState({
|
||
activeConversationId,
|
||
sessionEngine
|
||
});
|
||
const {
|
||
activeEngineSession,
|
||
activePendingActivation,
|
||
activePendingSubmits,
|
||
activeQueuedPrompts,
|
||
activeSessionState,
|
||
isCreatingConversation
|
||
} = sessionEngineState;
|
||
const selectActiveSessionFamily = useMemo(
|
||
() => createAgentSessionFamilySnapshotSelector(activeConversationId),
|
||
[activeConversationId]
|
||
);
|
||
const activeSessionFamily = useEngineSelector(
|
||
sessionEngine,
|
||
selectActiveSessionFamily
|
||
);
|
||
const activeRelatedPendingInteractions =
|
||
activeSessionFamily.pendingInteractions;
|
||
const activeRelatedIsRespondingToInteraction = useEngineSelector(
|
||
sessionEngine,
|
||
(state) =>
|
||
activeRelatedPendingInteractions.some((interaction) =>
|
||
selectEngineSessionIsRespondingToInteraction(
|
||
state,
|
||
interaction.agentSessionId
|
||
)
|
||
)
|
||
);
|
||
const optimisticComposerTarget =
|
||
useMemo<OptimisticComposerTarget | null>(() => {
|
||
if (
|
||
!isCreatingConversation ||
|
||
activePendingActivation?.mode !== "new" ||
|
||
activePendingActivation.agentSessionId !== activeConversationId ||
|
||
activePendingActivation.agentTargetId !==
|
||
selectedComposerTargetData.agentTargetId
|
||
) {
|
||
return null;
|
||
}
|
||
return {
|
||
agentSessionId: activePendingActivation.agentSessionId,
|
||
target: selectedComposerTargetData
|
||
};
|
||
}, [
|
||
activeConversationId,
|
||
activePendingActivation,
|
||
isCreatingConversation,
|
||
selectedComposerTargetData
|
||
]);
|
||
// Bridges submitInteractivePrompt
|
||
// updateComposerSettings (defined later); assigned right after the
|
||
// callback's definition.
|
||
const updateComposerSettingsRef = useRef<
|
||
(nextSettings: Partial<AgentSessionComposerSettings>) => void
|
||
>(() => {});
|
||
// Bridges submitInteractivePrompt (defined earlier) to the client-side plan
|
||
// decision handlers (defined later); assigned after those callbacks.
|
||
const planActionsRef = useRef<{
|
||
implement: () => boolean;
|
||
feedback: (text: string) => boolean;
|
||
skip: () => boolean;
|
||
}>({ implement: () => false, feedback: () => false, skip: () => false });
|
||
const composerCapabilities = useAgentGUIComposerCapabilities({
|
||
activeConversationId,
|
||
activeEngineSession,
|
||
activeSessionState,
|
||
data,
|
||
draftSettingsBySessionId,
|
||
optimisticComposerTarget,
|
||
selectedComposerTargetData,
|
||
sessionEngine
|
||
});
|
||
const {
|
||
composerSupport,
|
||
composerTargetData,
|
||
defaultReasoningEffort,
|
||
providerComposerOptions
|
||
} = composerCapabilities;
|
||
const planImplementationTurnIdRef = useRef<string | null>(null);
|
||
const accountProfilesByUserId = useAccountStore(
|
||
(state) => state.profilesByUserId
|
||
);
|
||
const controllerRefs = useAgentGUIControllerRefs({
|
||
activeConversationId,
|
||
conversations,
|
||
data,
|
||
draftByScopeKey,
|
||
draftSettingsBySessionId,
|
||
effectiveSelectedProviderTarget,
|
||
homeComposerTargetOverride,
|
||
isComposerHome,
|
||
isCreatingConversation,
|
||
onDataChange,
|
||
onRememberComposerDefaults,
|
||
onShowMessage,
|
||
agentTargetsProvided: agentTargets !== undefined,
|
||
selectedComposerTargetData,
|
||
selectedProjectPath,
|
||
selectedAgentTargetIsExplicit,
|
||
userProjects
|
||
});
|
||
const {
|
||
activeConversationIdRef,
|
||
conversationIdsRef,
|
||
conversationsRef,
|
||
dataRef,
|
||
draftSettingsBySessionIdRef,
|
||
handledOpenSessionSequenceRef,
|
||
isComposerHomeRef,
|
||
isMountedRef,
|
||
loadDraftComposerOptionsRef,
|
||
onDataChangeRef,
|
||
onComposerDefaultsAuthorityReloadedRef,
|
||
pendingOpenSessionRequestRef,
|
||
selectedComposerTargetDataRef,
|
||
selectedProjectPathRef,
|
||
userProjectsLoadSeqRef,
|
||
userProjectsRef
|
||
} = controllerRefs;
|
||
const sessionDetailTransport = useAgentGUISessionDetailTransport({
|
||
activeConversationId,
|
||
activeConversationIdRef,
|
||
agentActivityRuntime,
|
||
agentActivityRuntimeOrigin,
|
||
dataRef,
|
||
isMountedRef,
|
||
sessionFamily: activeSessionFamily,
|
||
sessionEngine,
|
||
workspaceId
|
||
});
|
||
const {
|
||
loadSelectedConversationMessages,
|
||
loadSessionState,
|
||
markSelectedConversationDetailPending,
|
||
resolveSessionMessages,
|
||
setActiveMessageSession
|
||
} = sessionDetailTransport;
|
||
const storedActiveMessages = activeConversationId
|
||
? resolveSessionMessages(activeConversationId)
|
||
: EMPTY_AGENT_GUI_MESSAGES;
|
||
const { activeMessages, activeTimelineItems } = useAgentGUIActiveMessages({
|
||
activeConversationId,
|
||
activePendingActivation,
|
||
activePendingSubmits,
|
||
activeQueuedPrompts,
|
||
currentUserId,
|
||
storedMessages: storedActiveMessages,
|
||
workspaceId
|
||
});
|
||
const transientConversation =
|
||
useMemo<AgentGUIConversationSummary | null>(() => {
|
||
const session = activeEngineSession;
|
||
if (
|
||
!session ||
|
||
conversations.some(
|
||
(conversation) => conversation.id === session.agentSessionId
|
||
)
|
||
) {
|
||
return null;
|
||
}
|
||
return {
|
||
...conversationSummaryFromAgentSession(session, {
|
||
needsUserAction: activeRelatedPendingInteractions.length > 0,
|
||
userProjects
|
||
}),
|
||
isTransient: true
|
||
};
|
||
}, [
|
||
activeEngineSession,
|
||
activeRelatedPendingInteractions.length,
|
||
agentActivityDisplayStatuses,
|
||
conversations,
|
||
userProjects
|
||
]);
|
||
// Stashes the error message from a failed first-message create so the
|
||
// activeConversationId-null effect (which otherwise clears detailError on
|
||
// every home transition) can surface it on the home composer instead of
|
||
// wiping it out during the optimistic-entry revert.
|
||
const activation = useAgentGUIActivation({
|
||
engine: sessionEngine,
|
||
workspaceId,
|
||
getErrorMessage: getAgentGUIErrorMessage,
|
||
getErrorCode: getAgentGUIErrorCode
|
||
});
|
||
const activeConversationLiveState = activation.stateFor(activeConversationId);
|
||
const setUserProjectsSnapshot = useCallback(
|
||
(projects: readonly AgentHostUserProject[]) => {
|
||
setUserProjects((current) =>
|
||
areAgentGUIUserProjectsEqual(current, projects)
|
||
? current
|
||
: [...projects]
|
||
);
|
||
},
|
||
[]
|
||
);
|
||
|
||
useEffect(() => {
|
||
const api = agentHostApi.userProjects;
|
||
let disposed = false;
|
||
setUserProjectsSnapshot(readAgentGUIUserProjectSnapshot(api));
|
||
setIsUserProjectMutationPending(
|
||
readAgentGUIUserProjectMutationPending(api)
|
||
);
|
||
const loadUserProjects = async () => {
|
||
const requestSeq = ++userProjectsLoadSeqRef.current;
|
||
if (!api) {
|
||
if (!disposed && requestSeq === userProjectsLoadSeqRef.current) {
|
||
setUserProjectsSnapshot([]);
|
||
}
|
||
return;
|
||
}
|
||
try {
|
||
const result = await api.list();
|
||
if (!disposed && requestSeq === userProjectsLoadSeqRef.current) {
|
||
setUserProjectsSnapshot(result.projects);
|
||
}
|
||
} catch {
|
||
if (!disposed && requestSeq === userProjectsLoadSeqRef.current) {
|
||
setUserProjectsSnapshot([]);
|
||
}
|
||
}
|
||
};
|
||
void loadUserProjects();
|
||
const unsubscribe = api?.subscribe?.(() => {
|
||
setIsUserProjectMutationPending(
|
||
readAgentGUIUserProjectMutationPending(api)
|
||
);
|
||
void loadUserProjects();
|
||
});
|
||
return () => {
|
||
disposed = true;
|
||
unsubscribe?.();
|
||
};
|
||
}, [
|
||
agentHostApi.userProjects,
|
||
setIsUserProjectMutationPending,
|
||
setUserProjectsSnapshot
|
||
]);
|
||
|
||
// NOTE: project metadata is intentionally NOT written back into the shared
|
||
// conversation store. `conversation.project` is a per-window exact JOIN of
|
||
// railSectionKey × userProjects.sectionKey; deriving it here and persisting it
|
||
// caused cross-window update storms. Rail membership comes from the same
|
||
// backend railSectionKey contract.
|
||
|
||
useEffect(() => {
|
||
if (activeConversationId === null && isComposerHome) {
|
||
return;
|
||
}
|
||
setHomeComposerTargetOverride(null);
|
||
}, [activeConversationId, isComposerHome]);
|
||
|
||
const conversationSelection = useAgentGUIConversationSelectionController({
|
||
activation,
|
||
activeConversationId,
|
||
activeConversationIdRef,
|
||
activePendingActivation,
|
||
activeSessionReconcileErrorCode:
|
||
sessionEngineState.activeSessionReconcileErrorCode,
|
||
agentActivityRuntime,
|
||
attentionReadRecordsBySessionId: attentionReadState.recordsBySessionId,
|
||
conversationIdsRef,
|
||
conversationsRef,
|
||
conversationListQuery,
|
||
clearRailRevealRequest,
|
||
currentUserId,
|
||
data,
|
||
dataRef,
|
||
intent,
|
||
isComposerHomeRef,
|
||
isMountedRef,
|
||
isSurfaceActive,
|
||
isSurfaceVisible,
|
||
loadDraftComposerOptions: () => loadDraftComposerOptionsRef.current(),
|
||
loadSelectedConversationMessages,
|
||
loadSessionState,
|
||
markSelectedConversationDetailPending,
|
||
nodeId,
|
||
onDataChangeRef,
|
||
sessionEngine,
|
||
requestRailReveal,
|
||
setActiveConversationId,
|
||
setDetailError,
|
||
setIntent,
|
||
setIsComposerHome,
|
||
setIsLoadingMessages,
|
||
setActiveMessageSession,
|
||
transientConversation,
|
||
workspaceId
|
||
});
|
||
const persistActiveConversation =
|
||
conversationSelection.persistActiveConversation;
|
||
const selectConversation = conversationSelection.selectConversation;
|
||
const syncConversationListProjection =
|
||
conversationSelection.syncConversationListProjection;
|
||
|
||
const updateSelectedProjectPath = useCallback(
|
||
(
|
||
path: string | null,
|
||
metadata?: {
|
||
action: "clear" | "create_new" | "import_directory" | "select_existing";
|
||
project?: {
|
||
id: string;
|
||
path: string;
|
||
label: string;
|
||
sectionKey?: string;
|
||
createdAtUnixMs?: number;
|
||
updatedAtUnixMs?: number;
|
||
lastUsedAtUnixMs?: number | null;
|
||
pinnedAtUnixMs: number;
|
||
};
|
||
}
|
||
) => {
|
||
const normalizedPath = normalizeAgentComposerDraftProjectPath(path);
|
||
const project = metadata?.project;
|
||
if (
|
||
project &&
|
||
normalizedPath &&
|
||
areAgentComposerProjectPathsEqual(project.path, normalizedPath)
|
||
) {
|
||
const nextProjects = upsertAgentGUIUserProject(
|
||
userProjectsRef.current,
|
||
project
|
||
);
|
||
userProjectsRef.current = nextProjects;
|
||
setUserProjectsSnapshot(nextProjects);
|
||
}
|
||
selectedProjectPathRef.current = normalizedPath;
|
||
setSelectedProjectPath(normalizedPath);
|
||
trackAgentGUISettingsProjectChange({
|
||
agentActivityRuntime,
|
||
agentSessionId: activeConversationIdRef.current,
|
||
metadata,
|
||
provider: dataRef.current.provider,
|
||
workspaceId
|
||
});
|
||
},
|
||
[agentActivityRuntime, workspaceId]
|
||
);
|
||
|
||
useEffect(() => {
|
||
if (!hasLoadedConversations) {
|
||
return;
|
||
}
|
||
const nextConversationCount = mergeVisibleConversations(
|
||
conversations,
|
||
transientConversation
|
||
).filter((conversation) => !conversation.hiddenFromRail).length;
|
||
onDataChangeRef.current((current) =>
|
||
current.conversationCount === nextConversationCount
|
||
? current
|
||
: { ...current, conversationCount: nextConversationCount }
|
||
);
|
||
}, [conversations.length, hasLoadedConversations, transientConversation]);
|
||
|
||
useAgentGUIConversationRouting({
|
||
activeConversationIdRef,
|
||
conversationListQuery,
|
||
conversations,
|
||
conversationsRef,
|
||
handledOpenSessionSequenceRef,
|
||
hasLoadedConversations,
|
||
intent,
|
||
openSessionRequest,
|
||
pendingOpenSessionRequestRef,
|
||
selectConversation,
|
||
sessionEngine,
|
||
setIntent,
|
||
transientConversation,
|
||
workspaceId
|
||
});
|
||
|
||
const { loadDraftComposerOptions, reloadComposerOptionsForTarget } =
|
||
useAgentGUIComposerOptionsSync({
|
||
activeConversationId,
|
||
activeConversationIdRef,
|
||
activeSessionTarget: activeEngineSession,
|
||
agentActivityRuntime,
|
||
composerTargetData,
|
||
conversationFilter,
|
||
currentUserId,
|
||
data,
|
||
dataRef,
|
||
defaultReasoningEffort,
|
||
draftSettingsBySessionIdRef,
|
||
isComposerHome,
|
||
isComposerHomeRef,
|
||
isCreatingConversation,
|
||
loadDraftComposerOptionsRef,
|
||
loadSessionState,
|
||
onComposerDefaultsAuthorityReloadedRef,
|
||
optimisticComposerTarget,
|
||
providerComposerOptions,
|
||
selectedComposerTargetDataRef,
|
||
selectedProjectPath,
|
||
selectedProjectPathRef,
|
||
syncConversationListProjection,
|
||
workspaceId,
|
||
workspacePath
|
||
});
|
||
const operationActions = useAgentGUIOperationActions({
|
||
...providerCatalogSelection,
|
||
...localState,
|
||
...conversationList,
|
||
...sessionEngineState,
|
||
...composerCapabilities,
|
||
...controllerRefs,
|
||
...sessionDetailTransport,
|
||
...conversationSelection,
|
||
activeEnginePendingInteractions: activeRelatedPendingInteractions,
|
||
accountProfilesByUserId,
|
||
activation,
|
||
agentActivityRuntime,
|
||
agentHostApi,
|
||
composerTargetDataFromProviderTarget,
|
||
composerAppendRequest,
|
||
codexSaverModeEntryEnabled,
|
||
rtkSaverModeEntryEnabled,
|
||
onComposerAppendHandled,
|
||
composerSupportPermissionModeChangeDeferred:
|
||
composerSupport.permissionModeChangeDeferred,
|
||
currentProvider: data.provider,
|
||
currentUserId,
|
||
data,
|
||
defaultAgentTargetId,
|
||
isExplicitAgentGUIAgentTarget,
|
||
isRespondingToInteraction: activeRelatedIsRespondingToInteraction,
|
||
loadDraftComposerOptions,
|
||
reloadComposerOptionsForTarget,
|
||
normalizedExplicitProviderTargets,
|
||
normalizedProviderTargets,
|
||
planActionsRef,
|
||
planImplementationTurnIdRef,
|
||
interactionReadinessSource,
|
||
prefillPromptRequest,
|
||
reportActiveConversationCleared: reportAgentGUIActiveConversationCleared,
|
||
sessionEngine,
|
||
setUserProjectsSnapshot,
|
||
transientConversation,
|
||
tuttiModeDraftKey,
|
||
unactivate: activation.unactivate,
|
||
updateComposerSettingsRef,
|
||
workspaceId
|
||
});
|
||
const isLoadingMessages =
|
||
localState.isLoadingMessages ||
|
||
sessionEngineState.activeSessionDetailLoading;
|
||
const detailAvailability: AgentGUIDetailViewModel["availability"] =
|
||
activeConversationId === null
|
||
? "ready"
|
||
: sessionEngineState.activeEngineSessionDeleted
|
||
? "not_found"
|
||
: isLoadingMessages
|
||
? "loading"
|
||
: sessionEngineState.activeSessionReconcileError ||
|
||
localState.detailError
|
||
? "error"
|
||
: "ready";
|
||
const viewAssembly = useAgentGUIViewAssembly({
|
||
...providerCatalogSelection,
|
||
...localState,
|
||
...conversationList,
|
||
...sessionEngineState,
|
||
...composerCapabilities,
|
||
...controllerRefs,
|
||
...sessionDetailTransport,
|
||
...conversationSelection,
|
||
...operationActions,
|
||
activeCancelStatus: sessionEngineState.activeCancelState?.status ?? null,
|
||
activePendingInteractions: activeRelatedPendingInteractions,
|
||
activeTurn: sessionEngineState.activeEngineActiveTurn,
|
||
activeLatestPendingSubmitTurnId:
|
||
sessionEngineState.activeLatestPendingSubmit?.turnId ?? null,
|
||
activeMessages,
|
||
activeSessionFamily,
|
||
activeTimelineItems,
|
||
activeEngineHasPendingInteractions:
|
||
activeRelatedPendingInteractions.length > 0,
|
||
isRespondingToInteraction: activeRelatedIsRespondingToInteraction,
|
||
activeConversationLiveState,
|
||
activationState: activeConversationLiveState,
|
||
activityDisplayStatus: activeAgentActivityDisplayStatus,
|
||
activityDisplayStatuses: agentActivityDisplayStatuses,
|
||
agentActivityRuntime,
|
||
avoidGroupingEdits,
|
||
currentUserId,
|
||
codeFor: activation.codeFor,
|
||
composerTargetProvider: composerTargetData.provider,
|
||
codexSaverModeEntryEnabled,
|
||
rtkSaverModeEntryEnabled,
|
||
data,
|
||
defaultAgentTargetId,
|
||
errorFor: activation.errorFor,
|
||
detailAvailability,
|
||
isCreatingConversation,
|
||
isLoadingConversations,
|
||
isLoadingMessages,
|
||
normalizedComingSoonProviders,
|
||
nodeId,
|
||
operationActions,
|
||
persistActiveConversation,
|
||
planImplementationTurnIdRef,
|
||
providerRailMode,
|
||
providerReadinessGates,
|
||
targetConnectionSource,
|
||
interactionReadinessSource,
|
||
observationGapSource,
|
||
agentTargetsLoading,
|
||
selectedComposerTargetData,
|
||
sessionEngine,
|
||
transientConversation,
|
||
tuttiModeActivation,
|
||
unactivate: activation.unactivate,
|
||
updateSelectedProjectPath,
|
||
userProjects,
|
||
workspaceId,
|
||
workspacePath
|
||
});
|
||
return viewAssembly;
|
||
}
|