源码快照 @ 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:
+187
@@ -0,0 +1,187 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import type { AgentGUINodeData } from "../../../types";
|
||||
import type { AgentGUIConversationSummary } from "../model/agentGuiConversationModel";
|
||||
import type { AgentGUIConversationRailRevealReason } from "../model/agentGuiConversationRailViewState";
|
||||
import { rememberAgentGUIActiveConversation } from "../model/agentGuiSessionNavigationMemory";
|
||||
|
||||
export type ConversationIntent =
|
||||
| { tag: "home" }
|
||||
| { tag: "requested"; id: string }
|
||||
| {
|
||||
tag: "active";
|
||||
id: string;
|
||||
source?: "activation" | "user-selection";
|
||||
};
|
||||
|
||||
export interface AgentGUIConversationSelectionOptions {
|
||||
reloadConversations?: boolean;
|
||||
reveal?: Extract<AgentGUIConversationRailRevealReason, "external-open">;
|
||||
}
|
||||
|
||||
export function resolveConversationSummaryById(
|
||||
conversations: readonly AgentGUIConversationSummary[],
|
||||
conversationId: string | null | undefined,
|
||||
transientConversation: AgentGUIConversationSummary | null = null
|
||||
): AgentGUIConversationSummary | null {
|
||||
const normalized = conversationId?.trim() ?? "";
|
||||
if (!normalized) return null;
|
||||
return (
|
||||
conversations.find((conversation) => conversation.id === normalized) ??
|
||||
(transientConversation?.id === normalized ? transientConversation : null)
|
||||
);
|
||||
}
|
||||
|
||||
interface AgentConversationSelectionInput {
|
||||
activation: {
|
||||
canReload(agentSessionId: string): boolean;
|
||||
forget(agentSessionId: string): void;
|
||||
isPending(agentSessionId: string): boolean;
|
||||
};
|
||||
conversations: {
|
||||
agentTargetIdFor(agentSessionId: string): string | null;
|
||||
contains(agentSessionId: string): boolean;
|
||||
};
|
||||
detail: {
|
||||
ensureHydrated(agentSessionId: string): void;
|
||||
ensureStateHydrated(agentSessionId: string): void;
|
||||
isHydrated(agentSessionId: string): boolean;
|
||||
isStateHydrated(agentSessionId: string): boolean;
|
||||
markPending(agentSessionId: string): void;
|
||||
setLoading(loading: boolean): void;
|
||||
};
|
||||
hasConversationListQuery(): boolean;
|
||||
isMounted(): boolean;
|
||||
onMissingConversationListQuery(previousAgentSessionId: string | null): void;
|
||||
persistence: {
|
||||
update(updater: (current: AgentGUINodeData) => AgentGUINodeData): void;
|
||||
};
|
||||
rail: {
|
||||
clearRevealRequest(): void;
|
||||
requestReveal(
|
||||
agentSessionId: string,
|
||||
reason: AgentGUIConversationRailRevealReason
|
||||
): void;
|
||||
};
|
||||
selection: {
|
||||
clearDetailError(): void;
|
||||
getActiveSessionId(): string | null;
|
||||
setActiveSessionId(agentSessionId: string | null): void;
|
||||
setComposerHome(home: boolean): void;
|
||||
setIntent(intent: ConversationIntent): void;
|
||||
};
|
||||
}
|
||||
|
||||
export function useAgentConversationSelection(
|
||||
input: AgentConversationSelectionInput
|
||||
) {
|
||||
const inputRef = useRef(input);
|
||||
inputRef.current = input;
|
||||
|
||||
const persistActiveConversation = useCallback(
|
||||
(agentSessionId: string | null, agentTargetId?: string | null) => {
|
||||
inputRef.current.persistence.update((current) => {
|
||||
const resolvedAgentTargetId = agentSessionId
|
||||
? agentTargetId?.trim() ||
|
||||
inputRef.current.conversations.agentTargetIdFor(agentSessionId)
|
||||
: null;
|
||||
return rememberAgentGUIActiveConversation(
|
||||
current,
|
||||
agentSessionId,
|
||||
resolvedAgentTargetId
|
||||
);
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const syncConversationListProjection = useCallback(
|
||||
async (_preferredSessionId?: string | null) => {
|
||||
const current = inputRef.current;
|
||||
if (current.hasConversationListQuery()) return;
|
||||
const previous = current.selection.getActiveSessionId();
|
||||
current.onMissingConversationListQuery(previous);
|
||||
current.selection.setIntent({ tag: "home" });
|
||||
current.selection.setComposerHome(true);
|
||||
current.selection.setActiveSessionId(null);
|
||||
current.detail.setLoading(false);
|
||||
current.selection.clearDetailError();
|
||||
current.rail.clearRevealRequest();
|
||||
persistActiveConversation(null);
|
||||
},
|
||||
[persistActiveConversation]
|
||||
);
|
||||
|
||||
const selectConversation = useCallback(
|
||||
(
|
||||
agentSessionId: string,
|
||||
options?: AgentGUIConversationSelectionOptions
|
||||
) => {
|
||||
const normalized = agentSessionId.trim();
|
||||
if (!normalized) return;
|
||||
const current = inputRef.current;
|
||||
const previous = current.selection.getActiveSessionId();
|
||||
const detailHydrated = current.detail.isHydrated(normalized);
|
||||
const stateHydrated = current.detail.isStateHydrated(normalized);
|
||||
const activationPending = current.activation.isPending(normalized);
|
||||
current.selection.setComposerHome(false);
|
||||
if (previous && previous !== normalized)
|
||||
current.activation.forget(previous);
|
||||
if (previous !== normalized) {
|
||||
if (detailHydrated || activationPending) {
|
||||
current.detail.setLoading(false);
|
||||
} else {
|
||||
current.detail.markPending(normalized);
|
||||
}
|
||||
}
|
||||
const reloadConversations =
|
||||
options?.reloadConversations !== false &&
|
||||
current.conversations.contains(normalized);
|
||||
current.selection.setIntent({
|
||||
id: normalized,
|
||||
source: "user-selection",
|
||||
tag: "active"
|
||||
});
|
||||
current.selection.setActiveSessionId(normalized);
|
||||
current.selection.clearDetailError();
|
||||
if (options?.reveal) {
|
||||
current.rail.requestReveal(normalized, options.reveal);
|
||||
} else {
|
||||
current.rail.clearRevealRequest();
|
||||
}
|
||||
if (!activationPending && current.activation.canReload(normalized)) {
|
||||
if (reloadConversations) {
|
||||
void syncConversationListProjection(normalized);
|
||||
}
|
||||
if (!detailHydrated) {
|
||||
current.detail.ensureHydrated(normalized);
|
||||
} else if (!stateHydrated) {
|
||||
current.detail.ensureStateHydrated(normalized);
|
||||
}
|
||||
}
|
||||
persistActiveConversation(normalized);
|
||||
},
|
||||
[persistActiveConversation, syncConversationListProjection]
|
||||
);
|
||||
|
||||
const isCurrentConversation = useCallback(
|
||||
(agentSessionId: string) =>
|
||||
inputRef.current.isMounted() &&
|
||||
inputRef.current.selection.getActiveSessionId() === agentSessionId.trim(),
|
||||
[]
|
||||
);
|
||||
const isConversationStale = useCallback(
|
||||
(agentSessionId: string) => {
|
||||
const normalized = agentSessionId.trim();
|
||||
return Boolean(normalized && !isCurrentConversation(normalized));
|
||||
},
|
||||
[isCurrentConversation]
|
||||
);
|
||||
|
||||
return {
|
||||
isCurrentConversation,
|
||||
isConversationStale,
|
||||
persistActiveConversation,
|
||||
selectConversation,
|
||||
syncConversationListProjection
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user