源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
import { useMemo } from "react";
|
||||
import { selectPendingSessionForkThroughTurnIds } from "@tutti-os/agent-activity-core";
|
||||
import { useAgentGUIViewModel } from "../model/useAgentGUIViewModel";
|
||||
import type { AgentGUIProviderRailMode } from "../../../types";
|
||||
import type { AgentGUIDetailViewModel } from "../model/agentGuiNodeTypes";
|
||||
import { useAgentGUIComposerPresentation } from "./useAgentGUIComposerPresentation";
|
||||
import { useAgentGUIControllerActions } from "./useAgentGUIControllerActions";
|
||||
import { useAgentGUIConversationDetail } from "./useAgentGUIConversationDetail";
|
||||
import { useAgentGUIConversationPresentation } from "./useAgentGUIConversationPresentation";
|
||||
import { useAgentGUIProviderHome } from "./useAgentGUIProviderHome";
|
||||
import { useAgentGUISessionPresentation } from "./useAgentGUISessionPresentation";
|
||||
import type { useAgentGUIOperationActions } from "./useAgentGUIOperationActions";
|
||||
import type { useAgentGUIProviderCatalogSelection } from "./useAgentGUIProviderCatalogSelection";
|
||||
import type { useAgentGUILocalState } from "./useAgentGUILocalState";
|
||||
import type { useAgentGUIComposerCapabilities } from "./useAgentGUIComposerCapabilities";
|
||||
import type { useAgentGUISessionDetailTransport } from "./useAgentGUISessionDetailTransport";
|
||||
import { resolveAgentGUIProviderReadinessGateForView } from "../model/agentGuiProviderReadiness";
|
||||
import type { useAgentGUITuttiModeActivation } from "./useAgentGUITuttiModeActivation";
|
||||
import { targetConnectionForAgentGUIView } from "./agentGuiController.providerHelpers";
|
||||
import { isAgentGUIAgentTargetComingSoon } from "../../../agentTargets";
|
||||
import { useEngineSelector } from "../../../shared/engine/useEngineSelector";
|
||||
|
||||
type ConversationPresentationInput = Parameters<
|
||||
typeof useAgentGUIConversationPresentation
|
||||
>[0];
|
||||
type ConversationDetailInput = Omit<
|
||||
Parameters<typeof useAgentGUIConversationDetail>[0],
|
||||
"activeConversation" | "activeSessionView"
|
||||
>;
|
||||
type ActiveSessionViewProjection = Parameters<
|
||||
typeof useAgentGUIConversationDetail
|
||||
>[0]["activeSessionView"];
|
||||
type ComposerPresentationInput = Omit<
|
||||
Parameters<typeof useAgentGUIComposerPresentation>[0],
|
||||
"activeConversation"
|
||||
>;
|
||||
type SessionPresentationInput = Omit<
|
||||
Parameters<typeof useAgentGUISessionPresentation>[0],
|
||||
| "activeConversation"
|
||||
| "activeLiveState"
|
||||
| "activationError"
|
||||
| "activationErrorCode"
|
||||
| "conversation"
|
||||
| "isInterrupting"
|
||||
| "pendingApproval"
|
||||
| "providerReadinessGate"
|
||||
| "selectedAgentTargetUnavailable"
|
||||
| "selectedAgentTargetUnavailableReason"
|
||||
| "selectedAgentTargetOwnerLabel"
|
||||
| "serverInteractivePrompt"
|
||||
>;
|
||||
type ProviderHomeInput = Parameters<typeof useAgentGUIProviderHome>[0];
|
||||
type OperationActions = ReturnType<typeof useAgentGUIOperationActions>;
|
||||
type ProviderCatalog = ReturnType<typeof useAgentGUIProviderCatalogSelection>;
|
||||
type LocalState = ReturnType<typeof useAgentGUILocalState>;
|
||||
type ComposerCapabilities = ReturnType<typeof useAgentGUIComposerCapabilities>;
|
||||
type SessionDetailTransport = ReturnType<
|
||||
typeof useAgentGUISessionDetailTransport
|
||||
>;
|
||||
|
||||
type UseAgentGUIViewAssemblyInput = ConversationPresentationInput &
|
||||
ConversationDetailInput &
|
||||
ComposerPresentationInput &
|
||||
SessionPresentationInput &
|
||||
ProviderHomeInput &
|
||||
ProviderCatalog &
|
||||
LocalState &
|
||||
ComposerCapabilities &
|
||||
SessionDetailTransport &
|
||||
OperationActions & {
|
||||
nodeId?: string | null;
|
||||
operationActions: OperationActions;
|
||||
detailAvailability: AgentGUIDetailViewModel["availability"];
|
||||
updateSelectedProjectPath: Parameters<
|
||||
typeof useAgentGUIControllerActions
|
||||
>[0]["updateSelectedProjectPath"];
|
||||
selectConversation: Parameters<
|
||||
typeof useAgentGUIControllerActions
|
||||
>[0]["selectConversation"];
|
||||
providerRailMode: AgentGUIProviderRailMode | undefined;
|
||||
tuttiModeActivation: ReturnType<typeof useAgentGUITuttiModeActivation>;
|
||||
};
|
||||
|
||||
export function useAgentGUIViewAssembly(input: UseAgentGUIViewAssemblyInput) {
|
||||
const { activeConversation, visibleConversations } =
|
||||
useAgentGUIConversationPresentation(input);
|
||||
const forkThroughTurnPendingTurnIds = useEngineSelector(
|
||||
input.sessionEngine,
|
||||
(state) =>
|
||||
selectPendingSessionForkThroughTurnIds(state, {
|
||||
sourceAgentSessionId: input.activeConversationId,
|
||||
workspaceId: input.workspaceId
|
||||
}),
|
||||
equalStringArrays
|
||||
);
|
||||
const targetConnection = useMemo(
|
||||
() =>
|
||||
targetConnectionForAgentGUIView({
|
||||
activeConversation,
|
||||
selectedTarget: input.effectiveSelectedProviderTarget,
|
||||
targets: input.normalizedProviderTargets
|
||||
}),
|
||||
[
|
||||
activeConversation,
|
||||
input.effectiveSelectedProviderTarget,
|
||||
input.normalizedProviderTargets
|
||||
]
|
||||
);
|
||||
const stableActiveSessionViewProjection =
|
||||
useMemo<ActiveSessionViewProjection>(
|
||||
() =>
|
||||
input.activeSessionView
|
||||
? {
|
||||
hasOlderMessages: input.activeSessionView.hasOlderMessages,
|
||||
isLoadingOlderMessages:
|
||||
input.activeSessionView.isLoadingOlderMessages,
|
||||
olderMessageCount: input.activeMessages.length,
|
||||
oldestLoadedVersion: input.activeSessionView.oldestLoadedVersion
|
||||
}
|
||||
: null,
|
||||
[
|
||||
input.activeSessionView?.hasOlderMessages,
|
||||
input.activeSessionView?.isLoadingOlderMessages,
|
||||
input.activeSessionView?.oldestLoadedVersion,
|
||||
input.activeMessages.length
|
||||
]
|
||||
);
|
||||
const detail = useAgentGUIConversationDetail({
|
||||
...input,
|
||||
activeConversation,
|
||||
activeSessionView: stableActiveSessionViewProjection
|
||||
});
|
||||
const { stableComposerSettings } = useAgentGUIComposerPresentation({
|
||||
...input,
|
||||
activeConversation
|
||||
});
|
||||
const providerReadinessGate = useMemo(
|
||||
() =>
|
||||
input.activeConversationId === null &&
|
||||
isAgentGUIAgentTargetComingSoon(
|
||||
input.effectiveSelectedProviderTarget,
|
||||
input.normalizedComingSoonProviders
|
||||
)
|
||||
? ({ status: "coming_soon" } as const)
|
||||
: resolveAgentGUIProviderReadinessGateForView({
|
||||
activeConversationId: input.activeConversationId,
|
||||
providerReadinessGates: input.providerReadinessGates,
|
||||
selectedProvider: input.effectiveSelectedProviderTarget.provider
|
||||
}),
|
||||
[
|
||||
input.activeConversationId,
|
||||
input.effectiveSelectedProviderTarget,
|
||||
input.normalizedComingSoonProviders,
|
||||
input.providerReadinessGates
|
||||
]
|
||||
);
|
||||
const selectedAgentTargetUnavailable =
|
||||
input.activeConversationId === null &&
|
||||
input.effectiveSelectedProviderTarget.disabled === true &&
|
||||
!isAgentGUIAgentTargetComingSoon(
|
||||
input.effectiveSelectedProviderTarget,
|
||||
input.normalizedComingSoonProviders
|
||||
);
|
||||
const selectedAgentTargetUnavailableReason = selectedAgentTargetUnavailable
|
||||
? input.effectiveSelectedProviderTarget.availability?.reason?.trim() ||
|
||||
input.effectiveSelectedProviderTarget.unavailableReason?.trim() ||
|
||||
null
|
||||
: null;
|
||||
const selectedAgentTargetOwnerLabel =
|
||||
input.effectiveSelectedProviderTarget.ownerLabel?.trim() || null;
|
||||
const session = useAgentGUISessionPresentation({
|
||||
...input,
|
||||
activeConversation,
|
||||
currentUserId: input.currentUserId,
|
||||
ownerDeviceLabel: targetConnection.ownerDeviceLabel,
|
||||
providerReadinessGate,
|
||||
selectedAgentTargetUnavailable,
|
||||
selectedAgentTargetUnavailableReason,
|
||||
selectedAgentTargetOwnerLabel,
|
||||
targetConnectionAgentTargetId: targetConnection.agentTargetId,
|
||||
activeLiveState: detail.activeLiveState,
|
||||
activationError: detail.activationError,
|
||||
activationErrorCode: detail.activationErrorCode,
|
||||
conversation: detail.conversation,
|
||||
isInterrupting: detail.isInterrupting,
|
||||
pendingApproval: detail.pendingApproval,
|
||||
serverInteractivePrompt: detail.serverInteractivePrompt
|
||||
});
|
||||
const pendingQuestionComposerTarget = detail.pendingQuestionComposerTarget;
|
||||
const pendingInteractivePrompt = session.pendingInteractivePrompt;
|
||||
const canAnswerPendingInteractivePromptFromComposer =
|
||||
pendingInteractivePrompt?.kind === "ask-user" &&
|
||||
pendingQuestionComposerTarget !== null &&
|
||||
pendingInteractivePrompt.agentSessionId?.trim() ===
|
||||
pendingQuestionComposerTarget.agentSessionId &&
|
||||
pendingInteractivePrompt.turnId?.trim() ===
|
||||
pendingQuestionComposerTarget.turnId &&
|
||||
pendingInteractivePrompt.requestId ===
|
||||
pendingQuestionComposerTarget.requestId;
|
||||
const railConversations = useMemo(() => {
|
||||
const prompt = session.pendingInteractivePrompt;
|
||||
if (prompt?.kind !== "plan-implementation" || !input.activeConversationId) {
|
||||
return visibleConversations;
|
||||
}
|
||||
let changed = false;
|
||||
const next = visibleConversations.map((conversation) => {
|
||||
if (
|
||||
conversation.id !== input.activeConversationId ||
|
||||
conversation.needsUserAction
|
||||
) {
|
||||
return conversation;
|
||||
}
|
||||
changed = true;
|
||||
return { ...conversation, needsUserAction: true };
|
||||
});
|
||||
return changed ? next : visibleConversations;
|
||||
}, [
|
||||
input.activeConversationId,
|
||||
session.pendingInteractivePrompt,
|
||||
visibleConversations
|
||||
]);
|
||||
const railActiveConversation = useMemo(() => {
|
||||
if (
|
||||
!activeConversation ||
|
||||
session.pendingInteractivePrompt?.kind !== "plan-implementation" ||
|
||||
activeConversation.needsUserAction
|
||||
) {
|
||||
return activeConversation;
|
||||
}
|
||||
return { ...activeConversation, needsUserAction: true };
|
||||
}, [activeConversation, session.pendingInteractivePrompt]);
|
||||
const providerHome = useAgentGUIProviderHome(input);
|
||||
const controllerActions = useAgentGUIControllerActions({
|
||||
...input.operationActions,
|
||||
...providerHome,
|
||||
loadOlderConversationMessages: input.loadOlderConversationMessages,
|
||||
selectConversation: input.selectConversation,
|
||||
setTuttiModeActive: input.tuttiModeActivation.setActive,
|
||||
setTuttiModeEffect: input.tuttiModeActivation.setEffect,
|
||||
setTuttiModeSpeed: input.tuttiModeActivation.setSpeed,
|
||||
retryTuttiModeActivation: input.tuttiModeActivation.retry,
|
||||
updateSelectedProjectPath: input.updateSelectedProjectPath
|
||||
});
|
||||
const viewData =
|
||||
input.activeConversationId === null
|
||||
? input.selectedComposerTargetData.data
|
||||
: input.data;
|
||||
const viewModel = useAgentGUIViewModel({
|
||||
shell: {
|
||||
nodeId: input.nodeId?.trim() || null,
|
||||
workspaceId: input.workspaceId,
|
||||
workspacePath: input.workspacePath,
|
||||
currentUserId: input.currentUserId,
|
||||
data: viewData
|
||||
},
|
||||
rail: {
|
||||
selectedAgentTarget: input.effectiveSelectedProviderTarget,
|
||||
agentTargets: input.normalizedProviderTargets,
|
||||
agentTargetsLoading: input.agentTargetsLoading,
|
||||
providerRailMode: input.providerRailMode ?? "catalog",
|
||||
comingSoonProviders: input.normalizedComingSoonProviders,
|
||||
conversationFilter: input.conversationFilter,
|
||||
conversations: railConversations,
|
||||
userProjects: input.userProjects,
|
||||
activeConversation: railActiveConversation,
|
||||
activeConversationId: input.activeConversationId,
|
||||
revealRequest: input.railRevealRequest,
|
||||
isLoadingConversations: input.isLoadingConversations,
|
||||
listError: input.listError
|
||||
},
|
||||
detail: {
|
||||
availability: input.detailAvailability,
|
||||
isLoadingMessages: input.isLoadingMessages,
|
||||
isLoadingOlderMessages:
|
||||
input.activeSessionView?.isLoadingOlderMessages ?? false,
|
||||
hasOlderMessages: input.activeSessionView?.hasOlderMessages ?? false,
|
||||
usage: input.usage,
|
||||
hasSentUserMessage: session.hasSentUserMessage,
|
||||
avoidGroupingEdits: input.avoidGroupingEdits,
|
||||
conversation: detail.conversation,
|
||||
conversationDetail: detail.conversationDetail
|
||||
},
|
||||
composer: {
|
||||
handoffAgentTargets: input.handoffAgentTargets,
|
||||
availableCommands: detail.availableCommands,
|
||||
availableSkills: detail.availableSkills,
|
||||
draftPrompt: detail.draftPrompt,
|
||||
draftContent: detail.draftContent,
|
||||
isCreatingConversation: input.isCreatingConversation,
|
||||
isSubmitting: input.isSubmitting,
|
||||
isInterrupting: detail.isInterrupting,
|
||||
isCancelPending: detail.isCancelPending,
|
||||
hasPendingSubmitStopTarget: session.hasPendingSubmitStopTarget,
|
||||
promptImagesSupported: input.promptImagesSupported,
|
||||
compactSupported: input.compactSupported,
|
||||
goalPauseSupported: input.goalPauseSupported,
|
||||
gate: session.composerGate,
|
||||
isTuttiModeActive: input.tuttiModeActivation.active,
|
||||
isTuttiModeUpdating: input.tuttiModeActivation.updatePending,
|
||||
tuttiModeEffect:
|
||||
input.tuttiModeActivation.effect ??
|
||||
input.tuttiModeActivation.orchestrationIntensity,
|
||||
tuttiModeSpeed: input.tuttiModeActivation.speed ?? 50,
|
||||
tuttiModeUpdateStatus: input.tuttiModeActivation.updateStatus,
|
||||
composerSettings: stableComposerSettings,
|
||||
queueStatus: detail.queueStatus,
|
||||
queuedPrompts: detail.queuedPrompts,
|
||||
drainingQueuedPromptId: detail.drainingQueuedPromptId
|
||||
},
|
||||
interaction: {
|
||||
approvalDisabledReason: session.approvalDisabledReason,
|
||||
interactivePromptDisabledReason: session.interactivePromptDisabledReason,
|
||||
isRespondingApproval: session.isRespondingApproval,
|
||||
isRespondingInteractivePrompt: session.isRespondingInteractivePrompt,
|
||||
canAnswerPendingInteractivePromptFromComposer,
|
||||
pendingApproval: session.pendingApproval,
|
||||
pendingInteractivePrompt: session.pendingInteractivePrompt,
|
||||
sessionChrome: session.sessionChrome,
|
||||
inlineNotice: detail.effectiveDetailError
|
||||
? {
|
||||
id: `agent-gui-detail-error:${input.activeConversationId ?? "current"}`,
|
||||
message: detail.effectiveDetailError,
|
||||
tone: "error" as const,
|
||||
autoDismissMs: null
|
||||
}
|
||||
: null
|
||||
},
|
||||
readiness: {
|
||||
activeLiveState: detail.activeLiveState,
|
||||
activationError: detail.activationError,
|
||||
providerReadinessGate
|
||||
},
|
||||
operations: {
|
||||
forkThroughTurnPendingTurnIds,
|
||||
goalClearNoticeSequence: input.goalClearNoticeSequence,
|
||||
isDeletingConversation: input.isDeletingConversation,
|
||||
isDeletingProjectConversations: input.isDeletingProjectConversations,
|
||||
isUserProjectMutationPending: input.isUserProjectMutationPending,
|
||||
pendingDeleteConversation: input.pendingDeleteConversation,
|
||||
pendingDeleteProjectConversations: input.pendingDeleteProjectConversations
|
||||
}
|
||||
});
|
||||
return useMemo(
|
||||
() => ({ viewModel, actions: controllerActions }),
|
||||
[controllerActions, viewModel]
|
||||
);
|
||||
}
|
||||
|
||||
function equalStringArrays(
|
||||
previous: readonly string[],
|
||||
next: readonly string[]
|
||||
): boolean {
|
||||
return (
|
||||
previous.length === next.length &&
|
||||
previous.every((value, index) => value === next[index])
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user