feat(gateway): wire safe-point runtime cancellation for agent.cancel
This commit is contained in:
@@ -182,8 +182,6 @@ export function createAgentHandlers(deps: AgentHandlerDeps) {
|
||||
},
|
||||
|
||||
'agent.cancel': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
// Cancel is a placeholder — proper cancellation requires abort controller support in NativeAgent.
|
||||
// For now, just report whether the agent was busy.
|
||||
const params = request.params as { connectionId?: string } | undefined;
|
||||
const connectionId = params?.connectionId as string;
|
||||
|
||||
@@ -191,9 +189,22 @@ export function createAgentHandlers(deps: AgentHandlerDeps) {
|
||||
return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required');
|
||||
}
|
||||
|
||||
const wasBusy = deps.sessionBridge.isBusy(connectionId);
|
||||
// TODO: Wire AbortController into NativeAgent for actual cancellation
|
||||
return { id: request.id, result: { cancelled: wasBusy } };
|
||||
const sessionId = deps.sessionBridge.getSessionId(connectionId);
|
||||
const laneId = sessionId ?? connectionId;
|
||||
|
||||
// Clear any queued (not-yet-started) work first.
|
||||
deps.laneQueue.cancel(laneId);
|
||||
|
||||
const cancelled = deps.sessionBridge.cancel(connectionId);
|
||||
return {
|
||||
id: request.id,
|
||||
result: {
|
||||
cancelled,
|
||||
message: cancelled
|
||||
? 'Cancellation requested. The active operation will stop at the next safe point.'
|
||||
: 'No active operation to cancel.',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ describe('agent handlers', () => {
|
||||
getAgent: vi.fn(() => mockAgent),
|
||||
getSessionId: vi.fn(() => 'ws:conn-1'),
|
||||
isBusy: vi.fn(() => false),
|
||||
cancel: vi.fn(() => false),
|
||||
setBusy: vi.fn(),
|
||||
setOnToolUse: vi.fn(),
|
||||
};
|
||||
@@ -265,6 +266,7 @@ describe('agent handlers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockBridge.isBusy.mockReturnValue(false);
|
||||
mockBridge.cancel.mockReturnValue(false);
|
||||
mockBridge.getAgent.mockReturnValue(mockAgent);
|
||||
mockAgent.process.mockResolvedValue('response text');
|
||||
});
|
||||
@@ -399,11 +401,22 @@ describe('agent handlers', () => {
|
||||
});
|
||||
|
||||
it('agent.cancel returns cancelled state', async () => {
|
||||
mockBridge.isBusy.mockReturnValue(true);
|
||||
mockBridge.cancel.mockReturnValue(true);
|
||||
const req: GatewayRequest = { id: 7, method: 'agent.cancel', params: { connectionId: 'conn-1' } };
|
||||
const result = await handlers['agent.cancel'](req) as GatewayResponse;
|
||||
|
||||
expect((result.result as any).cancelled).toBe(true);
|
||||
expect((result.result as any).message).toContain('Cancellation requested');
|
||||
expect(mockBridge.cancel).toHaveBeenCalledWith('conn-1');
|
||||
});
|
||||
|
||||
it('agent.cancel returns not-cancelled when no active operation exists', async () => {
|
||||
mockBridge.cancel.mockReturnValue(false);
|
||||
const req: GatewayRequest = { id: 8, method: 'agent.cancel', params: { connectionId: 'conn-1' } };
|
||||
const result = await handlers['agent.cancel'](req) as GatewayResponse;
|
||||
|
||||
expect((result.result as any).cancelled).toBe(false);
|
||||
expect((result.result as any).message).toContain('No active operation');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user