refactor(core): introduce InjectionService with source-aware injection and backend-native background completions (#22544)

This commit is contained in:
Adam Weidman
2026-03-16 17:06:29 -04:00
committed by GitHub
parent b91f75cd6d
commit 44ce90d76c
17 changed files with 807 additions and 198 deletions
+31 -10
View File
@@ -63,7 +63,11 @@ import { getVersion } from '../utils/version.js';
import { getToolCallContext } from '../utils/toolCallContext.js';
import { scheduleAgentTools } from './agent-scheduler.js';
import { DeadlineTimer } from '../utils/deadlineTimer.js';
import { formatUserHintsForModel } from '../utils/fastAckHelper.js';
import {
formatUserHintsForModel,
formatBackgroundCompletionForModel,
} from '../utils/fastAckHelper.js';
import type { InjectionSource } from '../config/injectionService.js';
/** A callback function to report on agent activity. */
export type ActivityCallback = (activity: SubagentActivityEvent) => void;
@@ -513,18 +517,25 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
: DEFAULT_QUERY_STRING;
const pendingHintsQueue: string[] = [];
const hintListener = (hint: string) => {
pendingHintsQueue.push(hint);
const pendingBgCompletionsQueue: string[] = [];
const injectionListener = (text: string, source: InjectionSource) => {
if (source === 'user_steering') {
pendingHintsQueue.push(text);
} else if (source === 'background_completion') {
pendingBgCompletionsQueue.push(text);
}
};
// 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.config.userHintService.getLatestHintIndex();
this.config.userHintService.onUserHint(hintListener);
const startIndex = this.config.injectionService.getLatestInjectionIndex();
this.config.injectionService.onInjection(injectionListener);
try {
const initialHints =
this.config.userHintService.getUserHintsAfter(startIndex);
const initialHints = this.config.injectionService.getInjectionsAfter(
startIndex,
'user_steering',
);
const formattedInitialHints = formatUserHintsForModel(initialHints);
let currentMessage: Content = formattedInitialHints
@@ -572,20 +583,30 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
// If status is 'continue', update message for the next loop
currentMessage = turnResult.nextMessage;
// Check for new user steering hints collected via subscription
// Prepend inter-turn injections. User hints are unshifted first so
// that bg completions (unshifted second) appear before them in the
// final message — the model sees context before the user's reaction.
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 });
}
}
if (pendingBgCompletionsQueue.length > 0) {
const bgText = pendingBgCompletionsQueue.join('\n');
pendingBgCompletionsQueue.length = 0;
currentMessage.parts ??= [];
currentMessage.parts.unshift({
text: formatBackgroundCompletionForModel(bgText),
});
}
}
} finally {
this.config.userHintService.offUserHint(hintListener);
this.config.injectionService.offInjection(injectionListener);
}
// === UNIFIED RECOVERY BLOCK ===