super-dsh 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-agy/bridge/agy_bridge.py +250 -0
- package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
- package/agent-agy/cordis.patch.yml +49 -0
- package/agent-agy/dist/adapter.d.ts +15 -0
- package/agent-agy/dist/adapter.js +42 -0
- package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
- package/agent-agy/dist/agent-preset-agy.js +169 -0
- package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
- package/agent-agy/dist/agent-preset-projection.js +33 -0
- package/agent-agy/dist/agent.d.ts +137 -0
- package/agent-agy/dist/agent.js +493 -0
- package/agent-agy/dist/agy-cli-client.d.ts +137 -0
- package/agent-agy/dist/agy-cli-client.js +301 -0
- package/agent-agy/dist/agy-client.d.ts +160 -0
- package/agent-agy/dist/agy-client.js +252 -0
- package/agent-agy/dist/agy-sessions.d.ts +24 -0
- package/agent-agy/dist/agy-sessions.js +116 -0
- package/agent-agy/dist/agy-store.d.ts +39 -0
- package/agent-agy/dist/agy-store.js +83 -0
- package/agent-agy/dist/inbox.d.ts +26 -0
- package/agent-agy/dist/inbox.js +53 -0
- package/agent-agy/dist/index.d.ts +80 -0
- package/agent-agy/dist/index.js +505 -0
- package/agent-agy/dist/models.d.ts +41 -0
- package/agent-agy/dist/models.js +74 -0
- package/agent-agy/dist/world-plugin.d.ts +9 -0
- package/agent-agy/dist/world-plugin.js +97 -0
- package/agent-agy/package.json +64 -0
- package/agent-claude/cordis.patch.yml +42 -0
- package/agent-claude/dist/adapter.js +62 -0
- package/agent-claude/dist/agent-preset-claude.js +169 -0
- package/agent-claude/dist/agent-preset-projection.js +30 -0
- package/agent-claude/dist/agent.js +1271 -0
- package/agent-claude/dist/claude-client.js +357 -0
- package/agent-claude/dist/claude-events.js +223 -0
- package/agent-claude/dist/claude-home.js +29 -0
- package/agent-claude/dist/commands.js +182 -0
- package/agent-claude/dist/content.js +134 -0
- package/agent-claude/dist/inbox.js +53 -0
- package/agent-claude/dist/index.js +555 -0
- package/agent-claude/dist/input-queue.js +64 -0
- package/agent-claude/dist/interaction.js +271 -0
- package/agent-claude/dist/models.js +158 -0
- package/agent-claude/dist/permission.js +120 -0
- package/agent-claude/dist/session-id.js +20 -0
- package/agent-claude/dist/session-map.js +165 -0
- package/agent-claude/dist/world-plugin.js +14 -0
- package/agent-claude/package.json +74 -0
- package/agent-codex/README.md +74 -0
- package/agent-codex/cordis.patch.yml +77 -0
- package/agent-codex/dist/adapter.js +55 -0
- package/agent-codex/dist/agent-preset-codex.js +171 -0
- package/agent-codex/dist/agent-preset-projection.js +37 -0
- package/agent-codex/dist/agent.js +1056 -0
- package/agent-codex/dist/app-home.js +65 -0
- package/agent-codex/dist/codex-client.js +469 -0
- package/agent-codex/dist/codex-events.js +297 -0
- package/agent-codex/dist/codex-store.js +190 -0
- package/agent-codex/dist/inbox.js +53 -0
- package/agent-codex/dist/index.js +605 -0
- package/agent-codex/dist/models.js +152 -0
- package/agent-codex/dist/permission.js +71 -0
- package/agent-codex/dist/session-map.js +170 -0
- package/agent-codex/dist/spike.js +46 -0
- package/agent-codex/dist/world-plugin.js +96 -0
- package/agent-codex/package.json +78 -0
- package/agent-hermes/README.md +144 -0
- package/agent-hermes/cordis.patch.yml +83 -0
- package/agent-hermes/dist/adapter.js +83 -0
- package/agent-hermes/dist/agent-preset-hermes.js +171 -0
- package/agent-hermes/dist/agent-preset-projection.js +34 -0
- package/agent-hermes/dist/agent.js +1068 -0
- package/agent-hermes/dist/hermes-client.js +953 -0
- package/agent-hermes/dist/hermes-events.js +289 -0
- package/agent-hermes/dist/hermes-store.js +158 -0
- package/agent-hermes/dist/inbox.js +53 -0
- package/agent-hermes/dist/index.js +541 -0
- package/agent-hermes/dist/models.js +324 -0
- package/agent-hermes/dist/permission.js +60 -0
- package/agent-hermes/dist/world-plugin.js +101 -0
- package/agent-hermes/package.json +76 -0
- package/agent-hub/cordis.patch.yml +12 -0
- package/agent-hub/dist/agent-roster.d.ts +34 -0
- package/agent-hub/dist/agent-roster.js +27 -0
- package/agent-hub/dist/carrier.d.ts +43 -0
- package/agent-hub/dist/carrier.js +241 -0
- package/agent-hub/dist/client-shim.d.ts +28 -0
- package/agent-hub/dist/client-shim.js +251 -0
- package/agent-hub/dist/envelope.d.ts +54 -0
- package/agent-hub/dist/envelope.js +92 -0
- package/agent-hub/dist/gateway.d.ts +28 -0
- package/agent-hub/dist/gateway.js +18 -0
- package/agent-hub/dist/index-pass.d.ts +22 -0
- package/agent-hub/dist/index-pass.js +47 -0
- package/agent-hub/dist/index.d.ts +29 -0
- package/agent-hub/dist/index.js +29 -0
- package/agent-hub/dist/labels.d.ts +45 -0
- package/agent-hub/dist/labels.js +83 -0
- package/agent-hub/dist/ownership.d.ts +37 -0
- package/agent-hub/dist/ownership.js +54 -0
- package/agent-hub/dist/roster.d.ts +28 -0
- package/agent-hub/dist/roster.js +38 -0
- package/agent-hub/dist/rpc.d.ts +38 -0
- package/agent-hub/dist/rpc.js +52 -0
- package/agent-hub/dist/spawn-world.d.ts +46 -0
- package/agent-hub/dist/spawn-world.js +72 -0
- package/agent-hub/dist/targets.d.ts +33 -0
- package/agent-hub/dist/targets.js +30 -0
- package/agent-hub/dist/world-entry.d.ts +31 -0
- package/agent-hub/dist/world-entry.js +51 -0
- package/agent-hub/dist/world-host.d.ts +69 -0
- package/agent-hub/dist/world-host.js +44 -0
- package/agent-hub/dist/world-join.d.ts +16 -0
- package/agent-hub/dist/world-join.js +122 -0
- package/agent-hub/dist/world-mount.d.ts +52 -0
- package/agent-hub/dist/world-mount.js +52 -0
- package/agent-hub/dist/world-mux.d.ts +72 -0
- package/agent-hub/dist/world-mux.js +224 -0
- package/agent-hub/dist/world-provision.d.ts +20 -0
- package/agent-hub/dist/world-provision.js +122 -0
- package/agent-hub/dist/world-web-server.d.ts +162 -0
- package/agent-hub/dist/world-web-server.js +248 -0
- package/agent-hub/lib/client/index.js +242 -0
- package/agent-hub/package.json +65 -0
- package/agent-omp/README.md +77 -0
- package/agent-omp/cordis.patch.yml +92 -0
- package/agent-omp/dist/adapter.js +36 -0
- package/agent-omp/dist/agent-preset-omp.js +171 -0
- package/agent-omp/dist/agent-preset-projection.js +36 -0
- package/agent-omp/dist/agent.js +1145 -0
- package/agent-omp/dist/app-home.js +86 -0
- package/agent-omp/dist/discovery.js +116 -0
- package/agent-omp/dist/inbox.js +53 -0
- package/agent-omp/dist/index.js +574 -0
- package/agent-omp/dist/knobs.js +50 -0
- package/agent-omp/dist/lazy-rpc.js +161 -0
- package/agent-omp/dist/mobile/gesture.js +148 -0
- package/agent-omp/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp/dist/mobile-boot.js +38 -0
- package/agent-omp/dist/models.js +299 -0
- package/agent-omp/dist/omp-cli.js +147 -0
- package/agent-omp/dist/omp-disk-discovery.js +178 -0
- package/agent-omp/dist/omp-store.js +518 -0
- package/agent-omp/dist/pairing.js +45 -0
- package/agent-omp/dist/permission.js +96 -0
- package/agent-omp/dist/protocol.js +13 -0
- package/agent-omp/dist/rpc-types.js +1 -0
- package/agent-omp/dist/sdk-client.js +378 -0
- package/agent-omp/dist/sidecar-client.js +195 -0
- package/agent-omp/dist/store/adapter.js +47 -0
- package/agent-omp/dist/store/db.js +210 -0
- package/agent-omp/dist/store/index.js +59 -0
- package/agent-omp/dist/store/reconcile.js +51 -0
- package/agent-omp/dist/store/schema.js +40 -0
- package/agent-omp/dist/world-plugin.js +96 -0
- package/agent-omp/package.json +97 -0
- package/agent-omp/sidecar/main.ts +481 -0
- package/agent-pi/README.md +67 -0
- package/agent-pi/cordis.patch.yml +80 -0
- package/agent-pi/dist/adapter.js +53 -0
- package/agent-pi/dist/agent-preset-pi.js +169 -0
- package/agent-pi/dist/agent-preset-projection.js +29 -0
- package/agent-pi/dist/agent.js +949 -0
- package/agent-pi/dist/inbox.js +53 -0
- package/agent-pi/dist/index.js +595 -0
- package/agent-pi/dist/knobs.js +24 -0
- package/agent-pi/dist/models.js +200 -0
- package/agent-pi/dist/permission.js +66 -0
- package/agent-pi/dist/pi-client.js +224 -0
- package/agent-pi/dist/pi-events.js +155 -0
- package/agent-pi/dist/pi-home.js +55 -0
- package/agent-pi/dist/safe-json.js +25 -0
- package/agent-pi/dist/session-map.js +119 -0
- package/agent-pi/dist/world-plugin.js +97 -0
- package/agent-pi/package.json +77 -0
- package/cordis.patch.yml +22 -19
- package/package.json +88 -25
- package/client.js +0 -4
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-world Remote stream mux server (AW-B DL6).
|
|
3
|
+
*
|
|
4
|
+
* Upstream's `RemoteStreamMuxServer` owns `/api/remote.mux` for the native
|
|
5
|
+
* gateway and is NOT reachable from outside its package (root exports omit it;
|
|
6
|
+
* `./src/*` cannot be imported at runtime). So this is our own wire-compatible
|
|
7
|
+
* implementation of the same protocol, bound to the ADDRESSED world's
|
|
8
|
+
* `openWireStream`:
|
|
9
|
+
*
|
|
10
|
+
* browser → server { type:'open', streamId, endpoint, payload }
|
|
11
|
+
* { type:'cancel', streamId }
|
|
12
|
+
* server → browser { type:'item', streamId, value }
|
|
13
|
+
* { type:'end', streamId }
|
|
14
|
+
* { type:'error', streamId, error:{code,message,details} }
|
|
15
|
+
*
|
|
16
|
+
* Heartbeat: ping every interval, terminate after two missed pongs (same
|
|
17
|
+
* policy as upstream).
|
|
18
|
+
*/
|
|
19
|
+
/** Open one world stream (the world gateway's `openWireStream`). */
|
|
20
|
+
export type MuxOpen = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<AsyncIterable<unknown>>;
|
|
21
|
+
/** Wire failure body (upstream `RemoteStreamFailure`). */
|
|
22
|
+
export interface MuxFailure {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
details: object;
|
|
26
|
+
}
|
|
27
|
+
/** Options. */
|
|
28
|
+
export interface WorldMuxOptions {
|
|
29
|
+
/** Ping interval; upstream default is 30s. */
|
|
30
|
+
heartbeatIntervalMs?: number;
|
|
31
|
+
/** Error → wire failure mapper. */
|
|
32
|
+
failure?: (error: unknown) => MuxFailure;
|
|
33
|
+
}
|
|
34
|
+
/** One client request frame. */
|
|
35
|
+
type ClientMessage = {
|
|
36
|
+
type: 'open';
|
|
37
|
+
streamId: string;
|
|
38
|
+
endpoint: string;
|
|
39
|
+
payload: unknown;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'cancel';
|
|
42
|
+
streamId: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Parse one browser text frame.
|
|
46
|
+
* @param text - complete WebSocket text message.
|
|
47
|
+
* @returns the validated request.
|
|
48
|
+
* @throws {Error} on a malformed frame.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseClientMessage(text: string): ClientMessage;
|
|
51
|
+
/** Default failure mapper: keep the error code when it looks like one. */
|
|
52
|
+
export declare function defaultFailure(error: unknown): MuxFailure;
|
|
53
|
+
/** One world's mux endpoint. */
|
|
54
|
+
export declare class WorldMuxServer {
|
|
55
|
+
private readonly open;
|
|
56
|
+
private readonly options;
|
|
57
|
+
private server;
|
|
58
|
+
private ws;
|
|
59
|
+
private heartbeat;
|
|
60
|
+
private readonly connections;
|
|
61
|
+
private readonly missed;
|
|
62
|
+
private closed;
|
|
63
|
+
constructor(open: MuxOpen, options?: WorldMuxOptions);
|
|
64
|
+
/** Take over one accepted upgrade request. */
|
|
65
|
+
handleUpgrade(req: unknown, socket: unknown, head: Buffer): Promise<void>;
|
|
66
|
+
/** Terminate every socket and wait for the active streams to unwind. */
|
|
67
|
+
close(): Promise<void>;
|
|
68
|
+
private load;
|
|
69
|
+
private runConnection;
|
|
70
|
+
private startHeartbeat;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-world Remote stream mux server (AW-B DL6).
|
|
3
|
+
*
|
|
4
|
+
* Upstream's `RemoteStreamMuxServer` owns `/api/remote.mux` for the native
|
|
5
|
+
* gateway and is NOT reachable from outside its package (root exports omit it;
|
|
6
|
+
* `./src/*` cannot be imported at runtime). So this is our own wire-compatible
|
|
7
|
+
* implementation of the same protocol, bound to the ADDRESSED world's
|
|
8
|
+
* `openWireStream`:
|
|
9
|
+
*
|
|
10
|
+
* browser → server { type:'open', streamId, endpoint, payload }
|
|
11
|
+
* { type:'cancel', streamId }
|
|
12
|
+
* server → browser { type:'item', streamId, value }
|
|
13
|
+
* { type:'end', streamId }
|
|
14
|
+
* { type:'error', streamId, error:{code,message,details} }
|
|
15
|
+
*
|
|
16
|
+
* Heartbeat: ping every interval, terminate after two missed pongs (same
|
|
17
|
+
* policy as upstream).
|
|
18
|
+
*/
|
|
19
|
+
/** Specifier is widened on purpose: `ws` is a runtime peer, not a typed dep. */
|
|
20
|
+
const WS_MODULE = 'ws';
|
|
21
|
+
const MAX_MISSED_HEARTBEATS = 2;
|
|
22
|
+
const DEFAULT_HEARTBEAT_MS = 30_000;
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function exactKeys(value, keys) {
|
|
27
|
+
const own = Object.keys(value);
|
|
28
|
+
return own.length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
29
|
+
}
|
|
30
|
+
function validId(value) {
|
|
31
|
+
return typeof value === 'string' && value.length > 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse one browser text frame.
|
|
35
|
+
* @param text - complete WebSocket text message.
|
|
36
|
+
* @returns the validated request.
|
|
37
|
+
* @throws {Error} on a malformed frame.
|
|
38
|
+
*/
|
|
39
|
+
export function parseClientMessage(text) {
|
|
40
|
+
let raw;
|
|
41
|
+
try {
|
|
42
|
+
raw = JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new Error('world-mux: invalid client message');
|
|
46
|
+
}
|
|
47
|
+
if (!isRecord(raw))
|
|
48
|
+
throw new Error('world-mux: invalid client message');
|
|
49
|
+
if (raw['type'] === 'cancel' && exactKeys(raw, ['type', 'streamId']) && validId(raw['streamId'])) {
|
|
50
|
+
return { type: 'cancel', streamId: raw['streamId'] };
|
|
51
|
+
}
|
|
52
|
+
if (raw['type'] === 'open'
|
|
53
|
+
&& exactKeys(raw, ['type', 'streamId', 'endpoint', 'payload'])
|
|
54
|
+
&& validId(raw['streamId'])
|
|
55
|
+
&& typeof raw['endpoint'] === 'string'
|
|
56
|
+
&& raw['endpoint'].length > 0) {
|
|
57
|
+
return { type: 'open', streamId: raw['streamId'], endpoint: raw['endpoint'], payload: raw['payload'] };
|
|
58
|
+
}
|
|
59
|
+
throw new Error('world-mux: invalid client message');
|
|
60
|
+
}
|
|
61
|
+
/** Default failure mapper: keep the error code when it looks like one. */
|
|
62
|
+
export function defaultFailure(error) {
|
|
63
|
+
if (isRecord(error)) {
|
|
64
|
+
const code = typeof error['code'] === 'string' ? error['code'] : undefined;
|
|
65
|
+
const message = typeof error['message'] === 'string' ? error['message'] : undefined;
|
|
66
|
+
if (code !== undefined)
|
|
67
|
+
return { code, message: message ?? code, details: {} };
|
|
68
|
+
}
|
|
69
|
+
return { code: 'gateway/stream-error', message: error instanceof Error ? error.message : String(error), details: {} };
|
|
70
|
+
}
|
|
71
|
+
/** One world's mux endpoint. */
|
|
72
|
+
export class WorldMuxServer {
|
|
73
|
+
open;
|
|
74
|
+
options;
|
|
75
|
+
server;
|
|
76
|
+
ws;
|
|
77
|
+
heartbeat;
|
|
78
|
+
connections = new Set();
|
|
79
|
+
missed = new WeakMap();
|
|
80
|
+
closed = false;
|
|
81
|
+
constructor(open, options = {}) {
|
|
82
|
+
this.open = open;
|
|
83
|
+
this.options = options;
|
|
84
|
+
}
|
|
85
|
+
/** Take over one accepted upgrade request. */
|
|
86
|
+
async handleUpgrade(req, socket, head) {
|
|
87
|
+
const { server, ws } = await this.load();
|
|
88
|
+
server.handleUpgrade(req, socket, head, (client) => {
|
|
89
|
+
this.missed.set(client, 0);
|
|
90
|
+
client.on('pong', () => { this.missed.set(client, 0); });
|
|
91
|
+
this.startHeartbeat();
|
|
92
|
+
const done = this.runConnection(client, ws);
|
|
93
|
+
this.connections.add(done);
|
|
94
|
+
void done.then(() => { this.connections.delete(done); });
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Terminate every socket and wait for the active streams to unwind. */
|
|
98
|
+
async close() {
|
|
99
|
+
this.closed = true;
|
|
100
|
+
clearInterval(this.heartbeat);
|
|
101
|
+
this.heartbeat = undefined;
|
|
102
|
+
const server = this.server;
|
|
103
|
+
if (server !== undefined) {
|
|
104
|
+
for (const client of server.clients)
|
|
105
|
+
client.terminate();
|
|
106
|
+
await new Promise((resolve) => { server.close(() => resolve()); });
|
|
107
|
+
}
|
|
108
|
+
await Promise.all([...this.connections]);
|
|
109
|
+
}
|
|
110
|
+
async load() {
|
|
111
|
+
if (this.server !== undefined && this.ws !== undefined)
|
|
112
|
+
return { server: this.server, ws: this.ws };
|
|
113
|
+
const mod = (await import(WS_MODULE));
|
|
114
|
+
const ws = typeof mod.WebSocketServer === 'function' ? mod : mod.default;
|
|
115
|
+
const server = new ws.WebSocketServer({ noServer: true });
|
|
116
|
+
this.server = server;
|
|
117
|
+
this.ws = ws;
|
|
118
|
+
return { server, ws };
|
|
119
|
+
}
|
|
120
|
+
async runConnection(client, ws) {
|
|
121
|
+
const streams = new Map();
|
|
122
|
+
let writes = Promise.resolve();
|
|
123
|
+
const send = (message) => {
|
|
124
|
+
let text;
|
|
125
|
+
try {
|
|
126
|
+
text = JSON.stringify(message);
|
|
127
|
+
}
|
|
128
|
+
catch (cause) {
|
|
129
|
+
return Promise.reject(new Error('world-mux: item is not JSON serializable', { cause }));
|
|
130
|
+
}
|
|
131
|
+
const delivery = writes.then(() => new Promise((resolve, reject) => {
|
|
132
|
+
if (client.readyState !== ws.WebSocket.OPEN) {
|
|
133
|
+
reject(new Error('world-mux: socket is closed'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
client.send(text, (error) => { if (error)
|
|
137
|
+
reject(error);
|
|
138
|
+
else
|
|
139
|
+
resolve(); });
|
|
140
|
+
}));
|
|
141
|
+
writes = delivery.catch(() => undefined);
|
|
142
|
+
return delivery;
|
|
143
|
+
};
|
|
144
|
+
const pump = async (streamId, endpoint, payload, active) => {
|
|
145
|
+
try {
|
|
146
|
+
const source = await this.open(endpoint, payload, active.abort.signal);
|
|
147
|
+
for await (const value of source) {
|
|
148
|
+
await send({ type: 'item', streamId, value });
|
|
149
|
+
}
|
|
150
|
+
if (!active.abort.signal.aborted)
|
|
151
|
+
await send({ type: 'end', streamId });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (!active.abort.signal.aborted && client.readyState === ws.WebSocket.OPEN) {
|
|
155
|
+
const failure = (this.options.failure ?? defaultFailure)(error);
|
|
156
|
+
try {
|
|
157
|
+
await send({ type: 'error', streamId, error: failure });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
client.close(1011, 'Remote stream failure could not be delivered');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
await new Promise((resolve) => {
|
|
166
|
+
client.once('close', () => { resolve(); });
|
|
167
|
+
client.on('error', () => { client.terminate(); });
|
|
168
|
+
client.on('message', (data, isBinary) => {
|
|
169
|
+
if (isBinary === true) {
|
|
170
|
+
client.close(1003, 'text messages required');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let message;
|
|
174
|
+
try {
|
|
175
|
+
message = parseClientMessage(String(data));
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
client.close(1008, 'invalid Remote stream request');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (message.type === 'cancel') {
|
|
182
|
+
streams.get(message.streamId)?.abort.abort(new Error('Remote stream cancelled'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (streams.has(message.streamId)) {
|
|
186
|
+
client.close(1008, 'duplicate Remote stream id');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const abort = new AbortController();
|
|
190
|
+
const active = { abort, done: Promise.resolve() };
|
|
191
|
+
streams.set(message.streamId, active);
|
|
192
|
+
const done = pump(message.streamId, message.endpoint, message.payload, active);
|
|
193
|
+
active.done = done;
|
|
194
|
+
void done.then(() => streams.delete(message.streamId), () => streams.delete(message.streamId));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
const active = [...streams.values()];
|
|
198
|
+
for (const stream of active)
|
|
199
|
+
stream.abort.abort(new Error('Remote stream socket closed'));
|
|
200
|
+
await Promise.all(active.map(stream => stream.done));
|
|
201
|
+
}
|
|
202
|
+
startHeartbeat() {
|
|
203
|
+
if (this.heartbeat !== undefined || this.closed)
|
|
204
|
+
return;
|
|
205
|
+
const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
206
|
+
this.heartbeat = setInterval(() => {
|
|
207
|
+
const server = this.server;
|
|
208
|
+
if (server === undefined)
|
|
209
|
+
return;
|
|
210
|
+
for (const client of server.clients) {
|
|
211
|
+
if (client.readyState !== (this.ws?.WebSocket.OPEN ?? 1))
|
|
212
|
+
continue;
|
|
213
|
+
const missed = this.missed.get(client) ?? 0;
|
|
214
|
+
if (missed >= MAX_MISSED_HEARTBEATS) {
|
|
215
|
+
client.terminate();
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
this.missed.set(client, missed + 1);
|
|
219
|
+
client.ping();
|
|
220
|
+
}
|
|
221
|
+
}, interval);
|
|
222
|
+
this.heartbeat.unref();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ProvisionWorldOptions {
|
|
2
|
+
/** Runtime key — also the mount label and the `agents/<key>` home segment. */
|
|
3
|
+
key: string;
|
|
4
|
+
/** The adapter's own package name (the world profile's third bundle). */
|
|
5
|
+
adapterPkg: string;
|
|
6
|
+
/** Nested home root: `<DSH_HOME>/agents/<key>`. */
|
|
7
|
+
worldHome: string;
|
|
8
|
+
/** Profile directory name under `<worldHome>/profiles`. Default `'web'`. */
|
|
9
|
+
profileName?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Provision one world's nested profile (idempotent: rewrites profile files,
|
|
13
|
+
* re-links packages — lstat-safe against dangling links). Returns the
|
|
14
|
+
* `bareModuleBaseUrl` for {@link spawnWorld}, or null when unresolvable.
|
|
15
|
+
*
|
|
16
|
+
* Never throws: any provisioning failure is logged and returns null so the
|
|
17
|
+
* calling world plugin degrades to a not-ready roster row instead of failing
|
|
18
|
+
* the loader entry — a failed apply would take down the whole host boot.
|
|
19
|
+
*/
|
|
20
|
+
export declare function provisionWorldProfile(opts: ProvisionWorldOptions): string | null;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nested-world profile provisioning — the publish story.
|
|
3
|
+
*
|
|
4
|
+
* The dev line (test/smoke.mjs) hand-provisions every world's nested profile
|
|
5
|
+
* (`<home>/agents/<label>/profiles/web`) and the `@pgmi-builds/*` links its
|
|
6
|
+
* resolution needs. A foreign install has NO line bootstrap:
|
|
7
|
+
*
|
|
8
|
+
* dsh plugin --profile <name> add super-dsh
|
|
9
|
+
*
|
|
10
|
+
* mounts the hub + adapter world plugins into the consumer's profile, and the
|
|
11
|
+
* spawn call sites (adapter world-plugins, hub join) must provision the same
|
|
12
|
+
* layout THEMSELVES or every world spawn fails on a missing profile.
|
|
13
|
+
*
|
|
14
|
+
* This module is the library form of that bootstrap, called by all six spawn
|
|
15
|
+
* sites. Layout invariants (super-dsh/AGENTS.md S7, smoke.mjs parity):
|
|
16
|
+
*
|
|
17
|
+
* - `<worldHome>/profiles/web/package.json` — bundles `[dsh-base, dsh-web-app,
|
|
18
|
+
* adapterPkg]`; the world tree is a full sibling root, nothing shared.
|
|
19
|
+
* - `<worldHome>/profiles/web/cordis.patch.yml` — per-world home pinning:
|
|
20
|
+
* every home-resolving DSH plugin reads THIS tree's nested root.
|
|
21
|
+
* - `<worldHome>/profiles/node_modules/@pgmi-builds/{adapter,hub}` — links
|
|
22
|
+
* for the profile's bundle resolution and the inserted mount rows
|
|
23
|
+
* (`@deepseek-ai/*` is loadProfile's heal farm, not ours).
|
|
24
|
+
*
|
|
25
|
+
* Link identity rule: both links point INSIDE the running line tree — the hub
|
|
26
|
+
* at this module's own package root, the adapter at its sibling
|
|
27
|
+
* `agent-<key>` directory — identical in the dev repo layout and inside the
|
|
28
|
+
* published single `super-dsh` package, so ctx0 and every world root share
|
|
29
|
+
* one hub/adapter instance with zero registry involvement.
|
|
30
|
+
*
|
|
31
|
+
* Returns the bare module base for `spawnWorld` (`…/profiles/node_modules/`),
|
|
32
|
+
* or null when the adapter sibling does not exist next to the hub (a foreign
|
|
33
|
+
* composition this module does not own — keep the caller's env/fallback path).
|
|
34
|
+
*/
|
|
35
|
+
import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { dirname, join } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import { createRequire } from 'node:module';
|
|
39
|
+
const HUB_SCOPE = '@pgmi-builds';
|
|
40
|
+
const HARNESS_SCOPE = '@deepseek-ai';
|
|
41
|
+
/** The profile bundles every world tree composes besides its adapter. */
|
|
42
|
+
const WORLD_BASE_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];
|
|
43
|
+
/**
|
|
44
|
+
* Provision one world's nested profile (idempotent: rewrites profile files,
|
|
45
|
+
* re-links packages — lstat-safe against dangling links). Returns the
|
|
46
|
+
* `bareModuleBaseUrl` for {@link spawnWorld}, or null when unresolvable.
|
|
47
|
+
*
|
|
48
|
+
* Never throws: any provisioning failure is logged and returns null so the
|
|
49
|
+
* calling world plugin degrades to a not-ready roster row instead of failing
|
|
50
|
+
* the loader entry — a failed apply would take down the whole host boot.
|
|
51
|
+
*/
|
|
52
|
+
export function provisionWorldProfile(opts) {
|
|
53
|
+
try {
|
|
54
|
+
return provisionWorldProfileUnchecked(opts);
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
console.error(`[super-dsh] provisioning world '${opts.key}' failed:`, cause);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function provisionWorldProfileUnchecked(opts) {
|
|
62
|
+
// Single-pack topology: this module lives at `<line>/agent-hub/dist/`, and
|
|
63
|
+
// every adapter is a SIBLING directory (`<line>/agent-<key>`) — the same
|
|
64
|
+
// layout in the repo (dev link: lines) and inside the published `super-dsh`
|
|
65
|
+
// package. The adapter link therefore points at the sibling; no registry
|
|
66
|
+
// resolution is involved anywhere.
|
|
67
|
+
const hubRoot = dirname(dirname(fileURLToPath(import.meta.url))); // <line>/agent-hub
|
|
68
|
+
const lineRoot = dirname(hubRoot); // monolith root / repo super-dsh/
|
|
69
|
+
const adapterDir = join(lineRoot, `agent-${opts.key}`);
|
|
70
|
+
if (!existsSync(join(adapterDir, 'package.json')))
|
|
71
|
+
return null;
|
|
72
|
+
const profileName = opts.profileName ?? 'web';
|
|
73
|
+
const profDir = join(opts.worldHome, 'profiles', profileName);
|
|
74
|
+
mkdirSync(profDir, { recursive: true });
|
|
75
|
+
writeFileSync(join(profDir, 'package.json'), JSON.stringify({
|
|
76
|
+
name: `aw-${opts.key}-world-profile`,
|
|
77
|
+
private: true,
|
|
78
|
+
dsh: { profile: { bundles: [...WORLD_BASE_BUNDLES, opts.adapterPkg] } },
|
|
79
|
+
}, null, 2) + '\n');
|
|
80
|
+
// Per-world home pinning (smoke.mjs parity): settings, credentials,
|
|
81
|
+
// attachments, shell env and skills all land inside `agents/<key>/`.
|
|
82
|
+
// (`agent-instructions` keeps its base `maxBytes` home — instruction
|
|
83
|
+
// discovery root, not agent data.)
|
|
84
|
+
writeFileSync(join(profDir, 'cordis.patch.yml'), [
|
|
85
|
+
'# Per-world home pinning (dshHomePath is this tree\'s nested home).',
|
|
86
|
+
...['settings', 'credentials', 'attachment-local', 'shell-env', 'skill-filesystem'].flatMap((id) => [`- id: ${id}`, ' config:', ' dshHome: !!js dshHomePath()']),
|
|
87
|
+
'',
|
|
88
|
+
].join('\n'));
|
|
89
|
+
// @pgmi-builds links for the world tree's bare-name resolution (profile
|
|
90
|
+
// bundles + the inserted mount rows). Dangling links first (lstat:
|
|
91
|
+
// existsSync is false for them, which skipped rmSync and crashed
|
|
92
|
+
// symlinkSync with EEXIST after repo moves).
|
|
93
|
+
const linkRoot = join(opts.worldHome, 'profiles', 'node_modules');
|
|
94
|
+
mkdirSync(join(linkRoot, HUB_SCOPE), { recursive: true });
|
|
95
|
+
const links = [
|
|
96
|
+
[opts.adapterPkg.slice(HUB_SCOPE.length + 1), adapterDir],
|
|
97
|
+
['agent-hub', hubRoot],
|
|
98
|
+
];
|
|
99
|
+
for (const [name, target] of links) {
|
|
100
|
+
const dst = join(linkRoot, HUB_SCOPE, name);
|
|
101
|
+
const st = lstatSync(dst, { throwIfNoEntry: false });
|
|
102
|
+
if (st)
|
|
103
|
+
rmSync(dst, { recursive: true, force: true });
|
|
104
|
+
symlinkSync(target, dst);
|
|
105
|
+
}
|
|
106
|
+
// The harness scope rides along as ONE scope-level link into the first
|
|
107
|
+
// `@deepseek-ai` directory visible from this module (the shared heal farm
|
|
108
|
+
// in a dsh home, the repo farm in dev) — every `@deepseek-ai/*` row of the
|
|
109
|
+
// world composition resolves through it without farming per package.
|
|
110
|
+
for (const searchPath of createRequire(import.meta.url).resolve.paths(`${HARNESS_SCOPE}/x`) ?? []) {
|
|
111
|
+
const scopeDir = join(searchPath, HARNESS_SCOPE);
|
|
112
|
+
if (!existsSync(scopeDir))
|
|
113
|
+
continue;
|
|
114
|
+
const dst = join(linkRoot, HARNESS_SCOPE);
|
|
115
|
+
const st = lstatSync(dst, { throwIfNoEntry: false });
|
|
116
|
+
if (st)
|
|
117
|
+
rmSync(dst, { recursive: true, force: true });
|
|
118
|
+
symlinkSync(scopeDir, dst);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
return join(opts.worldHome, 'profiles', 'node_modules') + '/';
|
|
122
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-world virtual webServer (AW-B DL3).
|
|
3
|
+
*
|
|
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:
|
|
8
|
+
*
|
|
9
|
+
* world registers { kind, path } → real registers { kind, `${labelPath}${path}` }
|
|
10
|
+
* real receives /<label>/<path>/... → plugin handler sees /<path>/...
|
|
11
|
+
*
|
|
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).
|
|
21
|
+
*/
|
|
22
|
+
import { type IndexInjection } from '@deepseek-ai/dsh-host-webserver';
|
|
23
|
+
/** Route match kind (mirrors the upstream contract). */
|
|
24
|
+
export type WebRouteKind = 'exact' | 'prefix';
|
|
25
|
+
/** The slice of node:http we touch (kept structural for testability). */
|
|
26
|
+
export interface WorldRequest {
|
|
27
|
+
url?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WorldResponse {
|
|
30
|
+
writeHead(status: number, headers: Record<string, string>): unknown;
|
|
31
|
+
end(...args: unknown[]): unknown;
|
|
32
|
+
}
|
|
33
|
+
/** A world route handler as plugins write it. */
|
|
34
|
+
export type WorldHandler = (req: WorldRequest, ...rest: never[]) => void | Promise<void>;
|
|
35
|
+
/** One named route registration (structural mirror of the upstream type). */
|
|
36
|
+
export interface WorldWebRoute {
|
|
37
|
+
kind: WebRouteKind;
|
|
38
|
+
path: string;
|
|
39
|
+
handler: (req: WorldRequest, res: WorldResponse) => void | Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/** One exact-path HTTP upgrade registration. */
|
|
42
|
+
export interface WorldWebUpgradeRoute {
|
|
43
|
+
path: string;
|
|
44
|
+
handler: (req: WorldRequest, socket: unknown, head: Buffer) => void | Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/** The real (ctx0) webServer surface this world forwards into. */
|
|
47
|
+
export interface RealWebServerFace {
|
|
48
|
+
register(route: {
|
|
49
|
+
kind: WebRouteKind;
|
|
50
|
+
path: string;
|
|
51
|
+
handler: never;
|
|
52
|
+
}): () => void;
|
|
53
|
+
registerUpgrade(route: {
|
|
54
|
+
path: string;
|
|
55
|
+
handler: never;
|
|
56
|
+
}): () => void;
|
|
57
|
+
registerFallback(handler: never): () => void;
|
|
58
|
+
tapIndex?(transform: (html: string) => string): () => void;
|
|
59
|
+
readonly port?: number;
|
|
60
|
+
readonly host?: string;
|
|
61
|
+
}
|
|
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
|
+
/** Remove the mount label from `req.url`, preserving the trailing query. */
|
|
80
|
+
export declare function stripLabelFromUrl(url: string | undefined, labelPath: string): string;
|
|
81
|
+
/** One world's web surface, mounted under `<labelPath>`. */
|
|
82
|
+
export declare class WorldWebServer {
|
|
83
|
+
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
|
+
/**
|
|
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).
|
|
108
|
+
*/
|
|
109
|
+
private static hubOwned;
|
|
110
|
+
/**
|
|
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.
|
|
118
|
+
*/
|
|
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;
|
|
130
|
+
/**
|
|
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.
|
|
139
|
+
*/
|
|
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[];
|
|
147
|
+
/**
|
|
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.
|
|
155
|
+
*/
|
|
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;
|
|
162
|
+
}
|