Context
Operator-named need from the 2026-06-11 night shift ("it might need NL enhancement tickets"): during the #12878/#12880/#12883-adjacent grid verification work, agent-side drag simulation via the Neural Link MCP simulate_event tool proved unroutable for devindex — and the root is general: non-SharedWorker apps never register their window with the NL bridge.
The Problem
Reproduced live: the bridge's window topology listed only the Portal (SharedWorker app); devindex's app worker connected (Neo.ai.Client: Connected to MCP Server) and its components carry a real windowId internally, but no window_connected notification ever reaches the bridge — across multiple fresh boots. simulate_event requires a windowId per event → every event-simulation call against a non-shared app has no route. Agent-side workarounds used tonight: in-page MouseEvent dispatch via an agent-controlled browser, and playwright page.mouse (both documented on #12807) — they work, but they bypass the NL tool surface entirely, so pure-NL sessions (no browser-control harness) cannot drive interactions on non-shared apps at all.
The Architectural Reality
src/ai/Client.mjs (~L220 onAppWorkerWindowConnect / onAppWorkerWindowDisconnect, wired at ~L127) registers windows from App-worker connect/disconnect events — SharedWorker port semantics. A non-shared app's single implicit window never fires that event, so WindowManager.items stays empty and the rehydration loop (~L292) has nothing to send. The window EXISTS (components carry windowId); only the bridge notification path is missing.
The Fix (shape, for intake validation)
On AI-client startup in a non-SharedWorker app, emit the window_connected notification for the app's own window (id, appName, rects) — mirroring what the SharedWorker connect path sends — and window_disconnected on unload. Acceptance: get_window_topology lists the non-shared app's window; simulate_event routes to it (verify with a click on a devindex button); SharedWorker apps unchanged.
Acceptance Criteria
Contract Ledger
| Target Surface |
Contract detail |
Client#onSocketOpen non-shared registration |
Source: windowId-keyed Neo.apps (key = windowId, app.name = appName). Emits window_connected {appName, windowId}. Rects are omitted here; they are optional and the SharedWorker path also omits them until a WindowManager entry exists. Idempotent: skips any window already in WindowManager, so SharedWorker and multi-window apps stay untouched. |
| Disconnect semantics |
Unchanged in PR #13072. The non-shared implicit window teardown rides socket close and bridge session cleanup. A dedicated per-window window_disconnected on unload remains a follow-up outside this registration/routing slice. |
ConnectionService.handleNotification() topology |
Unchanged: stores the window_connected entry (windowId + appName; rects undefined) under the session, identically to a SharedWorker window. |
get_window_topology / healthcheck visibility |
The non-shared window appears with windowId + appName; rects remain null/absent until a WindowPosition measurement lands. |
simulate_event routing |
Contract unchanged: every event still requires a windowId; the non-shared window now has a registered one, so events can route. Absent/stale windows use the existing not-found behavior. |
| Evidence |
L2 emission unit in PR #13072. AC1/AC2 require L3 live devindex topology + simulate_event click verification after merge; both are annotated above as operator-handoff residuals. |
Out of Scope
- A dedicated
simulateDrag convenience primitive (possible follow-up once routing works; shape sketched on #12807).
- The grid defect itself (#12883-adjacent — separate board ticket).
Decision Record impact
none.
Related
Found during: #12878 verification (rig build). Consumers: #12807 (whitebox-e2e simulatability, @neo-opus-ada), the grid cross-region regression spec. Evidence trail: #12807 comments from 2026-06-11.
Release classification: boardless — the playwright/whitebox path covers the release-critical verification need; this unblocks the pure-NL interaction class. Operator may board it under the verification-substrate scope at his discretion.
Origin Session ID: 567191c3-16f6-4235-914c-b51fc94d1514
Retrieval Hint: query_raw_memories("neural link window registration non-shared simulate_event unroutable")
Live latest-open sweep: checked latest 12 open issues at 2026-06-11T01:48Z; no equivalent NL-routing ticket. A2A in-flight sweep: mailbox zero.
Context
Operator-named need from the 2026-06-11 night shift ("it might need NL enhancement tickets"): during the #12878/#12880/#12883-adjacent grid verification work, agent-side drag simulation via the Neural Link MCP
simulate_eventtool proved unroutable for devindex — and the root is general: non-SharedWorker apps never register their window with the NL bridge.The Problem
Reproduced live: the bridge's window topology listed only the Portal (SharedWorker app); devindex's app worker connected (
Neo.ai.Client: Connected to MCP Server) and its components carry a realwindowIdinternally, but nowindow_connectednotification ever reaches the bridge — across multiple fresh boots.simulate_eventrequires awindowIdper event → every event-simulation call against a non-shared app has no route. Agent-side workarounds used tonight: in-pageMouseEventdispatch via an agent-controlled browser, and playwrightpage.mouse(both documented on #12807) — they work, but they bypass the NL tool surface entirely, so pure-NL sessions (no browser-control harness) cannot drive interactions on non-shared apps at all.The Architectural Reality
src/ai/Client.mjs(~L220onAppWorkerWindowConnect/onAppWorkerWindowDisconnect, wired at ~L127) registers windows from App-workerconnect/disconnectevents — SharedWorker port semantics. A non-shared app's single implicit window never fires that event, soWindowManager.itemsstays empty and the rehydration loop (~L292) has nothing to send. The window EXISTS (components carrywindowId); only the bridge notification path is missing.The Fix (shape, for intake validation)
On AI-client startup in a non-SharedWorker app, emit the
window_connectednotification for the app's own window (id, appName, rects) — mirroring what the SharedWorker connect path sends — andwindow_disconnectedon unload. Acceptance:get_window_topologylists the non-shared app's window;simulate_eventroutes to it (verify with a click on a devindex button); SharedWorker apps unchanged.Acceptance Criteria
get_window_topology/ healthcheckwindows[]after boot; post-merge verification log requiredsimulate_event(click) executes against devindex via its windowId; post-merge verification log requiredsimulate_event'sevents[]withdelayshould then support themousedown → ≥100ms → mousemove(Δ≥5) → mouseuparming sequence)Contract Ledger
Client#onSocketOpennon-shared registrationNeo.apps(key =windowId,app.name= appName). Emitswindow_connected {appName, windowId}. Rects are omitted here; they are optional and the SharedWorker path also omits them until aWindowManagerentry exists. Idempotent: skips any window already inWindowManager, so SharedWorker and multi-window apps stay untouched.window_disconnectedon unload remains a follow-up outside this registration/routing slice.ConnectionService.handleNotification()topologywindow_connectedentry (windowId+appName; rects undefined) under the session, identically to a SharedWorker window.get_window_topology/ healthcheck visibilitywindowId+appName; rects remain null/absent until aWindowPositionmeasurement lands.simulate_eventroutingwindowId; the non-shared window now has a registered one, so events can route. Absent/stale windows use the existing not-found behavior.simulate_eventclick verification after merge; both are annotated above as operator-handoff residuals.Out of Scope
simulateDragconvenience primitive (possible follow-up once routing works; shape sketched on #12807).Decision Record impact
none.
Related
Found during: #12878 verification (rig build). Consumers: #12807 (whitebox-e2e simulatability, @neo-opus-ada), the grid cross-region regression spec. Evidence trail: #12807 comments from 2026-06-11.
Release classification: boardless — the playwright/whitebox path covers the release-critical verification need; this unblocks the pure-NL interaction class. Operator may board it under the verification-substrate scope at his discretion.
Origin Session ID: 567191c3-16f6-4235-914c-b51fc94d1514 Retrieval Hint:
query_raw_memories("neural link window registration non-shared simulate_event unroutable")Live latest-open sweep: checked latest 12 open issues at 2026-06-11T01:48Z; no equivalent NL-routing ticket. A2A in-flight sweep: mailbox zero.