super-dsh 0.1.4 → 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.
Files changed (55) hide show
  1. package/README.md +6 -7
  2. package/agent-agy/dist/world-plugin.js +18 -33
  3. package/agent-codex/dist/world-plugin.js +18 -33
  4. package/agent-hermes/dist/world-plugin.js +18 -34
  5. package/agent-omp/dist/world-plugin.js +16 -32
  6. package/agent-pi/dist/world-plugin.js +18 -33
  7. package/client/index.js +2086 -0
  8. package/cordis.patch.yml +20 -7
  9. package/package.json +13 -3
  10. package/superd-hub/dist/agent-roster.d.ts +3 -1
  11. package/superd-hub/dist/agent-roster.js +4 -2
  12. package/superd-hub/dist/carrier.d.ts +41 -28
  13. package/superd-hub/dist/carrier.js +148 -202
  14. package/superd-hub/dist/fusion-transport.d.ts +148 -0
  15. package/superd-hub/dist/fusion-transport.js +397 -0
  16. package/superd-hub/dist/gateway.d.ts +7 -2
  17. package/superd-hub/dist/gateway.js +28 -2
  18. package/superd-hub/dist/hub-route.d.ts +74 -0
  19. package/superd-hub/dist/hub-route.js +188 -0
  20. package/superd-hub/dist/index.d.ts +10 -4
  21. package/superd-hub/dist/index.js +10 -4
  22. package/superd-hub/dist/roster.d.ts +2 -2
  23. package/superd-hub/dist/spawn-world.js +5 -3
  24. package/superd-hub/dist/switch-core.d.ts +630 -0
  25. package/superd-hub/dist/switch-core.js +734 -0
  26. package/superd-hub/dist/web-trust.d.ts +5 -5
  27. package/superd-hub/dist/web-trust.js +5 -5
  28. package/superd-hub/dist/world-entry.d.ts +20 -13
  29. package/superd-hub/dist/world-entry.js +15 -19
  30. package/superd-hub/dist/world-host.d.ts +15 -20
  31. package/superd-hub/dist/world-host.js +25 -4
  32. package/superd-hub/dist/world-join.js +14 -29
  33. package/superd-hub/dist/world-mount.d.ts +48 -26
  34. package/superd-hub/dist/world-mount.js +59 -27
  35. package/superd-hub/dist/world-provision.js +11 -4
  36. package/superd-hub/dist/world-web-server.d.ts +57 -106
  37. package/superd-hub/dist/world-web-server.js +61 -216
  38. package/superd-hub/lib/client/index.js +2086 -0
  39. package/superd-hub/package.json +12 -0
  40. package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
  41. package/agent-agy/dist/agent-preset-projection.js +0 -33
  42. package/agent-claude/dist/agent-preset-projection.js +0 -30
  43. package/agent-codex/dist/agent-preset-projection.js +0 -37
  44. package/agent-codex/dist/app-home.js +0 -65
  45. package/agent-codex/dist/spike.js +0 -46
  46. package/agent-hermes/dist/agent-preset-projection.js +0 -34
  47. package/agent-omp/dist/agent-preset-projection.js +0 -36
  48. package/agent-omp/dist/app-home.js +0 -86
  49. package/agent-pi/dist/agent-preset-projection.js +0 -29
  50. package/superd-hub/dist/client-shim.d.ts +0 -28
  51. package/superd-hub/dist/client-shim.js +0 -251
  52. package/superd-hub/dist/index-pass.d.ts +0 -22
  53. package/superd-hub/dist/index-pass.js +0 -47
  54. package/superd-hub/dist/labels.d.ts +0 -45
  55. package/superd-hub/dist/labels.js +0 -83
@@ -1,25 +1,35 @@
1
1
  /**
2
- * Per-world virtual webServer (AW-B DL3).
2
+ * Per-world virtual webServer (AW-B DL3; path-separation T-C silent sink →
3
+ * T-D registration-only shell).
3
4
  *
4
- * A world's plugins must be able to declare ANY path they like (`/api`,
5
- * `/plugins`, `/whatever-name-i-like`). Enumerating those namespaces in the
6
- * carrier is wrong by construction — a third-party plugin may declare a new
7
- * one at any time. So this class translates at REGISTRATION time:
5
+ * A world's plugins still declare ANY path they like (`/api`, `/plugins`,
6
+ * `/whatever-name-i-like`), and the world root still needs a `webServer`
7
+ * provider for them to bind (the `modules` row injects it; `worldMountPatches`
8
+ * pins that inject). It used to translate every registration onto ctx0's real
9
+ * webServer as `/<label>+path` — the per-label page family. T-C (2026-09-26,
10
+ * user ruling "strip off the per agent url path … no need the url path
11
+ * fallback (we have git history)") severed that: every route a world plugin
12
+ * declares is accepted and SWALLOWED — no `/<label>/…` route ever lands on
13
+ * ctx0's real webServer. T-D (2026-09-26, "disable their webui build, just
14
+ * like disable their webserver") then disabled the world webui rows
15
+ * (`web-runtime`/`client-hmr`, see `worldMountPatches`) and retired the whole
16
+ * index face with them: no `renderIndex`, no taps, no index injections, no
17
+ * client mount shim — nothing renders a world page anywhere, and the mount
18
+ * HTML pass (`index-pass.ts`) plus the shim retired with the last consumer.
8
19
  *
9
- * world registers { kind, path } → real registers { kind, `${labelPath}${path}` }
10
- * real receives /<label>/<path>/... → plugin handler sees /<path>/...
20
+ * What REMAINS and why (the data-plane coupling the guardrail protects):
21
+ * - the `webServer` provider itself: world-tree rows (`modules`, adapter
22
+ * rows) inject/bind it; removing the provider would pend them and fail
23
+ * the world boot;
24
+ * - `stripLabelFromUrl`/`RealWebServerFace`: the `/hub` family
25
+ * (`hub-route.ts`) types ctx0's real server and strips the `/hub` prefix
26
+ * with the same URL rule;
27
+ * - the neutral `port`/`host` getters: the shape every remaining reader
28
+ * sees (a world owns no socket).
11
29
  *
12
- * Consequences:
13
- * - any declared prefix lands under the label automatically; no list;
14
- * - two worlds may both own `/api` (they become `/omp/api`, `/codex/api`);
15
- * - ctx0 only ever sees the label namespaces, so a world cannot collide with
16
- * ctx0's own routes.
17
- *
18
- * Index rows stay world-local: this instance owns its taps and emits nothing
19
- * on the shared tree (worlds run as their own cordis ROOTS, whose event buses
20
- * are isolated — probe 2026-09-16: emit on root B never reaches root A).
30
+ * The RPC channel is hub-owned: the `/hub` family (`hub-route.ts`) is the
31
+ * sole foreign entry.
21
32
  */
22
- import { type IndexInjection } from '@deepseek-ai/dsh-host-webserver';
23
33
  /** Route match kind (mirrors the upstream contract). */
24
34
  export type WebRouteKind = 'exact' | 'prefix';
25
35
  /** The slice of node:http we touch (kept structural for testability). */
@@ -43,7 +53,11 @@ export interface WorldWebUpgradeRoute {
43
53
  path: string;
44
54
  handler: (req: WorldRequest, socket: unknown, head: Buffer) => void | Promise<void>;
45
55
  }
46
- /** The real (ctx0) webServer surface this world forwards into. */
56
+ /**
57
+ * The real (ctx0) webServer surface — since T-C no longer reachable from a
58
+ * world mount. The type stays for the `/hub` family (`hub-route.ts`), which
59
+ * registers on ctx0's real webServer directly.
60
+ */
47
61
  export interface RealWebServerFace {
48
62
  register(route: {
49
63
  kind: WebRouteKind;
@@ -59,104 +73,41 @@ export interface RealWebServerFace {
59
73
  readonly port?: number;
60
74
  readonly host?: string;
61
75
  }
62
- /** Options: how this world collects its own index rows. */
63
- export interface WorldWebServerOptions {
64
- /**
65
- * Push the world's index injection rows. The world plugin supplies a
66
- * function that emits on the WORLD root (never on ctx0's tree).
67
- */
68
- onIndexInject?(table: IndexInjection[]): void;
69
- /**
70
- * ctx0's index authorization, applied to every route this mount forwards
71
- * (pages, assets, fallback): the SAME gate native uses, so one cookie opens
72
- * everything under the origin. The world owns no listener and the browser
73
- * reaches it only through this mount, so authentication is decided HERE, in
74
- * ctx0's auth domain — never by the world's own browser-auth gate. A false
75
- * return means the authorizer already wrote the response (401 / 303).
76
- */
77
- authorize?(req: WorldRequest, res: WorldResponse): boolean;
78
- }
79
76
  /** Remove the mount label from `req.url`, preserving the trailing query. */
80
77
  export declare function stripLabelFromUrl(url: string | undefined, labelPath: string): string;
81
- /** One world's web surface, mounted under `<labelPath>`. */
78
+ /**
79
+ * One world's web surface — a registration-only shell: it accepts every
80
+ * registration a world plugin makes (so the tree boots and composes exactly
81
+ * as before) and forwards none of them anywhere; nothing is stored, nothing
82
+ * is served (T-C), and nothing renders (T-D).
83
+ */
82
84
  export declare class WorldWebServer {
83
85
  readonly labelPath: string;
84
- private readonly real;
85
- private readonly options;
86
- private readonly disposers;
87
- private readonly taps;
88
- /**
89
- * @param labelPath - mount root, e.g. `/omp` (no trailing slash).
90
- * @param real - the ctx0 webServer this world's routes forward into.
91
- * @param options - world-local index row source.
92
- */
93
- constructor(labelPath: string, real: RealWebServerFace, options?: WorldWebServerOptions);
94
- /** The ctx0 listener port (read-through; the world owns no socket). */
95
- get port(): number;
96
- /** The ctx0 bind host (read-through). */
97
- get host(): string;
98
86
  /**
99
- * The RPC channel belongs to the HUB, not to the world: the hub's carrier
100
- * answers `/<label>/api/*` and `/<label>/api/remote.mux` with ctx0's auth
101
- * domain, so the world's own connection/gateway registrations are swallowed
102
- * here (returning a no-op disposer) instead of colliding on the real table.
103
- * Non-envelope GET/HEAD traffic on `/<label>/api` still reaches the world's
104
- * exact Fetch routes: `world-entry` publishes the world connection's shared
105
- * `/api` fetch handler through `world-host`, and the carrier forwards into
106
- * it AFTER ctx0's fence — the world's own browser-auth gate is never in the
107
- * path (a different tree, a different secret).
87
+ * @param labelPath - mount root, e.g. `/omp` (no trailing slash) — the
88
+ * world's identity record; no page is ever served under it.
108
89
  */
109
- private static hubOwned;
90
+ constructor(labelPath: string);
110
91
  /**
111
- * Mount-side authentication for one forwarded world route.
112
- *
113
- * The world's own browser-auth gate never sees a browser: it is reached
114
- * in-ctx through this mount, and its cookie secret belongs to a different
115
- * tree (every runtime is nested with its own home). So the decision is made
116
- * here, with ctx0's connection: the same cookie that authenticated `<label>`
117
- * in ctx0 authorizes its pages and assets.
92
+ * The ctx0 listener facts (read-through retired with the mount): worlds
93
+ * keep the same neutral defaults the disconnected read-through always
94
+ * fell back to — a world owns no socket and no page anyone can hit.
118
95
  */
119
- private gate;
120
- /** Register a world route; the real table stores its translated path. */
121
- register(route: WorldWebRoute): () => void;
122
- /** Register a world upgrade route (verbatim exact path; hub-owned RPC paths are swallowed). */
123
- registerUpgrade(route: WorldWebUpgradeRoute): () => void;
124
- /**
125
- * Claim the world's SPA fallback: the ctx0 fallback seat is already owned by
126
- * native frontend-static, so the world's fallback lives as a PREFIX route on
127
- * the mount path (`/<label>`), which also keeps it inside the label namespace.
128
- */
129
- registerFallback(handler: (req: WorldRequest, res: WorldResponse) => void | Promise<void>): () => void;
96
+ get port(): number;
97
+ /** The neutral bind host (see {@link WorldWebServer.port}). */
98
+ get host(): string;
130
99
  /**
131
- * Fix up HTML on its way out. Two passes belong here, in this order:
132
- *
133
- * 1. the world's own frontend-static injects `<base href="/">` AFTER
134
- * `renderIndex`, and the FIRST `<base>` wins in a document, so the mount
135
- * base is re-pinned on the bytes actually sent;
136
- * 2. the agent roster is injected LAST — its values are the mount paths of
137
- * OTHER worlds (`/`, `/omp/`, `/codex/`), which the mount pass above
138
- * would otherwise re-root under this world's label.
100
+ * Accept one world route — and swallow it (T-C): pages have no road to
101
+ * ctx0. The disposer is a no-op, matching the swallowed-registration
102
+ * contract the `/api` family always had.
139
103
  */
140
- private htmlRewrite;
141
- /** World-local raw index transform (never forwarded to ctx0's taps). */
142
- tapIndex(transform: (html: string) => string): () => void;
143
- /** Apply this world's taps in registration order. */
144
- applyIndexTaps(html: string): string;
145
- /** Collect this world's structured rows (supplied by the world root). */
146
- collectIndexInjections(): IndexInjection[];
104
+ register(_route: WorldWebRoute): () => void;
105
+ /** Accept one world upgrade route — and swallow it (see {@link register}). */
106
+ registerUpgrade(_route: WorldWebUpgradeRoute): () => void;
147
107
  /**
148
- * Render the world's index: the client mount shim first (it must run before
149
- * the app entry module), then the world's rows, then its taps, then the mount
150
- * pass (base pinned at `/<label>/`, root-absolute strings re-rooted).
151
- *
152
- * The agent roster is injected LAST, after the mount pass: its values are
153
- * mount paths of OTHER worlds (`/`, `/omp/`, `/codex/`), and the pass would
154
- * happily re-root them under this world's label.
108
+ * Accept the world's SPA fallback claim — and swallow it (see
109
+ * {@link register}): the ctx0 fallback seat was always native
110
+ * frontend-static's alone.
155
111
  */
156
- renderIndex(html: string): string;
157
- /** Unregister every translated route (world unloaded). */
158
- dispose(): void;
159
- /** Wrap a plugin handler so it sees the world-relative URL. */
160
- private strip;
161
- private track;
112
+ registerFallback(_handler: (req: WorldRequest, res: WorldResponse) => void | Promise<void>): () => void;
162
113
  }
@@ -1,34 +1,35 @@
1
1
  /**
2
- * Per-world virtual webServer (AW-B DL3).
2
+ * Per-world virtual webServer (AW-B DL3; path-separation T-C silent sink →
3
+ * T-D registration-only shell).
3
4
  *
4
- * A world's plugins must be able to declare ANY path they like (`/api`,
5
- * `/plugins`, `/whatever-name-i-like`). Enumerating those namespaces in the
6
- * carrier is wrong by construction — a third-party plugin may declare a new
7
- * one at any time. So this class translates at REGISTRATION time:
5
+ * A world's plugins still declare ANY path they like (`/api`, `/plugins`,
6
+ * `/whatever-name-i-like`), and the world root still needs a `webServer`
7
+ * provider for them to bind (the `modules` row injects it; `worldMountPatches`
8
+ * pins that inject). It used to translate every registration onto ctx0's real
9
+ * webServer as `/<label>+path` — the per-label page family. T-C (2026-09-26,
10
+ * user ruling "strip off the per agent url path … no need the url path
11
+ * fallback (we have git history)") severed that: every route a world plugin
12
+ * declares is accepted and SWALLOWED — no `/<label>/…` route ever lands on
13
+ * ctx0's real webServer. T-D (2026-09-26, "disable their webui build, just
14
+ * like disable their webserver") then disabled the world webui rows
15
+ * (`web-runtime`/`client-hmr`, see `worldMountPatches`) and retired the whole
16
+ * index face with them: no `renderIndex`, no taps, no index injections, no
17
+ * client mount shim — nothing renders a world page anywhere, and the mount
18
+ * HTML pass (`index-pass.ts`) plus the shim retired with the last consumer.
8
19
  *
9
- * world registers { kind, path } → real registers { kind, `${labelPath}${path}` }
10
- * real receives /<label>/<path>/... → plugin handler sees /<path>/...
20
+ * What REMAINS and why (the data-plane coupling the guardrail protects):
21
+ * - the `webServer` provider itself: world-tree rows (`modules`, adapter
22
+ * rows) inject/bind it; removing the provider would pend them and fail
23
+ * the world boot;
24
+ * - `stripLabelFromUrl`/`RealWebServerFace`: the `/hub` family
25
+ * (`hub-route.ts`) types ctx0's real server and strips the `/hub` prefix
26
+ * with the same URL rule;
27
+ * - the neutral `port`/`host` getters: the shape every remaining reader
28
+ * sees (a world owns no socket).
11
29
  *
12
- * Consequences:
13
- * - any declared prefix lands under the label automatically; no list;
14
- * - two worlds may both own `/api` (they become `/omp/api`, `/codex/api`);
15
- * - ctx0 only ever sees the label namespaces, so a world cannot collide with
16
- * ctx0's own routes.
17
- *
18
- * Index rows stay world-local: this instance owns its taps and emits nothing
19
- * on the shared tree (worlds run as their own cordis ROOTS, whose event buses
20
- * are isolated — probe 2026-09-16: emit on root B never reaches root A).
30
+ * The RPC channel is hub-owned: the `/hub` family (`hub-route.ts`) is the
31
+ * sole foreign entry.
21
32
  */
22
- import { renderIndexInjections } from '@deepseek-ai/dsh-host-webserver';
23
- import { rewriteIndexHtml } from './index-pass.js';
24
- import { renderClientShim } from './client-shim.js';
25
- import { agentRosterRow } from './agent-roster.js';
26
- function translatePath(labelPath, path) {
27
- if (typeof path !== 'string' || !path.startsWith('/')) {
28
- throw new Error(`world-web-server: route path must start with "/", got ${JSON.stringify(path)}`);
29
- }
30
- return path === '/' ? labelPath : `${labelPath}${path}`;
31
- }
32
33
  /** Remove the mount label from `req.url`, preserving the trailing query. */
33
34
  export function stripLabelFromUrl(url, labelPath) {
34
35
  const raw = typeof url === 'string' && url !== '' ? url : '/';
@@ -41,214 +42,58 @@ export function stripLabelFromUrl(url, labelPath) {
41
42
  return `${path.slice(labelPath.length)}${rest}`;
42
43
  return raw;
43
44
  }
44
- /** One world's web surface, mounted under `<labelPath>`. */
45
+ /** No-op registration disposer (the swallowed-route shape). */
46
+ function swallowed() {
47
+ return () => { };
48
+ }
49
+ /**
50
+ * One world's web surface — a registration-only shell: it accepts every
51
+ * registration a world plugin makes (so the tree boots and composes exactly
52
+ * as before) and forwards none of them anywhere; nothing is stored, nothing
53
+ * is served (T-C), and nothing renders (T-D).
54
+ */
45
55
  export class WorldWebServer {
46
56
  labelPath;
47
- real;
48
- options;
49
- disposers = [];
50
- taps = [];
51
57
  /**
52
- * @param labelPath - mount root, e.g. `/omp` (no trailing slash).
53
- * @param real - the ctx0 webServer this world's routes forward into.
54
- * @param options - world-local index row source.
58
+ * @param labelPath - mount root, e.g. `/omp` (no trailing slash) — the
59
+ * world's identity record; no page is ever served under it.
55
60
  */
56
- constructor(labelPath, real, options = {}) {
61
+ constructor(labelPath) {
57
62
  this.labelPath = labelPath;
58
- this.real = real;
59
- this.options = options;
60
63
  if (typeof labelPath !== 'string' || !labelPath.startsWith('/') || labelPath === '/' || labelPath.endsWith('/')) {
61
64
  throw new Error(`world-web-server: labelPath must start with "/" and carry no trailing slash, got ${JSON.stringify(labelPath)}`);
62
65
  }
63
66
  }
64
- /** The ctx0 listener port (read-through; the world owns no socket). */
65
- get port() {
66
- return this.real.port ?? 0;
67
- }
68
- /** The ctx0 bind host (read-through). */
69
- get host() {
70
- return this.real.host ?? '127.0.0.1';
71
- }
72
- /**
73
- * The RPC channel belongs to the HUB, not to the world: the hub's carrier
74
- * answers `/<label>/api/*` and `/<label>/api/remote.mux` with ctx0's auth
75
- * domain, so the world's own connection/gateway registrations are swallowed
76
- * here (returning a no-op disposer) instead of colliding on the real table.
77
- * Non-envelope GET/HEAD traffic on `/<label>/api` still reaches the world's
78
- * exact Fetch routes: `world-entry` publishes the world connection's shared
79
- * `/api` fetch handler through `world-host`, and the carrier forwards into
80
- * it AFTER ctx0's fence — the world's own browser-auth gate is never in the
81
- * path (a different tree, a different secret).
82
- */
83
- static hubOwned(path) {
84
- return path === '/api' || path.startsWith('/api/');
85
- }
86
67
  /**
87
- * Mount-side authentication for one forwarded world route.
88
- *
89
- * The world's own browser-auth gate never sees a browser: it is reached
90
- * in-ctx through this mount, and its cookie secret belongs to a different
91
- * tree (every runtime is nested with its own home). So the decision is made
92
- * here, with ctx0's connection: the same cookie that authenticated `<label>`
93
- * in ctx0 authorizes its pages and assets.
68
+ * The ctx0 listener facts (read-through retired with the mount): worlds
69
+ * keep the same neutral defaults the disconnected read-through always
70
+ * fell back to — a world owns no socket and no page anyone can hit.
94
71
  */
95
- gate(handler) {
96
- return (req, ...rest) => {
97
- const res = rest[0];
98
- // false => the authorizer already answered (401, or token→cookie 303).
99
- if (res !== undefined && this.options.authorize !== undefined
100
- && !this.options.authorize(req, res))
101
- return;
102
- return handler(req, ...rest);
103
- };
104
- }
105
- /** Register a world route; the real table stores its translated path. */
106
- register(route) {
107
- if (WorldWebServer.hubOwned(route.path))
108
- return () => { };
109
- return this.track(this.real.register({
110
- kind: route.kind,
111
- path: translatePath(this.labelPath, route.path),
112
- handler: this.strip(this.gate(route.handler)),
113
- }));
114
- }
115
- /** Register a world upgrade route (verbatim exact path; hub-owned RPC paths are swallowed). */
116
- registerUpgrade(route) {
117
- if (WorldWebServer.hubOwned(route.path))
118
- return () => { };
119
- return this.track(this.real.registerUpgrade({
120
- path: translatePath(this.labelPath, route.path),
121
- handler: this.strip(route.handler),
122
- }));
72
+ get port() {
73
+ return 0;
123
74
  }
124
- /**
125
- * Claim the world's SPA fallback: the ctx0 fallback seat is already owned by
126
- * native frontend-static, so the world's fallback lives as a PREFIX route on
127
- * the mount path (`/<label>`), which also keeps it inside the label namespace.
128
- */
129
- registerFallback(handler) {
130
- // The world's index arrives HERE (its own frontend-static claims the
131
- // fallback seat), so this is the route the mount must authenticate.
132
- const gated = this.gate((req, ...rest) => handler(req, rest[0]));
133
- return this.track(this.real.register({
134
- kind: 'prefix',
135
- path: this.labelPath,
136
- handler: this.htmlRewrite(this.strip(gated)),
137
- }));
75
+ /** The neutral bind host (see {@link WorldWebServer.port}). */
76
+ get host() {
77
+ return '127.0.0.1';
138
78
  }
139
79
  /**
140
- * Fix up HTML on its way out. Two passes belong here, in this order:
141
- *
142
- * 1. the world's own frontend-static injects `<base href="/">` AFTER
143
- * `renderIndex`, and the FIRST `<base>` wins in a document, so the mount
144
- * base is re-pinned on the bytes actually sent;
145
- * 2. the agent roster is injected LAST — its values are the mount paths of
146
- * OTHER worlds (`/`, `/omp/`, `/codex/`), which the mount pass above
147
- * would otherwise re-root under this world's label.
80
+ * Accept one world route — and swallow it (T-C): pages have no road to
81
+ * ctx0. The disposer is a no-op, matching the swallowed-registration
82
+ * contract the `/api` family always had.
148
83
  */
149
- htmlRewrite(handler) {
150
- const labelPath = this.labelPath;
151
- return (req, ...rest) => {
152
- const res = rest[0];
153
- const originalEnd = res?.end?.bind(res);
154
- if (res !== undefined && originalEnd !== undefined) {
155
- res.end = ((chunk, ...tail) => {
156
- if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) {
157
- const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
158
- if (/<head[\s>]/i.test(text) && /<base\b/i.test(text)) {
159
- const mounted = rewriteIndexHtml(text, labelPath);
160
- // A world that composes the hub row emits its own roster row during
161
- // renderIndex — i.e. BEFORE this mount pass, so its paths were
162
- // already re-rooted. Drop every earlier copy and inject the one
163
- // this pass has just built from unrewritten values.
164
- const stale = /<script>globalThis\["__DSH_AGENT_ROSTER__"\][^<]*<\/script>/g;
165
- const cleaned = mounted.replace(stale, '');
166
- return originalEnd(renderIndexInjections(cleaned, [agentRosterRow()]), ...tail);
167
- }
168
- }
169
- return originalEnd(chunk, ...tail);
170
- });
171
- }
172
- return handler(req, ...rest);
173
- };
84
+ register(_route) {
85
+ return swallowed();
174
86
  }
175
- /** World-local raw index transform (never forwarded to ctx0's taps). */
176
- tapIndex(transform) {
177
- this.taps.push(transform);
178
- return () => {
179
- const at = this.taps.indexOf(transform);
180
- if (at !== -1)
181
- this.taps.splice(at, 1);
182
- };
183
- }
184
- /** Apply this world's taps in registration order. */
185
- applyIndexTaps(html) {
186
- let out = html;
187
- for (const transform of this.taps)
188
- out = transform(out);
189
- return out;
190
- }
191
- /** Collect this world's structured rows (supplied by the world root). */
192
- collectIndexInjections() {
193
- const table = [];
194
- this.options.onIndexInject?.(table);
195
- return table;
87
+ /** Accept one world upgrade route — and swallow it (see {@link register}). */
88
+ registerUpgrade(_route) {
89
+ return swallowed();
196
90
  }
197
91
  /**
198
- * Render the world's index: the client mount shim first (it must run before
199
- * the app entry module), then the world's rows, then its taps, then the mount
200
- * pass (base pinned at `/<label>/`, root-absolute strings re-rooted).
201
- *
202
- * The agent roster is injected LAST, after the mount pass: its values are
203
- * mount paths of OTHER worlds (`/`, `/omp/`, `/codex/`), and the pass would
204
- * happily re-root them under this world's label.
92
+ * Accept the world's SPA fallback claim — and swallow it (see
93
+ * {@link register}): the ctx0 fallback seat was always native
94
+ * frontend-static's alone.
205
95
  */
206
- renderIndex(html) {
207
- // Injection rows FIRST, the client shim LAST: index-inject rows may need
208
- // to own the transport global before anything else materializes — the
209
- // page-trust component's `window.__DSH_TRANSPORT__ = { ownsHost: true }`
210
- // guards on absence, while the shim MERGES into an existing object
211
- // (`globalThis.__DSH_TRANSPORT__ = globalThis.__DSH_TRANSPORT__ || {}`).
212
- // Shim-first would create the object and silently eat the verdict.
213
- const rows = [
214
- // No hub-invented widget here: the world composes the hub's own client
215
- // plugin (see worldMountPatches), so its selector IS the native one.
216
- ...this.collectIndexInjections(),
217
- { kind: 'script', placement: 'head', text: renderClientShim(this.labelPath) },
218
- ];
219
- const rendered = this.applyIndexTaps(renderIndexInjections(html, rows));
220
- // The roster rides the outbound pass (htmlRewrite), not this one: the mount
221
- // rewrite above would re-root other worlds' paths under this label.
222
- return rewriteIndexHtml(rendered, this.labelPath);
223
- }
224
- /** Unregister every translated route (world unloaded). */
225
- dispose() {
226
- for (const dispose of this.disposers.splice(0).reverse()) {
227
- try {
228
- dispose();
229
- }
230
- catch {
231
- // release must never throw during teardown
232
- }
233
- }
234
- this.taps.length = 0;
235
- }
236
- /** Wrap a plugin handler so it sees the world-relative URL. */
237
- strip(handler) {
238
- const labelPath = this.labelPath;
239
- return (req, ...rest) => {
240
- req.url = stripLabelFromUrl(req.url, labelPath);
241
- return handler(req, ...rest);
242
- };
243
- }
244
- track(dispose) {
245
- const wrapped = () => {
246
- const at = this.disposers.indexOf(wrapped);
247
- if (at !== -1)
248
- this.disposers.splice(at, 1);
249
- dispose();
250
- };
251
- this.disposers.push(wrapped);
252
- return wrapped;
96
+ registerFallback(_handler) {
97
+ return swallowed();
253
98
  }
254
99
  }