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
455 lines
16 KiB
TypeScript
455 lines
16 KiB
TypeScript
import {
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
type Dispatch,
|
|
type SetStateAction
|
|
} from "react";
|
|
import type { AgentGUIRuntime } from "../../../agentActivityRuntime";
|
|
import {
|
|
selectEngineSession,
|
|
selectEngineSessionDeleted,
|
|
type AgentSessionEngine,
|
|
type PendingActivationIntentRecord
|
|
} from "@tutti-os/agent-activity-core";
|
|
import {
|
|
agentGUIAgentTargetRefsEqual,
|
|
resolveAgentGUIAgentTarget
|
|
} from "../../../agentTargets";
|
|
import type {
|
|
AgentGUINodeData,
|
|
AgentGUIProvider,
|
|
AgentGUIProviderReadinessGate,
|
|
AgentGUIAgentTarget
|
|
} from "../../../types";
|
|
import {
|
|
normalizeAgentGUIConversationFilter,
|
|
type AgentGUIConversationFilter
|
|
} from "../model/agentGuiConversationFilter";
|
|
import type { AgentGUIConversationSummary } from "../model/agentGuiConversationModel";
|
|
import {
|
|
forgetAgentGUISessionMemories,
|
|
rememberAgentGUIActiveConversation,
|
|
resolveAgentGUIRememberedSessionSelection,
|
|
resolveAgentGUISessionMemoryTarget
|
|
} from "../model/agentGuiSessionNavigationMemory";
|
|
import { type AgentGUIComposerTargetData } from "./agentGuiController.composerPresentation";
|
|
import {
|
|
agentGUINodeDataHasComposerTarget,
|
|
composerTargetDataFromProviderTarget,
|
|
resolveAgentGUIProviderRailTargetSelection
|
|
} from "./agentGuiController.providerHelpers";
|
|
import { reportAgentGUIConversationFilterTargetUnresolved } from "./agentGuiController.reporting";
|
|
import { isPendingNewConversationActivationForSession } from "./useAgentGUIActivation";
|
|
import {
|
|
resolveConversationSummaryById,
|
|
type ConversationIntent
|
|
} from "./useAgentConversationSelection";
|
|
|
|
interface CurrentValue<T> {
|
|
current: T;
|
|
}
|
|
|
|
interface UseAgentGUIProviderHomeInput {
|
|
activeConversationId: string | null;
|
|
activeConversationIdRef: CurrentValue<string | null>;
|
|
activePendingActivation: PendingActivationIntentRecord | null;
|
|
agentActivityRuntime: AgentGUIRuntime;
|
|
clearRailRevealRequest(): void;
|
|
conversationFilter: AgentGUIConversationFilter;
|
|
conversationFilterRef: CurrentValue<AgentGUIConversationFilter>;
|
|
conversationsRef: CurrentValue<readonly AgentGUIConversationSummary[]>;
|
|
data: AgentGUINodeData;
|
|
dataRef: CurrentValue<AgentGUINodeData>;
|
|
defaultAgentTargetId: string | null;
|
|
effectiveSelectedProviderTarget: AgentGUIAgentTarget;
|
|
firstReadyHomeComposerProviderTarget: AgentGUIAgentTarget | null;
|
|
homeComposerTargetOverride: AgentGUIAgentTarget | null;
|
|
isComposerHomeRef: CurrentValue<boolean>;
|
|
isLoadingConversations: boolean;
|
|
normalizedExplicitProviderTargets: readonly AgentGUIAgentTarget[];
|
|
normalizedProviderTargets: readonly AgentGUIAgentTarget[];
|
|
onDataChangeRef: CurrentValue<
|
|
(updater: (current: AgentGUINodeData) => AgentGUINodeData) => void
|
|
>;
|
|
persistActiveConversation(agentSessionId: string | null): void;
|
|
providerReadinessGates: Partial<
|
|
Record<AgentGUIProvider, AgentGUIProviderReadinessGate | null>
|
|
> | null;
|
|
selectedComposerTargetDataRef: CurrentValue<AgentGUIComposerTargetData>;
|
|
selectConversation(
|
|
agentSessionId: string,
|
|
options?: { reloadConversations?: boolean }
|
|
): void;
|
|
sessionEngine: AgentSessionEngine;
|
|
setActiveConversationId: Dispatch<SetStateAction<string | null>>;
|
|
setConversationFilter: Dispatch<SetStateAction<AgentGUIConversationFilter>>;
|
|
setDetailError: Dispatch<SetStateAction<string | null>>;
|
|
setHomeComposerTargetOverride: Dispatch<
|
|
SetStateAction<AgentGUIAgentTarget | null>
|
|
>;
|
|
setIntent: Dispatch<SetStateAction<ConversationIntent>>;
|
|
setIsComposerHome: Dispatch<SetStateAction<boolean>>;
|
|
setIsLoadingMessages: Dispatch<SetStateAction<boolean>>;
|
|
shouldUseStaticProviderTargets: boolean;
|
|
transientConversation: AgentGUIConversationSummary | null;
|
|
unactivate(agentSessionId: string): Promise<void>;
|
|
workspaceId: string;
|
|
}
|
|
|
|
export function useAgentGUIProviderHome(input: UseAgentGUIProviderHomeInput) {
|
|
const inputRef = useRef(input);
|
|
inputRef.current = input;
|
|
const resolveDefaultHomeComposerTarget = useCallback(() => {
|
|
const current = inputRef.current;
|
|
const defaultTargetId = current.defaultAgentTargetId?.trim() ?? "";
|
|
const explicitDefaultTarget = defaultTargetId
|
|
? (current.normalizedProviderTargets.find(
|
|
(target) =>
|
|
target.targetId === defaultTargetId ||
|
|
target.agentTargetId === defaultTargetId
|
|
) ?? null)
|
|
: null;
|
|
return (
|
|
explicitDefaultTarget ??
|
|
current.normalizedProviderTargets.find(
|
|
(target) => target.disabled !== true
|
|
) ??
|
|
current.normalizedProviderTargets[0] ??
|
|
null
|
|
);
|
|
}, []);
|
|
|
|
const resetHomeComposerAgentTargetToDefault = useCallback(() => {
|
|
const currentInput = inputRef.current;
|
|
|
|
const nextTarget = resolveDefaultHomeComposerTarget();
|
|
if (!nextTarget) return;
|
|
const nextTargetIsExplicit =
|
|
currentInput.normalizedExplicitProviderTargets.some(
|
|
(target) =>
|
|
target.provider === nextTarget.provider &&
|
|
target.targetId === nextTarget.targetId &&
|
|
agentGUIAgentTargetRefsEqual(target.ref, nextTarget.ref)
|
|
);
|
|
const nextTargetData = composerTargetDataFromProviderTarget({
|
|
current: currentInput.dataRef.current,
|
|
isExplicit: nextTargetIsExplicit,
|
|
target: nextTarget
|
|
});
|
|
currentInput.setHomeComposerTargetOverride(nextTarget);
|
|
currentInput.setIntent({ tag: "home" });
|
|
currentInput.isComposerHomeRef.current = true;
|
|
currentInput.setIsComposerHome(true);
|
|
currentInput.onDataChangeRef.current((current) => {
|
|
const currentNextTargetData = composerTargetDataFromProviderTarget({
|
|
current,
|
|
isExplicit: nextTargetIsExplicit,
|
|
target: nextTarget
|
|
});
|
|
const nextData = {
|
|
...currentNextTargetData.data,
|
|
lastActiveAgentSessionId: null
|
|
};
|
|
currentInput.dataRef.current = nextData;
|
|
return nextData;
|
|
});
|
|
currentInput.dataRef.current = {
|
|
...nextTargetData.data,
|
|
lastActiveAgentSessionId: null
|
|
};
|
|
}, [resolveDefaultHomeComposerTarget]);
|
|
|
|
const updateConversationFilter = useCallback(
|
|
(filter: AgentGUIConversationFilter) => {
|
|
const current = inputRef.current;
|
|
const nextFilter = normalizeAgentGUIConversationFilter(filter);
|
|
current.setConversationFilter(nextFilter);
|
|
if (
|
|
nextFilter.kind === "all" &&
|
|
current.activeConversationIdRef.current === null
|
|
) {
|
|
resetHomeComposerAgentTargetToDefault();
|
|
}
|
|
},
|
|
[resetHomeComposerAgentTargetToDefault]
|
|
);
|
|
|
|
const selectHomeComposerAgentTarget = useCallback(
|
|
(selection: {
|
|
provider: AgentGUIProvider;
|
|
agentTargetId?: string | null;
|
|
}) => {
|
|
const currentInput = inputRef.current;
|
|
|
|
const nextTarget = resolveAgentGUIAgentTarget({
|
|
agentTargetId: selection.agentTargetId,
|
|
defaultAgentTargetId: currentInput.defaultAgentTargetId,
|
|
provider: selection.provider,
|
|
agentTargets: currentInput.normalizedProviderTargets,
|
|
useStaticCatalog: currentInput.shouldUseStaticProviderTargets
|
|
});
|
|
if (!nextTarget) return;
|
|
currentInput.clearRailRevealRequest();
|
|
const nextTargetIsExplicit =
|
|
currentInput.normalizedExplicitProviderTargets.some(
|
|
(target) =>
|
|
target.provider === nextTarget.provider &&
|
|
target.targetId === nextTarget.targetId &&
|
|
agentGUIAgentTargetRefsEqual(target.ref, nextTarget.ref)
|
|
);
|
|
const shouldSyncScopedRailFilter =
|
|
currentInput.conversationFilterRef.current.kind === "agentTarget";
|
|
currentInput.setHomeComposerTargetOverride(nextTarget);
|
|
if (shouldSyncScopedRailFilter) {
|
|
const nextAgentTargetId = nextTarget.agentTargetId?.trim() ?? "";
|
|
currentInput.setConversationFilter(
|
|
nextAgentTargetId
|
|
? { kind: "agentTarget", agentTargetId: nextAgentTargetId }
|
|
: { kind: "all" }
|
|
);
|
|
}
|
|
const previous = currentInput.activeConversationIdRef.current;
|
|
const previousSummary = resolveConversationSummaryById(
|
|
currentInput.conversationsRef.current,
|
|
previous,
|
|
currentInput.transientConversation
|
|
);
|
|
const previousAgentTargetId = resolveAgentGUISessionMemoryTarget({
|
|
agentSessionId: previous,
|
|
canonicalAgentTargetId: previous
|
|
? selectEngineSession(
|
|
currentInput.sessionEngine.getSnapshot(),
|
|
previous
|
|
)?.agentTargetId
|
|
: null,
|
|
pendingActivation: currentInput.activePendingActivation,
|
|
projectedAgentTargetId: previousSummary?.agentTargetId
|
|
});
|
|
if (
|
|
previous &&
|
|
!isPendingNewConversationActivationForSession(
|
|
currentInput.activePendingActivation,
|
|
previous
|
|
)
|
|
) {
|
|
void currentInput.unactivate(previous);
|
|
}
|
|
currentInput.setIntent({ tag: "home" });
|
|
currentInput.isComposerHomeRef.current = true;
|
|
currentInput.setIsComposerHome(true);
|
|
currentInput.activeConversationIdRef.current = null;
|
|
currentInput.setActiveConversationId(null);
|
|
currentInput.setIsLoadingMessages(false);
|
|
currentInput.setDetailError(null);
|
|
currentInput.persistActiveConversation(null);
|
|
currentInput.onDataChangeRef.current((current) => {
|
|
const rememberedCurrent = previous
|
|
? rememberAgentGUIActiveConversation(
|
|
current,
|
|
previous,
|
|
previousAgentTargetId
|
|
)
|
|
: current;
|
|
const currentNextTargetData = composerTargetDataFromProviderTarget({
|
|
current: rememberedCurrent,
|
|
isExplicit: nextTargetIsExplicit,
|
|
target: nextTarget
|
|
});
|
|
const nextAgentTargetId = currentNextTargetData.agentTargetId;
|
|
const currentTargetId = current.agentTargetId ?? null;
|
|
const nextTargetId = nextAgentTargetId ?? nextTarget.targetId;
|
|
const providerTargetChanged =
|
|
current.provider !== selection.provider ||
|
|
((currentTargetId !== null || nextAgentTargetId !== null) &&
|
|
currentTargetId !== nextTargetId);
|
|
const nextData: AgentGUINodeData = {
|
|
...rememberedCurrent,
|
|
provider: currentNextTargetData.provider,
|
|
agentTargetId: currentNextTargetData.agentTargetId,
|
|
lastActiveAgentSessionId: null,
|
|
composerOverrides: providerTargetChanged
|
|
? null
|
|
: rememberedCurrent.composerOverrides
|
|
};
|
|
currentInput.dataRef.current = nextData;
|
|
return nextData;
|
|
});
|
|
},
|
|
[]
|
|
);
|
|
|
|
useEffect(() => {
|
|
const effectiveAgentTargetId =
|
|
input.effectiveSelectedProviderTarget.agentTargetId?.trim() ?? "";
|
|
if (
|
|
input.activeConversationId === null &&
|
|
input.conversationFilter.kind === "agentTarget" &&
|
|
effectiveAgentTargetId &&
|
|
effectiveAgentTargetId !== input.conversationFilter.agentTargetId.trim()
|
|
) {
|
|
input.setConversationFilter({
|
|
kind: "agentTarget",
|
|
agentTargetId: effectiveAgentTargetId
|
|
});
|
|
}
|
|
if (
|
|
input.activeConversationId !== null ||
|
|
input.conversationFilter.kind !== "all" ||
|
|
input.homeComposerTargetOverride !== null ||
|
|
agentGUINodeDataHasComposerTarget(input.data) ||
|
|
!input.providerReadinessGates ||
|
|
!input.firstReadyHomeComposerProviderTarget
|
|
) {
|
|
return;
|
|
}
|
|
const readyTarget = input.firstReadyHomeComposerProviderTarget;
|
|
if (
|
|
readyTarget.provider === input.effectiveSelectedProviderTarget.provider &&
|
|
readyTarget.targetId === input.effectiveSelectedProviderTarget.targetId &&
|
|
agentGUIAgentTargetRefsEqual(
|
|
readyTarget.ref,
|
|
input.effectiveSelectedProviderTarget.ref
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
if (
|
|
!Object.prototype.hasOwnProperty.call(
|
|
input.providerReadinessGates,
|
|
input.effectiveSelectedProviderTarget.provider
|
|
) ||
|
|
!input.providerReadinessGates[
|
|
input.effectiveSelectedProviderTarget.provider
|
|
]
|
|
) {
|
|
return;
|
|
}
|
|
selectHomeComposerAgentTarget({
|
|
provider: readyTarget.provider,
|
|
agentTargetId: readyTarget.targetId
|
|
});
|
|
}, [
|
|
input.activeConversationId,
|
|
input.conversationFilter,
|
|
input.data,
|
|
input.effectiveSelectedProviderTarget,
|
|
input.firstReadyHomeComposerProviderTarget,
|
|
input.homeComposerTargetOverride,
|
|
input.providerReadinessGates,
|
|
input.setConversationFilter,
|
|
selectHomeComposerAgentTarget
|
|
]);
|
|
|
|
const selectConversationFilterTarget = useCallback(
|
|
(selection: { provider: AgentGUIProvider; agentTargetId: string }) => {
|
|
const current = inputRef.current;
|
|
const agentTargetId = selection.agentTargetId.trim();
|
|
if (!agentTargetId) {
|
|
reportAgentGUIConversationFilterTargetUnresolved({
|
|
provider: selection.provider,
|
|
agentTargetId: null,
|
|
providerTargetCount: current.normalizedProviderTargets.length,
|
|
reason: "unresolved",
|
|
runtime: current.agentActivityRuntime,
|
|
workspaceId: current.workspaceId
|
|
});
|
|
return;
|
|
}
|
|
const nextTarget = resolveAgentGUIAgentTarget({
|
|
agentTargetId,
|
|
defaultAgentTargetId: current.defaultAgentTargetId,
|
|
provider: selection.provider,
|
|
agentTargets: current.normalizedProviderTargets,
|
|
useStaticCatalog: current.shouldUseStaticProviderTargets
|
|
});
|
|
if (!nextTarget) {
|
|
reportAgentGUIConversationFilterTargetUnresolved({
|
|
provider: selection.provider,
|
|
agentTargetId,
|
|
providerTargetCount: current.normalizedProviderTargets.length,
|
|
reason: "unresolved",
|
|
runtime: current.agentActivityRuntime,
|
|
workspaceId: current.workspaceId
|
|
});
|
|
return;
|
|
}
|
|
current.clearRailRevealRequest();
|
|
const nextFilter = { kind: "agentTarget" as const, agentTargetId };
|
|
current.setConversationFilter(nextFilter);
|
|
const activeId = current.activeConversationIdRef.current;
|
|
const activeSummary = resolveConversationSummaryById(
|
|
current.conversationsRef.current,
|
|
activeId,
|
|
current.transientConversation
|
|
);
|
|
if (
|
|
resolveAgentGUIProviderRailTargetSelection({
|
|
activeConversation: activeSummary,
|
|
nextFilter
|
|
}) === "keep-active-conversation"
|
|
) {
|
|
return;
|
|
}
|
|
const rememberedAgentSessionId =
|
|
agentTargetId &&
|
|
current.dataRef.current.lastActiveAgentSessionIdByAgentTargetId?.[
|
|
agentTargetId
|
|
];
|
|
const rememberedSummary = resolveConversationSummaryById(
|
|
current.conversationsRef.current,
|
|
rememberedAgentSessionId,
|
|
current.transientConversation
|
|
);
|
|
const rememberedEngineSession = selectEngineSession(
|
|
current.sessionEngine.getSnapshot(),
|
|
rememberedAgentSessionId
|
|
);
|
|
const rememberedSelection = resolveAgentGUIRememberedSessionSelection({
|
|
data: current.dataRef.current,
|
|
deleted: selectEngineSessionDeleted(
|
|
current.sessionEngine.getSnapshot(),
|
|
rememberedAgentSessionId
|
|
),
|
|
knownAgentTargetId:
|
|
rememberedEngineSession?.agentTargetId ??
|
|
rememberedSummary?.agentTargetId ??
|
|
null,
|
|
targetAgentTargetId: agentTargetId || null
|
|
});
|
|
if (rememberedSelection.kind === "restore") {
|
|
selectHomeComposerAgentTarget({
|
|
provider: nextTarget.provider,
|
|
agentTargetId
|
|
});
|
|
current.selectConversation(rememberedSelection.agentSessionId, {
|
|
reloadConversations: false
|
|
});
|
|
return;
|
|
}
|
|
if (rememberedSelection.kind === "stale") {
|
|
const staleIds = new Set([rememberedSelection.agentSessionId]);
|
|
current.onDataChangeRef.current((data) =>
|
|
forgetAgentGUISessionMemories(data, staleIds)
|
|
);
|
|
current.dataRef.current = forgetAgentGUISessionMemories(
|
|
current.dataRef.current,
|
|
staleIds
|
|
);
|
|
}
|
|
selectHomeComposerAgentTarget({
|
|
provider: nextTarget.provider,
|
|
agentTargetId
|
|
});
|
|
},
|
|
[selectHomeComposerAgentTarget]
|
|
);
|
|
|
|
return {
|
|
resetHomeComposerAgentTargetToDefault,
|
|
selectConversationFilterTarget,
|
|
selectHomeComposerAgentTarget,
|
|
updateConversationFilter
|
|
};
|
|
}
|