super-dsh 0.1.3 → 0.1.5
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/README.md +3 -3
- package/agent-agy/cordis.patch.yml +8 -4
- package/agent-agy/dist/agent-preset-agy.d.ts +23 -43
- package/agent-agy/dist/agent-preset-agy.js +29 -166
- package/agent-agy/dist/agent.d.ts +2 -1
- package/agent-agy/dist/agent.js +3 -2
- package/agent-agy/dist/agy-sessions.d.ts +2 -1
- package/agent-agy/dist/agy-sessions.js +2 -11
- package/agent-agy/dist/index.js +13 -12
- package/agent-agy/dist/world-plugin.d.ts +2 -1
- package/agent-agy/dist/world-plugin.js +31 -51
- package/agent-agy/package.json +6 -6
- package/agent-claude/cordis.patch.yml +8 -4
- package/agent-claude/dist/agent-preset-claude.js +29 -166
- package/agent-claude/dist/agent.js +10 -10
- package/agent-claude/dist/claude-client.js +22 -3
- package/agent-claude/dist/claude-home.js +0 -8
- package/agent-claude/dist/index.js +12 -15
- package/agent-claude/dist/session-map.js +2 -5
- package/agent-claude/dist/world-plugin.js +3 -3
- package/agent-claude/package.json +9 -9
- package/agent-codex/README.md +8 -8
- package/agent-codex/cordis.patch.yml +9 -5
- package/agent-codex/dist/agent-preset-codex.js +29 -168
- package/agent-codex/dist/agent.js +17 -12
- package/agent-codex/dist/codex-store.js +0 -9
- package/agent-codex/dist/index.js +13 -16
- package/agent-codex/dist/session-map.js +2 -5
- package/agent-codex/dist/world-plugin.js +31 -51
- package/agent-codex/package.json +14 -11
- package/agent-hermes/README.md +9 -8
- package/agent-hermes/cordis.patch.yml +8 -4
- package/agent-hermes/dist/agent-preset-hermes.js +29 -168
- package/agent-hermes/dist/agent.js +33 -19
- package/agent-hermes/dist/hermes-client.js +5 -5
- package/agent-hermes/dist/hermes-store.js +2 -11
- package/agent-hermes/dist/index.js +13 -12
- package/agent-hermes/dist/world-plugin.js +31 -52
- package/agent-hermes/package.json +14 -11
- package/agent-omp/README.md +2 -2
- package/agent-omp/cordis.patch.yml +12 -7
- package/agent-omp/dist/agent-preset-omp.js +30 -169
- package/agent-omp/dist/agent.js +13 -5
- package/agent-omp/dist/index.js +12 -22
- package/agent-omp/dist/mobile/gesture.js +2 -2
- package/agent-omp/dist/mobile/zoom-guard.js +4 -4
- package/agent-omp/dist/mobile-boot.js +2 -2
- package/agent-omp/dist/omp-cli.js +1 -1
- package/agent-omp/dist/omp-disk-discovery.js +1 -1
- package/agent-omp/dist/protocol.js +1 -1
- package/agent-omp/dist/sidecar-client.js +1 -1
- package/agent-omp/dist/world-plugin.js +37 -50
- package/agent-omp/package.json +18 -14
- package/agent-omp/sidecar/main.ts +2 -2
- package/agent-pi/README.md +5 -5
- package/agent-pi/cordis.patch.yml +8 -4
- package/agent-pi/dist/agent-preset-pi.js +29 -166
- package/agent-pi/dist/agent.js +18 -12
- package/agent-pi/dist/index.js +14 -16
- package/agent-pi/dist/pi-home.js +2 -12
- package/agent-pi/dist/world-plugin.js +31 -51
- package/agent-pi/package.json +14 -11
- package/client/index.js +1994 -150
- package/cordis.patch.yml +38 -15
- package/package.json +32 -30
- package/{agent-hub → superd-hub}/cordis.patch.yml +3 -3
- package/{agent-hub → superd-hub}/dist/agent-roster.d.ts +3 -1
- package/{agent-hub → superd-hub}/dist/agent-roster.js +4 -2
- package/superd-hub/dist/carrier.d.ts +62 -0
- package/superd-hub/dist/carrier.js +213 -0
- package/superd-hub/dist/fusion-transport.d.ts +148 -0
- package/superd-hub/dist/fusion-transport.js +397 -0
- package/{agent-hub → superd-hub}/dist/gateway.d.ts +9 -4
- package/superd-hub/dist/gateway.js +44 -0
- package/superd-hub/dist/hub-route.d.ts +74 -0
- package/superd-hub/dist/hub-route.js +188 -0
- package/{agent-hub → superd-hub}/dist/index.d.ts +14 -6
- package/{agent-hub → superd-hub}/dist/index.js +14 -6
- package/{agent-hub → superd-hub}/dist/roster.d.ts +2 -2
- package/{agent-hub → superd-hub}/dist/rpc.js +1 -1
- package/{agent-hub → superd-hub}/dist/spawn-world.d.ts +27 -6
- package/superd-hub/dist/spawn-world.js +279 -0
- package/superd-hub/dist/switch-core.d.ts +630 -0
- package/superd-hub/dist/switch-core.js +734 -0
- package/superd-hub/dist/system-head.d.ts +69 -0
- package/superd-hub/dist/system-head.js +194 -0
- package/superd-hub/dist/web-trust.d.ts +112 -0
- package/superd-hub/dist/web-trust.js +126 -0
- package/superd-hub/dist/world-entry.d.ts +38 -0
- package/superd-hub/dist/world-entry.js +47 -0
- package/{agent-hub → superd-hub}/dist/world-host.d.ts +15 -20
- package/superd-hub/dist/world-host.js +65 -0
- package/{agent-hub → superd-hub}/dist/world-join.d.ts +2 -1
- package/{agent-hub → superd-hub}/dist/world-join.js +30 -50
- package/superd-hub/dist/world-mount.d.ts +82 -0
- package/superd-hub/dist/world-mount.js +105 -0
- package/{agent-hub → superd-hub}/dist/world-mux.d.ts +21 -6
- package/superd-hub/dist/world-mux.js +392 -0
- package/{agent-hub → superd-hub}/dist/world-provision.d.ts +20 -4
- package/superd-hub/dist/world-provision.js +203 -0
- package/superd-hub/dist/world-web-server.d.ts +113 -0
- package/superd-hub/dist/world-web-server.js +99 -0
- package/superd-hub/lib/client/index.js +2086 -0
- package/{agent-hub → superd-hub}/package.json +12 -7
- package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
- package/agent-agy/dist/agent-preset-projection.js +0 -33
- package/agent-claude/dist/agent-preset-projection.js +0 -30
- package/agent-codex/dist/agent-preset-projection.js +0 -37
- package/agent-codex/dist/app-home.js +0 -65
- package/agent-codex/dist/spike.js +0 -46
- package/agent-hermes/dist/agent-preset-projection.js +0 -34
- package/agent-hub/dist/carrier.d.ts +0 -43
- package/agent-hub/dist/carrier.js +0 -241
- package/agent-hub/dist/client-shim.d.ts +0 -28
- package/agent-hub/dist/client-shim.js +0 -251
- package/agent-hub/dist/gateway.js +0 -18
- package/agent-hub/dist/index-pass.d.ts +0 -22
- package/agent-hub/dist/index-pass.js +0 -47
- package/agent-hub/dist/labels.d.ts +0 -45
- package/agent-hub/dist/labels.js +0 -83
- package/agent-hub/dist/spawn-world.js +0 -72
- package/agent-hub/dist/world-entry.d.ts +0 -31
- package/agent-hub/dist/world-entry.js +0 -51
- package/agent-hub/dist/world-host.js +0 -44
- package/agent-hub/dist/world-mount.d.ts +0 -52
- package/agent-hub/dist/world-mount.js +0 -52
- package/agent-hub/dist/world-mux.js +0 -224
- package/agent-hub/dist/world-provision.js +0 -122
- package/agent-hub/dist/world-web-server.d.ts +0 -162
- package/agent-hub/dist/world-web-server.js +0 -248
- package/agent-hub/lib/client/index.js +0 -242
- package/agent-omp/dist/agent-preset-projection.js +0 -36
- package/agent-omp/dist/app-home.js +0 -86
- package/agent-pi/dist/agent-preset-projection.js +0 -29
- /package/{agent-hub → superd-hub}/dist/envelope.d.ts +0 -0
- /package/{agent-hub → superd-hub}/dist/envelope.js +0 -0
- /package/{agent-hub → superd-hub}/dist/ownership.d.ts +0 -0
- /package/{agent-hub → superd-hub}/dist/ownership.js +0 -0
- /package/{agent-hub → superd-hub}/dist/roster.js +0 -0
- /package/{agent-hub → superd-hub}/dist/rpc.d.ts +0 -0
- /package/{agent-hub → superd-hub}/dist/targets.d.ts +0 -0
- /package/{agent-hub → superd-hub}/dist/targets.js +0 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch orchestration core (native UI fusion, plan Task 4) — the pure logic
|
|
3
|
+
* behind the in-page agent switch: storage-namespace key computation, the
|
|
4
|
+
* session landing decision over the persisted `dsh.sessions.current`
|
|
5
|
+
* selection, the agent-selector click-interception decision (a fusion page
|
|
6
|
+
* never navigates on a left click — spec: 融合页 = state 切换,拦截 click,无导航;
|
|
7
|
+
* T-B: consumed by the Hub UI surfaces, the sole switch surface since the
|
|
8
|
+
* RuntimeSeat retirement), and the fallback state machine (timeout or
|
|
9
|
+
* roster-probe failure → revert to native + a visible toast, never a page
|
|
10
|
+
* lock).
|
|
11
|
+
*
|
|
12
|
+
* Host-half on purpose: plain TS with every effect injected through
|
|
13
|
+
* {@link SwitchHost} — no DOM, no client imports — so `test/client-switch.test.mjs`
|
|
14
|
+
* imports it from `dist/` like every other host module. The client half
|
|
15
|
+
* (`src/client/switch.ts`) builds the real host face from the client context:
|
|
16
|
+
* the `__SUPERD_UI_STATE__` atom (Task 3), `ctx.connection`,
|
|
17
|
+
* `ctx.uiWorkspace`, the Task-3 namespaced `Storage`, the control-plane
|
|
18
|
+
* roster fetch, and a minimal self-owned toast overlay.
|
|
19
|
+
*
|
|
20
|
+
* Orchestration sequence (spec §3.5 as amended 2026-09-26, T-A + T-F
|
|
21
|
+
* errata; user rulings 7/8 and the T-F ruling "按原生的语义去理解…把这个
|
|
22
|
+
* last visit session 也做一下"):
|
|
23
|
+
* 1. `setAgent(label)` — state + NS swap immediately; subsequent requests
|
|
24
|
+
* demux to the target world (Task 3's call-time patches read the atom).
|
|
25
|
+
* 2. `reconnect()` — the generation re-opens against the retargeted data
|
|
26
|
+
* plane; the gateway emits `connection/reset` on establishment, the
|
|
27
|
+
* designed "wire-derived caches must repull" signal, so sessions /
|
|
28
|
+
* config / theme re-derive on their own. Nothing navigates before the
|
|
29
|
+
* repull: the chat area is never cleared and no loading skeleton is
|
|
30
|
+
* inserted — old content stays in place awaiting the landing.
|
|
31
|
+
* 3. Session landing (T-F: NATIVE restore semantics) — upstream's
|
|
32
|
+
* fresh-load restore is `watchNavigation`/`restoreSelection`
|
|
33
|
+
* (navigation.ts:284-347): it fires only when BOTH `workspaces.list`
|
|
34
|
+
* and `sessions.list` reached phase `'ready'`, and then walks
|
|
35
|
+
* `saved.subagentAddress` → `saved.sessionId` looked up in the FRESH
|
|
36
|
+
* `sessions.byId` (stale-proof) → recent-workspace → the fresh-boot
|
|
37
|
+
* default workspace. The landing replicates exactly that: it fires once
|
|
38
|
+
* when BOTH wire-derived caches have installed fresh post-establishment
|
|
39
|
+
* baselines ({@link observeCachesRefreshed} — the T-A gate watched
|
|
40
|
+
* `workspaces.list` alone, so a plane whose sessions list landed later
|
|
41
|
+
* served stale rows to the decision and broke native's own
|
|
42
|
+
* last-visited restore; T-G additionally holds the gate while a
|
|
43
|
+
* persisted pointer has not yet joined the freshly minted INCREMENTAL
|
|
44
|
+
* sessions list — membership via `byId` — firing on membership or on
|
|
45
|
+
* the quiet-window ladder, {@link LANDING_QUIET_MS}, so partial-list
|
|
46
|
+
* first edges stop laddering every switch into a brand-new session),
|
|
47
|
+
* then reads the target NS's persisted
|
|
48
|
+
* `dsh.sessions.current` (the NS swap has long since happened) and
|
|
49
|
+
* executes {@link planRestoreLanding}'s precedence through the public
|
|
50
|
+
* faces. The landing is BOUNDED: if the baselines never install within
|
|
51
|
+
* one further fallback window after the settle, a staleness watchdog
|
|
52
|
+
* disarms it — an armed landing never outlives its switch by more than
|
|
53
|
+
* that window (the safe direction: no landing at all beats a late
|
|
54
|
+
* surprise navigation).
|
|
55
|
+
* 4. Fallback: no generation within {@link SWITCH_FALLBACK_MS}, or a failed
|
|
56
|
+
* roster probe → `setAgent('native')` + `reconnect()` back to the native
|
|
57
|
+
* plane + toast. A revert also cancels a pending landing — a
|
|
58
|
+
* reverted switch never lands.
|
|
59
|
+
*
|
|
60
|
+
* Upstream anchors (dsh-v0.1.7-rc.1):
|
|
61
|
+
* - `ctx.connection.reconnect()` — `packages/client/connection/src/client/index.ts`
|
|
62
|
+
* `ConnectionHandle.reconnect` (declared :148, wired :264-266); the
|
|
63
|
+
* controller resets retry progression and replaces the current attempt
|
|
64
|
+
* immediately (`connection.ts:113-121`).
|
|
65
|
+
* - `connection/reset` — emitted by the gateway client on every generation
|
|
66
|
+
* establishment (`packages/api/gateway/src/client/index.ts:169`),
|
|
67
|
+
* declared as the repull signal
|
|
68
|
+
* (`packages/client/connection/src/client/index.ts:9-17`).
|
|
69
|
+
* - `uiWorkspace.openSession(target)` / `startSession(workspaceId?)` /
|
|
70
|
+
* `openWorkspace(workspaceId)` — the navigation face
|
|
71
|
+
* (`packages/client/ui-workspace/src/client/navigation.ts:34-92`);
|
|
72
|
+
* `SessionTarget = SessionId | SubagentAddress`
|
|
73
|
+
* (`packages/api/session-controller/src/client/contract/sessions.ts:22`).
|
|
74
|
+
* - `watchNavigation` / `restoreSelection` — the fresh-load restore this
|
|
75
|
+
* landing replicates (`navigation.ts:284-347`): both-lists ready gate
|
|
76
|
+
* (:292), `subagentAddress` first (:320-323), the fresh-`byId` session
|
|
77
|
+
* lookup with the blank-reuse nuance (:324-335), `recentWorkspace`
|
|
78
|
+
* fallback (:337, the tie-break at :434-453), and
|
|
79
|
+
* `initializeDefaultWorkspace` on a fresh boot (:338-342, :349-361).
|
|
80
|
+
* - `startSession()`'s workspace resolution — `workspaceId ??
|
|
81
|
+
* currentWorkspaceId ?? recent` over the wire-derived `workspaces.list`
|
|
82
|
+
* / `sessions.list` snapshots (`navigation.ts:223-238`); `connectWorkspace`
|
|
83
|
+
* → `reuseOrCreateBlank` reuses the pointer's own blank whenever the
|
|
84
|
+
* private restore path would (:179-198).
|
|
85
|
+
* - The wire-derived stores: `workspaces.list` installs fresh data only
|
|
86
|
+
* through its follow-stream baseline (`packages/api/workspace-controller/src/client/model.ts`
|
|
87
|
+
* `replaceBaseline` → `installViews`, a new items array every install);
|
|
88
|
+
* `sessions.list` is projected with a fresh `ids` array on every manager
|
|
89
|
+
* change (`packages/api/session-controller/src/client/sessions/service.ts:607-677`)
|
|
90
|
+
* — array identity is the freshness token for both.
|
|
91
|
+
* - `dsh.sessions.current` — the per-tab last-selection store
|
|
92
|
+
* (`navigation.ts:130-131`); its restore reads `subagentAddress` first,
|
|
93
|
+
* then `sessionId` (`navigation.ts:319-330`) — {@link parseLandingTarget}
|
|
94
|
+
* keeps that precedence. The store hydrates once at page boot and
|
|
95
|
+
* persists on write (`packages/client/store/src/index.ts:155-174`), so
|
|
96
|
+
* the landing read must go through the namespaced `Storage` (Task 3
|
|
97
|
+
* patch 5), which prefixes `'<label>:'` at call time.
|
|
98
|
+
*
|
|
99
|
+
* @module @pgmi-builds/superd-hub/switch-core
|
|
100
|
+
*/
|
|
101
|
+
/** The active-agent label meaning the native (ctx0) data plane. */
|
|
102
|
+
export declare const NATIVE_AGENT = "native";
|
|
103
|
+
/** The persisted last-selection key ui-workspace owns (survey §2.6). */
|
|
104
|
+
export declare const SESSIONS_CURRENT_KEY = "dsh.sessions.current";
|
|
105
|
+
/**
|
|
106
|
+
* The fallback window: how long a switch may wait for the target world's
|
|
107
|
+
* generation (mux re-open + repull) before reverting to native. A
|
|
108
|
+
* human-scale constant; Task 5's config face may expose it later.
|
|
109
|
+
*/
|
|
110
|
+
export declare const SWITCH_FALLBACK_MS = 10000;
|
|
111
|
+
/**
|
|
112
|
+
* The membership-wait quiet window (T-G): after both navigation caches are
|
|
113
|
+
* fresh, a persisted pointer the sessions list has not shown yet holds the
|
|
114
|
+
* landing gate — the list is minted INCREMENTALLY post-reconnect (upstream
|
|
115
|
+
* `handleSessionAdded` applies one session at a time, each application
|
|
116
|
+
* re-projecting the store with a fresh `ids` array, service.ts:677), so the
|
|
117
|
+
* first fresh edge sees a partial `byId` that legitimately lacks the
|
|
118
|
+
* pointer's session. The gate fires on membership; while the pointer is
|
|
119
|
+
* still absent, this window of list SILENCE (no `ids` identity change) is
|
|
120
|
+
* the concession point: the gate fires anyway and the plan takes the ladder
|
|
121
|
+
* — an honest miss, because a stored blank pointer legitimately never
|
|
122
|
+
* appears (matching upstream's own boot restore of a stale/absent id). An
|
|
123
|
+
* identity change during the window restarts it (the list is still growing).
|
|
124
|
+
* The machine's post-settle staleness watchdog remains the hard bound on
|
|
125
|
+
* the whole wait.
|
|
126
|
+
*/
|
|
127
|
+
export declare const LANDING_QUIET_MS = 600;
|
|
128
|
+
/**
|
|
129
|
+
* The storage namespace for an active agent: native prefixes nothing
|
|
130
|
+
* (byte-identical to stock behavior), a foreign agent owns `'<label>:'`.
|
|
131
|
+
* Mirrors Task 3's storage patch (`ns()` in fusion-transport).
|
|
132
|
+
*/
|
|
133
|
+
export declare function storageNamespace(label: string): string;
|
|
134
|
+
/** The full storage key for `key` while `label` is the active agent. */
|
|
135
|
+
export declare function namespacedKey(label: string, key: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* The persisted-selection parse feeding {@link planRestoreLanding}.
|
|
138
|
+
*
|
|
139
|
+
* A value is navigable when it parses to a ui-workspace `MainSelection`
|
|
140
|
+
* carrying a usable `SessionTarget`: a non-null `subagentAddress` object
|
|
141
|
+
* wins (upstream restore order), else a non-empty string `sessionId`.
|
|
142
|
+
* Everything else — no value, a read failure (surfaced here as `null`), a
|
|
143
|
+
* parse failure, an unusable shape — parses to `null` and the plan falls to
|
|
144
|
+
* the recent-workspace / default-workspace ladder.
|
|
145
|
+
*
|
|
146
|
+
* @returns the subagent address object, the session id string, or `null`.
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseLandingTarget(raw: string | null | undefined): string | object | null;
|
|
149
|
+
/** Structural slice of one `workspaces.list` row the landing plan reads. */
|
|
150
|
+
export interface RestoreWorkspaceView {
|
|
151
|
+
readonly workspaceId: string;
|
|
152
|
+
readonly sessionIds: readonly string[];
|
|
153
|
+
readonly createdAt?: string;
|
|
154
|
+
}
|
|
155
|
+
/** Structural slice of one `sessions.list` `byId` row the landing plan reads. */
|
|
156
|
+
export interface RestoreSessionSummary {
|
|
157
|
+
readonly id: string;
|
|
158
|
+
readonly blank?: boolean;
|
|
159
|
+
readonly updatedAt?: number;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The FRESH wire-derived navigation caches the landing decides over —
|
|
163
|
+
* upstream `restoreSelection` receives exactly these two snapshots once both
|
|
164
|
+
* phases are ready (navigation.ts:290-292, :318). The structural slices keep
|
|
165
|
+
* this module host-half: the client binding projects the real
|
|
166
|
+
* `ctx.workspaces.list` / `ctx.sessions.list` snapshots into it.
|
|
167
|
+
*/
|
|
168
|
+
export interface RestoreCaches {
|
|
169
|
+
readonly workspaces: {
|
|
170
|
+
readonly items: readonly RestoreWorkspaceView[];
|
|
171
|
+
readonly phase: unknown;
|
|
172
|
+
};
|
|
173
|
+
readonly sessions: {
|
|
174
|
+
readonly ids: readonly string[];
|
|
175
|
+
readonly byId: Readonly<Record<string, RestoreSessionSummary>>;
|
|
176
|
+
readonly phase: unknown;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** One landing action, in upstream `restoreSelection`'s order of outcomes. */
|
|
180
|
+
export type RestoreLandingPlan = {
|
|
181
|
+
kind: 'open-session';
|
|
182
|
+
target: string | object;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The new-session flow through the public `uiWorkspace.startSession` face:
|
|
186
|
+
* with a workspace id it connects THAT workspace (its `reuseOrCreateBlank`
|
|
187
|
+
* reuses the pointer's own blank exactly when upstream's private
|
|
188
|
+
* `reuseBlank` pre-check would pass); `undefined` inherits the
|
|
189
|
+
* current-then-recent resolution.
|
|
190
|
+
*/
|
|
191
|
+
| {
|
|
192
|
+
kind: 'start-session';
|
|
193
|
+
workspaceId?: string;
|
|
194
|
+
}
|
|
195
|
+
/** Upstream's `initializeDefaultWorkspace`: the fresh-boot empty-plane case. */
|
|
196
|
+
| {
|
|
197
|
+
kind: 'default-workspace';
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* The landing decision with NATIVE restore semantics (T-F): replicate
|
|
201
|
+
* upstream `restoreSelection` (navigation.ts:318-347) against the fresh
|
|
202
|
+
* caches and the parsed persisted selection ({@link parseLandingTarget}'s
|
|
203
|
+
* result, which already applies the `subagentAddress`-first precedence).
|
|
204
|
+
*
|
|
205
|
+
* - `subagentAddress` → open it directly, no cache consultation
|
|
206
|
+
* (navigation.ts:320-323).
|
|
207
|
+
* - `sessionId` found in the FRESH `byId`: non-blank — or blank but
|
|
208
|
+
* workspace-less — opens the id (navigation.ts:324-329); blank with a
|
|
209
|
+
* workspace resolves to that workspace (navigation.ts:333-335's private
|
|
210
|
+
* `reuseBlank` + :337's `workspace.workspaceId` target — the public
|
|
211
|
+
* `startSession(workspaceId)` face covers both: its `connectWorkspace`
|
|
212
|
+
* → `reuseOrCreateBlank` reuses the pointer's own blank whenever the
|
|
213
|
+
* private pre-check would pass, because the eligibility scan is the
|
|
214
|
+
* same).
|
|
215
|
+
* - A stale id (absent from the fresh `byId`) falls through to the
|
|
216
|
+
* no-pointer ladder exactly as upstream's `summary === undefined` does.
|
|
217
|
+
* - No pointer / stale pointer: the recent workspace gets the new-session
|
|
218
|
+
* flow (navigation.ts:337); both lists empty is the fresh boot and
|
|
219
|
+
* mirrors `initializeDefaultWorkspace` (navigation.ts:338-342).
|
|
220
|
+
* - `caches` unavailable (the host's read failed) → `undefined`: land
|
|
221
|
+
* nothing — no landing beats a wrong one. Same for the degenerate
|
|
222
|
+
* zero-workspaces-with-sessions shape, where upstream simply leaves the
|
|
223
|
+
* view as it is (no target, both-empty guard false, :337-342).
|
|
224
|
+
*
|
|
225
|
+
* @param parsed - the persisted-selection parse ({@link parseLandingTarget}).
|
|
226
|
+
* @param caches - the fresh both-lists snapshots, or `undefined` unreadable.
|
|
227
|
+
* @returns the action to execute, or `undefined` to land nothing.
|
|
228
|
+
*/
|
|
229
|
+
export declare function planRestoreLanding(parsed: string | object | null, caches: RestoreCaches | undefined): RestoreLandingPlan | undefined;
|
|
230
|
+
/** The structural face of one wire-derived list store (workspaces or sessions). */
|
|
231
|
+
export interface NavigationCacheFace {
|
|
232
|
+
/**
|
|
233
|
+
* Read the current snapshot: the `items` ARRAY IDENTITY is the freshness
|
|
234
|
+
* token. For `workspaces.list` this is the snapshot's items array; for
|
|
235
|
+
* `sessions.list` the client binding adapts the projection's `ids` array
|
|
236
|
+
* (a fresh array is minted on every projection — service.ts:607-677).
|
|
237
|
+
*/
|
|
238
|
+
getSnapshot(): {
|
|
239
|
+
items: readonly unknown[];
|
|
240
|
+
phase: unknown;
|
|
241
|
+
};
|
|
242
|
+
/** Subscribe to snapshot invalidation; the returned disposer stops it. */
|
|
243
|
+
subscribe(listener: () => void): () => void;
|
|
244
|
+
/**
|
|
245
|
+
* T-G membership face: does the store's CURRENT snapshot contain `id` as
|
|
246
|
+
* a listed session? The client binding answers from the sessions
|
|
247
|
+
* projection's `byId` (the same face upstream's boot restore resolves the
|
|
248
|
+
* pointer against, navigation.ts:324) — NOT from `ids`, and deliberately
|
|
249
|
+
* not from a stale copy: `byId` can gain a row without a new `ids`
|
|
250
|
+
* identity (publishRetention re-sets `byId` alone, service.ts:565), so
|
|
251
|
+
* the face must read the live snapshot at call time. Optional; without
|
|
252
|
+
* it the gate can never observe membership and the quiet window becomes
|
|
253
|
+
* the pointer's only concession point (the watchdog still bounds it).
|
|
254
|
+
*/
|
|
255
|
+
contains?(id: string): boolean;
|
|
256
|
+
}
|
|
257
|
+
/** The structural face of the connection generation observable. */
|
|
258
|
+
export interface GenerationFace {
|
|
259
|
+
/** The live generation, or `undefined` while no attempt has succeeded. */
|
|
260
|
+
getSnapshot(): unknown;
|
|
261
|
+
/** Subscribe to generation changes; the returned disposer stops it. */
|
|
262
|
+
subscribe(listener: () => void): () => void;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* What the membership-aware gate needs beyond the two lists (T-G), all
|
|
266
|
+
* injected so this module stays host-half pure. Every field is optional:
|
|
267
|
+
* an absent/empty options object restores the T-F gate verbatim (fire on
|
|
268
|
+
* the first fresh edge of both lists — the no-pointer fresh-boot path).
|
|
269
|
+
*/
|
|
270
|
+
export interface LandingGateOptions {
|
|
271
|
+
/**
|
|
272
|
+
* Read the landing pointer's plain session id at DECISION time — on every
|
|
273
|
+
* gate check, not once at arming. `undefined` means no navigable pointer
|
|
274
|
+
* OR a subagent-address pointer (which opens directly, upstream
|
|
275
|
+
* navigation.ts:320-323, and needs no cache consultation): either way the
|
|
276
|
+
* gate falls back to the T-F first-fresh-edge behavior. A throwing read
|
|
277
|
+
* is an honest `undefined` (the machine's own landing read treats a
|
|
278
|
+
* storage failure as `null` → the same ladder).
|
|
279
|
+
*/
|
|
280
|
+
pointerSessionId?(): string | undefined;
|
|
281
|
+
/** The quiet window in ms; defaults to {@link LANDING_QUIET_MS}. */
|
|
282
|
+
quietMs?: number;
|
|
283
|
+
/** Timer injection for the quiet window (the SwitchHost faces in practice). */
|
|
284
|
+
setTimeout?(fn: () => void, ms: number): unknown;
|
|
285
|
+
clearTimeout?(handle: unknown): void;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The caches-fresh gate behind the deferred landing (T-F; T-G made it
|
|
289
|
+
* pointer-membership-aware): fires `listener` AT MOST once, when BOTH
|
|
290
|
+
* navigation caches demonstrably hold the TARGET plane's data — the
|
|
291
|
+
* client-face equivalent of upstream's both-lists ready gate
|
|
292
|
+
* (`watchNavigation`, navigation.ts:292: `workspace.phase !== 'ready'
|
|
293
|
+
* || sessions.phase !== 'ready'` → return).
|
|
294
|
+
*
|
|
295
|
+
* The freshness token is each snapshot's `items` ARRAY IDENTITY: upstream's
|
|
296
|
+
* workspace model mints a new array on every baseline install
|
|
297
|
+
* (`installViews` → `[...installed.values()]`, model.ts:398) and the session
|
|
298
|
+
* list projection mints a fresh `ids` array on every manager change
|
|
299
|
+
* (service.ts:607-677), while carrier churn keeps references. The gate arms
|
|
300
|
+
* at the first generation establishment OBSERVED BY ITS OWN subscription —
|
|
301
|
+
* the anchors are retaken there, and nothing before that point can pass, so
|
|
302
|
+
* the dying old plane's last increments racing the reconnect are absorbed;
|
|
303
|
+
* the first replacements after establishment are the target plane's
|
|
304
|
+
* baselines. The `phase === 'ready'` checks ride along as a guard against a
|
|
305
|
+
* page whose first baseline never arrived. Both edges are tracked
|
|
306
|
+
* independently, in either arrival order (the T-A gap: watching workspaces
|
|
307
|
+
* alone let a late sessions baseline leave the pointer lookup reading stale
|
|
308
|
+
* `byId` rows).
|
|
309
|
+
*
|
|
310
|
+
* T-G — the pointer holds the gate. The sessions list is minted
|
|
311
|
+
* INCREMENTALLY after the reconnect (one `handleSessionAdded` per session,
|
|
312
|
+
* each re-projecting the store with a fresh `ids` array), so the first fresh
|
|
313
|
+
* edge sees a partial `byId` that legitimately lacks the pointer's session;
|
|
314
|
+
* consuming the landing there laddered every switch into a brand-new
|
|
315
|
+
* session (the browser-proven defect). When a pointer exists (the injected
|
|
316
|
+
* {@link LandingGateOptions.pointerSessionId} face, read at decision time),
|
|
317
|
+
* the gate therefore does NOT fire on the first fresh edge. It fires when:
|
|
318
|
+
* - the sessions list CONTAINS the pointer's session — the membership
|
|
319
|
+
* face (`byId`, consulted on EVERY sessions notification: `byId` can
|
|
320
|
+
* gain a row without a new `ids` identity, service.ts:565), or
|
|
321
|
+
* - the sessions list has gone QUIET — no `ids` identity change for the
|
|
322
|
+
* configured quiet window ({@link LANDING_QUIET_MS}; an identity change
|
|
323
|
+
* during the window restarts it) while the pointer is still absent:
|
|
324
|
+
* the honest miss, firing with the ladder exactly like upstream's boot
|
|
325
|
+
* restore of a stored id its fresh list does not know.
|
|
326
|
+
* No pointer → the first fresh edge fires, byte-identical to T-F (the
|
|
327
|
+
* fresh-boot path). The machine's post-settle staleness watchdog bounds the
|
|
328
|
+
* whole membership wait: it disposes this gate, which also cancels a
|
|
329
|
+
* running quiet window.
|
|
330
|
+
*
|
|
331
|
+
* Establishment strictly precedes any target-plane baseline (the follow
|
|
332
|
+
* streams re-open on top of the established generation), so a fire implies
|
|
333
|
+
* both "generation established" and "repull landed" — the two preconditions
|
|
334
|
+
* of a correct landing resolution.
|
|
335
|
+
*
|
|
336
|
+
* Host-half pure logic: the client binding composes it with the real
|
|
337
|
+
* `ctx.workspaces.list` + `ctx.sessions.list` stores and the connection
|
|
338
|
+
* generation observable; the machine consumes it through
|
|
339
|
+
* {@link SwitchHost.onCachesRefreshed}.
|
|
340
|
+
*
|
|
341
|
+
* @returns the disposer stopping all three subscriptions (and the quiet window).
|
|
342
|
+
*/
|
|
343
|
+
export declare function observeCachesRefreshed(workspaces: NavigationCacheFace, sessions: NavigationCacheFace, generation: GenerationFace, listener: () => void, gate?: LandingGateOptions): () => void;
|
|
344
|
+
/** The decision one roster-entry left click produces in the agent selector. */
|
|
345
|
+
export interface SeatClickDecision {
|
|
346
|
+
/**
|
|
347
|
+
* `true` = the anchor's default action must be prevented: a fusion page
|
|
348
|
+
* never navigates on a left click (the href survives only for middle-click
|
|
349
|
+
* / open-in-new-tab / copy link). `false` = the world-page arm — the URL is
|
|
350
|
+
* the state and the browser owns navigation, byte-identical to stock.
|
|
351
|
+
*/
|
|
352
|
+
preventDefault: boolean;
|
|
353
|
+
/** Whether the switch orchestration was invoked for the clicked entry. */
|
|
354
|
+
attempted: boolean;
|
|
355
|
+
}
|
|
356
|
+
/** The inputs of one seat click: the page mode, the entry, the attempt. */
|
|
357
|
+
export interface SeatClickInput {
|
|
358
|
+
/** The page carries the baked `__SUPERD_UI_FUSION__` flag global. */
|
|
359
|
+
fusion: boolean;
|
|
360
|
+
/** The clicked roster entry is the currently active agent. */
|
|
361
|
+
active: boolean;
|
|
362
|
+
/**
|
|
363
|
+
* The switch attempt (`attemptSwitch`). Invoked exactly when
|
|
364
|
+
* `fusion && !active`; its return value is recorded but can never re-open
|
|
365
|
+
* navigation — an unbound orchestration swallows the click instead of
|
|
366
|
+
* abandoning the fused page (review round 1).
|
|
367
|
+
*/
|
|
368
|
+
attempt: () => boolean;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* The agent-selector click-interception contract as a pure table (native UI
|
|
372
|
+
* fusion; originally landed for the sidebar-bottom RuntimeSeat, which T-B
|
|
373
|
+
* retired — the Hub UI surfaces (HubPage rows + the HubPanelIcon chevron
|
|
374
|
+
* menu) are now the sole switch surface and consume this SAME table, so the
|
|
375
|
+
* contract below is inherited verbatim).
|
|
376
|
+
*
|
|
377
|
+
* - World page (`fusion: false`): no interception, no orchestration — the
|
|
378
|
+
* real hyperlink navigates (spec §5 双半边).
|
|
379
|
+
* - Fusion page, active entry: intercepted, no attempt — switching to the
|
|
380
|
+
* active agent is a no-op, and the click must never fall through to the
|
|
381
|
+
* anchor (an `href="/"` reload would discard the in-page switch state; a
|
|
382
|
+
* foreign `href` would abandon the fused page entirely).
|
|
383
|
+
* - Fusion page, inactive entry: intercepted and the attempt runs. The
|
|
384
|
+
* attempt's `false` (orchestration unbound) still keeps the page: a
|
|
385
|
+
* swallowed click beats silent navigation away from the fusion surface.
|
|
386
|
+
*
|
|
387
|
+
* Host-half pure logic so `test/client-switch.test.mjs` pins the table from
|
|
388
|
+
* `dist/`; the Hub surfaces map `preventDefault` onto the DOM event.
|
|
389
|
+
*/
|
|
390
|
+
export declare function seatClickDecision(input: SeatClickInput): SeatClickDecision;
|
|
391
|
+
/** The inputs of one Hub UI roster row's active indication. */
|
|
392
|
+
export interface HubRowActiveInput {
|
|
393
|
+
/** The page carries the baked `__SUPERD_UI_FUSION__` flag global. */
|
|
394
|
+
fusion: boolean;
|
|
395
|
+
/** The row's roster key (`'native'` or a runtime key). */
|
|
396
|
+
agentKey: string;
|
|
397
|
+
/** The row's mount path. */
|
|
398
|
+
agentPath: string;
|
|
399
|
+
/** The page atom's active agent (fusion pages; absent otherwise). */
|
|
400
|
+
activeAgent?: string;
|
|
401
|
+
/** The page's normalized mount path (world pages). */
|
|
402
|
+
mountPath: string;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Which Hub UI roster row shows as the active agent (T-B: the Hub surfaces —
|
|
406
|
+
* HubPage rows and the HubPanelIcon chevron menu — are the sole switch
|
|
407
|
+
* surface after the RuntimeSeat retirement).
|
|
408
|
+
*
|
|
409
|
+
* - Fusion page: the page atom's `activeAgent` — the switch state — decides
|
|
410
|
+
* by roster key; the URL is irrelevant on a fused page. The `native`
|
|
411
|
+
* entry participates like any other key.
|
|
412
|
+
* - Non-fusion page (flag off; the UI's navigation arm is retired since
|
|
413
|
+
* T-C): the mount path is the selection — the entry whose path equals
|
|
414
|
+
* the page's normalized mount. The pure arm survives as contract; no
|
|
415
|
+
* served page can hit the foreign case anymore.
|
|
416
|
+
*
|
|
417
|
+
* Host-half pure logic so `test/client-switch.test.mjs` pins it from `dist/`.
|
|
418
|
+
*/
|
|
419
|
+
export declare function hubRowActive(input: HubRowActiveInput): boolean;
|
|
420
|
+
/** The degradation hint a Hub UI surface shows while the flag is off (T-E). */
|
|
421
|
+
export declare const FUSION_OFF_HINT = "fusion off \u2014 no foreign entry";
|
|
422
|
+
/** What one Hub UI surface renders as its switch affordance (T-E closeout). */
|
|
423
|
+
export interface SwitchAffordance {
|
|
424
|
+
/** Whether the in-place switch affordance may render at all. */
|
|
425
|
+
available: boolean;
|
|
426
|
+
/** The degradation hint; non-empty exactly when {@link available} is false. */
|
|
427
|
+
hint: string;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* The flag-off shape of the Hub UI switch surfaces (path-separation Part 2
|
|
431
|
+
* T-E closeout). With the per-label URL family retired (T-C) and the world
|
|
432
|
+
* web UIs disabled (T-D), a flag-OFF ctx0 page has NO foreign entry at all —
|
|
433
|
+
* and since T-C the Hub UI ships no hyperlinks either, so the smallest honest
|
|
434
|
+
* rendering is an inert roster with a visible degradation hint instead of a
|
|
435
|
+
* silently dead control:
|
|
436
|
+
*
|
|
437
|
+
* - `available: true` (fusion page): the affordance renders — the HubPage
|
|
438
|
+
* `switch` action and the HubPanelIcon chevron menu.
|
|
439
|
+
* - `available: false` (flag off): the affordance is NOT rendered; the
|
|
440
|
+
* roster stays informational and the surface shows the degradation hint
|
|
441
|
+
* ({@link FUSION_OFF_HINT}) once (HubPage: a single chip under the
|
|
442
|
+
* section head, not one per row). Nothing navigates — there is
|
|
443
|
+
* nothing to navigate to.
|
|
444
|
+
*
|
|
445
|
+
* Host-half pure logic so `test/client-switch.test.mjs` pins it from `dist/`.
|
|
446
|
+
*/
|
|
447
|
+
export declare function switchAffordance(fusion: boolean): SwitchAffordance;
|
|
448
|
+
/**
|
|
449
|
+
* Structural slice of one roster row the Hub UI helpers read (the client
|
|
450
|
+
* roster's `AgentLink` shape mirrored structurally — this module never imports
|
|
451
|
+
* from the client half).
|
|
452
|
+
*/
|
|
453
|
+
export interface HubRosterRow {
|
|
454
|
+
/** The roster key (`'native'` or a runtime key). */
|
|
455
|
+
readonly key: string;
|
|
456
|
+
/** The display label the roster projected for the key. */
|
|
457
|
+
readonly label: string;
|
|
458
|
+
/** The row's mount path. */
|
|
459
|
+
readonly path: string;
|
|
460
|
+
/** Machine the runtime comes from (the roster parser guarantees it — `'local'` when an older host shape omitted it). */
|
|
461
|
+
readonly machine: string;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* The active agent's display name for the Hub row's right-edge selector: the
|
|
465
|
+
* roster label of the atom's `activeAgent` key — the name the selector's
|
|
466
|
+
* `title` carries, since the row label itself is the constant `Hub`. A key the
|
|
467
|
+
* (seeded) roster does not know degrades to the raw key. `undefined`/empty
|
|
468
|
+
* (flag off, or no atom read) means "no active agent".
|
|
469
|
+
*
|
|
470
|
+
* Host-half pure logic so `test/client-switch.test.mjs` pins it from `dist/`.
|
|
471
|
+
*/
|
|
472
|
+
export declare function activeAgentName(activeAgent: string | undefined, roster: readonly HubRosterRow[]): string | undefined;
|
|
473
|
+
/** Which machine one roster row groups under: the row's own machine (parser-guaranteed). */
|
|
474
|
+
export declare function machineGroup(row: HubRosterRow): string;
|
|
475
|
+
/** One machine's group of roster rows, in roster order. */
|
|
476
|
+
export interface MachineGroup {
|
|
477
|
+
/** The grouping key (the members' shared `machine`). */
|
|
478
|
+
readonly machine: string;
|
|
479
|
+
/** The rows of this machine, keeping the roster's own order. */
|
|
480
|
+
readonly agents: readonly HubRosterRow[];
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Group a roster by machine for the HubPanelIcon chevron menu's native
|
|
484
|
+
* workspace-list look: one group per distinct `machineGroup` key, groups in
|
|
485
|
+
* first-appearance order, members keeping the roster's order inside their
|
|
486
|
+
* group. A single-group roster (today's local-only shape) renders exactly
|
|
487
|
+
* like the old flat list minus the card border.
|
|
488
|
+
*
|
|
489
|
+
* Host-half pure logic so `test/client-switch.test.mjs` pins it from `dist/`.
|
|
490
|
+
*/
|
|
491
|
+
export declare function machineGroups(rows: readonly HubRosterRow[]): readonly MachineGroup[];
|
|
492
|
+
/**
|
|
493
|
+
* What the machine hands the host's gate composition (T-G): the machine-side
|
|
494
|
+
* ingredients of {@link LandingGateOptions}. Structurally a full
|
|
495
|
+
* `LandingGateOptions` — the binding passes it straight through after adding
|
|
496
|
+
* its own membership face on the sessions store.
|
|
497
|
+
*/
|
|
498
|
+
export interface LandingGateRequest {
|
|
499
|
+
/**
|
|
500
|
+
* The persisted pointer's plain session id, read at DECISION time through
|
|
501
|
+
* this host's {@link readPersistedSelection} + {@link parseLandingTarget}
|
|
502
|
+
* — the same storage face and precedence the landing callback itself uses
|
|
503
|
+
* at fire time. `undefined` for no pointer or a subagent-address pointer.
|
|
504
|
+
*/
|
|
505
|
+
pointerSessionId(): string | undefined;
|
|
506
|
+
/** The quiet window ({@link SwitchOptions.quietWindowMs} ?? {@link LANDING_QUIET_MS}). */
|
|
507
|
+
quietMs: number;
|
|
508
|
+
/** This host's timer faces, so the quiet window is drivable in tests. */
|
|
509
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
510
|
+
clearTimeout(handle: unknown): void;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* The effects the switch machine needs, all injected. Every method used by
|
|
514
|
+
* the guarded steps ({@link setAgent}, the landing reads/writes,
|
|
515
|
+
* {@link reconnect}, {@link toast}) may throw; the machine contains the
|
|
516
|
+
* damage — a broken effect degrades its own step, never the orchestration
|
|
517
|
+
* and never the page. Timer and generation-subscription arming are the
|
|
518
|
+
* wiring's own faces (host `setTimeout`, the connection's generation
|
|
519
|
+
* observable) and are used directly.
|
|
520
|
+
*/
|
|
521
|
+
export interface SwitchHost {
|
|
522
|
+
/** Swap the state atom (+ NS) to `label` — orchestration step 1. */
|
|
523
|
+
setAgent(label: string): void;
|
|
524
|
+
/**
|
|
525
|
+
* Read the active NS's persisted `dsh.sessions.current` raw value. The
|
|
526
|
+
* machine reads it at LANDING time — inside the both-lists gate callback,
|
|
527
|
+
* long after the NS swap, exactly where upstream `restoreSelection` reads
|
|
528
|
+
* its hydrated selection store relative to its ready gate. A read failure
|
|
529
|
+
* may be reported either by throwing or by returning `null`.
|
|
530
|
+
*/
|
|
531
|
+
readPersistedSelection(): string | null;
|
|
532
|
+
/** Navigate the main view to a parsed landing target (open-session plan). */
|
|
533
|
+
openSession(target: string | object): void;
|
|
534
|
+
/**
|
|
535
|
+
* Run the new-session flow (the public `uiWorkspace.startSession` face):
|
|
536
|
+
* with a workspace id the flow targets that workspace (blank reuse
|
|
537
|
+
* included); without, the inherited current-then-recent resolution —
|
|
538
|
+
* which on a fresh, plane-consistent cache set IS the recent-workspace
|
|
539
|
+
* ladder. Executed for the start-session plan.
|
|
540
|
+
*/
|
|
541
|
+
startSession(workspaceId?: string): void;
|
|
542
|
+
/**
|
|
543
|
+
* Mirror upstream `initializeDefaultWorkspace` (navigation.ts:349-361)
|
|
544
|
+
* through the public faces: prepare the default workspace and open it.
|
|
545
|
+
* Executed for the default-workspace plan. Asynchronous upstream; a
|
|
546
|
+
* failure is feedback, never a revert.
|
|
547
|
+
*/
|
|
548
|
+
openDefaultWorkspace(): void;
|
|
549
|
+
/**
|
|
550
|
+
* Read the target plane's fresh navigation caches — the `workspaces.list`
|
|
551
|
+
* and `sessions.list` snapshots the landing plan decides over. The both-
|
|
552
|
+
* lists gate has already fired, so both phases are ready; a read failure
|
|
553
|
+
* may throw (a failed read lands nothing).
|
|
554
|
+
*/
|
|
555
|
+
readRestoreCaches(): RestoreCaches;
|
|
556
|
+
/**
|
|
557
|
+
* Observe the target plane's wire-derived navigation caches being replaced
|
|
558
|
+
* after the switch began — BOTH stores' post-`connection/reset` repull
|
|
559
|
+
* baselines landed (workspaces and sessions, either order), AND — T-G —
|
|
560
|
+
* when the landing pointer exists, the sessions list contains its session
|
|
561
|
+
* or has gone quiet for {@link LandingGateRequest.quietMs}. The all-clear
|
|
562
|
+
* for the landing: only then do the snapshots hold the target agent's data
|
|
563
|
+
* and upstream's resolution faces read the target plane. The listener must
|
|
564
|
+
* fire at most once per subscription; the returned disposer stops it (and
|
|
565
|
+
* any running quiet window).
|
|
566
|
+
*
|
|
567
|
+
* The `gate` request carries what the MACHINE owns — the pointer face
|
|
568
|
+
* (over this host's {@link readPersistedSelection}), the quiet window from
|
|
569
|
+
* {@link SwitchOptions.quietWindowMs}, and this host's own timers — so the
|
|
570
|
+
* binding can compose the membership-aware {@link observeCachesRefreshed}
|
|
571
|
+
* by adding only its membership face (`sessions.list` `byId`).
|
|
572
|
+
*/
|
|
573
|
+
onCachesRefreshed(listener: () => void, gate: LandingGateRequest): () => void;
|
|
574
|
+
/** Replace the connection attempt immediately (step 3, and the revert). */
|
|
575
|
+
reconnect(): void;
|
|
576
|
+
/**
|
|
577
|
+
* Observe generation establishment (post-`connection/reset`). The listener
|
|
578
|
+
* fires whenever a generation exists again; the returned disposer stops it.
|
|
579
|
+
*/
|
|
580
|
+
onGenerationEstablished(listener: () => void): () => void;
|
|
581
|
+
/**
|
|
582
|
+
* The roster probe: resolve `true` while `label` is still a live roster
|
|
583
|
+
* entry on the control plane. Reject or resolve `false` = probe failure.
|
|
584
|
+
*/
|
|
585
|
+
probeTarget(label: string): Promise<boolean>;
|
|
586
|
+
/** One visible toast/message; the revert's user feedback. */
|
|
587
|
+
toast(message: string): void;
|
|
588
|
+
/** Timers, injected so tests drive time by hand. */
|
|
589
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
590
|
+
clearTimeout(handle: unknown): void;
|
|
591
|
+
}
|
|
592
|
+
/** Options; every field optional. */
|
|
593
|
+
export interface SwitchOptions {
|
|
594
|
+
/** Fallback window in ms; defaults to {@link SWITCH_FALLBACK_MS}. */
|
|
595
|
+
fallbackMs?: number;
|
|
596
|
+
/**
|
|
597
|
+
* T-G: the landing gate's membership-wait quiet window in ms; defaults to
|
|
598
|
+
* {@link LANDING_QUIET_MS}. Ridden through {@link LandingGateRequest} to
|
|
599
|
+
* the gate the binding composes.
|
|
600
|
+
*/
|
|
601
|
+
quietWindowMs?: number;
|
|
602
|
+
}
|
|
603
|
+
/** The produced orchestration interface (brief: `switchAgent(label): Promise<void>`). */
|
|
604
|
+
export interface SwitchController {
|
|
605
|
+
/**
|
|
606
|
+
* Run the full switch lifecycle for `label`: state swap, landing,
|
|
607
|
+
* reconnect, then settle on generation establishment or revert on the
|
|
608
|
+
* fallback. Resolves when the attempt ends — a revert is a handled
|
|
609
|
+
* outcome, not a rejection. A new run supersedes any prior attempt —
|
|
610
|
+
* pending or already settled — silently (cancelled, no revert) and kills
|
|
611
|
+
* its still-armed deferred landing through the controller-scope handle,
|
|
612
|
+
* so a settled switch can never land over a newer one.
|
|
613
|
+
*/
|
|
614
|
+
run(label: string): Promise<void>;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Build the switch machine over an injected {@link SwitchHost}.
|
|
618
|
+
*
|
|
619
|
+
* Only one attempt is ever armed at a time: starting a new switch finishes
|
|
620
|
+
* the previous one silently (timer cleared, subscription released, promise
|
|
621
|
+
* resolved) and cancels its pending landing, so a stale attempt can never
|
|
622
|
+
* revert or land over a newer switch. The landing cancel is held at
|
|
623
|
+
* CONTROLLER scope, not reached through the current-attempt pointer: the
|
|
624
|
+
* deferred landing deliberately survives the settle — which removes the
|
|
625
|
+
* attempt from `current` — so a supersede must still kill the armed landing
|
|
626
|
+
* of a PRIOR, ALREADY-SETTLED attempt (final-review round 2: without the
|
|
627
|
+
* controller-scope handle, a settled switch's landing fired over the newer
|
|
628
|
+
* switch's pointer-restored view).
|
|
629
|
+
*/
|
|
630
|
+
export declare function createSwitchController(host: SwitchHost, options?: SwitchOptions): SwitchController;
|