refactor: align remote background wiring with execution ID naming

This commit is contained in:
Adam Weidman
2026-03-08 20:19:00 -04:00
parent b3850edb8b
commit db32a6185b
2 changed files with 39 additions and 29 deletions
@@ -611,20 +611,23 @@ describe('RemoteAgentInvocation', () => {
}, },
); );
let pid: number | undefined; let executionId: number | undefined;
const onExit = vi.fn(); const onExit = vi.fn();
let unsubscribeOnExit: (() => void) | undefined; let unsubscribeOnExit: (() => void) | undefined;
const streamedOutputChunks: string[] = []; const streamedOutputChunks: string[] = [];
let unsubscribeStream: (() => void) | undefined; let unsubscribeStream: (() => void) | undefined;
const updateOutput = vi.fn((output: unknown) => { const updateOutput = vi.fn((output: unknown) => {
if (output === 'Chunk 1' && pid) { if (output === 'Chunk 1' && executionId) {
ShellExecutionService.background(pid); ShellExecutionService.background(executionId);
unsubscribeStream = ShellExecutionService.subscribe(pid, (event) => { unsubscribeStream = ShellExecutionService.subscribe(
executionId,
(event) => {
if (event.type === 'data' && typeof event.chunk === 'string') { if (event.type === 'data' && typeof event.chunk === 'string') {
streamedOutputChunks.push(event.chunk); streamedOutputChunks.push(event.chunk);
} }
}); },
);
} }
}); });
@@ -638,19 +641,23 @@ describe('RemoteAgentInvocation', () => {
new AbortController().signal, new AbortController().signal,
updateOutput, updateOutput,
undefined, undefined,
(newPid) => { (newExecutionId) => {
pid = newPid; executionId = newExecutionId;
unsubscribeOnExit = ShellExecutionService.onExit(newPid, onExit); unsubscribeOnExit = ShellExecutionService.onExit(
newExecutionId,
onExit,
);
}, },
); );
const result = await resultPromise; const result = await resultPromise;
expect(pid).toBeDefined(); expect(executionId).toBeDefined();
expect(result.returnDisplay).toContain( expect(result.returnDisplay).toContain(
'Remote agent moved to background', 'Remote agent moved to background',
); );
expect(result.data).toMatchObject({ expect(result.data).toMatchObject({
pid, executionId,
pid: executionId,
initialOutput: 'Chunk 1', initialOutput: 'Chunk 1',
}); });
+19 -16
View File
@@ -6,6 +6,7 @@
import { import {
BaseToolInvocation, BaseToolInvocation,
type BackgroundExecutionData,
type ToolConfirmationOutcome, type ToolConfirmationOutcome,
type ToolResult, type ToolResult,
type ToolCallConfirmationDetails, type ToolCallConfirmationDetails,
@@ -147,7 +148,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
} }
private publishBackgroundDelta( private publishBackgroundDelta(
pid: number, executionId: number,
previousOutput: string, previousOutput: string,
nextOutput: string, nextOutput: string,
): string { ): string {
@@ -157,7 +158,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
if (nextOutput.startsWith(previousOutput)) { if (nextOutput.startsWith(previousOutput)) {
ExecutionLifecycleService.appendOutput( ExecutionLifecycleService.appendOutput(
pid, executionId,
nextOutput.slice(previousOutput.length), nextOutput.slice(previousOutput.length),
); );
return nextOutput; return nextOutput;
@@ -166,7 +167,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
// If the reassembled output changes non-monotonically, resync by appending // If the reassembled output changes non-monotonically, resync by appending
// the full latest snapshot with a clear separator. // the full latest snapshot with a clear separator.
ExecutionLifecycleService.appendOutput( ExecutionLifecycleService.appendOutput(
pid, executionId,
`\n\n[Output updated]\n${nextOutput}`, `\n\n[Output updated]\n${nextOutput}`,
); );
return nextOutput; return nextOutput;
@@ -176,7 +177,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
_signal: AbortSignal, _signal: AbortSignal,
updateOutput?: (output: string | AnsiOutput) => void, updateOutput?: (output: string | AnsiOutput) => void,
_shellExecutionConfig?: unknown, _shellExecutionConfig?: unknown,
setPidCallback?: (pid: number) => void, setExecutionIdCallback?: (executionId: number) => void,
): Promise<ToolResult> { ): Promise<ToolResult> {
const reassembler = new A2AResultReassembler(); const reassembler = new A2AResultReassembler();
const executionController = new AbortController(); const executionController = new AbortController();
@@ -199,8 +200,8 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
}, },
}; };
} }
const backgroundPid = pid; const backgroundExecutionId = pid;
setPidCallback?.(backgroundPid); setExecutionIdCallback?.(backgroundExecutionId);
const run = async () => { const run = async () => {
let lastOutput = ''; let lastOutput = '';
@@ -244,7 +245,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
const currentOutput = reassembler.toString(); const currentOutput = reassembler.toString();
lastOutput = this.publishBackgroundDelta( lastOutput = this.publishBackgroundDelta(
backgroundPid, backgroundExecutionId,
lastOutput, lastOutput,
currentOutput, currentOutput,
); );
@@ -273,20 +274,20 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
`[RemoteAgent] Final response from ${this.definition.name}:\n${JSON.stringify(finalResponse, null, 2)}`, `[RemoteAgent] Final response from ${this.definition.name}:\n${JSON.stringify(finalResponse, null, 2)}`,
); );
ExecutionLifecycleService.completeExecution(backgroundPid, { ExecutionLifecycleService.completeExecution(backgroundExecutionId, {
exitCode: 0, exitCode: 0,
}); });
} catch (error: unknown) { } catch (error: unknown) {
const partialOutput = reassembler.toString(); const partialOutput = reassembler.toString();
lastOutput = this.publishBackgroundDelta( lastOutput = this.publishBackgroundDelta(
backgroundPid, backgroundExecutionId,
lastOutput, lastOutput,
partialOutput, partialOutput,
); );
const errorMessage = `Error calling remote agent: ${ const errorMessage = `Error calling remote agent: ${
error instanceof Error ? error.message : String(error) error instanceof Error ? error.message : String(error)
}`; }`;
ExecutionLifecycleService.completeExecution(backgroundPid, { ExecutionLifecycleService.completeExecution(backgroundExecutionId, {
error: new Error(errorMessage), error: new Error(errorMessage),
aborted: executionController.signal.aborted, aborted: executionController.signal.aborted,
exitCode: executionController.signal.aborted ? 130 : 1, exitCode: executionController.signal.aborted ? 130 : 1,
@@ -306,15 +307,17 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
if (executionResult.backgrounded) { if (executionResult.backgrounded) {
const command = `${this.getDescription()}: ${this.params.query}`; const command = `${this.getDescription()}: ${this.params.query}`;
const backgroundMessage = `Remote agent moved to background (PID: ${backgroundPid}). Output hidden. Press Ctrl+B to view.`; const backgroundMessage = `Remote agent moved to background (PID: ${backgroundExecutionId}). Output hidden. Press Ctrl+B to view.`;
const data: BackgroundExecutionData = {
executionId: backgroundExecutionId,
pid: backgroundExecutionId,
command,
initialOutput: executionResult.output,
};
return { return {
llmContent: [{ text: backgroundMessage }], llmContent: [{ text: backgroundMessage }],
returnDisplay: backgroundMessage, returnDisplay: backgroundMessage,
data: { data,
pid: backgroundPid,
command,
initialOutput: executionResult.output,
},
}; };
} }