@linxin666/dsh-remote-web-ui 0.4.3 → 0.4.4

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/lib/index.js CHANGED
@@ -479,7 +479,7 @@ var PairingService = class {
479
479
  };
480
480
  /** Structural equality over the snapshot's wire fields. */
481
481
  function snapshotsEqual(a, b) {
482
- return a.phase === b.phase && a.lanAvailable === b.lanAvailable && sameStrings(a.lanAddresses, b.lanAddresses) && a.publicUrl === b.publicUrl && tunnelEqual(a.tunnel, b.tunnel) && relayEqual(a.relay, b.relay) && a.tokenId === b.tokenId && a.tokenExpiresAt === b.tokenExpiresAt && a.deviceCount === b.deviceCount && a.onlineCount === b.onlineCount && devicesEqual(a.devices, b.devices);
482
+ return a.phase === b.phase && a.lanAvailable === b.lanAvailable && sameStrings(a.lanAddresses, b.lanAddresses) && a.publicUrl === b.publicUrl && tunnelEqual(a.tunnel, b.tunnel) && relayEqual(a.relay, b.relay) && postureEqual(a.posture, b.posture) && a.tokenId === b.tokenId && a.tokenExpiresAt === b.tokenExpiresAt && a.deviceCount === b.deviceCount && a.onlineCount === b.onlineCount && devicesEqual(a.devices, b.devices);
483
483
  }
484
484
  /** Per-device roster equality (order is pairing time). */
485
485
  function devicesEqual(a, b) {
@@ -496,6 +496,42 @@ function tunnelEqual(a, b) {
496
496
  function relayEqual(a, b) {
497
497
  return a === b || a !== void 0 && b !== void 0 && a.state === b.state && a.url === b.url && a.error === b.error;
498
498
  }
499
+ /**
500
+ * Posture equality. A probe round can change this frame while every other field
501
+ * stays put — the common case, since the probe re-runs on a settings change and
502
+ * on a tunnel reaching running, and a quick-tunnel restart churns the probed
503
+ * host set. Leaving the frame out of the comparison suppressed those emits, so
504
+ * the loopback SSE stream and the desktop panel could sit on a stale "exposed"
505
+ * (or stale "clean") verdict for as long as the phase and roster stayed put.
506
+ */
507
+ function postureEqual(a, b) {
508
+ if (a === b) return true;
509
+ if (!isPostureFrame(a) || !isPostureFrame(b)) return false;
510
+ return a.checkedAt === b.checkedAt && hostsEqual(a.hosts, b.hosts);
511
+ }
512
+ /** Whether a value has the shape a posture frame is compared on. */
513
+ function isPostureFrame(value) {
514
+ return typeof value === "object" && value !== null;
515
+ }
516
+ /**
517
+ * Per-host posture equality (a round's verdicts, in probe order). Tolerates a
518
+ * frame that is not the expected shape: this runs inside the emit path, outside
519
+ * the listener error containment, so a malformed snapshot handed in by another
520
+ * host-fiber caller must degrade to "changed" instead of throwing out of the
521
+ * setter that produced it.
522
+ */
523
+ function hostsEqual(a, b) {
524
+ if (a === b) return true;
525
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
526
+ return a.length === b.length && a.every((host, index) => {
527
+ const other = b[index];
528
+ return isPostureHost(host) && isPostureHost(other) && host.host === other.host && host.exposed === other.exposed;
529
+ });
530
+ }
531
+ /** Whether a value has the shape one probed host is compared on. */
532
+ function isPostureHost(value) {
533
+ return typeof value === "object" && value !== null;
534
+ }
499
535
  /** Element-wise string list equality (interface order is meaningful). */
500
536
  function sameStrings(a, b) {
501
537
  return a.length === b.length && a.every((value, index) => value === b[index]);
@@ -2184,7 +2220,19 @@ function makeRemoteApiRoutes(deps) {
2184
2220
  }];
2185
2221
  }
2186
2222
  /**
2187
- * Map one outer upgrade URL onto the loopback path (query string included).
2223
+ * Map one outer upgrade URL onto the loopback path, dropping the cookieless
2224
+ * device credential on the way in.
2225
+ *
2226
+ * The outer handshake carries the device id as `?device=<id>` because WebSocket
2227
+ * handshakes cannot carry headers from the Web API. That id is a live session
2228
+ * credential — it opens the full host API through this same channel — and the
2229
+ * inner leg is a request the rest of the loopback origin sees (on a WebSocket
2230
+ * leg, a raw handshake request line written to a loopback socket), so forwarding
2231
+ * it handed a working credential to every other observer of the local port for
2232
+ * no benefit: the gate above has already authenticated the caller, and no
2233
+ * upstream route reads the parameter (the gateway mux matches on path alone).
2234
+ * The rest of the query rides through unchanged, so a caller's own parameters
2235
+ * still reach the upstream.
2188
2236
  */
2189
2237
  function upgradeInnerPath(reqUrl, fallbackPath) {
2190
2238
  if (reqUrl === void 0 || reqUrl === "") return fallbackPath;
@@ -2196,6 +2244,7 @@ function upgradeInnerPath(reqUrl, fallbackPath) {
2196
2244
  }
2197
2245
  const inner = innerPathOf(url.pathname);
2198
2246
  if (inner === void 0) return fallbackPath;
2247
+ url.searchParams.delete(REMOTE_DEVICE_QUERY);
2199
2248
  return `${inner}${url.search}`;
2200
2249
  }
2201
2250
  /**
@@ -4341,9 +4390,7 @@ function applyImpl(ctx, config) {
4341
4390
  console.log(`remote-web-ui: the paired Web GUI is reachable on LAN at ${urls}`);
4342
4391
  }
4343
4392
  if (ctx.webServer.host === "0.0.0.0") console.warn("remote-web-ui: LAN-exposed bind — pairing gates the /remote channel; direct /api stays under the harness fence + browser auth (stop() does not revoke an already-redeemed browser credential)");
4344
- const sync = () => {
4345
- const value = resolve();
4346
- service.config = pairingConfigOf(value);
4393
+ const applyLanBindWork = (value) => {
4347
4394
  if (value.lanBind !== void 0) {
4348
4395
  const startup = ctx.get("webStartup");
4349
4396
  const desiredHost = desiredBindHost(value.lanBind === true, startup?.host);
@@ -4369,6 +4416,27 @@ function applyImpl(ctx, config) {
4369
4416
  else console.error("remote-web-ui: the host firewall rule could not be updated (admin rights required on managed platforms)");
4370
4417
  }
4371
4418
  }
4419
+ };
4420
+ let lanBindWorkPending = false;
4421
+ const scheduleLanBindWork = () => {
4422
+ if (lanBindWorkPending) return;
4423
+ lanBindWorkPending = true;
4424
+ setImmediate(() => {
4425
+ lanBindWorkPending = false;
4426
+ try {
4427
+ applyLanBindWork(resolve());
4428
+ } catch (error) {
4429
+ console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
4430
+ }
4431
+ });
4432
+ };
4433
+ ctx.effect(() => () => {
4434
+ lanBindWorkPending = false;
4435
+ }, "remote-web-ui: lan-bind work");
4436
+ const sync = () => {
4437
+ const value = resolve();
4438
+ service.config = pairingConfigOf(value);
4439
+ scheduleLanBindWork();
4372
4440
  const plan = tunnelPlanOf(value, ctx.webServer.port);
4373
4441
  tunnelMode = plan.mode;
4374
4442
  publicBase.setMode(plan.mode);
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAmRD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAq5BvC"}
1
+ {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AA8RD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAqhCvC"}
@@ -30,6 +30,16 @@ const ADAPT_CSS_ID = 'dsh-remote-web-ui/mobile-adapt.css';
30
30
  const ACTIVE_CLASS = 'dsh-remote-portrait';
31
31
  /** Body class while the collapsed rail is hidden behind the whale. */
32
32
  const RAIL_HIDDEN_CLASS = 'dsh-remote-rail-hidden';
33
+ /**
34
+ * The official sidebar column root. Also the scope of the row sweep: every row
35
+ * the drag suppression covers is a descendant of it, so the document-wide scan
36
+ * it replaces only ever returned these.
37
+ */
38
+ const SIDEBAR_SELECTOR = '[class*="_sidebarCol"]';
39
+ /** The official draggable session/project rows (see disableRowDrag). */
40
+ const ROW_SELECTOR = '[class*="_sessionRow"], [class*="_projectRow"]';
41
+ /** The injected stylesheet, addressed through the same cached lookup. */
42
+ const STYLE_SELECTOR = `style[data-plugin-css="${ADAPT_CSS_ID}"]`;
33
43
  /** Whale button id. */
34
44
  const WHALE_ID = 'dshRemoteWhale';
35
45
  /** Compact picker: synthesized model button id. */
@@ -319,6 +329,8 @@ export function startMobileAdapt() {
319
329
  let whaleObserver = null;
320
330
  /** Header subtree observer: marks the geometry measurement dirty on re-render. */
321
331
  let headerObserver = null;
332
+ /** Document child-list observer: invalidates the cached-absent selector set. */
333
+ let domObserver = null;
322
334
  let observedHeader = null;
323
335
  /** Whether the seated-actions geometry needs re-measuring (see alignActionsText). */
324
336
  let headerGeometryDirty = true;
@@ -340,18 +352,29 @@ export function startMobileAdapt() {
340
352
  * rail compaction) while the body class stays.
341
353
  */
342
354
  function ensureAdaptStyle() {
343
- if (nodeOf(`style[data-plugin-css="${ADAPT_CSS_ID}"]`) !== null)
355
+ if (nodeOf(STYLE_SELECTOR) !== null)
344
356
  return;
345
357
  const tag = document.createElement('style');
346
358
  tag.dataset.plugin = 'remote-web-ui';
347
359
  tag.dataset.pluginCss = ADAPT_CSS_ID;
348
360
  tag.textContent = ADAPT_CSS.join('');
349
361
  document.head.appendChild(tag);
362
+ // Seed the lookup cache: the tag is created here, outside nodeOf, so the
363
+ // negative cache could otherwise report it missing for one more tick and
364
+ // stack a duplicate stylesheet.
365
+ nodeCache.set(STYLE_SELECTOR, tag);
366
+ nodeMissCache.delete(STYLE_SELECTOR);
350
367
  }
351
368
  function apply() {
352
369
  if (active)
353
370
  return;
354
371
  active = true;
372
+ // First statement of the activation, before any lookup: nothing observed the
373
+ // document while the layer was reverted (the observer is created below), so
374
+ // no "absent" verdict recorded earlier may be served now. Clearing here
375
+ // rather than beside the observer also covers the lookups this path runs
376
+ // before that point.
377
+ forgetAbsentSelectors();
355
378
  document.body.classList.add(ACTIVE_CLASS);
356
379
  // A details panel opened before the viewport rotated into portrait (or
357
380
  // restored across reloads) would sit behind the display:none above;
@@ -371,6 +394,7 @@ export function startMobileAdapt() {
371
394
  meta.setAttribute('content', `${savedViewportContent}, viewport-fit=cover`);
372
395
  }
373
396
  ensureWhale();
397
+ ensureDomObserver();
374
398
  syncWhale();
375
399
  // React builds nodes with attributes before inserting them, so
376
400
  // attribute observers miss the initial collapsed state — a light
@@ -404,6 +428,45 @@ export function startMobileAdapt() {
404
428
  whaleObserver.disconnect();
405
429
  whaleObserver = null;
406
430
  }
431
+ if (domObserver !== null) {
432
+ domObserver.disconnect();
433
+ domObserver = null;
434
+ }
435
+ // Nothing observes the document from here on; a verdict recorded now could
436
+ // not be trusted after the next apply().
437
+ forgetAbsentSelectors();
438
+ }
439
+ /**
440
+ * Observe the document for the two kinds of change that can make a cached-absent
441
+ * target discoverable again (see nodeOf): node insertion/removal, and the
442
+ * class / compat-stamp attributes the cached selectors match on.
443
+ *
444
+ * Body class writes are excluded by target: the layer toggles three body
445
+ * classes every tick, and counting those would invalidate the negative cache
446
+ * on every tick and restore the very scan this cache removes. They cannot
447
+ * create a target either — every cached selector matches an element other
448
+ * than <body>.
449
+ *
450
+ * The record queue is drained on a microtask, so this stays off the layout
451
+ * path; a chat turn's insertions are a handful per second, not per element.
452
+ */
453
+ function ensureDomObserver() {
454
+ if (domObserver !== null || typeof MutationObserver === 'undefined' || !document.body)
455
+ return;
456
+ domObserver = new MutationObserver((records) => {
457
+ for (const record of records) {
458
+ if (record.type === 'childList' || record.target !== document.body) {
459
+ noteDomChange();
460
+ return;
461
+ }
462
+ }
463
+ });
464
+ domObserver.observe(document.body, {
465
+ childList: true,
466
+ subtree: true,
467
+ attributes: true,
468
+ attributeFilter: ['class', 'data-dsh-frame'],
469
+ });
407
470
  }
408
471
  /** Start/stop the 600ms sync tick; a no-op when already in the asked state. */
409
472
  function setWhaleTimer(on) {
@@ -701,7 +764,7 @@ export function startMobileAdapt() {
701
764
  function disableRowDrag() {
702
765
  if (!active)
703
766
  return;
704
- const rows = document.querySelectorAll('[class*="_sidebarCol"] [class*="_sessionRow"], [class*="_sidebarCol"] [class*="_projectRow"]');
767
+ const rows = sidebarRows();
705
768
  for (const row of rows) {
706
769
  if (row.getAttribute('draggable') !== 'false') {
707
770
  if (!dragOverridden.has(row))
@@ -732,17 +795,88 @@ export function startMobileAdapt() {
732
795
  * node on a major re-render, which the isConnected guard detects.
733
796
  */
734
797
  const nodeCache = new Map();
798
+ /**
799
+ * Selectors known to be absent as of {@link domGeneration}. A miss is the
800
+ * common case, not the exception: the layer holds no overlay of its own, and
801
+ * several official surfaces it looks for (tabs row, tools row, sidebar rows)
802
+ * legitimately do not exist at once. Re-walking a conversation-sized document
803
+ * for the same absent selector on every 600ms tick was waste that almost
804
+ * always resolved to null: five document-wide lookups per tick on the measured
805
+ * fixture, zero after this cache. That is a lookup count — the wall-clock
806
+ * saving is real while the mounted content is quiet and shrinks to the
807
+ * row-scope saving when a turn streams (see the Agent Note for the harness and
808
+ * the before/after numbers).
809
+ *
810
+ * A miss is trusted only while the observed content has not changed, so a
811
+ * surface that appears is still found on the very next tick — the same
812
+ * discovery latency the unconditional probe had. The cache is never aged by a
813
+ * clock: it is cleared by {@link noteDomChange} while the observer is live and
814
+ * by {@link forgetAbsentSelectors} across the windows where it is not (apply
815
+ * and revert). One blind spot is deliberate and load-bearing: the observer
816
+ * watches <body>, so a head-resident target is not covered by that signal —
817
+ * the one such target is seeded into the cache where it is created (see
818
+ * ensureAdaptStyle).
819
+ */
820
+ const nodeMissCache = new Set();
821
+ /**
822
+ * Bumped whenever a cached target may have appeared or disappeared. A surface
823
+ * qualifies on a class token or on the aggregate compat stamp
824
+ * (`data-dsh-frame`), so both insertions/removals AND those two attributes
825
+ * can change the answer. Nothing the layer writes per tick lands here: its own
826
+ * class writes are all on <body>, and its row/transform/label writes touch
827
+ * other attributes entirely (see ensureDomObserver).
828
+ */
829
+ let domGeneration = 0;
830
+ /** The generation the negative cache was recorded against. */
831
+ let missGeneration = -1;
832
+ function noteDomChange() {
833
+ domGeneration += 1;
834
+ }
835
+ /**
836
+ * Drop every cached "absent" verdict. Called on apply and revert: while the
837
+ * observer is disconnected the layer is blind to insertions, so a verdict
838
+ * recorded before the gap could otherwise outlive the change that falsified
839
+ * it and be served without a probe. The positive cache needs no equivalent —
840
+ * its `isConnected` guard already re-resolves a replaced node.
841
+ */
842
+ function forgetAbsentSelectors() {
843
+ nodeMissCache.clear();
844
+ domGeneration += 1;
845
+ }
735
846
  function nodeOf(selector) {
736
847
  const cached = nodeCache.get(selector);
737
- if (cached !== undefined && cached.isConnected)
738
- return cached;
739
- const found = document.querySelector(selector);
740
- if (found === null)
848
+ if (cached !== undefined) {
849
+ // A node React replaced is no longer connected; drop it and fall through
850
+ // so the lookup below can find its replacement.
851
+ if (cached.isConnected)
852
+ return cached;
741
853
  nodeCache.delete(selector);
742
- else
743
- nodeCache.set(selector, found);
854
+ }
855
+ if (missGeneration !== domGeneration) {
856
+ nodeMissCache.clear();
857
+ missGeneration = domGeneration;
858
+ }
859
+ if (nodeMissCache.has(selector))
860
+ return null;
861
+ const found = document.querySelector(selector);
862
+ if (found === null) {
863
+ nodeMissCache.add(selector);
864
+ return null;
865
+ }
866
+ nodeCache.set(selector, found);
744
867
  return found;
745
868
  }
869
+ /**
870
+ * The session/project rows the drag suppression covers, scoped to the cached
871
+ * sidebar root. Each row carries the sidebar column class as an ancestor, so
872
+ * the document-wide scan this replaces only ever returned rows from here.
873
+ */
874
+ function sidebarRows() {
875
+ const sidebar = nodeOf(SIDEBAR_SELECTOR);
876
+ if (sidebar === null)
877
+ return [];
878
+ return Array.from(sidebar.querySelectorAll(ROW_SELECTOR));
879
+ }
746
880
  /** The official application frame, through the same cached lookup. */
747
881
  function frameEl() {
748
882
  const frame = nodeOf(APP_FRAME_SELECTOR);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -536,18 +536,22 @@ function applyImpl(ctx, config) {
536
536
  if (ctx.webServer.host === '0.0.0.0') {
537
537
  console.warn('remote-web-ui: LAN-exposed bind — pairing gates the /remote channel; direct /api stays under the harness fence + browser auth (stop() does not revoke an already-redeemed browser credential)');
538
538
  }
539
- // Apply the effective configuration to the running surfaces once per
540
- // activation: the service tunables, the LAN-bind block/firewall, the tunnel
541
- // plan, the pairing routes, the presence sweep, and the posture probe.
542
- const sync = () => {
543
- const value = resolve();
544
- service.config = pairingConfigOf(value);
545
- // LAN bind toggle: only write the managed patch block once the user has
546
- // flipped it (undefined = untouched, never write). Desired host/port come
547
- // from the CLI flags when given (flags win), else from the toggle and
548
- // the currently bound port. The block takes effect on the next start, so
549
- // the re-assert at every boot keeps it in sync with both the toggle and
550
- // the flags.
539
+ // LAN bind toggle: only write the managed patch block once the user has
540
+ // flipped it (undefined = untouched, never write). Desired host/port come
541
+ // from the CLI flags when given (flags win), else from the toggle and
542
+ // the currently bound port. The block takes effect on the next start, so
543
+ // the re-assert at every boot keeps it in sync with both the toggle and
544
+ // the flags.
545
+ //
546
+ // This work must leave the caller's async context first: a settings save
547
+ // runs inside the Host's hmr.runExclusive, and cordis.patch.yml is exactly
548
+ // the file the HMR config watcher refreshes from. A write issued on that
549
+ // same context makes the watcher's refresh re-enter runExclusive, which
550
+ // rejects with "HMR transactions cannot be nested" and fails the user's
551
+ // save (issue #1751). setImmediate starts a fresh AsyncLocalStorage store,
552
+ // so the assertion runs outside the transaction; it is idempotent, so the
553
+ // coalesced follow-up sync() re-reading the same block writes nothing.
554
+ const applyLanBindWork = (value) => {
551
555
  if (value.lanBind !== undefined) {
552
556
  const startup = ctx.get('webStartup');
553
557
  const desiredHost = desiredBindHost(value.lanBind === true, startup?.host);
@@ -589,6 +593,36 @@ function applyImpl(ctx, config) {
589
593
  }
590
594
  }
591
595
  }
596
+ };
597
+ // The LAN bind assertion never blocks the caller: one pending work item is
598
+ // enough, and it always re-reads the config through `resolve()` at run time
599
+ // so a toggle flipped twice before the tick lands on the last value.
600
+ let lanBindWorkPending = false;
601
+ const scheduleLanBindWork = () => {
602
+ if (lanBindWorkPending)
603
+ return;
604
+ lanBindWorkPending = true;
605
+ setImmediate(() => {
606
+ lanBindWorkPending = false;
607
+ try {
608
+ applyLanBindWork(resolve());
609
+ }
610
+ catch (error) {
611
+ // A deferred assertion must never surface as an unhandled rejection:
612
+ // the settings save that triggered it already answered, and the card
613
+ // reads the live block state back on its own poll.
614
+ console.error(`remote-web-ui: the deferred lan-bind assertion failed: ${error instanceof Error ? error.message : String(error)}`);
615
+ }
616
+ });
617
+ };
618
+ ctx.effect(() => () => { lanBindWorkPending = false; }, 'remote-web-ui: lan-bind work');
619
+ // Apply the effective configuration to the running surfaces once per
620
+ // activation: the service tunables, the LAN-bind block/firewall, the tunnel
621
+ // plan, the pairing routes, the presence sweep, and the posture probe.
622
+ const sync = () => {
623
+ const value = resolve();
624
+ service.config = pairingConfigOf(value);
625
+ scheduleLanBindWork();
592
626
  // The plugin-managed tunnels own the public base while one runs: the URL
593
627
  // lands in the service through the tunnel's phase listener (the minted
594
628
  // quick URL, or the named tunnel's fixed public hostname). The manual
@@ -1 +1 @@
1
- {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,cAAc,CAAA;AAEhE,uDAAuD;AACvD,MAAM,MAAM,YAAY;AACtB,mEAAmE;AACjE,cAAc;AAChB,oEAAoE;GAClE,SAAS;AACX,4DAA4D;GAC1D,SAAS;AACX,sDAAsD;GACpD,WAAW;AACb,6EAA6E;GAC3E,cAAc,CAAA;AAElB,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,EAAE,EAAE,MAAM,CAAA;IACV,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,QAA2B,CAAA;AAK9D,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAA;IACf,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,KAAK,EAAE,KAAK,GAAG,aAAa,GAAG,SAAS,GAAG,QAAQ,CAAA;IACnD,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,YAAY,CAAA;IACnB,yEAAyE;IACzE,YAAY,EAAE,OAAO,CAAA;IACrB,oEAAoE;IACpE,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,uEAAuE;IACvE,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;IACnB,iFAAiF;IACjF,OAAO,EAAE,cAAc,EAAE,CAAA;CAC1B;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAElC,yEAAyE;AACzE,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C;;OAEG;gBACS,OAAO,EAAE,MAAM;CAI5B;AAED,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,GAAG,IAAI,MAAM,CAAA;IACb,WAAW,IAAI,MAAM,CAAA;CACtB;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,yDAAyD;AACzD,eAAO,MAAM,YAAY,EAAE,YAG1B,CAAA;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;IAyBhB,MAAM,EAAE,aAAa;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE;IA1BrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiD;IAC3E,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,WAAW,CAAI;IACvB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAA4B;IAC5C,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAoB;IACtC,mEAAmE;IACnE,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,OAAO,CAA6B;IAC5C,8EAA8E;IAC9E,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;;OAKG;gBAEM,MAAM,EAAE,aAAa,EACX,KAAK,GAAE,YAA2B,EAClC,EAAE,GAAE,cAAmB;IAK1C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,aAAa;IA4BrB,iEAAiE;IACjE,OAAO,CAAC,SAAS;IAajB;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO;IAkBf;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAazB,wFAAwF;IACxF,IAAI,UAAU,IAAI,MAAM,GAAG,SAAS,CAEnC;IAED,oFAAoF;IACpF,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIlD,wEAAwE;IACxE,IAAI,YAAY,IAAI,MAAM,EAAE,CAE3B;IAED,6EAA6E;IAC7E,WAAW,CAAC,OAAO,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,IAAI;IAKxE,+DAA+D;IAC/D,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAK/C,qFAAqF;IACrF,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI;IAKvD,iFAAiF;IACjF,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI;IAKrD,iEAAiE;IACjE,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI;IAKvD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAuBnF;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY;IA+BvD;;;;OAIG;IACH,IAAI,IAAI,IAAI;IAQZ;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQjC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQtC,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAOb,2EAA2E;IAC3E,QAAQ,IAAI,eAAe;IAsB3B,+EAA+E;IAC/E,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;OAKG;IACH,WAAW,IAAI,MAAM;IAIrB,yEAAyE;IACzE,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlE,OAAO,CAAC,WAAW;IASnB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,MAAM;CAaf;AA0FD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK7E"}
@@ -509,6 +509,7 @@ function snapshotsEqual(a, b) {
509
509
  && a.publicUrl === b.publicUrl
510
510
  && tunnelEqual(a.tunnel, b.tunnel)
511
511
  && relayEqual(a.relay, b.relay)
512
+ && postureEqual(a.posture, b.posture)
512
513
  && a.tokenId === b.tokenId
513
514
  && a.tokenExpiresAt === b.tokenExpiresAt
514
515
  && a.deviceCount === b.deviceCount
@@ -537,6 +538,48 @@ function relayEqual(a, b) {
537
538
  return a === b || (a !== undefined && b !== undefined
538
539
  && a.state === b.state && a.url === b.url && a.error === b.error);
539
540
  }
541
+ /**
542
+ * Posture equality. A probe round can change this frame while every other field
543
+ * stays put — the common case, since the probe re-runs on a settings change and
544
+ * on a tunnel reaching running, and a quick-tunnel restart churns the probed
545
+ * host set. Leaving the frame out of the comparison suppressed those emits, so
546
+ * the loopback SSE stream and the desktop panel could sit on a stale "exposed"
547
+ * (or stale "clean") verdict for as long as the phase and roster stayed put.
548
+ */
549
+ function postureEqual(a, b) {
550
+ if (a === b)
551
+ return true;
552
+ // A frame that is not an object at all (a hand-built context can pass
553
+ // anything) counts as changed instead of throwing out of the setter.
554
+ if (!isPostureFrame(a) || !isPostureFrame(b))
555
+ return false;
556
+ return a.checkedAt === b.checkedAt && hostsEqual(a.hosts, b.hosts);
557
+ }
558
+ /** Whether a value has the shape a posture frame is compared on. */
559
+ function isPostureFrame(value) {
560
+ return typeof value === 'object' && value !== null;
561
+ }
562
+ /**
563
+ * Per-host posture equality (a round's verdicts, in probe order). Tolerates a
564
+ * frame that is not the expected shape: this runs inside the emit path, outside
565
+ * the listener error containment, so a malformed snapshot handed in by another
566
+ * host-fiber caller must degrade to "changed" instead of throwing out of the
567
+ * setter that produced it.
568
+ */
569
+ function hostsEqual(a, b) {
570
+ if (a === b)
571
+ return true;
572
+ if (!Array.isArray(a) || !Array.isArray(b))
573
+ return false;
574
+ return a.length === b.length && a.every((host, index) => {
575
+ const other = b[index];
576
+ return isPostureHost(host) && isPostureHost(other) && host.host === other.host && host.exposed === other.exposed;
577
+ });
578
+ }
579
+ /** Whether a value has the shape one probed host is compared on. */
580
+ function isPostureHost(value) {
581
+ return typeof value === 'object' && value !== null;
582
+ }
540
583
  /** Element-wise string list equality (interface order is meaningful). */
541
584
  function sameStrings(a, b) {
542
585
  return a.length === b.length && a.every((value, index) => value === b[index]);
@@ -70,7 +70,19 @@ export declare function pairedDeviceIdOf(req: IncomingMessage, service: PairingS
70
70
  */
71
71
  export declare function makeRemoteApiRoutes(deps: RemoteApiDeps): WebRoute[];
72
72
  /**
73
- * Map one outer upgrade URL onto the loopback path (query string included).
73
+ * Map one outer upgrade URL onto the loopback path, dropping the cookieless
74
+ * device credential on the way in.
75
+ *
76
+ * The outer handshake carries the device id as `?device=<id>` because WebSocket
77
+ * handshakes cannot carry headers from the Web API. That id is a live session
78
+ * credential — it opens the full host API through this same channel — and the
79
+ * inner leg is a request the rest of the loopback origin sees (on a WebSocket
80
+ * leg, a raw handshake request line written to a loopback socket), so forwarding
81
+ * it handed a working credential to every other observer of the local port for
82
+ * no benefit: the gate above has already authenticated the caller, and no
83
+ * upstream route reads the parameter (the gateway mux matches on path alone).
84
+ * The rest of the query rides through unchanged, so a caller's own parameters
85
+ * still reach the upstream.
74
86
  */
75
87
  export declare function upgradeInnerPath(reqUrl: string | undefined, fallbackPath: string): string;
76
88
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAUhD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CAwCnE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,EAAE,CAyCjF"}
1
+ {"version":3,"file":"remote-api.d.ts","sourceRoot":"","sources":["../../src/remote-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAUhD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBvD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAQlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CAwCnE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAYzF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,EAAE,CAyCjF"}
@@ -137,7 +137,19 @@ export function makeRemoteApiRoutes(deps) {
137
137
  return [{ kind: 'prefix', path: REMOTE_PREFIX, handler }];
138
138
  }
139
139
  /**
140
- * Map one outer upgrade URL onto the loopback path (query string included).
140
+ * Map one outer upgrade URL onto the loopback path, dropping the cookieless
141
+ * device credential on the way in.
142
+ *
143
+ * The outer handshake carries the device id as `?device=<id>` because WebSocket
144
+ * handshakes cannot carry headers from the Web API. That id is a live session
145
+ * credential — it opens the full host API through this same channel — and the
146
+ * inner leg is a request the rest of the loopback origin sees (on a WebSocket
147
+ * leg, a raw handshake request line written to a loopback socket), so forwarding
148
+ * it handed a working credential to every other observer of the local port for
149
+ * no benefit: the gate above has already authenticated the caller, and no
150
+ * upstream route reads the parameter (the gateway mux matches on path alone).
151
+ * The rest of the query rides through unchanged, so a caller's own parameters
152
+ * still reach the upstream.
141
153
  */
142
154
  export function upgradeInnerPath(reqUrl, fallbackPath) {
143
155
  if (reqUrl === undefined || reqUrl === '')
@@ -152,6 +164,7 @@ export function upgradeInnerPath(reqUrl, fallbackPath) {
152
164
  const inner = innerPathOf(url.pathname);
153
165
  if (inner === undefined)
154
166
  return fallbackPath;
167
+ url.searchParams.delete(REMOTE_DEVICE_QUERY);
155
168
  return `${inner}${url.search}`;
156
169
  }
157
170
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Scan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and revocable device sessions, with a LAN bind toggle, optional Cloudflare tunnel, and one-click family self-update",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "engines": {
@@ -38,7 +38,7 @@
38
38
  "license": "Apache-2.0",
39
39
  "dsh": {
40
40
  "engines": {
41
- "dsh": ">=0.1.7-rc.2"
41
+ "dsh": ">=0.2.0-rc.1"
42
42
  },
43
43
  "bundle": {
44
44
  "patch": "./cordis.patch.yml"
@@ -61,28 +61,28 @@
61
61
  "zod": "^4.4.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@deepseek-ai/dsh": ">=0.1.7-rc.2",
64
+ "@deepseek-ai/dsh": ">=0.2.0-rc.1",
65
65
  "react": "^18.2.0",
66
66
  "react-dom": "^18.2.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@deepseek-ai/cordis": "^4.0.4",
70
- "@deepseek-ai/dsh-agent": "^0.1.7-rc.2",
71
- "@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.2",
72
- "@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.2",
73
- "@deepseek-ai/dsh-api-workspace-controller": "^0.1.7-rc.2",
74
- "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.2",
75
- "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
76
- "@deepseek-ai/dsh-client-store": "^0.1.7-rc.2",
77
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2",
78
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
79
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
80
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.7-rc.2",
81
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
82
- "@deepseek-ai/dsh-commands": "^0.1.7-rc.2",
83
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
84
- "@deepseek-ai/dsh-invariants": "^0.1.7-rc.2",
85
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
70
+ "@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
71
+ "@deepseek-ai/dsh-api-remotes": "^0.2.0-rc.1",
72
+ "@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.1",
73
+ "@deepseek-ai/dsh-api-workspace-controller": "^0.2.0-rc.1",
74
+ "@deepseek-ai/dsh-client-connection": "^0.2.0-rc.1",
75
+ "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
76
+ "@deepseek-ai/dsh-client-store": "^0.2.0-rc.1",
77
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.1",
79
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.1",
80
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.1",
81
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
82
+ "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
83
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
84
+ "@deepseek-ai/dsh-invariants": "^0.2.0-rc.1",
85
+ "@deepseek-ai/dsh-settings": "^0.2.0-rc.1",
86
86
  "@testing-library/dom": "^10.4.1",
87
87
  "@testing-library/react": "^16.3.2",
88
88
  "@types/node": "^22.20.0",