dsh-webui-studio 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/PRODUCT.md +11 -7
  2. package/README.md +69 -21
  3. package/README.zh-CN.md +64 -20
  4. package/dist/bridge.js +10 -10
  5. package/dist/studio.css +1 -1
  6. package/dist/studio.js +16691 -10202
  7. package/docs/bidirectional-connection-handoff.md +729 -0
  8. package/docs/harmony-api-requirements.md +17 -13
  9. package/docs/remote-development.md +80 -0
  10. package/lib/bridge/element-style-selector.d.ts +1 -0
  11. package/lib/bridge/element-style-selector.js +53 -0
  12. package/lib/contracts.d.ts +152 -83
  13. package/lib/contracts.js +0 -2
  14. package/lib/host/agent.d.ts +15 -13
  15. package/lib/host/agent.js +213 -56
  16. package/lib/host/automatic-patch.d.ts +9 -0
  17. package/lib/host/automatic-patch.js +433 -0
  18. package/lib/host/backend.d.ts +134 -4
  19. package/lib/host/backend.js +465 -114
  20. package/lib/host/drafts.d.ts +1 -1
  21. package/lib/host/drafts.js +65 -15
  22. package/lib/host/element-source.d.ts +9 -0
  23. package/lib/host/element-source.js +295 -0
  24. package/lib/host/mcp.d.ts +4 -0
  25. package/lib/host/mcp.js +97 -0
  26. package/lib/host/preview-draft.d.ts +22 -0
  27. package/lib/host/preview-draft.js +162 -0
  28. package/lib/host/preview-port.d.ts +8 -0
  29. package/lib/host/preview-port.js +32 -0
  30. package/lib/host/preview-worker.d.ts +65 -2
  31. package/lib/host/preview-worker.js +203 -76
  32. package/lib/host/preview.d.ts +26 -5
  33. package/lib/host/preview.js +130 -49
  34. package/lib/host/readiness.d.ts +2 -2
  35. package/lib/host/readiness.js +14 -17
  36. package/lib/host/routes.d.ts +1 -7
  37. package/lib/host/routes.js +41 -50
  38. package/lib/host/runtime-profile.d.ts +2 -1
  39. package/lib/host/runtime-profile.js +30 -8
  40. package/lib/host/source-resolution.d.ts +11 -1
  41. package/lib/host/source-resolution.js +69 -24
  42. package/lib/host/studio-service.d.ts +129 -0
  43. package/lib/host/studio-service.js +53 -0
  44. package/lib/index.d.ts +5 -0
  45. package/lib/index.js +64 -30
  46. package/lib/studio-remote.d.ts +126 -0
  47. package/lib/studio-remote.js +188 -0
  48. package/lib/variable-tree.d.ts +2 -0
  49. package/lib/variable-tree.js +13 -0
  50. package/package.json +62 -25
  51. package/studio.patch.yml +12 -0
@@ -0,0 +1,729 @@
1
+ # Bidirectional Connection / Typert Gateway implementation handoff
2
+
3
+ Status: proposed
4
+ Date: 2026-08-20
5
+ Implementation home: a new independent DSH plugin
6
+ First consumer: `dsh-webui-studio`
7
+
8
+ ## 1. Objective
9
+
10
+ Build one Harmony-enabled plugin that makes the existing DSH Connection and
11
+ Typert Gateway bidirectional, then migrate WebUI Studio onto that path.
12
+
13
+ The completed topology is:
14
+
15
+ ```text
16
+ Studio browser
17
+ <=> stable Host Connection + Typert
18
+ <=> Preview Host Connection + Typert
19
+
20
+ Parent Studio page
21
+ <=> Preview iframe MessageChannel (UI data plane only)
22
+ ```
23
+
24
+ For every pair of Cordis environments there is one canonical Connection. The
25
+ plugin must not open an additional WebSocket, HTTP server, port, MessagePort,
26
+ or peer transport for service RPC.
27
+
28
+ Completion means:
29
+
30
+ - Client can call Host Cordis Services through the existing Typert projection.
31
+ - Host can call a specifically addressed Client Cordis Service.
32
+ - A Host can connect to another Host as a Node client and use the same RPC
33
+ contract.
34
+ - Studio browser calls use Connection/Typert instead of `/studio/api`.
35
+ - Stable Host calls to a Preview Host use Connection/Typert instead of
36
+ `/dsh-harmony/studio-preview/api`.
37
+ - Replaced Studio routes and envelopes are deleted. There is no runtime
38
+ fallback to the old paths.
39
+
40
+ ## 2. Non-goals
41
+
42
+ - Do not replace Cordis service discovery or lifecycle.
43
+ - Do not introduce a general service mesh, broker, registry server, or relay
44
+ daemon.
45
+ - Do not route Preview DOM inspection, pointer pan/zoom, element highlighting,
46
+ or live style editing through Host RPC. The existing iframe `MessageChannel`
47
+ remains the direct UI data plane.
48
+ - Do not claim that `trustedHosts` authenticates a remote peer. It is a browser
49
+ trust fence, not peer authentication.
50
+ - Do not support arbitrary remote-network Host-to-Host connections until an
51
+ authentication policy exists. The first release may be loopback-only.
52
+
53
+ ## 3. Version baseline
54
+
55
+ Harmony source patches target compiled package structure and therefore must be
56
+ version exact.
57
+
58
+ At handoff time the Studio manifest declares DSH `0.1.0-rc.7`, while the local
59
+ `node_modules` inspected for this design contains these packages at
60
+ `0.1.0-rc.6`:
61
+
62
+ - `@deepseek-ai/dsh-api-gateway`
63
+ - `@deepseek-ai/dsh-client-connection`
64
+ - `@deepseek-ai/dsh-host-apiproxy`
65
+ - `@deepseek-ai/dsh-client-runtime`
66
+ - `@deepseek-ai/dsh-api-remotes`
67
+
68
+ The implementation plugin must select one installed DSH release, lock it, and
69
+ develop every Patch and test against that single release. Do not publish a
70
+ semver range that has not been inspected. Every Harmony Patch must declare an
71
+ exact target version and an exact `expect` count so an upstream layout change
72
+ fails during Harmony preflight rather than partially applying.
73
+
74
+ ## 4. Current facts
75
+
76
+ ### 4.1 Connection
77
+
78
+ The current browser Connection has asymmetric public roles:
79
+
80
+ - Client `ctx.connection.rpc.call()` sends unary HTTP RPC to Host.
81
+ - Host `ctx.connection.rpc.handle()` / `intercept()` receives RPC.
82
+ - `/api/events.mux` and `/api/events.host` are downlink-only WebSockets.
83
+ - Client responses to Host-originated API requests already use HTTP
84
+ `/api/respond` and the `ClientResponse` envelope.
85
+ - One consumer owns the Client connection stream loop. A second call to
86
+ `connection.start()` fails.
87
+
88
+ The wire already has the four envelope shapes needed for bidirectional RPC:
89
+
90
+ ```text
91
+ ClientRequest -> ServerResponse
92
+ ServerRequest -> ClientResponse
93
+ ```
94
+
95
+ What is missing is a generic Host-to-Client request registry, explicit peer
96
+ identity, Client-side handlers, and pending-call lifecycle.
97
+
98
+ ### 4.2 API Gateway
99
+
100
+ The current Gateway is role-asymmetric:
101
+
102
+ - Host installs `ctx.typertGateway` and intercepts claimed `/api` endpoints.
103
+ - Client installs generated `ctx.remote.<namespace>` projections.
104
+ - Client Remote always calls Host through `ctx.connection.rpc.call()`.
105
+
106
+ The Typert descriptor, argument codec, lookup resolution, result codec, and
107
+ error translation are already carrier-independent concepts. They should be
108
+ shared by both local Gateway roles rather than reimplemented for reverse RPC.
109
+
110
+ ### 4.3 Studio
111
+
112
+ Studio currently has two custom control paths in addition to the iframe UI
113
+ bridge:
114
+
115
+ 1. Standalone Studio browser -> stable Host:
116
+ `/studio/api/<method>`, implemented by `src/browser/rpc.ts`,
117
+ `src/host/routes.ts`, and `StudioBackend.call()`.
118
+ 2. Stable Host -> Preview Host:
119
+ `/dsh-harmony/studio-preview/api/<method>`, implemented by
120
+ `StudioPreviewSupervisor.worker()` and `applyPreviewWorker()`.
121
+
122
+ The standalone Studio browser also opens `/api/events.mux` and
123
+ `/api/events.host` directly through `src/browser/events.ts`; the migrated
124
+ Connection client must become the sole owner of these streams.
125
+
126
+ The parent page and Preview iframe communicate through a capability-bound
127
+ `MessageChannel`. That channel is not a competing Cordis control plane and
128
+ must remain.
129
+
130
+ ## 5. Architectural decisions
131
+
132
+ ### 5.1 Reuse the existing carrier
133
+
134
+ Reverse RPC uses:
135
+
136
+ ```text
137
+ Host -> Client request existing /api/events.host WebSocket
138
+ Client -> Host response existing HTTP /api/respond
139
+ Host -> Client cancel existing /api/events.host WebSocket
140
+ ```
141
+
142
+ Do not add a third WebSocket. Do not create a new top-level HTTP prefix. A
143
+ small Connection-owned handshake endpoint under `/api` is part of the same
144
+ carrier, not a parallel API.
145
+
146
+ ### 5.2 Address a peer explicitly
147
+
148
+ A Host may have multiple browser tabs, a Node client, or more than one Preview
149
+ consumer. Host-to-Client calls must never mean “broadcast and accept the first
150
+ response”.
151
+
152
+ Every reverse call targets a live `PeerHandle` issued by Connection. A
153
+ generation loss invalidates that handle and rejects its pending calls. Callers
154
+ must reacquire a new handle from Connection; Connection must not silently
155
+ retry a business invocation on a new generation.
156
+
157
+ Recommended public shape:
158
+
159
+ ```ts
160
+ interface ConnectionPeer {
161
+ readonly id: string
162
+ readonly kind: 'browser' | 'node'
163
+ call(endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcResult<unknown>>
164
+ }
165
+
166
+ interface HostConnectionPeers {
167
+ get(id: string): ConnectionPeer | undefined
168
+ list(): readonly ConnectionPeer[]
169
+ subscribe(listener: (change: PeerChange) => void): () => void
170
+ }
171
+ ```
172
+
173
+ The exact names may follow the target package conventions, but the semantics
174
+ are required: explicit peer, generation-scoped validity, no implicit current
175
+ browser.
176
+
177
+ ### 5.3 Keep request dispatch inside Connection
178
+
179
+ Reverse protocol frames must be recognized by Client Connection before the
180
+ ordinary `HostFrame` schema is applied. This avoids changing
181
+ `@deepseek-ai/dsh-client-runtime` and prevents its one stream consumer from
182
+ becoming an extension point.
183
+
184
+ Client Connection adds an inbound registry alongside its existing outbound
185
+ caller. The API Gateway registers its Client local dispatcher with this
186
+ registry.
187
+
188
+ Conceptual shape:
189
+
190
+ ```ts
191
+ interface ClientConnectionRpc {
192
+ call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcResult<unknown>>
193
+ intercept(
194
+ channel: string,
195
+ matches: (endpoint: string) => boolean,
196
+ handler: ConnectionRpcHandler,
197
+ ): () => void
198
+ }
199
+ ```
200
+
201
+ Inbound registrations must be Cordis effect-scoped and withdrawn when their
202
+ owning fiber is disposed.
203
+
204
+ ### 5.4 Make Gateway roles symmetric
205
+
206
+ Both sides install a local dispatcher and a remote projection:
207
+
208
+ ```text
209
+ Host local Gateway <-> Client Remote
210
+ Host Remote(peer) <-> Client local Gateway
211
+ ```
212
+
213
+ The Host Remote must require a `ConnectionPeer`:
214
+
215
+ ```ts
216
+ const client = ctx.remote.for(peer)
217
+ await client.studioPreview.someMethod()
218
+ ```
219
+
220
+ Do not create a JavaScript dynamic Proxy for method discovery. Preserve the
221
+ current generated Typert contribution model. A descriptor is installed as
222
+ local on the side owning the Service and as remote on the opposite side.
223
+
224
+ The local invocation engine must remain one implementation shared by Host and
225
+ Client. It continues to own:
226
+
227
+ - exact endpoint claim checks;
228
+ - exact named-argument validation;
229
+ - Typert codecs;
230
+ - direct and Context-scoped receiver resolution;
231
+ - lookup providers;
232
+ - AbortSignal injection;
233
+ - result validation;
234
+ - stable `RpcResult` error translation.
235
+
236
+ ## 6. Wire contract
237
+
238
+ ### 6.1 Peer establishment
239
+
240
+ Connection owns a trusted `/api/connection.open` operation. It returns an
241
+ opaque peer id and an unguessable, memory-only peer capability. The capability
242
+ is bound to the connection generation.
243
+
244
+ The browser presents the capability on both existing WebSocket upgrades using
245
+ `Sec-WebSocket-Protocol`, not a URL query parameter. HTTP reverse responses
246
+ present it in a Connection-owned request header. The Host publishes a
247
+ `PeerHandle` only after the required downlinks for that generation are ready.
248
+
249
+ Requirements:
250
+
251
+ - Generate capabilities with at least 256 bits of cryptographic randomness.
252
+ - Never put a capability in a URL, log, Cordis event, Typert payload, or error
253
+ message.
254
+ - Reject duplicate or mismatched socket attachment.
255
+ - Bind a response to both `rpcId` and the authenticated peer generation.
256
+ - Close either socket and reject every pending reverse call when the
257
+ generation fails, matching current Connection reconnect semantics.
258
+
259
+ For the loopback Studio Preview, the existing Preview control token is the
260
+ bootstrap credential for `connection.open`. The new connection capability
261
+ replaces it only after successful establishment. Do not remove the existing
262
+ security boundary without an equivalent check.
263
+
264
+ ### 6.2 Reverse request
265
+
266
+ Use the existing `ServerRequest` full envelope:
267
+
268
+ ```ts
269
+ {
270
+ type: 'server-request',
271
+ rpcId,
272
+ method: 'connection.rpc',
273
+ payload: {
274
+ channel: '/api',
275
+ endpoint: 'namespace/method',
276
+ payload: unknown,
277
+ },
278
+ }
279
+ ```
280
+
281
+ The Host writes ordinary Host event frames and Connection control frames
282
+ through one serialized outbound queue for the peer's existing host downlink.
283
+
284
+ ### 6.3 Reverse response
285
+
286
+ Client executes the matching Connection handler and submits the existing
287
+ `ClientResponse` envelope to `/api/respond`:
288
+
289
+ ```ts
290
+ {
291
+ type: 'client-response',
292
+ rpcId,
293
+ result: { ok: true, value } | { ok: false, error },
294
+ }
295
+ ```
296
+
297
+ Connection owns pending reverse ids. `/api/respond` first checks the
298
+ Connection pending table for the authenticated generation. If no Connection
299
+ request owns the id, the request continues to the existing API Proxy response
300
+ handler for approvals and user questions. This is deterministic ownership of
301
+ one endpoint, not a fallback transport.
302
+
303
+ ### 6.4 Cancellation
304
+
305
+ When the Host caller aborts, send a `ServerRequest` with the same `rpcId` and
306
+ `method: 'connection.cancel'`. Client aborts the invocation signal. A response
307
+ arriving after Host cancellation is rejected as not pending.
308
+
309
+ When Client handler disposal or generation loss aborts an invocation, return a
310
+ cancelled `RpcResult` when the response carrier is still available. Otherwise
311
+ the Host learns the same failure from generation teardown.
312
+
313
+ Do not automatically retry cancelled, disconnected, or timed-out business
314
+ calls.
315
+
316
+ ## 7. Independent plugin layout
317
+
318
+ Suggested package layout:
319
+
320
+ ```text
321
+ dsh-bidirectional-gateway/
322
+ ├── package.json
323
+ ├── harmony.patch.yml
324
+ ├── index.ts
325
+ ├── client.ts
326
+ ├── src/
327
+ │ ├── connection-host.ts
328
+ │ ├── connection-client.ts
329
+ │ ├── gateway-core.ts
330
+ │ ├── gateway-host.ts
331
+ │ ├── gateway-client.ts
332
+ │ └── node-peer-client.ts
333
+ ├── patches/
334
+ │ ├── connection-host.patch.cjs
335
+ │ ├── connection-client.patch.cjs
336
+ │ ├── gateway-host.patch.cjs
337
+ │ └── gateway-client.patch.cjs
338
+ └── test/
339
+ ```
340
+
341
+ The plugin owns protocol and Gateway core logic. Harmony patches should insert
342
+ the smallest hooks needed for native Connection/Gateway code to delegate to
343
+ that logic. Do not paste the complete implementation independently into four
344
+ compiled bundles.
345
+
346
+ The plugin's browser module must be present in the DSH Client module graph and
347
+ load after Client Connection and the Typert registry. Patched API Gateway
348
+ client code may require the plugin-owned browser core by module id. Host code
349
+ imports the same package-owned core normally.
350
+
351
+ Declare Cordis type augmentation in the independent plugin. Do not Patch
352
+ upstream `.d.ts` files at runtime.
353
+
354
+ ## 8. Harmony Patch inventory
355
+
356
+ ### 8.1 `@deepseek-ai/dsh-client-connection/lib/index.js`
357
+
358
+ Add only the Host integration points:
359
+
360
+ - peer registry owned by `HostConnectionService`;
361
+ - Connection-owned `/api/connection.open` and reverse-response interception;
362
+ - peer authentication on both existing WebSocket upgrades;
363
+ - a serialized reverse-request writer on the existing host downlink;
364
+ - pending-call cancellation and teardown;
365
+ - public Host peer access through `ctx.connection`.
366
+
367
+ Do not modify API Proxy business handlers.
368
+
369
+ ### 8.2 `@deepseek-ai/dsh-client-connection/lib/client.js`
370
+
371
+ Add only the Client integration points:
372
+
373
+ - establish and retain the generation capability;
374
+ - attach both existing sockets to the peer generation;
375
+ - identify `connection.rpc` / `connection.cancel` before `HostFrame` parsing;
376
+ - Client inbound handler registry;
377
+ - send `ClientResponse` through the existing respond leg;
378
+ - expose the registry on `ctx.connection.rpc`;
379
+ - Node-independent shared protocol code where possible.
380
+
381
+ Ordinary Host and Mux frames must continue to reach the current sinks
382
+ unchanged. Do not call `connection.start()` a second time.
383
+
384
+ ### 8.3 `@deepseek-ai/dsh-api-gateway/lib/index.js`
385
+
386
+ Add the Host outbound projection:
387
+
388
+ - retain the existing Host local `TypertGatewayService` behavior;
389
+ - install a generated Remote projection bound explicitly to a
390
+ `ConnectionPeer`;
391
+ - delegate descriptor encoding and result decoding to the shared Gateway core.
392
+
393
+ ### 8.4 `@deepseek-ai/dsh-api-gateway/lib/client.js`
394
+
395
+ Add the Client local Gateway:
396
+
397
+ - retain the existing Client Remote projection unchanged;
398
+ - install the local invocation dispatcher;
399
+ - register that dispatcher through Client Connection's inbound registry;
400
+ - use the same Gateway core and error contract as Host.
401
+
402
+ ### 8.5 Packages intentionally not patched
403
+
404
+ The design should not require Harmony patches to:
405
+
406
+ - `@deepseek-ai/dsh-client-runtime`;
407
+ - `@deepseek-ai/dsh-host-apiproxy`;
408
+ - `@deepseek-ai/dsh-api-remotes`.
409
+
410
+ If implementation evidence shows one is unavoidable, update this handoff with
411
+ the missing ownership boundary before adding the Patch. Do not piggyback on
412
+ `host/remote-event`; it has event broadcast semantics and cannot safely model
413
+ targeted request/response.
414
+
415
+ ## 9. Node peer client
416
+
417
+ Studio's stable Host needs to connect to each Preview Host without pretending
418
+ to be a browser tab. The independent plugin must export a Node peer client
419
+ using the same protocol:
420
+
421
+ ```ts
422
+ interface NodePeerClient {
423
+ readonly remote: TypertRemote
424
+ connect(signal?: AbortSignal): Promise<void>
425
+ close(): Promise<void>
426
+ }
427
+ ```
428
+
429
+ It uses the Preview Host's existing HTTP port, the same `/api` endpoints, and
430
+ the same two downlinks. It must not start another server or tunnel. It owns one
431
+ Connection generation at a time and exposes connection loss directly to its
432
+ caller.
433
+
434
+ Node 22 supplies `fetch`; use the project's existing `ws` dependency for the
435
+ WebSocket carrier unless the selected DSH baseline already exports a suitable
436
+ Node carrier.
437
+
438
+ ## 10. Studio migration
439
+
440
+ Migration happens only after Connection and Gateway integration tests pass.
441
+ Each cutover deletes the path it replaces in the same change.
442
+
443
+ ### 10.1 Preview Host service
444
+
445
+ Replace the Preview worker control route with one strict Typert Service owned
446
+ by the Preview Host. Suggested namespace: `studioPreviewWorker`.
447
+
448
+ Required methods:
449
+
450
+ ```text
451
+ health
452
+ state
453
+ activate
454
+ applyBuild
455
+ inspect
456
+ profile
457
+ updateProfile
458
+ resolveSource
459
+ readSource
460
+ readPatchTarget
461
+ ```
462
+
463
+ The Service delegates to the existing `StudioPreviewDraft`, Harmony service,
464
+ and `StudioSourceResolver`. Preserve the current readiness state and input
465
+ validation, but express failures through the Gateway error contract rather
466
+ than HTTP status branching.
467
+
468
+ `applyPreviewWorker()` is then reduced to Preview composition and iframe
469
+ assets:
470
+
471
+ - construct/dispose `StudioPreviewDraft`;
472
+ - provide the Typert Service;
473
+ - serve `/studio/bridge.js`;
474
+ - inject the Preview bridge configuration into the Preview HTML.
475
+
476
+ Delete:
477
+
478
+ - `STUDIO_PREVIEW_API_PATH`;
479
+ - the Preview worker `WebRoute`;
480
+ - its manual JSON reader/writer;
481
+ - `StudioWorkerResponseError`;
482
+ - `StudioPreviewSupervisor.worker()` and its custom envelopes.
483
+
484
+ ### 10.2 Stable Host to Preview Host
485
+
486
+ `StudioPreviewSupervisor` still owns child process startup, the isolated
487
+ `DSH_HOME`, Preview port allocation, logs, and termination. After the child
488
+ publishes its URL it creates a Node peer client with the existing Preview
489
+ control token as the bootstrap credential.
490
+
491
+ Replace calls as follows:
492
+
493
+ ```text
494
+ worker('health') -> remote.studioPreviewWorker.health()
495
+ worker('state') -> remote.studioPreviewWorker.state()
496
+ worker('activate') -> remote.studioPreviewWorker.activate()
497
+ worker('apply-build') -> remote.studioPreviewWorker.applyBuild()
498
+ worker('inspect') -> remote.studioPreviewWorker.inspect()
499
+ worker('profile') -> remote.studioPreviewWorker.profile()
500
+ worker('update-profile') -> remote.studioPreviewWorker.updateProfile()
501
+ worker('resolve-source') -> remote.studioPreviewWorker.resolveSource()
502
+ worker('read-source') -> remote.studioPreviewWorker.readSource()
503
+ worker('read-patch-target') -> remote.studioPreviewWorker.readPatchTarget()
504
+ ```
505
+
506
+ Stopping the Preview first closes the peer client, then terminates the child.
507
+ Child exit invalidates the peer and rejects in-flight calls. Do not retry an
508
+ operation on a restarted child.
509
+
510
+ ### 10.3 Studio browser to stable Host
511
+
512
+ Expose `StudioBackend` through one strict Typert Service. Suggested namespace:
513
+ `studio`. Keep the implementation modular internally, but do not create one
514
+ Cordis Service per current dotted HTTP name merely to preserve the old path.
515
+
516
+ Suggested method names:
517
+
518
+ ```text
519
+ draftsList draftsCreate
520
+ draftsRename draftsExport
521
+ draftsStart draftsStop
522
+ workspaceGet workspaceUpdate
523
+ harmonyProfile harmonyInspect
524
+ harmonyUpdateProfile
525
+ projectState projectActivate
526
+ projectFiles projectReadFile
527
+ projectWriteFile projectBuild
528
+ projectCancelBuild
529
+ elementsStyles elementsSaveSource
530
+ patchesAnalyzeAutomatic patchesCreateAutomatic
531
+ readinessInspect readinessPack
532
+ previewStatus previewUpdate
533
+ previewResolveSource
534
+ agentCreate agentAttach
535
+ agentLeave
536
+ ```
537
+
538
+ The standalone Studio bundle is not currently running inside the ordinary DSH
539
+ WebUI Cordis Client graph. It therefore consumes a browser client exported by
540
+ the independent plugin, which establishes the same Connection and mounts the
541
+ generated Studio Remote contribution. `callStudio()` may remain temporarily as
542
+ an internal TypeScript helper name, but its implementation must call the
543
+ generated Remote; it must not fetch `/studio/api`.
544
+
545
+ The capability currently embedded in the Studio HTML remains the bootstrap
546
+ credential for peer establishment. Once the cutover works, delete:
547
+
548
+ - `STUDIO_API_PATH`;
549
+ - the API route in `createStudioRoutes()`;
550
+ - `StudioClientRequest` / `StudioServerResponse` custom envelopes;
551
+ - manual RPC ids and fetch code in `src/browser/rpc.ts`;
552
+ - the method-string dispatcher in `StudioBackend.call()`.
553
+
554
+ Keep `/studio` and `/studio/assets/*` because they serve the standalone page
555
+ and static assets.
556
+
557
+ The migrated browser Connection becomes the sole owner of mux/host streams.
558
+ Remove direct socket ownership from `src/browser/events.ts` and feed the
559
+ existing Studio event consumers from Connection sinks or an exported event
560
+ source.
561
+
562
+ ### 10.4 Preview iframe bridge
563
+
564
+ Keep the current `MessageChannel` and its capability/nonce checks. It directly
565
+ connects two browser windows for:
566
+
567
+ - Preview readiness and graph revision;
568
+ - DOM selection and React trace metadata;
569
+ - registry snapshots;
570
+ - pointer pan/zoom;
571
+ - element styles and live variables.
572
+
573
+ Routing these through Client -> Host -> Host -> Client would add two network
574
+ hops, require extra peer selection, and provide no Cordis lifecycle benefit.
575
+
576
+ ## 11. Delivery sequence
577
+
578
+ ### Phase 0: lock baseline
579
+
580
+ - Select one DSH release.
581
+ - Install a clean dependency tree.
582
+ - Record exact target package versions.
583
+ - Add Harmony inspect/preflight to CI.
584
+
585
+ Exit gate: all four Patch targets bind with exact match counts.
586
+
587
+ ### Phase 1: bidirectional Connection
588
+
589
+ - Implement peer establishment and authentication.
590
+ - Implement targeted Host call, Client dispatch, response, cancellation, and
591
+ teardown.
592
+ - Preserve existing Client-to-Host RPC and ordinary event streams.
593
+
594
+ Exit gate: Connection integration tests pass without API Gateway.
595
+
596
+ ### Phase 2: symmetric Typert Gateway
597
+
598
+ - Run the local dispatcher on Client.
599
+ - Add Host Remote bound to a peer.
600
+ - Mount strict generated descriptors on the correct side.
601
+
602
+ Exit gate: the same test Service can be invoked in both directions with strict
603
+ arguments, result validation, cancellation, and structured failure.
604
+
605
+ ### Phase 3: Node peer client
606
+
607
+ - Implement the Node carrier over the same Preview Host port.
608
+ - Verify connect, reverse request, reconnect failure, and close.
609
+
610
+ Exit gate: one Host process invokes a Service in another Host process without
611
+ an additional transport.
612
+
613
+ ### Phase 4: migrate Studio Preview control
614
+
615
+ - Add `studioPreviewWorker` Service.
616
+ - Replace Supervisor worker fetches.
617
+ - Delete the custom Preview API route and envelopes.
618
+
619
+ Exit gate: Studio Preview lifecycle and Harmony operations pass; requests to
620
+ the removed Preview API return 404.
621
+
622
+ ### Phase 5: migrate Studio browser control
623
+
624
+ - Add the strict `studio` Service and generated contribution.
625
+ - Replace `callStudio()` transport.
626
+ - Transfer event stream ownership to Connection.
627
+ - Delete `/studio/api` and the method-string dispatcher.
628
+
629
+ Exit gate: the complete Studio integration suite passes; requests to the
630
+ removed Studio API return 404; the iframe bridge still works.
631
+
632
+ ## 12. Required tests
633
+
634
+ ### Connection
635
+
636
+ - Client -> Host existing unary RPC remains unchanged.
637
+ - Host -> one selected Client succeeds.
638
+ - Two Clients with the same endpoint receive only their targeted calls.
639
+ - Concurrent reverse calls correlate by `rpcId` without cross-settlement.
640
+ - Client business failure returns a structured `RpcResult` failure.
641
+ - Host cancellation aborts the Client handler.
642
+ - Client disposal aborts its active handlers.
643
+ - Generation loss rejects all pending Host calls exactly once.
644
+ - A late response is rejected as not pending.
645
+ - Wrong or missing peer capability cannot open a socket or settle a response.
646
+ - Ordinary Mux/Host frames still reach their original sinks in order.
647
+
648
+ ### Gateway
649
+
650
+ - Direct Service invocation works in both directions.
651
+ - Context-scoped invocation resolves the receiver on the serving side.
652
+ - Lookup parameters resolve on the serving side.
653
+ - Missing, extra, and invalid arguments fail identically in both directions.
654
+ - Result codecs run before returning to the caller.
655
+ - A withdrawn strict definition does not fall back to SRC dispatch.
656
+ - Disposing a contribution removes both its Remote projection and local claim.
657
+
658
+ ### Studio
659
+
660
+ - Start and stop one Preview Draft.
661
+ - Start two Drafts and prove their peer calls do not cross.
662
+ - Activate, apply build, inspect Harmony, update profile, resolve source, and
663
+ read dependency source through Typert.
664
+ - Preview child exit rejects the in-flight operation and updates runtime state.
665
+ - Studio browser can execute every method previously exposed by
666
+ `StudioBackend.call()`.
667
+ - Main Studio capability and Preview control capability are both enforced.
668
+ - `/studio/api/*` returns 404 after migration.
669
+ - `/dsh-harmony/studio-preview/api/*` returns 404 after migration.
670
+ - `/studio`, assets, Preview iframe loading, selection, variables, and element
671
+ style editing continue to work.
672
+
673
+ ## 13. Patch maintenance policy
674
+
675
+ - Pin each Patch to one exact upstream version.
676
+ - Prefer TSQuery selectors over raw text offsets or broad regular expressions.
677
+ - Set `expect` for every selector.
678
+ - Keep protocol/business code in the independent plugin; keep source Patches
679
+ limited to native hook insertion.
680
+ - Run `dsh harmony inspect` for every target file in CI.
681
+ - Run the Connection/Gateway contract suite before accepting a new upstream
682
+ DSH version.
683
+ - Treat a failed match as an upgrade task. Do not loosen the selector or add a
684
+ second compatibility Patch without reviewing the new upstream behavior.
685
+ - Remove the previous version's Patch when advancing the supported baseline.
686
+
687
+ ## 14. Definition of done
688
+
689
+ - [ ] One Connection carrier per environment pair; no added socket or port.
690
+ - [ ] Explicit generation-scoped peer addressing.
691
+ - [ ] Peer capability enforced on socket attachment and reverse response.
692
+ - [ ] Client local handler registry is Cordis effect-scoped.
693
+ - [ ] Host reverse pending calls support result, failure, cancellation, and
694
+ disconnect teardown.
695
+ - [ ] Host and Client use one Typert invocation engine.
696
+ - [ ] Host Remote requires an explicit peer.
697
+ - [ ] Node peer client uses the same protocol.
698
+ - [ ] Studio Preview worker custom API is deleted.
699
+ - [ ] Studio main custom API is deleted.
700
+ - [ ] Studio direct event sockets no longer compete with Connection ownership.
701
+ - [ ] Preview iframe MessageChannel remains functional.
702
+ - [ ] Removed routes are asserted as 404 in integration tests.
703
+ - [ ] Harmony Patch targets and match counts are exact.
704
+ - [ ] No compatibility fallback or dual routing remains.
705
+
706
+ ## 15. First source locations to inspect
707
+
708
+ Independent plugin implementation should begin from these installed files for
709
+ the selected DSH baseline:
710
+
711
+ ```text
712
+ node_modules/@deepseek-ai/dsh-client-connection/lib/index.js
713
+ node_modules/@deepseek-ai/dsh-client-connection/lib/client.js
714
+ node_modules/@deepseek-ai/dsh-api-gateway/lib/index.js
715
+ node_modules/@deepseek-ai/dsh-api-gateway/lib/client.js
716
+ ```
717
+
718
+ Studio migration starts from:
719
+
720
+ ```text
721
+ src/host/preview.ts
722
+ src/host/preview-worker.ts
723
+ src/host/backend.ts
724
+ src/host/routes.ts
725
+ src/browser/rpc.ts
726
+ src/browser/events.ts
727
+ src/bridge/main.ts
728
+ src/contracts.ts
729
+ ```