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.
- package/PRODUCT.md +11 -7
- package/README.md +69 -21
- package/README.zh-CN.md +64 -20
- package/dist/bridge.js +10 -10
- package/dist/studio.css +1 -1
- package/dist/studio.js +16691 -10202
- package/docs/bidirectional-connection-handoff.md +729 -0
- package/docs/harmony-api-requirements.md +17 -13
- package/docs/remote-development.md +80 -0
- package/lib/bridge/element-style-selector.d.ts +1 -0
- package/lib/bridge/element-style-selector.js +53 -0
- package/lib/contracts.d.ts +152 -83
- package/lib/contracts.js +0 -2
- package/lib/host/agent.d.ts +15 -13
- package/lib/host/agent.js +213 -56
- package/lib/host/automatic-patch.d.ts +9 -0
- package/lib/host/automatic-patch.js +433 -0
- package/lib/host/backend.d.ts +134 -4
- package/lib/host/backend.js +465 -114
- package/lib/host/drafts.d.ts +1 -1
- package/lib/host/drafts.js +65 -15
- package/lib/host/element-source.d.ts +9 -0
- package/lib/host/element-source.js +295 -0
- package/lib/host/mcp.d.ts +4 -0
- package/lib/host/mcp.js +97 -0
- package/lib/host/preview-draft.d.ts +22 -0
- package/lib/host/preview-draft.js +162 -0
- package/lib/host/preview-port.d.ts +8 -0
- package/lib/host/preview-port.js +32 -0
- package/lib/host/preview-worker.d.ts +65 -2
- package/lib/host/preview-worker.js +203 -76
- package/lib/host/preview.d.ts +26 -5
- package/lib/host/preview.js +130 -49
- package/lib/host/readiness.d.ts +2 -2
- package/lib/host/readiness.js +14 -17
- package/lib/host/routes.d.ts +1 -7
- package/lib/host/routes.js +41 -50
- package/lib/host/runtime-profile.d.ts +2 -1
- package/lib/host/runtime-profile.js +30 -8
- package/lib/host/source-resolution.d.ts +11 -1
- package/lib/host/source-resolution.js +69 -24
- package/lib/host/studio-service.d.ts +129 -0
- package/lib/host/studio-service.js +53 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +64 -30
- package/lib/studio-remote.d.ts +126 -0
- package/lib/studio-remote.js +188 -0
- package/lib/variable-tree.d.ts +2 -0
- package/lib/variable-tree.js +13 -0
- package/package.json +62 -25
- 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
|
+
```
|