import { useAtomValue } from "@effect/atom-react";import { EMPTY_THREAD_DETAIL_ATOM, EMPTY_THREAD_DETAIL_STATE, createThreadDetailManager, getThreadDetailTargetKey, threadDetailStateAtom, type ThreadDetailState, type ThreadDetailTarget,} from "@t3tools/client-runtime";import { useEffect, useMemo } from "react"; import { derivePendingApprovals, derivePendingUserInputs } from "../lib/threadActivity";import { appAtomRegistry } from "./atom-registry";import { getEnvironmentClient, subscribeEnvironmentConnections,} from "./environment-session-registry";import { useThreadSelection } from "./use-thread-selection"; function shouldKeepThreadDetailWarm(state: ThreadDetailState): boolean { const thread = state.data; if (!thread || state.isDeleted) { return false; } if (thread.latestTurn?.sourceProposedPlan) { return true; } const sessionStatus = thread.session?.status; if (sessionStatus && sessionStatus !== "idle" && sessionStatus !== "stopped") { return true; } return ( derivePendingApprovals(thread.activities).length > 0 || derivePendingUserInputs(thread.activities).length > 0 );} const threadDetailManager = createThreadDetailManager({ getRegistry: () => appAtomRegistry, getClient: (environmentId) => { const client = getEnvironmentClient(environmentId); return client ? client.orchestration : null; }, getClientIdentity: (environmentId) => { return getEnvironmentClient(environmentId) ? environmentId : null; }, subscribeClientChanges: subscribeEnvironmentConnections, retention: { idleTtlMs: 5 * 60 * 1_000, maxRetainedEntries: 24, shouldKeepWarm: (_target, state) => shouldKeepThreadDetailWarm(state), },}); export function useThreadDetail(target: ThreadDetailTarget): ThreadDetailState { const { environmentId, threadId } = target; const targetKey = getThreadDetailTargetKey(target); useEffect( () => threadDetailManager.watch({ environmentId, threadId }), [environmentId, threadId], ); const state = useAtomValue( targetKey !== null ? threadDetailStateAtom(targetKey) : EMPTY_THREAD_DETAIL_ATOM, ); return targetKey === null ? EMPTY_THREAD_DETAIL_STATE : state;} export function useSelectedThreadDetail() { const { selectedThread } = useThreadSelection(); const state = useThreadDetail({ environmentId: selectedThread?.environmentId ?? null, threadId: selectedThread?.id ?? null, }); return useMemo(() => state.data, [state.data]);}
import { useAtomValue } from "@effect/atom-react";
import {
EMPTY_THREAD_DETAIL_ATOM,
EMPTY_THREAD_DETAIL_STATE,
createThreadDetailManager,
getThreadDetailTargetKey,
threadDetailStateAtom,
type ThreadDetailState,
type ThreadDetailTarget,
} from "@t3tools/client-runtime";
import { useEffect, useMemo } from "react";
import { derivePendingApprovals, derivePendingUserInputs } from "../lib/threadActivity";
import { appAtomRegistry } from "./atom-registry";
import {
getEnvironmentClient,
subscribeEnvironmentConnections,
} from "./environment-session-registry";
import { useThreadSelection } from "./use-thread-selection";
function shouldKeepThreadDetailWarm(state: ThreadDetailState): boolean {
const thread = state.data;
if (!thread || state.isDeleted) {
return false;
}
if (thread.latestTurn?.sourceProposedPlan) {
return true;
}
const sessionStatus = thread.session?.status;
if (sessionStatus && sessionStatus !== "idle" && sessionStatus !== "stopped") {
return true;
}
return (
derivePendingApprovals(thread.activities).length > 0 ||
derivePendingUserInputs(thread.activities).length > 0
);
}
const threadDetailManager = createThreadDetailManager({
getRegistry: () => appAtomRegistry,
getClient: (environmentId) => {
const client = getEnvironmentClient(environmentId);
return client ? client.orchestration : null;
},
getClientIdentity: (environmentId) => {
return getEnvironmentClient(environmentId) ? environmentId : null;
},
subscribeClientChanges: subscribeEnvironmentConnections,
retention: {
idleTtlMs: 5 * 60 * 1_000,
maxRetainedEntries: 24,
shouldKeepWarm: (_target, state) => shouldKeepThreadDetailWarm(state),
},
});
export function useThreadDetail(target: ThreadDetailTarget): ThreadDetailState {
const { environmentId, threadId } = target;
const targetKey = getThreadDetailTargetKey(target);
useEffect(
() => threadDetailManager.watch({ environmentId, threadId }),
[environmentId, threadId],
);
const state = useAtomValue(
targetKey !== null ? threadDetailStateAtom(targetKey) : EMPTY_THREAD_DETAIL_ATOM,
);
return targetKey === null ? EMPTY_THREAD_DETAIL_STATE : state;
}
export function useSelectedThreadDetail() {
const { selectedThread } = useThreadSelection();
const state = useThreadDetail({
environmentId: selectedThread?.environmentId ?? null,
threadId: selectedThread?.id ?? null,
});
return useMemo(() => state.data, [state.data]);
}