super-dsh 0.3.1 → 0.3.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.
- package/client/index.js +135 -14
- package/package.json +1 -1
- package/remote/helper-bin/MANIFEST.json +35 -0
- package/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/superd-run.exe +0 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
- package/remote/plugin.d.ts +172 -51
- package/remote/plugin.js +2161 -711
- package/remote/provenance.json +32 -20
- package/superd-hub/dist/a2a-host.d.ts +11 -3
- package/superd-hub/dist/a2a-host.js +7 -7
- package/superd-hub/dist/a2a-row.js +7 -1
- package/superd-hub/dist/carrier.d.ts +9 -7
- package/superd-hub/dist/carrier.js +3 -3
- package/superd-hub/dist/client/pill-geometry.d.ts +70 -0
- package/superd-hub/dist/client/pill-geometry.js +49 -0
- package/superd-hub/dist/client/switch.d.ts +15 -0
- package/superd-hub/dist/client/switch.js +54 -1
- package/superd-hub/dist/companions.js +8 -6
- package/superd-hub/dist/fusion-transport.d.ts +18 -1
- package/superd-hub/dist/fusion-transport.js +44 -5
- package/superd-hub/dist/gateway.js +8 -1
- package/superd-hub/dist/hub-route.d.ts +5 -2
- package/superd-hub/dist/hub-route.js +6 -6
- package/superd-hub/dist/rpc.d.ts +5 -0
- package/superd-hub/dist/rpc.js +20 -1
- package/superd-hub/dist/switch-core.d.ts +41 -0
- package/superd-hub/dist/switch-core.js +40 -0
- package/superd-hub/dist/targets.d.ts +7 -0
- package/superd-hub/dist/world-lifecycle.js +1 -1
- package/superd-hub/lib/client/index.js +135 -14
|
@@ -130,15 +130,15 @@ export function registerHubRoute(options) {
|
|
|
130
130
|
});
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
133
|
+
const target = options.gatewayOf(agent);
|
|
134
|
+
if (target === undefined) {
|
|
135
135
|
answer404(res, { error: 'unknown-agent', label: agent });
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
// Consume the family plumbing: the kernel forwards a native-shaped URL
|
|
139
139
|
// (the kernel carries the same fence face as the carrier's own kernel).
|
|
140
140
|
req.url = worldUrlOf(req.url);
|
|
141
|
-
await forwardApiCall({ gateway, label: agent, mountPath: HUB_MOUNT_PATH, requestRejection: options.requestRejection }, req, res);
|
|
141
|
+
await forwardApiCall({ gateway: target.gateway, peer: target.peer, label: agent, mountPath: HUB_MOUNT_PATH, requestRejection: options.requestRejection }, req, res);
|
|
142
142
|
};
|
|
143
143
|
const handleUpgrade = (req, socket, head) => {
|
|
144
144
|
if (!gateUpgrade(options, req, socket))
|
|
@@ -155,8 +155,8 @@ export function registerHubRoute(options) {
|
|
|
155
155
|
rejectUpgrade(socket, 404, JSON.stringify({ error: 'foreign-only' }), 'application/json');
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
158
|
+
const target = options.gatewayOf(agent);
|
|
159
|
+
if (target === undefined) {
|
|
160
160
|
rejectUpgrade(socket, 404, JSON.stringify({ error: 'unknown-agent', label: agent }), 'application/json');
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
@@ -165,7 +165,7 @@ export function registerHubRoute(options) {
|
|
|
165
165
|
// a heartbeat interval and a per-connection WebSocketServer; on socket
|
|
166
166
|
// close it must be CLOSED (world-mux clears the heartbeat only in close),
|
|
167
167
|
// or every reconnect leaks the timer plus the mux graph forever.
|
|
168
|
-
const mux = worldMuxOf(gateway);
|
|
168
|
+
const mux = worldMuxOf(target.gateway, target.peer);
|
|
169
169
|
socket.once?.('close', () => {
|
|
170
170
|
void mux.close().catch(() => undefined);
|
|
171
171
|
});
|
package/superd-hub/dist/rpc.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface AgentRuntimeDeps {
|
|
|
37
37
|
* ADDITIVE contract (mesh P1): the GET body gained `hub: {machineId, label,
|
|
38
38
|
* version}` and every `agents` row gained `id` (a6); `available` and all
|
|
39
39
|
* pre-existing agents fields are unchanged — old clients ignore the new
|
|
40
|
+
* fields, new clients read them. Live-install staleness (2026-10-18): the
|
|
41
|
+
* hub block also carries `fusion` — the fusion row's armed state — so a
|
|
42
|
+
* page that booted before super-dsh was installed can tell its own
|
|
43
|
+
* staleness apart from a flag-off deployment and reload once (switch-core
|
|
44
|
+
* `fusionReloadDecision`).
|
|
40
45
|
* fields, new clients read them.
|
|
41
46
|
* @param request - the incoming `/api/agent-runtime` request.
|
|
42
47
|
* @param source - live runtime registry view.
|
package/superd-hub/dist/rpc.js
CHANGED
|
@@ -3,6 +3,7 @@ import { PACK_VERSION } from './pack-version.js';
|
|
|
3
3
|
import { listRuntimeKeys } from './targets.js';
|
|
4
4
|
import { agentLinks } from './agent-roster.js';
|
|
5
5
|
import { ensureSelf } from './machines.js';
|
|
6
|
+
import { fusionTransportArmed } from './fusion-transport.js';
|
|
6
7
|
/** The shared-channel Fetch route path this RPC face owns. */
|
|
7
8
|
export const AGENT_RUNTIME_RPC_PATH = '/api/agent-runtime';
|
|
8
9
|
/** The live registry view: native plus every registered foreign context. */
|
|
@@ -27,6 +28,11 @@ function json(status, body) {
|
|
|
27
28
|
* ADDITIVE contract (mesh P1): the GET body gained `hub: {machineId, label,
|
|
28
29
|
* version}` and every `agents` row gained `id` (a6); `available` and all
|
|
29
30
|
* pre-existing agents fields are unchanged — old clients ignore the new
|
|
31
|
+
* fields, new clients read them. Live-install staleness (2026-10-18): the
|
|
32
|
+
* hub block also carries `fusion` — the fusion row's armed state — so a
|
|
33
|
+
* page that booted before super-dsh was installed can tell its own
|
|
34
|
+
* staleness apart from a flag-off deployment and reload once (switch-core
|
|
35
|
+
* `fusionReloadDecision`).
|
|
30
36
|
* fields, new clients read them.
|
|
31
37
|
* @param request - the incoming `/api/agent-runtime` request.
|
|
32
38
|
* @param source - live runtime registry view.
|
|
@@ -51,7 +57,20 @@ export async function handleAgentRuntime(request, source, deps) {
|
|
|
51
57
|
const links = new URL(request.url).searchParams.get('mesh') === '1'
|
|
52
58
|
? agentLinks(home).filter((l) => l.machine === 'local')
|
|
53
59
|
: agentLinks(home);
|
|
54
|
-
return json(200, {
|
|
60
|
+
return json(200, {
|
|
61
|
+
available,
|
|
62
|
+
agents: links,
|
|
63
|
+
hub: {
|
|
64
|
+
machineId: self.id,
|
|
65
|
+
label: self.label,
|
|
66
|
+
version: HUB_VERSION,
|
|
67
|
+
// Additive (live-install staleness): `true` exactly while the fusion
|
|
68
|
+
// row is armed — the fact a pre-install page cannot read off its own
|
|
69
|
+
// HTML. Peers' `?mesh=1` probes ignore it (same additivity rule as
|
|
70
|
+
// every hub-block field before it).
|
|
71
|
+
fusion: fusionTransportArmed(),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
55
74
|
}
|
|
56
75
|
// A POST that used to switch a global selector has no meaning any more:
|
|
57
76
|
// the addressed world is the mount path of the request, not a shared value.
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* roster-probe failure → revert to native + a visible toast, never a page
|
|
10
10
|
* lock).
|
|
11
11
|
*
|
|
12
|
+
* Live-install staleness (2026-10-18): the fusion flag global and the
|
|
13
|
+
* transport script are baked into the SERVED index, so a page that booted
|
|
14
|
+
* before super-dsh was installed runs the freshly hot-loaded client half
|
|
15
|
+
* against a stale page — `fusionPage()` reads false and every switch
|
|
16
|
+
* affordance stays hidden until a manual restart. {@link fusionReloadDecision}
|
|
17
|
+
* is the pure decision behind the client half's heal: one roster answer later,
|
|
18
|
+
* reload the page once. The transport must precede app boot (its upload hook
|
|
19
|
+
* `__DSH_FILE_UPLOAD__` is read at construction), so a client-side flag flip
|
|
20
|
+
* can never arm a stale page honestly — the page must be re-served.
|
|
21
|
+
*
|
|
12
22
|
* Host-half on purpose: plain TS with every effect injected through
|
|
13
23
|
* {@link SwitchHost} — no DOM, no client imports — so `test/client-switch.test.mjs`
|
|
14
24
|
* imports it from `dist/` like every other host module. The client half
|
|
@@ -445,6 +455,37 @@ export interface SwitchAffordance {
|
|
|
445
455
|
* Host-half pure logic so `test/client-switch.test.mjs` pins it from `dist/`.
|
|
446
456
|
*/
|
|
447
457
|
export declare function switchAffordance(fusion: boolean): SwitchAffordance;
|
|
458
|
+
/** The sessionStorage marker guarding the stale-page reload: one per tab session. */
|
|
459
|
+
export declare const FUSION_RELOAD_MARKER = "superd.fusion-armed.reload";
|
|
460
|
+
/** What the stale-page probe decides (the client binding executes it). */
|
|
461
|
+
export type FusionReloadDecision =
|
|
462
|
+
/** Flag present — the page is armed; nothing to heal. */
|
|
463
|
+
'armed'
|
|
464
|
+
/** The marker is set — this tab already reloaded once; never loop. */
|
|
465
|
+
| 'already-reloaded'
|
|
466
|
+
/** The host answered `hub.fusion: false` — the T-E flag-off shape; stay degraded. */
|
|
467
|
+
| 'row-off'
|
|
468
|
+
/** No usable answer (old host without the additive field, or the fetch failed) — stay degraded. */
|
|
469
|
+
| 'host-silent'
|
|
470
|
+
/** Stale page + an armed host — set the marker and reload once. */
|
|
471
|
+
| 'reload';
|
|
472
|
+
/** The inputs of one stale-page probe decision. */
|
|
473
|
+
export interface FusionReloadInput {
|
|
474
|
+
/** Whether the page carries the baked `__SUPERD_UI_FUSION__` flag global. */
|
|
475
|
+
flagPresent: boolean;
|
|
476
|
+
/** Whether {@link FUSION_RELOAD_MARKER} is already in sessionStorage. */
|
|
477
|
+
markerPresent: boolean;
|
|
478
|
+
/** The roster answer's additive `hub.fusion` field (undefined: absent). */
|
|
479
|
+
hubFusion?: unknown;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The live-install staleness decision (host-half pure so the node tests pin
|
|
483
|
+
* the table from `dist/`). The host's `hub.fusion` field is the ONLY thing
|
|
484
|
+
* that distinguishes a page-booted-before-install from a deliberately
|
|
485
|
+
* flag-off deployment — without it the reload would loop forever on every
|
|
486
|
+
* `enabled: false` composition (the client half loads there too).
|
|
487
|
+
*/
|
|
488
|
+
export declare function fusionReloadDecision(input: FusionReloadInput): FusionReloadDecision;
|
|
448
489
|
/**
|
|
449
490
|
* Structural slice of one roster row the Hub UI helpers read (the client
|
|
450
491
|
* roster's `AgentLink` shape mirrored structurally — this module never imports
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* roster-probe failure → revert to native + a visible toast, never a page
|
|
10
10
|
* lock).
|
|
11
11
|
*
|
|
12
|
+
* Live-install staleness (2026-10-18): the fusion flag global and the
|
|
13
|
+
* transport script are baked into the SERVED index, so a page that booted
|
|
14
|
+
* before super-dsh was installed runs the freshly hot-loaded client half
|
|
15
|
+
* against a stale page — `fusionPage()` reads false and every switch
|
|
16
|
+
* affordance stays hidden until a manual restart. {@link fusionReloadDecision}
|
|
17
|
+
* is the pure decision behind the client half's heal: one roster answer later,
|
|
18
|
+
* reload the page once. The transport must precede app boot (its upload hook
|
|
19
|
+
* `__DSH_FILE_UPLOAD__` is read at construction), so a client-side flag flip
|
|
20
|
+
* can never arm a stale page honestly — the page must be re-served.
|
|
21
|
+
*
|
|
12
22
|
* Host-half on purpose: plain TS with every effect injected through
|
|
13
23
|
* {@link SwitchHost} — no DOM, no client imports — so `test/client-switch.test.mjs`
|
|
14
24
|
* imports it from `dist/` like every other host module. The client half
|
|
@@ -488,6 +498,36 @@ export const FUSION_OFF_HINT = 'fusion off — no foreign entry';
|
|
|
488
498
|
export function switchAffordance(fusion) {
|
|
489
499
|
return fusion ? { available: true, hint: '' } : { available: false, hint: FUSION_OFF_HINT };
|
|
490
500
|
}
|
|
501
|
+
// ---- Live-install staleness: the once-per-session reload heal -------------
|
|
502
|
+
//
|
|
503
|
+
// The flag global is `webserver/index-inject` state: a page served BEFORE
|
|
504
|
+
// the fusion row mounted never carries it, while the client graph hot-update
|
|
505
|
+
// (dsh-client-hmr) still applies the client half to that page — the Hub row
|
|
506
|
+
// appears, the selector does not, and only a manual restart healed it. The
|
|
507
|
+
// heal re-boots the page ONCE (a sessionStorage marker keeps a host that
|
|
508
|
+
// reports fusion but never bakes the flag from looping the tab), because the
|
|
509
|
+
// transport script is boot-time by design: a stale page cannot be armed in
|
|
510
|
+
// place, only re-served.
|
|
511
|
+
/** The sessionStorage marker guarding the stale-page reload: one per tab session. */
|
|
512
|
+
export const FUSION_RELOAD_MARKER = 'superd.fusion-armed.reload';
|
|
513
|
+
/**
|
|
514
|
+
* The live-install staleness decision (host-half pure so the node tests pin
|
|
515
|
+
* the table from `dist/`). The host's `hub.fusion` field is the ONLY thing
|
|
516
|
+
* that distinguishes a page-booted-before-install from a deliberately
|
|
517
|
+
* flag-off deployment — without it the reload would loop forever on every
|
|
518
|
+
* `enabled: false` composition (the client half loads there too).
|
|
519
|
+
*/
|
|
520
|
+
export function fusionReloadDecision(input) {
|
|
521
|
+
if (input.flagPresent)
|
|
522
|
+
return 'armed';
|
|
523
|
+
if (input.markerPresent)
|
|
524
|
+
return 'already-reloaded';
|
|
525
|
+
if (input.hubFusion === true)
|
|
526
|
+
return 'reload';
|
|
527
|
+
if (input.hubFusion === false)
|
|
528
|
+
return 'row-off';
|
|
529
|
+
return 'host-silent';
|
|
530
|
+
}
|
|
491
531
|
/**
|
|
492
532
|
* The active agent's display name for the Hub row's right-edge selector: the
|
|
493
533
|
* roster label of the atom's `activeAgent` key — the name the selector's
|
|
@@ -22,6 +22,13 @@ export interface ForeignTarget {
|
|
|
22
22
|
key: string;
|
|
23
23
|
/** The context's typertGateway service instance (or a wire adapter with the same face). */
|
|
24
24
|
gateway: GatewayFace;
|
|
25
|
+
/**
|
|
26
|
+
* The world's owner browser Peer (connection.browserPeer) for in-process
|
|
27
|
+
* faces — DSH 0.2.1 gateways read peer.signal/principal on every forwarded
|
|
28
|
+
* invocation, so the hub must present the world's shared owner identity.
|
|
29
|
+
* Wire adapters (mesh federation) omit it: the far end supplies its own.
|
|
30
|
+
*/
|
|
31
|
+
peer?: unknown;
|
|
25
32
|
}
|
|
26
33
|
/** Register (or replace) the target for a runtime key. */
|
|
27
34
|
export declare function registerForeignTarget(target: ForeignTarget): void;
|
|
@@ -48,7 +48,7 @@ export function ownWorld(ctx, entry, spawn, onFailure) {
|
|
|
48
48
|
unregisterForeignTarget(key);
|
|
49
49
|
setReady(key, false);
|
|
50
50
|
}, `superd.world.${key}: availability`);
|
|
51
|
-
target = { key, gateway: world.get('typertGateway') };
|
|
51
|
+
target = { key, gateway: world.get('typertGateway'), peer: world.get('connection')?.browserPeer };
|
|
52
52
|
registerForeignTarget(target);
|
|
53
53
|
setReady(key, true);
|
|
54
54
|
return world;
|
|
@@ -898,6 +898,16 @@ function machineLabelsFrom(list) {
|
|
|
898
898
|
* roster-probe failure → revert to native + a visible toast, never a page
|
|
899
899
|
* lock).
|
|
900
900
|
*
|
|
901
|
+
* Live-install staleness (2026-10-18): the fusion flag global and the
|
|
902
|
+
* transport script are baked into the SERVED index, so a page that booted
|
|
903
|
+
* before super-dsh was installed runs the freshly hot-loaded client half
|
|
904
|
+
* against a stale page — `fusionPage()` reads false and every switch
|
|
905
|
+
* affordance stays hidden until a manual restart. {@link fusionReloadDecision}
|
|
906
|
+
* is the pure decision behind the client half's heal: one roster answer later,
|
|
907
|
+
* reload the page once. The transport must precede app boot (its upload hook
|
|
908
|
+
* `__DSH_FILE_UPLOAD__` is read at construction), so a client-side flag flip
|
|
909
|
+
* can never arm a stale page honestly — the page must be re-served.
|
|
910
|
+
*
|
|
901
911
|
* Host-half on purpose: plain TS with every effect injected through
|
|
902
912
|
* {@link SwitchHost} — no DOM, no client imports — so `test/client-switch.test.mjs`
|
|
903
913
|
* imports it from `dist/` like every other host module. The client half
|
|
@@ -1343,6 +1353,22 @@ function switchAffordance(fusion) {
|
|
|
1343
1353
|
hint: FUSION_OFF_HINT
|
|
1344
1354
|
};
|
|
1345
1355
|
}
|
|
1356
|
+
/** The sessionStorage marker guarding the stale-page reload: one per tab session. */
|
|
1357
|
+
const FUSION_RELOAD_MARKER = "superd.fusion-armed.reload";
|
|
1358
|
+
/**
|
|
1359
|
+
* The live-install staleness decision (host-half pure so the node tests pin
|
|
1360
|
+
* the table from `dist/`). The host's `hub.fusion` field is the ONLY thing
|
|
1361
|
+
* that distinguishes a page-booted-before-install from a deliberately
|
|
1362
|
+
* flag-off deployment — without it the reload would loop forever on every
|
|
1363
|
+
* `enabled: false` composition (the client half loads there too).
|
|
1364
|
+
*/
|
|
1365
|
+
function fusionReloadDecision(input) {
|
|
1366
|
+
if (input.flagPresent) return "armed";
|
|
1367
|
+
if (input.markerPresent) return "already-reloaded";
|
|
1368
|
+
if (input.hubFusion === true) return "reload";
|
|
1369
|
+
if (input.hubFusion === false) return "row-off";
|
|
1370
|
+
return "host-silent";
|
|
1371
|
+
}
|
|
1346
1372
|
/**
|
|
1347
1373
|
* The active agent's display name for the Hub row's right-edge selector: the
|
|
1348
1374
|
* roster label of the atom's `activeAgent` key — the name the selector's
|
|
@@ -1621,6 +1647,53 @@ function createReadinessWaiter(faces, options = {}) {
|
|
|
1621
1647
|
function fusionPage() {
|
|
1622
1648
|
return globalThis.__SUPERD_UI_FUSION__ === true;
|
|
1623
1649
|
}
|
|
1650
|
+
/**
|
|
1651
|
+
* The sessionStorage marker read (undefined-safe: a storage failure is
|
|
1652
|
+
* `false` — the probe then reloads without the loop guard, which only
|
|
1653
|
+
* matters for a host that reports fusion but never bakes the flag).
|
|
1654
|
+
*/
|
|
1655
|
+
function staleReloadMarker() {
|
|
1656
|
+
try {
|
|
1657
|
+
return globalThis.sessionStorage?.getItem(FUSION_RELOAD_MARKER) !== null;
|
|
1658
|
+
} catch {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Live-install staleness probe (2026-10-18): the ONE call the client entry
|
|
1664
|
+
* makes when the page does not carry the fusion flag. A flag-absent page is
|
|
1665
|
+
* either a pre-install page (the client half hot-loaded into HTML served
|
|
1666
|
+
* before the row mounted — the fresh-install case where the Hub row renders
|
|
1667
|
+
* but every switch affordance stays hidden until a manual restart) or a
|
|
1668
|
+
* deliberate flag-off deployment (T-E). The roster's additive `hub.fusion`
|
|
1669
|
+
* answers which; on an armed host the page re-boots ONCE — the marker keeps
|
|
1670
|
+
* a misbehaving host from ever looping the tab. Every failure mode (fetch
|
|
1671
|
+
* error, non-OK, old host without the field) degrades to no action: the
|
|
1672
|
+
* flag-off shapes below are the honest rendering of an unanswered probe.
|
|
1673
|
+
*
|
|
1674
|
+
* Fire-and-forget by contract — never throws, never blocks the apply.
|
|
1675
|
+
*/
|
|
1676
|
+
function probeStaleFusionPage() {
|
|
1677
|
+
(async () => {
|
|
1678
|
+
let hubFusion;
|
|
1679
|
+
try {
|
|
1680
|
+
const response = await fetch("/api/agent-runtime", { credentials: "same-origin" });
|
|
1681
|
+
if (!response.ok) return;
|
|
1682
|
+
hubFusion = (await response.json()).hub?.fusion;
|
|
1683
|
+
} catch {
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (fusionReloadDecision({
|
|
1687
|
+
flagPresent: fusionPage(),
|
|
1688
|
+
markerPresent: staleReloadMarker(),
|
|
1689
|
+
hubFusion
|
|
1690
|
+
}) !== "reload") return;
|
|
1691
|
+
try {
|
|
1692
|
+
globalThis.sessionStorage?.setItem(FUSION_RELOAD_MARKER, "1");
|
|
1693
|
+
} catch {}
|
|
1694
|
+
globalThis.location?.reload();
|
|
1695
|
+
})();
|
|
1696
|
+
}
|
|
1624
1697
|
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
1625
1698
|
function uiState() {
|
|
1626
1699
|
const state = globalThis.__SUPERD_UI_STATE__;
|
|
@@ -3880,6 +3953,38 @@ function HubPage() {
|
|
|
3880
3953
|
});
|
|
3881
3954
|
}
|
|
3882
3955
|
|
|
3956
|
+
//#endregion
|
|
3957
|
+
//#region src/client/pill-geometry.ts
|
|
3958
|
+
/**
|
|
3959
|
+
* Derive the scroll-invariant offsets from one same-frame measurement.
|
|
3960
|
+
* Measuring the DELTAS (not absolute coordinates) is the whole point: they
|
|
3961
|
+
* survive any scroll of any ancestor, so nothing needs to re-run while the
|
|
3962
|
+
* sidebar scrolls.
|
|
3963
|
+
*/
|
|
3964
|
+
function measurePillBox(input) {
|
|
3965
|
+
return {
|
|
3966
|
+
dx: input.row.right - input.wrap.left,
|
|
3967
|
+
cy: (input.row.top + input.row.bottom) / 2 - input.wrap.top
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
3971
|
+
const PILL_ROW_INSET = 8;
|
|
3972
|
+
/**
|
|
3973
|
+
* The pill's placement, wrapper-relative: its right edge sits `inset` px
|
|
3974
|
+
* inside the row's right edge, vertically centered on the row
|
|
3975
|
+
* (`translateY(-50%)`). `selectorWidth` is the render-time pill width (the
|
|
3976
|
+
* mark is present only while an agent is active), so it stays a render
|
|
3977
|
+
* concern; the measured box is not.
|
|
3978
|
+
*/
|
|
3979
|
+
function pillStyle(box, selectorWidth, inset = PILL_ROW_INSET) {
|
|
3980
|
+
return {
|
|
3981
|
+
position: "absolute",
|
|
3982
|
+
left: box.dx - inset - selectorWidth,
|
|
3983
|
+
top: box.cy,
|
|
3984
|
+
transform: "translateY(-50%)"
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3883
3988
|
//#endregion
|
|
3884
3989
|
//#region src/client/HubPanelIcon.tsx
|
|
3885
3990
|
/** Runtime-mark edge inside the selector, and the chevron's own default edge. */
|
|
@@ -3893,8 +3998,6 @@ const CHEVRON_SIZE = 14;
|
|
|
3893
3998
|
const SELECTOR_HEIGHT = 28;
|
|
3894
3999
|
const SELECTOR_PAD = 8;
|
|
3895
4000
|
const SELECTOR_GAP = 4;
|
|
3896
|
-
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
3897
|
-
const ROW_INSET = 8;
|
|
3898
4001
|
function HubPanelIcon({ size }) {
|
|
3899
4002
|
const wide = size <= 16;
|
|
3900
4003
|
const rootRef = react.useRef(null);
|
|
@@ -3917,16 +4020,25 @@ function HubPanelIcon({ size }) {
|
|
|
3917
4020
|
}, [fusion]);
|
|
3918
4021
|
const measure = react.useCallback(() => {
|
|
3919
4022
|
const row = rootRef.current?.closest("button") ?? null;
|
|
3920
|
-
|
|
4023
|
+
const wrap = selectorRef.current?.parentElement ?? null;
|
|
4024
|
+
if (row === null || wrap === null) {
|
|
3921
4025
|
setBox(null);
|
|
3922
4026
|
return;
|
|
3923
4027
|
}
|
|
3924
|
-
const
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4028
|
+
const rowRect = row.getBoundingClientRect();
|
|
4029
|
+
const wrapRect = wrap.getBoundingClientRect();
|
|
4030
|
+
const next = measurePillBox({
|
|
4031
|
+
row: {
|
|
4032
|
+
top: rowRect.top,
|
|
4033
|
+
bottom: rowRect.bottom,
|
|
4034
|
+
right: rowRect.right
|
|
4035
|
+
},
|
|
4036
|
+
wrap: {
|
|
4037
|
+
left: wrapRect.left,
|
|
4038
|
+
top: wrapRect.top
|
|
4039
|
+
}
|
|
3929
4040
|
});
|
|
4041
|
+
setBox((prev) => prev !== null && prev.dx === next.dx && prev.cy === next.cy ? prev : next);
|
|
3930
4042
|
}, []);
|
|
3931
4043
|
react.useLayoutEffect(() => {
|
|
3932
4044
|
if (!wide || !switchUi.available) return;
|
|
@@ -4036,7 +4148,7 @@ function HubPanelIcon({ size }) {
|
|
|
4036
4148
|
children: [
|
|
4037
4149
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HubGlyph, { size }),
|
|
4038
4150
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuperdToastHost, {}),
|
|
4039
|
-
wide &&
|
|
4151
|
+
wide && switchUi.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4040
4152
|
open,
|
|
4041
4153
|
onClose: () => {
|
|
4042
4154
|
setOpen(false);
|
|
@@ -4057,10 +4169,11 @@ function HubPanelIcon({ size }) {
|
|
|
4057
4169
|
"aria-expanded": open,
|
|
4058
4170
|
title: activeLabel === void 0 ? "Agent selector" : `Agent selector: ${activeLabel}`,
|
|
4059
4171
|
style: {
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4172
|
+
...pillStyle(box ?? {
|
|
4173
|
+
dx: 0,
|
|
4174
|
+
cy: 0
|
|
4175
|
+
}, selectorWidth),
|
|
4176
|
+
visibility: box === null ? "hidden" : "visible",
|
|
4064
4177
|
display: "inline-flex",
|
|
4065
4178
|
alignItems: "center",
|
|
4066
4179
|
justifyContent: "center",
|
|
@@ -4105,9 +4218,17 @@ const inject = ["slots"];
|
|
|
4105
4218
|
* absent (a composition without the sidebar renders nothing — every seat
|
|
4106
4219
|
* here is an optional occupant by contract).
|
|
4107
4220
|
*
|
|
4108
|
-
*
|
|
4221
|
+
* Live-install staleness: the fusion flag is baked into the SERVED index, so
|
|
4222
|
+
* a page that booted before super-dsh was installed carries no flag even
|
|
4223
|
+
* though this very apply is the hot-loaded client half — the Hub row would
|
|
4224
|
+
* render with every switch affordance hidden until a manual restart. When
|
|
4225
|
+
* the flag is absent the entry probes the roster's `hub.fusion` once and
|
|
4226
|
+
* re-boots the page on an armed host (switch.ts `probeStaleFusionPage`; the
|
|
4227
|
+
* sessionStorage marker bounds it to one reload per tab session). A flag-off
|
|
4228
|
+
* deployment answers `false` and keeps the T-E inert shape with no reload.
|
|
4109
4229
|
*/
|
|
4110
4230
|
function apply(ctx) {
|
|
4231
|
+
if (!fusionPage()) probeStaleFusionPage();
|
|
4111
4232
|
ctx.inject(["slots"], (raw) => {
|
|
4112
4233
|
const scope = raw;
|
|
4113
4234
|
scope.effect(() => scope.slots.inject("main", () => scope.slots.register({
|