feat(core, cli): Implement sequential approval. (#11593)

This commit is contained in:
joshualitt
2025-10-27 09:59:08 -07:00
committed by GitHub
parent 23c906b085
commit 541eeb7a50
9 changed files with 1272 additions and 339 deletions
@@ -62,12 +62,20 @@ export type TrackedToolCall =
| TrackedCompletedToolCall
| TrackedCancelledToolCall;
export type CancelAllFn = (signal: AbortSignal) => void;
export function useReactToolScheduler(
onComplete: (tools: CompletedToolCall[]) => Promise<void>,
config: Config,
getPreferredEditor: () => EditorType | undefined,
onEditorClose: () => void,
): [TrackedToolCall[], ScheduleFn, MarkToolsAsSubmittedFn] {
): [
TrackedToolCall[],
ScheduleFn,
MarkToolsAsSubmittedFn,
React.Dispatch<React.SetStateAction<TrackedToolCall[]>>,
CancelAllFn,
] {
const [toolCallsForDisplay, setToolCallsForDisplay] = useState<
TrackedToolCall[]
>([]);
@@ -112,37 +120,36 @@ export function useReactToolScheduler(
);
const toolCallsUpdateHandler: ToolCallsUpdateHandler = useCallback(
(updatedCoreToolCalls: ToolCall[]) => {
setToolCallsForDisplay((prevTrackedCalls) =>
updatedCoreToolCalls.map((coreTc) => {
const existingTrackedCall = prevTrackedCalls.find(
(ptc) => ptc.request.callId === coreTc.request.callId,
);
// Start with the new core state, then layer on the existing UI state
// to ensure UI-only properties like pid are preserved.
(allCoreToolCalls: ToolCall[]) => {
setToolCallsForDisplay((prevTrackedCalls) => {
const prevCallsMap = new Map(
prevTrackedCalls.map((c) => [c.request.callId, c]),
);
return allCoreToolCalls.map((coreTc): TrackedToolCall => {
const existingTrackedCall = prevCallsMap.get(coreTc.request.callId);
const responseSubmittedToGemini =
existingTrackedCall?.responseSubmittedToGemini ?? false;
if (coreTc.status === 'executing') {
// Preserve live output if it exists from a previous render.
const liveOutput = (existingTrackedCall as TrackedExecutingToolCall)
?.liveOutput;
return {
...coreTc,
responseSubmittedToGemini,
liveOutput: (existingTrackedCall as TrackedExecutingToolCall)
?.liveOutput,
liveOutput,
pid: (coreTc as ExecutingToolCall).pid,
};
} else {
return {
...coreTc,
responseSubmittedToGemini,
};
}
// For other statuses, explicitly set liveOutput and pid to undefined
// to ensure they are not carried over from a previous executing state.
return {
...coreTc,
responseSubmittedToGemini,
liveOutput: undefined,
pid: undefined,
};
}),
);
});
});
},
[setToolCallsForDisplay],
);
@@ -178,9 +185,10 @@ export function useReactToolScheduler(
request: ToolCallRequestInfo | ToolCallRequestInfo[],
signal: AbortSignal,
) => {
setToolCallsForDisplay([]);
void scheduler.schedule(request, signal);
},
[scheduler],
[scheduler, setToolCallsForDisplay],
);
const markToolsAsSubmitted: MarkToolsAsSubmittedFn = useCallback(
@@ -196,7 +204,20 @@ export function useReactToolScheduler(
[],
);
return [toolCallsForDisplay, schedule, markToolsAsSubmitted];
const cancelAllToolCalls = useCallback(
(signal: AbortSignal) => {
scheduler.cancelAll(signal);
},
[scheduler],
);
return [
toolCallsForDisplay,
schedule,
markToolsAsSubmitted,
setToolCallsForDisplay,
cancelAllToolCalls,
];
}
/**