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.
- package/README.md +6 -7
- package/agent-agy/dist/world-plugin.js +18 -33
- package/agent-codex/dist/world-plugin.js +18 -33
- package/agent-hermes/dist/world-plugin.js +18 -34
- package/agent-omp/dist/world-plugin.js +16 -32
- package/agent-pi/dist/world-plugin.js +18 -33
- package/client/index.js +2086 -0
- package/cordis.patch.yml +20 -7
- package/package.json +13 -3
- package/superd-hub/dist/agent-roster.d.ts +3 -1
- package/superd-hub/dist/agent-roster.js +4 -2
- package/superd-hub/dist/carrier.d.ts +41 -28
- package/superd-hub/dist/carrier.js +148 -202
- package/superd-hub/dist/fusion-transport.d.ts +148 -0
- package/superd-hub/dist/fusion-transport.js +397 -0
- package/superd-hub/dist/gateway.d.ts +7 -2
- package/superd-hub/dist/gateway.js +28 -2
- package/superd-hub/dist/hub-route.d.ts +74 -0
- package/superd-hub/dist/hub-route.js +188 -0
- package/superd-hub/dist/index.d.ts +10 -4
- package/superd-hub/dist/index.js +10 -4
- package/superd-hub/dist/roster.d.ts +2 -2
- package/superd-hub/dist/spawn-world.js +5 -3
- package/superd-hub/dist/switch-core.d.ts +630 -0
- package/superd-hub/dist/switch-core.js +734 -0
- package/superd-hub/dist/web-trust.d.ts +5 -5
- package/superd-hub/dist/web-trust.js +5 -5
- package/superd-hub/dist/world-entry.d.ts +20 -13
- package/superd-hub/dist/world-entry.js +15 -19
- package/superd-hub/dist/world-host.d.ts +15 -20
- package/superd-hub/dist/world-host.js +25 -4
- package/superd-hub/dist/world-join.js +14 -29
- package/superd-hub/dist/world-mount.d.ts +48 -26
- package/superd-hub/dist/world-mount.js +59 -27
- package/superd-hub/dist/world-provision.js +11 -4
- package/superd-hub/dist/world-web-server.d.ts +57 -106
- package/superd-hub/dist/world-web-server.js +61 -216
- package/superd-hub/lib/client/index.js +2086 -0
- package/superd-hub/package.json +12 -0
- 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-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/superd-hub/dist/client-shim.d.ts +0 -28
- package/superd-hub/dist/client-shim.js +0 -251
- package/superd-hub/dist/index-pass.d.ts +0 -22
- package/superd-hub/dist/index-pass.js +0 -47
- package/superd-hub/dist/labels.d.ts +0 -45
- package/superd-hub/dist/labels.js +0 -83
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fusion transport (native UI fusion, plan Task 3) — the ctx0 page-level head
|
|
3
|
+
* script plus its injection component.
|
|
4
|
+
*
|
|
5
|
+
* The script installs, BEFORE any application bundle materializes:
|
|
6
|
+
* - the page-level state atom `__SUPERD_UI_STATE__`
|
|
7
|
+
* ({ get activeAgent(): 'native'|string, setAgent(label), subscribe(fn) }),
|
|
8
|
+
* initial `'native'`, never persisted (no localStorage);
|
|
9
|
+
* - five CALL-TIME patches (the atom is read at request time, never baked
|
|
10
|
+
* into a closure — after setAgent the same request expression retargets):
|
|
11
|
+
* 1. `globalThis.fetch` — while a FOREIGN agent is active, a same-origin
|
|
12
|
+
* request whose path starts with `/api` is re-rooted under `/hub`
|
|
13
|
+
* and gains exactly one `agent=<label>` param (a pre-existing agent
|
|
14
|
+
* key is replaced, never duplicated — the server demuxes repeated
|
|
15
|
+
* params to the FIRST value; other query bytes + hash preserved);
|
|
16
|
+
* 2. `WebSocket` / `EventSource` Proxy constructors + `XMLHttpRequest.open`
|
|
17
|
+
* — the same rule (the mux WS lands on `/hub/api/remote.mux`);
|
|
18
|
+
* 3. `__DSH_FILE_UPLOAD__ = { fetch }` — uploads ride patch 1 (the
|
|
19
|
+
* file-upload service reads the hook at construction, so the hook
|
|
20
|
+
* must exist before app boot);
|
|
21
|
+
* 4. `HTMLAnchorElement.prototype.click` — ONLY download anchors with a
|
|
22
|
+
* same-origin `/api` href rewrite (the narrow client-shim r2 rule);
|
|
23
|
+
* the rewrite is transient — saved around the native click and
|
|
24
|
+
* restored after, so an agent switch re-targets the same anchor;
|
|
25
|
+
* 5. `Storage.prototype` get/set/removeItem — namespace read at call
|
|
26
|
+
* time: native = no prefix, foreign = `'<label>:'`.
|
|
27
|
+
* - one control-plane exemption: `/api/agent-runtime` is NEVER rewritten —
|
|
28
|
+
* the roster always talks to ctx0, so the user can always switch back.
|
|
29
|
+
* Native mode is zero-interference: every patched face passes its exact
|
|
30
|
+
* arguments through untouched.
|
|
31
|
+
*
|
|
32
|
+
* The component's gated listener ALSO bakes `__SUPERD_UI_FUSION__ = true`
|
|
33
|
+
* into the ctx0 index (Task 4): the client half's fusion-branch
|
|
34
|
+
* discriminator, emitted by the same gate as the script so the halves never
|
|
35
|
+
* disagree (world pages never carry it and keep real hyperlinks — spec §5).
|
|
36
|
+
*
|
|
37
|
+
* Quoting discipline: the generated script contains NO double-quoted string —
|
|
38
|
+
* index-pass rewrites every DOUBLE-quoted root-absolute literal in served
|
|
39
|
+
* HTML, so all literals stay single-quoted (the client-shim precedent). The
|
|
40
|
+
* install is idempotent (per-leg guards) and best-effort (every leg is
|
|
41
|
+
* individually guarded; a failure disables that leg only).
|
|
42
|
+
*
|
|
43
|
+
* Injection (the web-trust emission path, ordered AFTER it — spec §3.4 hard
|
|
44
|
+
* constraint): the row is a `webserver/index-inject` head script pushed by the
|
|
45
|
+
* `superd-fusion-transport` component, composed through the composite row
|
|
46
|
+
* `super-dsh/fusion-transport` positioned after `super-dsh/web-trust`. Both
|
|
47
|
+
* components defer their listener registration behind `inject(['webServer'])`,
|
|
48
|
+
* so cordis's FIFO effect order guarantees trust pushes first and fusion
|
|
49
|
+
* pushes last — the non-merging `if(!window.__DSH_TRANSPORT__)` verdict in the
|
|
50
|
+
* trust script is always evaluated before anything could own that global.
|
|
51
|
+
* Flag gate (minimal, Task 5 formalizes the config face): row config
|
|
52
|
+
* `enabled`, defaulting OFF from the `SUPERD_UI_FUSION` env — while the flag
|
|
53
|
+
* is off the component stays dormant and injects no row at all.
|
|
54
|
+
*
|
|
55
|
+
* @module @pgmi-builds/superd-hub/fusion-transport
|
|
56
|
+
*/
|
|
57
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
58
|
+
import z from '@deepseek-ai/schemastery';
|
|
59
|
+
/**
|
|
60
|
+
* Render the page-level fusion transport as a classic script (head injection).
|
|
61
|
+
* Pure and constant: the active label is state, never a baked literal, so the
|
|
62
|
+
* script takes no arguments. All string literals are single-quoted (see the
|
|
63
|
+
* module doc block).
|
|
64
|
+
* @returns JavaScript source.
|
|
65
|
+
*/
|
|
66
|
+
export declare function renderFusionTransport(): string;
|
|
67
|
+
/** The env name the minimal Task-3 gate reads; Task 5 builds the config face on it. */
|
|
68
|
+
export declare const FUSION_FLAG_ENV = "SUPERD_UI_FUSION";
|
|
69
|
+
/**
|
|
70
|
+
* The page global the ctx0 index carries when the gate is on: the client
|
|
71
|
+
* half's fusion-branch discriminator (the Hub UI switch surfaces read its
|
|
72
|
+
* presence to intercept agent clicks into the switch orchestration instead
|
|
73
|
+
* of letting the hyperlinks navigate — the sole switch surface since the T-B
|
|
74
|
+
* RuntimeSeat retirement; world pages never carry it, so they keep the real
|
|
75
|
+
* navigation half — spec §5 双半边). Baked by the SAME gated listener as the
|
|
76
|
+
* transport script, so the two halves can never disagree.
|
|
77
|
+
*/
|
|
78
|
+
export declare const FUSION_FLAG_GLOBAL = "__SUPERD_UI_FUSION__";
|
|
79
|
+
/**
|
|
80
|
+
* The flag's SEMANTICS after the path-separation Part 2 closeout (T-C/T-D
|
|
81
|
+
* landed; this block is the T-E closeout record) — native-ui-only, there is
|
|
82
|
+
* no fallback route anywhere:
|
|
83
|
+
*
|
|
84
|
+
* - ON: the ctx0 page fuses. The transport script arms the in-place switch
|
|
85
|
+
* and `/hub` is the sole surface for every runtime. The per-label URL
|
|
86
|
+
* family (`/<label>/` ctx0 pages, T-C) and the world web UIs (T-D) are
|
|
87
|
+
* RETIRED from the product, not gated by this flag — there is nothing
|
|
88
|
+
* navigational behind them to fall back to.
|
|
89
|
+
* - OFF: NO foreign entry exists AT ALL. The served product is the bare
|
|
90
|
+
* native UI plus the Hub panel as an information surface: the roster
|
|
91
|
+
* still lists the runtimes (the control plane is not gated), but the
|
|
92
|
+
* switch affordances render inert with the degradation hint
|
|
93
|
+
* (`switchAffordance` in switch-core) instead of dead controls — a row
|
|
94
|
+
* has no /<label>/ page to reach and the Hub UI ships no hrefs to reach
|
|
95
|
+
* one with.
|
|
96
|
+
*
|
|
97
|
+
* The shipped default stays OFF (truthy env opt-in). Moving the default ON is
|
|
98
|
+
* the user's 放行 (acceptance/release) round — a two-gate decision per repo
|
|
99
|
+
* §〇.1, never a code-side choice.
|
|
100
|
+
*
|
|
101
|
+
* Derivation: truthy spellings only (`1` / `true` / `yes` / `on`,
|
|
102
|
+
* case-insensitive, trimmed). Anything else — unset included — is OFF.
|
|
103
|
+
*/
|
|
104
|
+
export declare function deriveFusionFlag(env: string | undefined): boolean;
|
|
105
|
+
/** Row config schema face. */
|
|
106
|
+
export interface FusionConfig {
|
|
107
|
+
/** Master gate: inject the transport script on ctx0's index. Default off. */
|
|
108
|
+
enabled?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/** Row config (schema-level default derives from the {@link FUSION_FLAG_ENV} env). */
|
|
111
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
112
|
+
enabled: z<boolean, boolean, "defined">;
|
|
113
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
114
|
+
enabled: z<boolean, boolean, "defined">;
|
|
115
|
+
}>>, "plain">;
|
|
116
|
+
/** Cordis plugin name. */
|
|
117
|
+
export declare const name = "superd-fusion-transport";
|
|
118
|
+
/**
|
|
119
|
+
* No static injects: `webServer` is composed conditionally inside
|
|
120
|
+
* {@link apply}, so the row loads (dormant) in compositions without one.
|
|
121
|
+
*/
|
|
122
|
+
export declare const inject: string[];
|
|
123
|
+
/**
|
|
124
|
+
* Mount the fusion injection: one `webserver/index-inject` listener pushing
|
|
125
|
+
* the rendered head script row — the same emission path as the web-trust
|
|
126
|
+
* component, composed AFTER it (the composite row order is load order, and
|
|
127
|
+
* both components register their listeners behind `inject(['webServer'])`, so
|
|
128
|
+
* cordis's FIFO effect order pushes the fusion row last; spec §3.4) — plus
|
|
129
|
+
* the {@link FUSION_FLAG_GLOBAL} baked global row (Task 4): one gate, both
|
|
130
|
+
* halves.
|
|
131
|
+
*
|
|
132
|
+
* Flag off → the component registers nothing: no listener, no row, no script,
|
|
133
|
+
* no flag global.
|
|
134
|
+
* @param ctx - host plugin context.
|
|
135
|
+
* @param config - the row config.
|
|
136
|
+
*/
|
|
137
|
+
export declare function apply(ctx: Context, config: FusionConfig | undefined): void;
|
|
138
|
+
declare const _default: {
|
|
139
|
+
name: string;
|
|
140
|
+
inject: string[];
|
|
141
|
+
Config: z<Schemastery.ObjectS<NoInfer<{
|
|
142
|
+
enabled: z<boolean, boolean, "defined">;
|
|
143
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
144
|
+
enabled: z<boolean, boolean, "defined">;
|
|
145
|
+
}>>, "plain">;
|
|
146
|
+
apply: typeof apply;
|
|
147
|
+
};
|
|
148
|
+
export default _default;
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fusion transport (native UI fusion, plan Task 3) — the ctx0 page-level head
|
|
3
|
+
* script plus its injection component.
|
|
4
|
+
*
|
|
5
|
+
* The script installs, BEFORE any application bundle materializes:
|
|
6
|
+
* - the page-level state atom `__SUPERD_UI_STATE__`
|
|
7
|
+
* ({ get activeAgent(): 'native'|string, setAgent(label), subscribe(fn) }),
|
|
8
|
+
* initial `'native'`, never persisted (no localStorage);
|
|
9
|
+
* - five CALL-TIME patches (the atom is read at request time, never baked
|
|
10
|
+
* into a closure — after setAgent the same request expression retargets):
|
|
11
|
+
* 1. `globalThis.fetch` — while a FOREIGN agent is active, a same-origin
|
|
12
|
+
* request whose path starts with `/api` is re-rooted under `/hub`
|
|
13
|
+
* and gains exactly one `agent=<label>` param (a pre-existing agent
|
|
14
|
+
* key is replaced, never duplicated — the server demuxes repeated
|
|
15
|
+
* params to the FIRST value; other query bytes + hash preserved);
|
|
16
|
+
* 2. `WebSocket` / `EventSource` Proxy constructors + `XMLHttpRequest.open`
|
|
17
|
+
* — the same rule (the mux WS lands on `/hub/api/remote.mux`);
|
|
18
|
+
* 3. `__DSH_FILE_UPLOAD__ = { fetch }` — uploads ride patch 1 (the
|
|
19
|
+
* file-upload service reads the hook at construction, so the hook
|
|
20
|
+
* must exist before app boot);
|
|
21
|
+
* 4. `HTMLAnchorElement.prototype.click` — ONLY download anchors with a
|
|
22
|
+
* same-origin `/api` href rewrite (the narrow client-shim r2 rule);
|
|
23
|
+
* the rewrite is transient — saved around the native click and
|
|
24
|
+
* restored after, so an agent switch re-targets the same anchor;
|
|
25
|
+
* 5. `Storage.prototype` get/set/removeItem — namespace read at call
|
|
26
|
+
* time: native = no prefix, foreign = `'<label>:'`.
|
|
27
|
+
* - one control-plane exemption: `/api/agent-runtime` is NEVER rewritten —
|
|
28
|
+
* the roster always talks to ctx0, so the user can always switch back.
|
|
29
|
+
* Native mode is zero-interference: every patched face passes its exact
|
|
30
|
+
* arguments through untouched.
|
|
31
|
+
*
|
|
32
|
+
* The component's gated listener ALSO bakes `__SUPERD_UI_FUSION__ = true`
|
|
33
|
+
* into the ctx0 index (Task 4): the client half's fusion-branch
|
|
34
|
+
* discriminator, emitted by the same gate as the script so the halves never
|
|
35
|
+
* disagree (world pages never carry it and keep real hyperlinks — spec §5).
|
|
36
|
+
*
|
|
37
|
+
* Quoting discipline: the generated script contains NO double-quoted string —
|
|
38
|
+
* index-pass rewrites every DOUBLE-quoted root-absolute literal in served
|
|
39
|
+
* HTML, so all literals stay single-quoted (the client-shim precedent). The
|
|
40
|
+
* install is idempotent (per-leg guards) and best-effort (every leg is
|
|
41
|
+
* individually guarded; a failure disables that leg only).
|
|
42
|
+
*
|
|
43
|
+
* Injection (the web-trust emission path, ordered AFTER it — spec §3.4 hard
|
|
44
|
+
* constraint): the row is a `webserver/index-inject` head script pushed by the
|
|
45
|
+
* `superd-fusion-transport` component, composed through the composite row
|
|
46
|
+
* `super-dsh/fusion-transport` positioned after `super-dsh/web-trust`. Both
|
|
47
|
+
* components defer their listener registration behind `inject(['webServer'])`,
|
|
48
|
+
* so cordis's FIFO effect order guarantees trust pushes first and fusion
|
|
49
|
+
* pushes last — the non-merging `if(!window.__DSH_TRANSPORT__)` verdict in the
|
|
50
|
+
* trust script is always evaluated before anything could own that global.
|
|
51
|
+
* Flag gate (minimal, Task 5 formalizes the config face): row config
|
|
52
|
+
* `enabled`, defaulting OFF from the `SUPERD_UI_FUSION` env — while the flag
|
|
53
|
+
* is off the component stays dormant and injects no row at all.
|
|
54
|
+
*
|
|
55
|
+
* @module @pgmi-builds/superd-hub/fusion-transport
|
|
56
|
+
*/
|
|
57
|
+
import z from '@deepseek-ai/schemastery';
|
|
58
|
+
/**
|
|
59
|
+
* Render the page-level fusion transport as a classic script (head injection).
|
|
60
|
+
* Pure and constant: the active label is state, never a baked literal, so the
|
|
61
|
+
* script takes no arguments. All string literals are single-quoted (see the
|
|
62
|
+
* module doc block).
|
|
63
|
+
* @returns JavaScript source.
|
|
64
|
+
*/
|
|
65
|
+
export function renderFusionTransport() {
|
|
66
|
+
return `(() => {
|
|
67
|
+
try {
|
|
68
|
+
const g = globalThis
|
|
69
|
+
|
|
70
|
+
// ---- the __SUPERD_UI_STATE__ atom (created once; re-runs keep live state) ----
|
|
71
|
+
if (g.__SUPERD_UI_STATE__ === undefined || g.__SUPERD_UI_STATE__ === null) {
|
|
72
|
+
let current = 'native'
|
|
73
|
+
const subscribers = []
|
|
74
|
+
g.__SUPERD_UI_STATE__ = {
|
|
75
|
+
get activeAgent() { return current },
|
|
76
|
+
setAgent(label) {
|
|
77
|
+
if (typeof label !== 'string' || label === '') return
|
|
78
|
+
if (label === current) return
|
|
79
|
+
current = label
|
|
80
|
+
for (const fn of subscribers.slice()) {
|
|
81
|
+
try { fn(current) } catch { /* a broken subscriber never breaks the atom */ }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
subscribe(fn) {
|
|
85
|
+
if (typeof fn !== 'function') return () => {}
|
|
86
|
+
subscribers.push(fn)
|
|
87
|
+
return () => {
|
|
88
|
+
const at = subscribers.indexOf(fn)
|
|
89
|
+
if (at !== -1) subscribers.splice(at, 1)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const state = g.__SUPERD_UI_STATE__
|
|
95
|
+
|
|
96
|
+
// ---- the one rewrite rule: '/api...' becomes '/hub/api...' + '?agent=<label>' ----
|
|
97
|
+
const HUB = '/hub'
|
|
98
|
+
const CONTROL = '/api/agent-runtime'
|
|
99
|
+
const origin = () => {
|
|
100
|
+
const loc = g.location
|
|
101
|
+
if (loc !== undefined && typeof loc.origin === 'string' && loc.origin !== 'null') return loc.origin
|
|
102
|
+
return 'http://dsh.internal'
|
|
103
|
+
}
|
|
104
|
+
// Same authority as the page (scheme-agnostic: ws/wss/http/https share host).
|
|
105
|
+
const sameAuthority = (url) => {
|
|
106
|
+
try {
|
|
107
|
+
return url.host === new URL(origin()).host
|
|
108
|
+
} catch {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const isApiPath = (path) => path === '/api' || path.startsWith('/api/')
|
|
113
|
+
// Control-plane exemption: the roster control plane always hits ctx0.
|
|
114
|
+
const isExempt = (path) => path === CONTROL || path.startsWith(CONTROL + '/')
|
|
115
|
+
// Key compare matches the server demux shape: searchParams.get('agent')
|
|
116
|
+
// looks up PERCENT-DECODED keys, so '%61gent=x' demuxes as agent=x and the
|
|
117
|
+
// surgery must strip those spellings too (+' ' wins the '+' spelling).
|
|
118
|
+
const decodedKey = (k) => {
|
|
119
|
+
try { return decodeURIComponent(k.replace(/\\+/g, ' ')) } catch { return k }
|
|
120
|
+
}
|
|
121
|
+
const isAgentKey = (k) => k === 'agent' || decodedKey(k) === 'agent'
|
|
122
|
+
// Pure string surgery on (path, search, hash): the '/hub' prefix grows on
|
|
123
|
+
// the path, the hash survives byte-for-byte, and EXACTLY ONE agent param
|
|
124
|
+
// carries the active label. A pre-existing agent key is stripped before the
|
|
125
|
+
// fresh one lands — appending blindly would leave two agent params and the
|
|
126
|
+
// server demuxes to the FIRST value (searchParams.get), i.e. the wrong
|
|
127
|
+
// world with no trace. Non-agent query pairs keep their exact bytes and
|
|
128
|
+
// order; a bare '?' (no pairs) collapses instead of doubling the question
|
|
129
|
+
// mark, which would swallow the demux key.
|
|
130
|
+
const retarget = (path, search, hash) => {
|
|
131
|
+
if (isExempt(path) || !isApiPath(path)) return null
|
|
132
|
+
let kept = ''
|
|
133
|
+
if (search.length > 1) {
|
|
134
|
+
const survivors = []
|
|
135
|
+
for (const pair of (search.charAt(0) === '?' ? search.slice(1) : search).split('&')) {
|
|
136
|
+
if (pair === '') continue
|
|
137
|
+
const eq = pair.indexOf('=')
|
|
138
|
+
if (!isAgentKey(eq === -1 ? pair : pair.slice(0, eq))) survivors.push(pair)
|
|
139
|
+
}
|
|
140
|
+
kept = survivors.join('&')
|
|
141
|
+
}
|
|
142
|
+
return HUB + path + (kept === '' ? '?' : '?' + kept + '&') + 'agent=' + encodeURIComponent(String(state.activeAgent)) + hash
|
|
143
|
+
}
|
|
144
|
+
const rewriteUrlString = (s) => {
|
|
145
|
+
if (typeof s !== 'string' || s === '') return s
|
|
146
|
+
if (s.startsWith('/')) {
|
|
147
|
+
if (s.startsWith('//')) return s // protocol-relative: another authority
|
|
148
|
+
const hashAt = s.indexOf('#')
|
|
149
|
+
const hash = hashAt === -1 ? '' : s.slice(hashAt)
|
|
150
|
+
const rest = hashAt === -1 ? s : s.slice(0, hashAt)
|
|
151
|
+
const queryAt = rest.indexOf('?')
|
|
152
|
+
const search = queryAt === -1 ? '' : rest.slice(queryAt)
|
|
153
|
+
const path = queryAt === -1 ? rest : rest.slice(0, queryAt)
|
|
154
|
+
const next = retarget(path, search, hash)
|
|
155
|
+
return next === null ? s : next
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const url = new URL(s, origin())
|
|
159
|
+
if (!sameAuthority(url)) return s
|
|
160
|
+
const next = retarget(url.pathname, url.search, url.hash)
|
|
161
|
+
return next === null ? s : url.origin + next
|
|
162
|
+
} catch {
|
|
163
|
+
return s
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const rewriteApiUrl = (input) => {
|
|
167
|
+
if (typeof input === 'string') return rewriteUrlString(input)
|
|
168
|
+
if (input !== null && typeof input === 'object' && typeof input.href === 'string') {
|
|
169
|
+
const next = rewriteUrlString(input.href)
|
|
170
|
+
return next === input.href ? input : next
|
|
171
|
+
}
|
|
172
|
+
return input
|
|
173
|
+
}
|
|
174
|
+
// Foreign means a non-native STRING label; anything else behaves native.
|
|
175
|
+
const foreign = () => typeof state.activeAgent === 'string' && state.activeAgent !== 'native'
|
|
176
|
+
// Never-throw wrapper: a failed rewrite falls back to the exact input.
|
|
177
|
+
const rewriteSafe = (input) => {
|
|
178
|
+
try {
|
|
179
|
+
return foreign() ? rewriteApiUrl(input) : input
|
|
180
|
+
} catch {
|
|
181
|
+
return input
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---- patches 1 + 3: globalThis.fetch, and the file-upload hook riding it ----
|
|
186
|
+
if (g.__SUPERD_UI_FUSION_FETCH__ !== true) {
|
|
187
|
+
g.__SUPERD_UI_FUSION_FETCH__ = true
|
|
188
|
+
try {
|
|
189
|
+
const nativeFetch = g.fetch
|
|
190
|
+
if (typeof nativeFetch === 'function') {
|
|
191
|
+
const shimFetch = (input, init) => {
|
|
192
|
+
if (!foreign()) return nativeFetch(input, init)
|
|
193
|
+
let target = input
|
|
194
|
+
try {
|
|
195
|
+
if (typeof Request === 'function' && input instanceof Request) {
|
|
196
|
+
const url = rewriteApiUrl(input.url)
|
|
197
|
+
target = url === input.url ? input : new Request(url, input)
|
|
198
|
+
} else {
|
|
199
|
+
target = rewriteApiUrl(input)
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
target = input
|
|
203
|
+
}
|
|
204
|
+
return nativeFetch(target, init)
|
|
205
|
+
}
|
|
206
|
+
g.fetch = shimFetch
|
|
207
|
+
// Read by the file-upload service at construction time: present means
|
|
208
|
+
// the page fetch carries uploads (no blob worker), and the hook IS the
|
|
209
|
+
// rewriting fetch, so uploads follow the active agent like patch 1.
|
|
210
|
+
g.__DSH_FILE_UPLOAD__ = { fetch: shimFetch }
|
|
211
|
+
}
|
|
212
|
+
} catch { /* best-effort: this leg dies alone */ }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---- patch 2: WebSocket / EventSource Proxy constructors + XMLHttpRequest.open ----
|
|
216
|
+
// Construct through a Proxy, never a hand-rolled wrapper: statics, prototype
|
|
217
|
+
// and instanceof must survive (the mux client compares readyState constants).
|
|
218
|
+
if (g.__SUPERD_UI_FUSION_SOCKETS__ !== true) {
|
|
219
|
+
g.__SUPERD_UI_FUSION_SOCKETS__ = true
|
|
220
|
+
try {
|
|
221
|
+
const NativeWebSocket = g.WebSocket
|
|
222
|
+
if (typeof NativeWebSocket === 'function') {
|
|
223
|
+
g.WebSocket = new Proxy(NativeWebSocket, {
|
|
224
|
+
construct: (Target, args) => {
|
|
225
|
+
const url = rewriteSafe(args[0])
|
|
226
|
+
if (url !== args[0]) return new Target(url, ...args.slice(1))
|
|
227
|
+
return Reflect.construct(Target, args)
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
} catch { /* best-effort */ }
|
|
232
|
+
try {
|
|
233
|
+
const NativeEventSource = g.EventSource
|
|
234
|
+
if (typeof NativeEventSource === 'function') {
|
|
235
|
+
g.EventSource = new Proxy(NativeEventSource, {
|
|
236
|
+
construct: (Target, args) => {
|
|
237
|
+
const url = rewriteSafe(args[0])
|
|
238
|
+
if (url !== args[0]) return new Target(url, ...args.slice(1))
|
|
239
|
+
return Reflect.construct(Target, args)
|
|
240
|
+
},
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
} catch { /* best-effort */ }
|
|
244
|
+
try {
|
|
245
|
+
const NativeXhr = g.XMLHttpRequest
|
|
246
|
+
if (typeof NativeXhr === 'function' && NativeXhr.prototype !== undefined && typeof NativeXhr.prototype.open === 'function') {
|
|
247
|
+
const nativeOpen = NativeXhr.prototype.open
|
|
248
|
+
NativeXhr.prototype.open = function (method, url) {
|
|
249
|
+
const target = rewriteSafe(url)
|
|
250
|
+
if (target === url) return nativeOpen.apply(this, arguments)
|
|
251
|
+
return nativeOpen.apply(this, [method, target].concat(Array.prototype.slice.call(arguments, 2)))
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch { /* best-effort */ }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---- patch 4: download anchors (the narrow rule, client-shim r2 shape) ----
|
|
258
|
+
// ONLY anchors carrying a download attribute AND a same-origin /api href
|
|
259
|
+
// rewrite; every other anchor (navigation links included) passes through.
|
|
260
|
+
try {
|
|
261
|
+
if (g.HTMLAnchorElement !== undefined && g.HTMLAnchorElement.prototype !== undefined
|
|
262
|
+
&& typeof g.HTMLAnchorElement.prototype.click === 'function'
|
|
263
|
+
&& g.__SUPERD_UI_FUSION_ANCHOR__ !== true) {
|
|
264
|
+
g.__SUPERD_UI_FUSION_ANCHOR__ = true
|
|
265
|
+
const nativeAnchorClick = g.HTMLAnchorElement.prototype.click
|
|
266
|
+
g.HTMLAnchorElement.prototype.click = function (...args) {
|
|
267
|
+
let original = null
|
|
268
|
+
try {
|
|
269
|
+
if (foreign() && this.hasAttribute('download')) {
|
|
270
|
+
const current = this.getAttribute('href')
|
|
271
|
+
if (typeof current === 'string' && current !== '') {
|
|
272
|
+
const next = rewriteSafe(current)
|
|
273
|
+
if (typeof next === 'string' && next !== current) {
|
|
274
|
+
this.setAttribute('href', next)
|
|
275
|
+
original = current
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch { /* best-effort: never break the click itself */ }
|
|
280
|
+
try {
|
|
281
|
+
return nativeAnchorClick.apply(this, args)
|
|
282
|
+
} finally {
|
|
283
|
+
// The rewrite is TRANSIENT: restored right after the native click,
|
|
284
|
+
// so the rewritten /hub/api href never outlives it. A persisted
|
|
285
|
+
// rewrite would survive an agent switch and the next click would
|
|
286
|
+
// browse the stale /hub/api form — no /api match, no re-rewrite,
|
|
287
|
+
// wrong-agent download.
|
|
288
|
+
if (original !== null) {
|
|
289
|
+
try { this.setAttribute('href', original) } catch { /* best-effort */ }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch { /* best-effort */ }
|
|
295
|
+
|
|
296
|
+
// ---- patch 5: storage namespace, derived at CALL time ----
|
|
297
|
+
// Native mode prefixes nothing (bare keys, byte-identical behavior); a
|
|
298
|
+
// foreign agent reads and writes only its own '<label>:' tree.
|
|
299
|
+
try {
|
|
300
|
+
const StorageProto = g.Storage && g.Storage.prototype
|
|
301
|
+
if (StorageProto !== undefined && g.localStorage !== undefined
|
|
302
|
+
&& g.__SUPERD_UI_FUSION_STORAGE__ !== true
|
|
303
|
+
&& typeof StorageProto.getItem === 'function'
|
|
304
|
+
&& typeof StorageProto.setItem === 'function'
|
|
305
|
+
&& typeof StorageProto.removeItem === 'function') {
|
|
306
|
+
g.__SUPERD_UI_FUSION_STORAGE__ = true
|
|
307
|
+
const nativeGetItem = StorageProto.getItem
|
|
308
|
+
const nativeSetItem = StorageProto.setItem
|
|
309
|
+
const nativeRemoveItem = StorageProto.removeItem
|
|
310
|
+
const ns = () => {
|
|
311
|
+
const active = state.activeAgent
|
|
312
|
+
return typeof active === 'string' && active !== 'native' ? active + ':' : ''
|
|
313
|
+
}
|
|
314
|
+
StorageProto.getItem = function (name) { return nativeGetItem.call(this, ns() + name) }
|
|
315
|
+
StorageProto.setItem = function (name, value) { return nativeSetItem.call(this, ns() + name, value) }
|
|
316
|
+
StorageProto.removeItem = function (name) { return nativeRemoveItem.call(this, ns() + name) }
|
|
317
|
+
}
|
|
318
|
+
} catch { /* storage namespace is best-effort: never break the page */ }
|
|
319
|
+
} catch { /* the whole install is best-effort: a partial page is still a page */ }
|
|
320
|
+
})()`;
|
|
321
|
+
}
|
|
322
|
+
/** The env name the minimal Task-3 gate reads; Task 5 builds the config face on it. */
|
|
323
|
+
export const FUSION_FLAG_ENV = 'SUPERD_UI_FUSION';
|
|
324
|
+
/**
|
|
325
|
+
* The page global the ctx0 index carries when the gate is on: the client
|
|
326
|
+
* half's fusion-branch discriminator (the Hub UI switch surfaces read its
|
|
327
|
+
* presence to intercept agent clicks into the switch orchestration instead
|
|
328
|
+
* of letting the hyperlinks navigate — the sole switch surface since the T-B
|
|
329
|
+
* RuntimeSeat retirement; world pages never carry it, so they keep the real
|
|
330
|
+
* navigation half — spec §5 双半边). Baked by the SAME gated listener as the
|
|
331
|
+
* transport script, so the two halves can never disagree.
|
|
332
|
+
*/
|
|
333
|
+
export const FUSION_FLAG_GLOBAL = '__SUPERD_UI_FUSION__';
|
|
334
|
+
/**
|
|
335
|
+
* The flag's SEMANTICS after the path-separation Part 2 closeout (T-C/T-D
|
|
336
|
+
* landed; this block is the T-E closeout record) — native-ui-only, there is
|
|
337
|
+
* no fallback route anywhere:
|
|
338
|
+
*
|
|
339
|
+
* - ON: the ctx0 page fuses. The transport script arms the in-place switch
|
|
340
|
+
* and `/hub` is the sole surface for every runtime. The per-label URL
|
|
341
|
+
* family (`/<label>/` ctx0 pages, T-C) and the world web UIs (T-D) are
|
|
342
|
+
* RETIRED from the product, not gated by this flag — there is nothing
|
|
343
|
+
* navigational behind them to fall back to.
|
|
344
|
+
* - OFF: NO foreign entry exists AT ALL. The served product is the bare
|
|
345
|
+
* native UI plus the Hub panel as an information surface: the roster
|
|
346
|
+
* still lists the runtimes (the control plane is not gated), but the
|
|
347
|
+
* switch affordances render inert with the degradation hint
|
|
348
|
+
* (`switchAffordance` in switch-core) instead of dead controls — a row
|
|
349
|
+
* has no /<label>/ page to reach and the Hub UI ships no hrefs to reach
|
|
350
|
+
* one with.
|
|
351
|
+
*
|
|
352
|
+
* The shipped default stays OFF (truthy env opt-in). Moving the default ON is
|
|
353
|
+
* the user's 放行 (acceptance/release) round — a two-gate decision per repo
|
|
354
|
+
* §〇.1, never a code-side choice.
|
|
355
|
+
*
|
|
356
|
+
* Derivation: truthy spellings only (`1` / `true` / `yes` / `on`,
|
|
357
|
+
* case-insensitive, trimmed). Anything else — unset included — is OFF.
|
|
358
|
+
*/
|
|
359
|
+
export function deriveFusionFlag(env) {
|
|
360
|
+
return ['1', 'true', 'yes', 'on'].includes((env ?? '').trim().toLowerCase());
|
|
361
|
+
}
|
|
362
|
+
/** Row config (schema-level default derives from the {@link FUSION_FLAG_ENV} env). */
|
|
363
|
+
export const Config = z.object({
|
|
364
|
+
enabled: z.boolean().default(deriveFusionFlag(process.env[FUSION_FLAG_ENV])),
|
|
365
|
+
});
|
|
366
|
+
/** Cordis plugin name. */
|
|
367
|
+
export const name = 'superd-fusion-transport';
|
|
368
|
+
/**
|
|
369
|
+
* No static injects: `webServer` is composed conditionally inside
|
|
370
|
+
* {@link apply}, so the row loads (dormant) in compositions without one.
|
|
371
|
+
*/
|
|
372
|
+
export const inject = [];
|
|
373
|
+
/**
|
|
374
|
+
* Mount the fusion injection: one `webserver/index-inject` listener pushing
|
|
375
|
+
* the rendered head script row — the same emission path as the web-trust
|
|
376
|
+
* component, composed AFTER it (the composite row order is load order, and
|
|
377
|
+
* both components register their listeners behind `inject(['webServer'])`, so
|
|
378
|
+
* cordis's FIFO effect order pushes the fusion row last; spec §3.4) — plus
|
|
379
|
+
* the {@link FUSION_FLAG_GLOBAL} baked global row (Task 4): one gate, both
|
|
380
|
+
* halves.
|
|
381
|
+
*
|
|
382
|
+
* Flag off → the component registers nothing: no listener, no row, no script,
|
|
383
|
+
* no flag global.
|
|
384
|
+
* @param ctx - host plugin context.
|
|
385
|
+
* @param config - the row config.
|
|
386
|
+
*/
|
|
387
|
+
export function apply(ctx, config) {
|
|
388
|
+
if (config?.enabled !== true)
|
|
389
|
+
return;
|
|
390
|
+
ctx.inject(['webServer'], () => {
|
|
391
|
+
ctx.on('webserver/index-inject', (table) => {
|
|
392
|
+
table.push({ kind: 'script', placement: 'head', text: renderFusionTransport() });
|
|
393
|
+
table.push({ kind: 'global', name: FUSION_FLAG_GLOBAL, value: true });
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
export default { name, inject, Config, apply };
|
|
@@ -19,10 +19,15 @@ export declare const name = "superd-hub";
|
|
|
19
19
|
/**
|
|
20
20
|
* The plugin form (row contract): applied when the row mounts.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
22
|
+
* Three CTX0-owned contributions, none carrying any selection value:
|
|
23
23
|
* - the world inventory control plane (read-only);
|
|
24
24
|
* - the agent roster row baked into ctx0's own index, so the selector lists
|
|
25
|
-
* every mount path the hub currently serves
|
|
25
|
+
* every mount path the hub currently serves;
|
|
26
|
+
* - the `/hub` route family (native UI fusion Task 2): the single foreign
|
|
27
|
+
* seam on ctx0's listener, registered once the listening stack is up.
|
|
28
|
+
* World trees compose this same row, but their `webServer` is the VIRTUAL
|
|
29
|
+
* per-world translation surface — the `/hub` family belongs to ctx0's
|
|
30
|
+
* real listener only (one family per process, demuxed by `?agent=`).
|
|
26
31
|
*/
|
|
27
32
|
export declare function apply(ctx: Context): void;
|
|
28
33
|
export default apply;
|
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
import { registerSelectorRpc, registryTargets } from './rpc.js';
|
|
2
2
|
import { agentRosterRow } from './agent-roster.js';
|
|
3
|
+
import { getTarget } from './targets.js';
|
|
4
|
+
import { registerHubRoute } from './hub-route.js';
|
|
5
|
+
import { WorldWebServer } from './world-web-server.js';
|
|
3
6
|
/** Stable Cordis plugin name. */
|
|
4
7
|
export const name = 'superd-hub';
|
|
5
8
|
/**
|
|
6
9
|
* The plugin form (row contract): applied when the row mounts.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
11
|
+
* Three CTX0-owned contributions, none carrying any selection value:
|
|
9
12
|
* - the world inventory control plane (read-only);
|
|
10
13
|
* - the agent roster row baked into ctx0's own index, so the selector lists
|
|
11
|
-
* every mount path the hub currently serves
|
|
14
|
+
* every mount path the hub currently serves;
|
|
15
|
+
* - the `/hub` route family (native UI fusion Task 2): the single foreign
|
|
16
|
+
* seam on ctx0's listener, registered once the listening stack is up.
|
|
17
|
+
* World trees compose this same row, but their `webServer` is the VIRTUAL
|
|
18
|
+
* per-world translation surface — the `/hub` family belongs to ctx0's
|
|
19
|
+
* real listener only (one family per process, demuxed by `?agent=`).
|
|
12
20
|
*/
|
|
13
21
|
export function apply(ctx) {
|
|
14
22
|
registerSelectorRpc(ctx, registryTargets);
|
|
15
23
|
const on = ctx.on;
|
|
16
24
|
on?.call(ctx, 'webserver/index-inject', (table) => { table.push(agentRosterRow()); });
|
|
25
|
+
ctx.inject(['webServer', 'connection'], (host) => {
|
|
26
|
+
const real = host.get('webServer');
|
|
27
|
+
if (real instanceof WorldWebServer)
|
|
28
|
+
return;
|
|
29
|
+
const connection = host.get('connection');
|
|
30
|
+
// The gate is ctx0's authorize source (connection.authorizeIndex); the
|
|
31
|
+
// addressed world resolves per request through the live foreign-target
|
|
32
|
+
// table — never a shared selection. The requestRejection face is the
|
|
33
|
+
// ctx0 browser-trust fence: auth first, then Host/Origin — the family is
|
|
34
|
+
// never weaker than the neighbors carrying identical traffic.
|
|
35
|
+
const dispose = registerHubRoute({
|
|
36
|
+
real: real,
|
|
37
|
+
authorize: (req, res) => connection.authorizeIndex(req, res),
|
|
38
|
+
requestRejection: (req) => connection.requestRejection(req),
|
|
39
|
+
gatewayOf: (label) => getTarget(label)?.gateway,
|
|
40
|
+
});
|
|
41
|
+
ctx.effect(() => dispose, 'superd-hub: /hub route family');
|
|
42
|
+
});
|
|
17
43
|
}
|
|
18
44
|
export default apply;
|