Skip to content

Commit e8372da

Browse files
committed
feat(ai): Implement DOM Event introspection tools (#8278, #8279)
- Implement client-side logic for getDomEventListeners and getDomEventSummary in RuntimeService - Update Client.mjs to route get_dom_event requests - Update Server-side RuntimeService to expose new methods - Update OpenAPI definition with new tools Closes #8278 Closes #8279
1 parent 28c9525 commit e8372da

4 files changed

Lines changed: 211 additions & 22 deletions

File tree

ai/mcp/server/neural-link/openapi.yaml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,91 @@ paths:
154154
schema:
155155
$ref: '#/components/schemas/ErrorResponse'
156156

157+
/component/dom/events/get:
158+
post:
159+
summary: Get DOM Event Listeners
160+
operationId: get_dom_event_listeners
161+
x-pass-as-object: true
162+
description: |
163+
Retrieves detailed DOM event listeners for a specific component.
164+
165+
**When to Use:**
166+
To debug event delegation and interaction logic on a specific component.
167+
tags: [Component]
168+
requestBody:
169+
required: true
170+
content:
171+
application/json:
172+
schema:
173+
$ref: '#/components/schemas/GetDomEventListenersRequest'
174+
responses:
175+
'200':
176+
description: List of event listeners
177+
content:
178+
application/json:
179+
schema:
180+
type: object
181+
properties:
182+
listeners:
183+
type: array
184+
items:
185+
type: object
186+
'400':
187+
description: Invalid request body
188+
content:
189+
application/json:
190+
schema:
191+
$ref: '#/components/schemas/ErrorResponse'
192+
'500':
193+
description: Internal server error
194+
content:
195+
application/json:
196+
schema:
197+
$ref: '#/components/schemas/ErrorResponse'
198+
199+
/component/dom/events/summary:
200+
post:
201+
summary: Get DOM Event Summary
202+
operationId: get_dom_event_summary
203+
x-pass-as-object: true
204+
description: |
205+
Retrieves a high-level summary of the DomEvent manager state.
206+
207+
**When to Use:**
208+
To understand the global event landscape and identify potential hotspots.
209+
tags: [Component]
210+
requestBody:
211+
content:
212+
application/json:
213+
schema:
214+
$ref: '#/components/schemas/GetDomEventSummaryRequest'
215+
responses:
216+
'200':
217+
description: Event summary
218+
content:
219+
application/json:
220+
schema:
221+
type: object
222+
properties:
223+
totalEvents:
224+
type: integer
225+
byEvent:
226+
type: object
227+
byComponent:
228+
type: object
229+
'400':
230+
description: Invalid request body
231+
content:
232+
application/json:
233+
schema:
234+
$ref: '#/components/schemas/ErrorResponse'
235+
'500':
236+
description: Internal server error
237+
content:
238+
application/json:
239+
schema:
240+
$ref: '#/components/schemas/ErrorResponse'
241+
157242
/component/tree:
158243
post:
159244
summary: Get Component Tree
@@ -911,6 +996,25 @@ components:
911996
type: string
912997
description: The target App Worker Session ID
913998

999+
GetDomEventListenersRequest:
1000+
type: object
1001+
required:
1002+
- componentId
1003+
properties:
1004+
componentId:
1005+
type: string
1006+
description: The component ID to inspect
1007+
sessionId:
1008+
type: string
1009+
description: The target App Worker Session ID
1010+
1011+
GetDomEventSummaryRequest:
1012+
type: object
1013+
properties:
1014+
sessionId:
1015+
type: string
1016+
description: The target App Worker Session ID
1017+
9141018
QueryComponentRequest:
9151019
type: object
9161020
required:

ai/mcp/server/neural-link/services/RuntimeService.mjs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ConnectionService from './ConnectionService.mjs';
44
/**
55
* @summary Manages application runtime and topology operations for the Neural Link MCP Server.
66
*
7-
* This service provides tools for inspecting the runtime structure (workers, windows) and
7+
* This service provides tools for inspecting the runtime structure (workers, windows) and
88
* controlling the application lifecycle (reloading).
99
*
1010
* @class Neo.ai.mcp.server.neural-link.services.RuntimeService
@@ -25,6 +25,25 @@ class RuntimeService extends Base {
2525
singleton: true
2626
}
2727

28+
/**
29+
* @param {Object} opts The options object.
30+
* @param {String} opts.componentId The component ID.
31+
* @param {String} opts.sessionId The target session ID.
32+
* @returns {Promise<Object>}
33+
*/
34+
async getDomEventListeners({componentId, sessionId}) {
35+
return await ConnectionService.call(sessionId, 'get_dom_event_listeners', {componentId})
36+
}
37+
38+
/**
39+
* @param {Object} opts The options object.
40+
* @param {String} opts.sessionId The target session ID.
41+
* @returns {Promise<Object>}
42+
*/
43+
async getDomEventSummary({sessionId}) {
44+
return await ConnectionService.call(sessionId, 'get_dom_event_summary', {})
45+
}
46+
2847
/**
2948
* Retrieves the navigation history stack.
3049
* @param {Object} opts The options object.

src/ai/Client.mjs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,26 +73,29 @@ class Client extends Base {
7373
runtime : Neo.create(RuntimeService, {client: me})
7474
};
7575

76+
const {component, data, runtime} = me.services;
77+
7678
me.serviceMap = {
77-
get_component : me.services.component,
78-
get_dom_rect : me.services.component,
79-
get_vdom : me.services.component,
80-
get_vnode : me.services.component,
81-
highlight_component : me.services.component,
82-
query_component : me.services.component,
83-
set_component : me.services.component,
84-
85-
get_record : me.services.data,
86-
inspect_state_provider: me.services.data,
87-
inspect_store : me.services.data,
88-
list_stores : me.services.data,
89-
modify_state_provider: me.services.data,
90-
91-
get_drag : me.services.runtime,
92-
get_route : me.services.runtime,
93-
get_window : me.services.runtime,
94-
reload_page : me.services.runtime,
95-
set_route : me.services.runtime
79+
get_component : component,
80+
get_dom_rect : component,
81+
get_vdom : component,
82+
get_vnode : component,
83+
highlight_component : component,
84+
query_component : component,
85+
set_component : component,
86+
87+
get_record : data,
88+
inspect_state_provider: data,
89+
inspect_store : data,
90+
list_stores : data,
91+
modify_state_provider : data,
92+
93+
get_dom_event : runtime,
94+
get_drag : runtime,
95+
get_route : runtime,
96+
get_window : runtime,
97+
reload_page : runtime,
98+
set_route : runtime
9699
};
97100

98101
Neo.currentWorker.on({

src/ai/client/RuntimeService.mjs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import HashHistory from '../../util/HashHistory.mjs';
2-
import Service from './Service.mjs';
1+
import DomEventManager from '../../manager/DomEvent.mjs';
2+
import HashHistory from '../../util/HashHistory.mjs';
3+
import Service from './Service.mjs';
34

45
/**
56
* Handles runtime environment related Neural Link requests.
@@ -15,6 +16,68 @@ class RuntimeService extends Service {
1516
className: 'Neo.ai.client.RuntimeService'
1617
}
1718

19+
/**
20+
* @param {Object} params
21+
* @param {String} params.componentId
22+
* @returns {Object}
23+
*/
24+
getDomEventListeners({componentId}) {
25+
const
26+
manager = DomEventManager,
27+
listeners = [],
28+
eventMap = manager.items?.[componentId];
29+
30+
if (eventMap) {
31+
Object.entries(eventMap).forEach(([eventName, events]) => {
32+
events.forEach(event => {
33+
listeners.push({
34+
event : eventName,
35+
delegate: event.delegate,
36+
priority: event.priority,
37+
handler : typeof event.fn === 'function' ? event.fn.name || 'anonymous' : event.fn,
38+
scope : event.scope?.id || 'unknown'
39+
})
40+
})
41+
})
42+
}
43+
44+
return {listeners}
45+
}
46+
47+
/**
48+
* @param {Object} params
49+
* @returns {Object}
50+
*/
51+
getDomEventSummary(params) {
52+
const
53+
manager = DomEventManager,
54+
summary = {
55+
totalEvents: 0,
56+
byEvent : {},
57+
byComponent: {}
58+
};
59+
60+
if (manager.items) {
61+
Object.entries(manager.items).forEach(([componentId, eventMap]) => {
62+
let componentCount = 0;
63+
64+
Object.entries(eventMap).forEach(([eventName, events]) => {
65+
const count = events.length;
66+
67+
summary.totalEvents += count;
68+
componentCount += count;
69+
summary.byEvent[eventName] = (summary.byEvent[eventName] || 0) + count
70+
});
71+
72+
if (componentCount > 0) {
73+
summary.byComponent[componentId] = componentCount
74+
}
75+
})
76+
}
77+
78+
return summary
79+
}
80+
1881
/**
1982
* @param {Object} params
2083
* @returns {Object}

0 commit comments

Comments
 (0)