dsh-zen-remote 2.0.0 → 2.0.2

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 (50) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/docs/remote-access.md +2 -2
  4. package/lan-gate.mjs +15 -1
  5. package/lib/client.js +271 -30
  6. package/lib/fingerprint.js +4 -2
  7. package/lib/fingerprint.js.map +1 -1
  8. package/lib/intercept.js +161 -14
  9. package/lib/intercept.js.map +1 -1
  10. package/lib/merge-streams.js +11 -4
  11. package/lib/merge-streams.js.map +1 -1
  12. package/lib/relay-access.js.map +1 -1
  13. package/lib/relay-server.js +32 -11
  14. package/lib/relay-server.js.map +1 -1
  15. package/lib/types/client/effects/model-group-side.d.ts +13 -0
  16. package/lib/types/client/effects/model-group-side.d.ts.map +1 -0
  17. package/lib/types/client/index.d.ts.map +1 -1
  18. package/lib/types/client/styles/compat.css.d.ts +1 -1
  19. package/lib/types/client/styles/compat.css.d.ts.map +1 -1
  20. package/lib/types/client/styles/composer.css.d.ts +1 -1
  21. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  22. package/lib/types/client/styles/remote-session.css.d.ts +1 -1
  23. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
  24. package/lib/types/client-data/model-group-side.d.ts +44 -0
  25. package/lib/types/client-data/model-group-side.d.ts.map +1 -0
  26. package/lib/types/fingerprint.d.ts +4 -2
  27. package/lib/types/fingerprint.d.ts.map +1 -1
  28. package/lib/types/intercept.d.ts +19 -9
  29. package/lib/types/intercept.d.ts.map +1 -1
  30. package/lib/types/merge-streams.d.ts +7 -2
  31. package/lib/types/merge-streams.d.ts.map +1 -1
  32. package/lib/types/relay-access.d.ts.map +1 -1
  33. package/lib/types/relay-server.d.ts.map +1 -1
  34. package/lib/types/virtual-id.d.ts +14 -0
  35. package/lib/types/virtual-id.d.ts.map +1 -1
  36. package/lib/virtual-id.js +14 -0
  37. package/lib/virtual-id.js.map +1 -1
  38. package/package.json +48 -49
  39. package/src/client/effects/model-group-side.ts +132 -0
  40. package/src/client/index.tsx +7 -0
  41. package/src/client/styles/compat.css.ts +14 -8
  42. package/src/client/styles/composer.css.ts +2 -1
  43. package/src/client/styles/remote-session.css.ts +43 -20
  44. package/src/client-data/model-group-side.ts +65 -0
  45. package/src/fingerprint.ts +4 -2
  46. package/src/intercept.ts +167 -12
  47. package/src/merge-streams.ts +11 -4
  48. package/src/relay-access.ts +6 -3
  49. package/src/relay-server.ts +49 -11
  50. package/src/virtual-id.ts +15 -0
@@ -399,6 +399,21 @@ interface ForwardableCancelFrame extends Record<string, unknown> {
399
399
  eventId: string
400
400
  }
401
401
 
402
+ /**
403
+ * T75: the ONE emit shape the relay forwards — a single session's
404
+ * running-state flip (`api-session/status`), exactly what the host emits on
405
+ * `agent/status` (RT dsh-api-session-controller lib/index.js:2884-2886:
406
+ * `ctx.emit("api-session/status", agent.id, status === "running")`), carried
407
+ * on the wire as the client face's exact-keys emit with a JSON-array `args`
408
+ * (RT dsh-api-gateway lib/index.js:846-853 builds `{type, event, args}`;
409
+ * lib/client.js `parseRemoteEventFrame` demands that shape back).
410
+ */
411
+ interface ForwardableStatusEmitFrame extends Record<string, unknown> {
412
+ type: 'emit'
413
+ event: 'api-session/status'
414
+ args: [string, boolean]
415
+ }
416
+
402
417
  function isForwardableWaterfall(frame: Record<string, unknown>): frame is ForwardableWaterfallFrame {
403
418
  return (
404
419
  Reflect.ownKeys(frame).length === 5 &&
@@ -421,6 +436,18 @@ function isForwardableCancel(frame: Record<string, unknown>): frame is Forwardab
421
436
  )
422
437
  }
423
438
 
439
+ function isForwardableStatusEmit(frame: Record<string, unknown>): frame is ForwardableStatusEmitFrame {
440
+ return (
441
+ Reflect.ownKeys(frame).length === 3 &&
442
+ ['type', 'event', 'args'].every((key) => Object.hasOwn(frame, key)) &&
443
+ frame.event === 'api-session/status' &&
444
+ Array.isArray(frame.args) &&
445
+ frame.args.length === 2 &&
446
+ isEventId(frame.args[0]) &&
447
+ typeof frame.args[1] === 'boolean'
448
+ )
449
+ }
450
+
424
451
  /**
425
452
  * Constant-time secret comparison. Different lengths are trivially unequal
426
453
  * (timingSafeEqual itself throws on that); an empty configured secret refuses
@@ -867,17 +894,22 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
867
894
  * - CANCEL frames are forwarded only for events this subscription
868
895
  * forwarded (an unshared session's activity timeline must not leak
869
896
  * through cancels), and the entry goes with the forward.
870
- * - EMIT frames are dropped entirely (T32-fix): they carry server-wide
871
- * state (`api-session/added` summaries and titles, account expirations,
872
- * cordis chatter) that is not share-scoped — forwarding them would leak
873
- * unshared sessions and feed the sub-client ids it would treat as
874
- * local. Everything a sub-client needs about a session's state travels
875
- * the workspace/control streams, which ARE share-filtered.
876
- * - Anything not shaped exactly like a forwardable waterfall or cancel
877
- * (T32-fix2, mirroring the client face's `parseRemoteEventFrame`
878
- * exact-keys validation) is dropped too: a frame that slipped through
879
- * would fail the sub-client's whole `$events` generation and leave it
880
- * failing and reconnecting in a loop.
897
+ * - EMIT frames are dropped, with ONE exception (T75): a
898
+ * `api-session/status` emit whose `args[0]` names an accessible session
899
+ * crosses as a fresh `{type, event, args:[id, running]}` — it is the
900
+ * only share-SCOPED emit the host produces (one session's running flip,
901
+ * judged per frame by `isAccessible` like a waterfall's `agentId`).
902
+ * Every other emit is server-wide state (`api-session/added` summaries
903
+ * and titles, account expirations, cordis chatter) that is not
904
+ * share-scoped: forwarding it would leak unshared sessions and feed the
905
+ * sub-client ids it would treat as local. Everything else a sub-client
906
+ * needs about a session's state travels the workspace/control streams,
907
+ * which ARE share-filtered.
908
+ * - Anything not shaped exactly like a forwardable waterfall, cancel, or
909
+ * status emit (T32-fix2, mirroring the client face's
910
+ * `parseRemoteEventFrame` exact-keys validation) is dropped too: a frame
911
+ * that slipped through would fail the sub-client's whole `$events`
912
+ * generation and leave it failing and reconnecting in a loop.
881
913
  *
882
914
  * DROPPED DELIVERIES STAY PENDING (T32-fix2): a dropped waterfall used to
883
915
  * be abstained `next` on behalf. That is gone on purpose. DSH's `next`
@@ -917,6 +949,12 @@ export function createRelayHandler(options: RelayHandlerOptions): RelayHandler {
917
949
  sub.events.delete(frame.eventId)
918
950
  return { ...frame, eventId: `${sub.token}.${frame.eventId}` }
919
951
  }
952
+ if (frame.type === 'emit') {
953
+ // T75: the one share-scoped emit. A NEW frame with a fresh args array
954
+ // leaves — never the host's object itself.
955
+ if (!isForwardableStatusEmit(frame) || !isAccessible(frame.args[0])) return null
956
+ return { type: 'emit', event: frame.event, args: [frame.args[0], frame.args[1]] }
957
+ }
920
958
  return null
921
959
  }
922
960
 
package/src/virtual-id.ts CHANGED
@@ -20,6 +20,21 @@
20
20
  /** The fixed prefix every virtual id starts with. */
21
21
  export const VIRTUAL_ID_PREFIX = 'zr~'
22
22
 
23
+ /**
24
+ * The invisible WORD JOINER (U+2060) every virtual GROUP NAME starts with
25
+ * (T74). DSH 0.2.0 renders the model menu's provider groups with the
26
+ * primitives' MenuGroup, whose `aria-labelledby` carries only a useId — the
27
+ * group id is gone from the markup — so the browser half can no longer tell
28
+ * a virtual group from a local one by attribute. The merged catalog's group
29
+ * NAME (merge-streams.ts mergeModelCatalogs) is the one mark that survives
30
+ * in both 0.1.7 and 0.2.0, and effects/model-group-side.ts classifies each
31
+ * group container by which headings start with it. Zero-width: headings
32
+ * read exactly as before. Sidebar workspace TITLES deliberately do NOT
33
+ * carry it — those groups are told apart by their `zr~` row ids, not by
34
+ * text.
35
+ */
36
+ export const VIRTUAL_GROUP_NAME_MARK = '\u2060'
37
+
23
38
  /** The server id half: exactly the 8 lowercase hex chars relay-server.ts
24
39
  * mints (`randomBytes(4).toString('hex')`). */
25
40
  const SERVER_ID_PATTERN = /^[0-9a-f]{8}$/