Files
memind/src/hooks/useGoalRunAwaiting.ts
T
john 666db0b939 feat(goal-run): add multi-checkpoint goal orchestration with H5 and admin surfaces.
Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval
UX in chat, and add admin inspection routes with local verify scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:03:16 +08:00

95 lines
2.5 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import {
isGoalRunApiAvailableError,
listAwaitingGoalRuns,
listGoalRuns,
type GoalRunAwaitingItem,
} from '../api/goalRun';
import type { ChatState } from '../types';
export function useGoalRunAwaiting({
userId,
sessionId,
chatState,
featureEnabled,
}: {
userId?: string | null;
sessionId?: string | null;
chatState: ChatState;
featureEnabled?: boolean;
}) {
const [enabled, setEnabled] = useState(false);
const [items, setItems] = useState<GoalRunAwaitingItem[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const unavailableRef = useRef(featureEnabled === false);
const refresh = useCallback(async () => {
if (!userId || unavailableRef.current) return;
if (featureEnabled === false) {
unavailableRef.current = true;
setEnabled(false);
setItems([]);
return;
}
setLoading(true);
setError(null);
try {
const goals = await listGoalRuns({
statuses: ['awaiting_user'],
limit: 20,
});
setEnabled(true);
const awaiting = listAwaitingGoalRuns(goals);
if (sessionId) {
awaiting.sort((left, right) => {
const leftMatch = left.goal.sourceSessionId === sessionId ? 1 : 0;
const rightMatch = right.goal.sourceSessionId === sessionId ? 1 : 0;
return rightMatch - leftMatch;
});
}
setItems(awaiting);
} catch (err) {
if (isGoalRunApiAvailableError(err)) {
unavailableRef.current = true;
setEnabled(false);
setItems([]);
return;
}
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [featureEnabled, sessionId, userId]);
useEffect(() => {
unavailableRef.current = featureEnabled === false;
if (!userId || featureEnabled === false) {
setEnabled(false);
setItems([]);
return;
}
void refresh();
}, [featureEnabled, refresh, userId]);
useEffect(() => {
if (!userId || unavailableRef.current) return;
if (chatState === 'idle' || chatState === 'error') {
void refresh();
}
}, [chatState, refresh, userId]);
const dismissItem = useCallback((goalRunId: string) => {
setItems((prev) => prev.filter((item) => item.goal.id !== goalRunId));
}, []);
return {
enabled,
items,
loading,
error,
refresh,
dismissItem,
};
}