refactor(core): move background injection wiring from UI to backend

Wire ExecutionLifecycleService.setInjectionService() in Config constructor
so backgrounded executions inject directly via settleExecution instead of
routing through a useEffect bridge in AppContainer.
This commit is contained in:
Adam Weidman
2026-03-15 15:11:13 -04:00
parent 931b80206b
commit 8b7321ea8d
3 changed files with 23 additions and 24 deletions
-24
View File
@@ -85,8 +85,6 @@ import {
buildUserSteeringHintPrompt,
logBillingEvent,
ApiKeyUpdatedEvent,
ExecutionLifecycleService,
type BackgroundCompletionInfo,
type InjectionSource,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
@@ -1119,28 +1117,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
};
}, [config]);
// Wire background completion events from ExecutionLifecycleService into the
// injection service so completed backgrounded executions are reinjected.
useEffect(() => {
const bgListener = (info: BackgroundCompletionInfo) => {
// Use the execution creator's custom injection text if provided.
let text = info.injectionText;
if (text === null || text === undefined) {
// Fallback: generic format for executions without a custom formatter.
const header = info.error
? `[Background execution (ID: ${info.executionId}) completed with error: ${info.error.message}]`
: `[Background execution (ID: ${info.executionId}) completed]`;
const body = info.output ? `\n${info.output}` : '';
text = `${header}${body}`;
}
config.injectionService.addInjection(text, 'background_completion');
};
ExecutionLifecycleService.onBackgroundComplete(bgListener);
return () => {
ExecutionLifecycleService.offBackgroundComplete(bgListener);
};
}, [config]);
const {
streamingState,
submitQuery,
+2
View File
@@ -148,6 +148,7 @@ import type { AgentDefinition } from '../agents/types.js';
import { fetchAdminControls } from '../code_assist/admin/admin_controls.js';
import { isSubpath, resolveToRealPath } from '../utils/paths.js';
import { InjectionService } from './injectionService.js';
import { ExecutionLifecycleService } from '../services/executionLifecycleService.js';
import { WORKSPACE_POLICY_TIER } from '../policy/config.js';
import { loadPoliciesFromToml } from '../policy/toml-loader.js';
@@ -938,6 +939,7 @@ export class Config implements McpContext, AgentLoopContext {
this.injectionService = new InjectionService(() =>
this.isModelSteeringEnabled(),
);
ExecutionLifecycleService.setInjectionService(this.injectionService);
this.toolOutputMasking = {
enabled: params.toolOutputMasking?.enabled ?? true,
toolProtectionThreshold:
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { InjectionService } from '../config/injectionService.js';
import type { AnsiOutput } from '../utils/terminalSerializer.js';
export type ExecutionMethod =
@@ -138,6 +139,15 @@ export class ExecutionLifecycleService {
>();
private static backgroundCompletionListeners =
new Set<BackgroundCompletionListener>();
private static injectionService: InjectionService | null = null;
/**
* Wires a singleton InjectionService so that backgrounded executions
* can inject their output directly without routing through the UI layer.
*/
static setInjectionService(service: InjectionService): void {
this.injectionService = service;
}
/**
* Registers a listener that fires when a previously-backgrounded
@@ -210,6 +220,7 @@ export class ExecutionLifecycleService {
this.activeListeners.clear();
this.exitedExecutionInfo.clear();
this.backgroundCompletionListeners.clear();
this.injectionService = null;
this.nextExecutionId = NON_PROCESS_EXECUTION_ID_START;
}
@@ -323,6 +334,16 @@ export class ExecutionLifecycleService {
error: result.error,
injectionText,
};
// Inject directly into the model conversation if injection text is
// available and the injection service has been wired up.
if (injectionText && this.injectionService) {
this.injectionService.addInjection(
injectionText,
'background_completion',
);
}
for (const listener of this.backgroundCompletionListeners) {
listener(info);
}