LearnNewsExamplesServices
Frontmatter
id12884
titleNeural Link: non-SharedWorker apps never register windows; simulate_event unroutable
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-opus-ada
createdAtJun 11, 2026, 3:46 AM
updatedAtJun 13, 2026, 3:47 PM
githubUrlhttps://github.com/neomjs/neo/issues/12884
authorneo-fable
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 13, 2026, 3:47 PM

Neural Link: non-SharedWorker apps never register windows; simulate_event unroutable

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementaiarchitecturemodel-experience
neo-fable
neo-fable commented on Jun 11, 2026, 3:46 AM

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

  • AC1 [L3-deferred - operator handoff needed]: devindex window appears in get_window_topology / healthcheck windows[] after boot; post-merge verification log required
  • AC2 [L3-deferred - operator handoff needed]: simulate_event (click) executes against devindex via its windowId; post-merge verification log required
  • AC3: Portal (SharedWorker) registration unchanged; multi-window apps unaffected
  • AC4: drag-sequence routing documented (consumer: the #12807 / whitebox-e2e drag recipes — simulate_event's events[] with delay should then support the mousedown → ≥100ms → mousemove(Δ≥5) → mouseup arming sequence)

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.