mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-06 16:17:34 -07:00
fix(core): source-aware injection getters, fix unshift ordering, remove dead code
Rename getUserHints/getUserHintsAfter/getLatestHintIndex to getInjections/getInjectionsAfter/getLatestInjectionIndex with optional source filter so bg completions don't get formatted as user hints. Swap unshift ordering so bg completions appear before user hints in the message — the model sees context before the user's reaction to it. Remove unused getLastUserHintAt().
This commit is contained in:
@@ -538,12 +538,14 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
// 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.injectionService.getLatestHintIndex();
|
||||
const startIndex = this.config.injectionService.getLatestInjectionIndex();
|
||||
this.config.injectionService.onInjection(injectionListener);
|
||||
|
||||
try {
|
||||
const initialHints =
|
||||
this.config.injectionService.getUserHintsAfter(startIndex);
|
||||
const initialHints = this.config.injectionService.getInjectionsAfter(
|
||||
startIndex,
|
||||
'user_steering',
|
||||
);
|
||||
const formattedInitialHints = formatUserHintsForModel(initialHints);
|
||||
|
||||
let currentMessage: Content = formattedInitialHints
|
||||
@@ -591,17 +593,9 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
// If status is 'continue', update message for the next loop
|
||||
currentMessage = turnResult.nextMessage;
|
||||
|
||||
// Inject background completion output into the next turn.
|
||||
if (pendingBgCompletionsQueue.length > 0) {
|
||||
const bgText = pendingBgCompletionsQueue.join('\n');
|
||||
pendingBgCompletionsQueue.length = 0;
|
||||
currentMessage.parts ??= [];
|
||||
currentMessage.parts.unshift({
|
||||
text: `Background execution update:\n${bgText}\n\nThe above background execution has completed. Review the output and continue your work accordingly.`,
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -611,6 +605,15 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
currentMessage.parts.unshift({ text: formattedHints });
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingBgCompletionsQueue.length > 0) {
|
||||
const bgText = pendingBgCompletionsQueue.join('\n');
|
||||
pendingBgCompletionsQueue.length = 0;
|
||||
currentMessage.parts ??= [];
|
||||
currentMessage.parts.unshift({
|
||||
text: `Background execution update:\n${bgText}\n\nThe above background execution has completed. Review the output and continue your work accordingly.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.config.injectionService.offInjection(injectionListener);
|
||||
|
||||
@@ -137,7 +137,7 @@ class SubAgentInvocation extends BaseToolInvocation<AgentInputs, ToolResult> {
|
||||
_toolName ?? definition.name,
|
||||
_toolDisplayName ?? definition.displayName ?? definition.name,
|
||||
);
|
||||
this.startIndex = context.config.injectionService.getLatestHintIndex();
|
||||
this.startIndex = context.config.injectionService.getLatestInjectionIndex();
|
||||
}
|
||||
|
||||
private get config(): Config {
|
||||
@@ -200,8 +200,9 @@ class SubAgentInvocation extends BaseToolInvocation<AgentInputs, ToolResult> {
|
||||
return agentArgs;
|
||||
}
|
||||
|
||||
const userHints = this.config.injectionService.getUserHintsAfter(
|
||||
const userHints = this.config.injectionService.getInjectionsAfter(
|
||||
this.startIndex,
|
||||
'user_steering',
|
||||
);
|
||||
const formattedHints = formatUserHintsForModel(userHints);
|
||||
if (!formattedHints) {
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('InjectionService', () => {
|
||||
it('is disabled by default and ignores user_steering injections', () => {
|
||||
const service = new InjectionService(() => false);
|
||||
service.addInjection('this hint should be ignored', 'user_steering');
|
||||
expect(service.getUserHints()).toEqual([]);
|
||||
expect(service.getLatestHintIndex()).toBe(-1);
|
||||
expect(service.getInjections()).toEqual([]);
|
||||
expect(service.getLatestInjectionIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
it('stores trimmed injections and exposes them via indexing when enabled', () => {
|
||||
@@ -22,25 +22,14 @@ describe('InjectionService', () => {
|
||||
service.addInjection('second hint', 'user_steering');
|
||||
service.addInjection(' ', 'user_steering');
|
||||
|
||||
expect(service.getUserHints()).toEqual(['first hint', 'second hint']);
|
||||
expect(service.getLatestHintIndex()).toBe(1);
|
||||
expect(service.getUserHintsAfter(-1)).toEqual([
|
||||
expect(service.getInjections()).toEqual(['first hint', 'second hint']);
|
||||
expect(service.getLatestInjectionIndex()).toBe(1);
|
||||
expect(service.getInjectionsAfter(-1)).toEqual([
|
||||
'first hint',
|
||||
'second hint',
|
||||
]);
|
||||
expect(service.getUserHintsAfter(0)).toEqual(['second hint']);
|
||||
expect(service.getUserHintsAfter(1)).toEqual([]);
|
||||
});
|
||||
|
||||
it('tracks the last injection timestamp', () => {
|
||||
const service = new InjectionService(() => true);
|
||||
|
||||
expect(service.getLastUserHintAt()).toBeNull();
|
||||
service.addInjection('hint', 'user_steering');
|
||||
|
||||
const timestamp = service.getLastUserHintAt();
|
||||
expect(timestamp).not.toBeNull();
|
||||
expect(typeof timestamp).toBe('number');
|
||||
expect(service.getInjectionsAfter(0)).toEqual(['second hint']);
|
||||
expect(service.getInjectionsAfter(1)).toEqual([]);
|
||||
});
|
||||
|
||||
it('notifies listeners when an injection is added', () => {
|
||||
@@ -68,11 +57,11 @@ describe('InjectionService', () => {
|
||||
const service = new InjectionService(() => true);
|
||||
service.addInjection('hint 1', 'user_steering');
|
||||
service.addInjection('hint 2', 'user_steering');
|
||||
expect(service.getUserHints()).toHaveLength(2);
|
||||
expect(service.getInjections()).toHaveLength(2);
|
||||
|
||||
service.clear();
|
||||
expect(service.getUserHints()).toHaveLength(0);
|
||||
expect(service.getLatestHintIndex()).toBe(-1);
|
||||
expect(service.getInjections()).toHaveLength(0);
|
||||
expect(service.getLatestInjectionIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
describe('source-specific behavior', () => {
|
||||
@@ -110,7 +99,30 @@ describe('InjectionService', () => {
|
||||
'bg output',
|
||||
'background_completion',
|
||||
);
|
||||
expect(service.getUserHints()).toEqual(['bg output']);
|
||||
expect(service.getInjections()).toEqual(['bg output']);
|
||||
});
|
||||
|
||||
it('filters injections by source when requested', () => {
|
||||
const service = new InjectionService(() => true);
|
||||
service.addInjection('hint', 'user_steering');
|
||||
service.addInjection('bg output', 'background_completion');
|
||||
service.addInjection('hint 2', 'user_steering');
|
||||
|
||||
expect(service.getInjections('user_steering')).toEqual([
|
||||
'hint',
|
||||
'hint 2',
|
||||
]);
|
||||
expect(service.getInjections('background_completion')).toEqual([
|
||||
'bg output',
|
||||
]);
|
||||
expect(service.getInjections()).toEqual(['hint', 'bg output', 'hint 2']);
|
||||
|
||||
expect(service.getInjectionsAfter(0, 'user_steering')).toEqual([
|
||||
'hint 2',
|
||||
]);
|
||||
expect(service.getInjectionsAfter(0, 'background_completion')).toEqual([
|
||||
'bg output',
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects user_steering when model steering is disabled', () => {
|
||||
@@ -121,7 +133,7 @@ describe('InjectionService', () => {
|
||||
service.addInjection('steering hint', 'user_steering');
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
expect(service.getUserHints()).toEqual([]);
|
||||
expect(service.getInjections()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,39 +69,34 @@ export class InjectionService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all collected injection texts (all sources).
|
||||
* Returns collected injection texts, optionally filtered by source.
|
||||
*/
|
||||
getUserHints(): string[] {
|
||||
return this.injections.map((h) => h.text);
|
||||
getInjections(source?: InjectionSource): string[] {
|
||||
const items = source
|
||||
? this.injections.filter((h) => h.source === source)
|
||||
: this.injections;
|
||||
return items.map((h) => h.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns injection texts added after a specific index.
|
||||
* Returns injection texts added after a specific index, optionally filtered by source.
|
||||
*/
|
||||
getUserHintsAfter(index: number): string[] {
|
||||
getInjectionsAfter(index: number, source?: InjectionSource): string[] {
|
||||
if (index < 0) {
|
||||
return this.getUserHints();
|
||||
return this.getInjections(source);
|
||||
}
|
||||
return this.injections.slice(index + 1).map((h) => h.text);
|
||||
const items = this.injections.slice(index + 1);
|
||||
const filtered = source ? items.filter((h) => h.source === source) : items;
|
||||
return filtered.map((h) => h.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the latest injection.
|
||||
*/
|
||||
getLatestHintIndex(): number {
|
||||
getLatestInjectionIndex(): number {
|
||||
return this.injections.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp of the last injection.
|
||||
*/
|
||||
getLastUserHintAt(): number | null {
|
||||
if (this.injections.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return this.injections[this.injections.length - 1].timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all collected injections.
|
||||
*/
|
||||
|
||||
@@ -339,7 +339,7 @@ describe('ExecutionLifecycleService', () => {
|
||||
'',
|
||||
undefined,
|
||||
'none',
|
||||
(output, error) => error ? `Error: ${error.message}` : output,
|
||||
(output, error) => (error ? `Error: ${error.message}` : output),
|
||||
);
|
||||
const executionId = handle.pid!;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user