666db0b939
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>
95 lines
2.5 KiB
TypeScript
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,
|
|
};
|
|
}
|