mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-01 08:51:11 -07:00
feat(core): experimental in-progress steering hints (2 of 2) (#19307)
This commit is contained in:
@@ -60,6 +60,7 @@ import { getToolCallContext } from '../utils/toolCallContext.js';
|
||||
import { scheduleAgentTools } from './agent-scheduler.js';
|
||||
import { DeadlineTimer } from '../utils/deadlineTimer.js';
|
||||
import { LlmRole } from '../telemetry/types.js';
|
||||
import { formatUserHintsForModel } from '../utils/fastAckHelper.js';
|
||||
|
||||
/** A callback function to report on agent activity. */
|
||||
export type ActivityCallback = (activity: SubagentActivityEvent) => void;
|
||||
@@ -463,45 +464,82 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
const query = this.definition.promptConfig.query
|
||||
? templateString(this.definition.promptConfig.query, augmentedInputs)
|
||||
: DEFAULT_QUERY_STRING;
|
||||
let currentMessage: Content = { role: 'user', parts: [{ text: query }] };
|
||||
|
||||
while (true) {
|
||||
// Check for termination conditions like max turns.
|
||||
const reason = this.checkTermination(turnCounter, maxTurns);
|
||||
if (reason) {
|
||||
terminateReason = reason;
|
||||
break;
|
||||
}
|
||||
const pendingHintsQueue: string[] = [];
|
||||
const hintListener = (hint: string) => {
|
||||
pendingHintsQueue.push(hint);
|
||||
};
|
||||
// Capture the index of the last hint before starting to avoid re-injecting old hints.
|
||||
// NOTE: Hints added AFTER this point will be broadcast to all currently running
|
||||
// local agents via the listener below.
|
||||
const startIndex =
|
||||
this.runtimeContext.userHintService.getLatestHintIndex();
|
||||
this.runtimeContext.userHintService.onUserHint(hintListener);
|
||||
|
||||
// Check for timeout or external abort.
|
||||
if (combinedSignal.aborted) {
|
||||
// Determine which signal caused the abort.
|
||||
terminateReason = deadlineTimer.signal.aborted
|
||||
? AgentTerminateMode.TIMEOUT
|
||||
: AgentTerminateMode.ABORTED;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const initialHints =
|
||||
this.runtimeContext.userHintService.getUserHintsAfter(startIndex);
|
||||
const formattedInitialHints = formatUserHintsForModel(initialHints);
|
||||
|
||||
const turnResult = await this.executeTurn(
|
||||
chat,
|
||||
currentMessage,
|
||||
turnCounter++,
|
||||
combinedSignal,
|
||||
deadlineTimer.signal,
|
||||
onWaitingForConfirmation,
|
||||
);
|
||||
let currentMessage: Content = formattedInitialHints
|
||||
? {
|
||||
role: 'user',
|
||||
parts: [{ text: formattedInitialHints }, { text: query }],
|
||||
}
|
||||
: { role: 'user', parts: [{ text: query }] };
|
||||
|
||||
if (turnResult.status === 'stop') {
|
||||
terminateReason = turnResult.terminateReason;
|
||||
// Only set finalResult if the turn provided one (e.g., error or goal).
|
||||
if (turnResult.finalResult) {
|
||||
finalResult = turnResult.finalResult;
|
||||
while (true) {
|
||||
// Check for termination conditions like max turns.
|
||||
const reason = this.checkTermination(turnCounter, maxTurns);
|
||||
if (reason) {
|
||||
terminateReason = reason;
|
||||
break;
|
||||
}
|
||||
break; // Exit the loop for *any* stop reason.
|
||||
}
|
||||
|
||||
// If status is 'continue', update message for the next loop
|
||||
currentMessage = turnResult.nextMessage;
|
||||
// Check for timeout or external abort.
|
||||
if (combinedSignal.aborted) {
|
||||
// Determine which signal caused the abort.
|
||||
terminateReason = deadlineTimer.signal.aborted
|
||||
? AgentTerminateMode.TIMEOUT
|
||||
: AgentTerminateMode.ABORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
const turnResult = await this.executeTurn(
|
||||
chat,
|
||||
currentMessage,
|
||||
turnCounter++,
|
||||
combinedSignal,
|
||||
deadlineTimer.signal,
|
||||
onWaitingForConfirmation,
|
||||
);
|
||||
|
||||
if (turnResult.status === 'stop') {
|
||||
terminateReason = turnResult.terminateReason;
|
||||
// Only set finalResult if the turn provided one (e.g., error or goal).
|
||||
if (turnResult.finalResult) {
|
||||
finalResult = turnResult.finalResult;
|
||||
}
|
||||
break; // Exit the loop for *any* stop reason.
|
||||
}
|
||||
|
||||
// If status is 'continue', update message for the next loop
|
||||
currentMessage = turnResult.nextMessage;
|
||||
|
||||
// Check for new user steering hints collected via subscription
|
||||
if (pendingHintsQueue.length > 0) {
|
||||
const hintsToProcess = [...pendingHintsQueue];
|
||||
pendingHintsQueue.length = 0;
|
||||
const formattedHints = formatUserHintsForModel(hintsToProcess);
|
||||
if (formattedHints) {
|
||||
// Append hints to the current message (next turn)
|
||||
currentMessage.parts ??= [];
|
||||
currentMessage.parts.unshift({ text: formattedHints });
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.runtimeContext.userHintService.offUserHint(hintListener);
|
||||
}
|
||||
|
||||
// === UNIFIED RECOVERY BLOCK ===
|
||||
|
||||
Reference in New Issue
Block a user