super-dsh 0.3.4-a → 0.3.4-c
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/dist/world-plugin.js +1 -0
- package/agent-agy-sdk/dist/world-plugin.js +1 -0
- package/agent-codex/dist/world-plugin.js +1 -0
- package/agent-codex-sdk/dist/world-plugin.js +1 -0
- package/agent-hermes/dist/hermes-client.js +34 -18
- package/agent-hermes/dist/world-plugin.js +1 -0
- package/agent-omp/dist/world-plugin.js +1 -0
- package/agent-omp-sdk/dist/world-plugin.js +1 -0
- package/agent-pi/dist/world-plugin.js +1 -0
- package/package.json +1 -1
- package/superd-hub/dist/world-join.js +1 -0
- package/superd-hub/dist/world-provision.d.ts +4 -0
- package/superd-hub/dist/world-provision.js +37 -0
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* HermesGatewayClient — the reusable TUI-gateway client seam (AW-H Task 3).
|
|
3
3
|
*
|
|
4
4
|
* One client = one gateway child bound to one gateway session. Transports:
|
|
5
|
-
* `stdio`
|
|
6
|
-
*
|
|
5
|
+
* `stdio` spawns `python -m tui_gateway.entry` (owns the Hermes home
|
|
6
|
+
* exclusively); `attach` (SUPERD_HERMES_TRANSPORT=attach) spawns the
|
|
7
7
|
* WS-attach bridge — same stdio ndjson JSON-RPC contract, frames relayed to
|
|
8
|
-
* the profile's canonical gateway WebSocket
|
|
8
|
+
* the profile's canonical gateway WebSocket; `auto` (default) = stdio first,
|
|
9
|
+
* flips to attach on the gateway ownership refusal (resolveHermesTransportMode).
|
|
9
10
|
* follow the upstream reference client `ui-tui/src/gatewayClient.ts` and the
|
|
10
11
|
* Task 1 spike (`src/spike.ts`, live-pinned protocol facts):
|
|
11
12
|
*
|
|
@@ -170,20 +171,20 @@ export function loadHermesDotEnv() {
|
|
|
170
171
|
return {};
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
*
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
export function resolveHermesTransportMode(dotEnv = {}) {
|
|
175
|
+
const raw = (process.env.SUPERD_HERMES_TRANSPORT?.trim() || dotEnv.SUPERD_HERMES_TRANSPORT?.trim() || "auto").toLowerCase();
|
|
176
|
+
return raw === "stdio" || raw === "attach" ? raw : "auto";
|
|
177
|
+
}
|
|
178
|
+
/** The hermes-side refusal that makes stdio impossible while a resident
|
|
179
|
+
* gateway holds the home: `... is owned by a running gateway; attach to it
|
|
180
|
+
* instead` (hermes' own exit text, observed 2026-11-02 on PC + dev3). */
|
|
181
|
+
const GATEWAY_OWNERSHIP_REFUSAL = /owned by a running gateway/i;
|
|
182
|
+
/** Process-wide sticky: a resident gateway owns `~/.hermes`; every future
|
|
183
|
+
* spawn in this process (any client) skips the doomed stdio attempt. */
|
|
184
|
+
let gatewayOwnershipDetected = false;
|
|
185
|
+
/** Test-only: clear the sticky auto-attach flip between tests. */
|
|
186
|
+
export function resetHermesAutoAttachForTests() {
|
|
187
|
+
gatewayOwnershipDetected = false;
|
|
187
188
|
}
|
|
188
189
|
/** The WS-attach bridge script, shipped at the package root (`bridge/`). */
|
|
189
190
|
export function hermesBridgeScriptPath() {
|
|
@@ -222,6 +223,8 @@ export class HermesGatewayClient {
|
|
|
222
223
|
#readySettled = false;
|
|
223
224
|
/** Pre-ready respawn attempts (full-lazy ruling: bounded at SPAWN_ATTEMPTS). */
|
|
224
225
|
#spawnAttempts = 0;
|
|
226
|
+
/** Transport used by the CURRENT child attempt (drives the auto-attach flip). */
|
|
227
|
+
#transport = "stdio";
|
|
225
228
|
#readyTimer = null;
|
|
226
229
|
#killTimer = null;
|
|
227
230
|
#failure = null;
|
|
@@ -271,7 +274,12 @@ export class HermesGatewayClient {
|
|
|
271
274
|
const root = resolveHermesRoot();
|
|
272
275
|
const python = resolveHermesPython(root);
|
|
273
276
|
const dotEnv = loadHermesDotEnv();
|
|
274
|
-
const
|
|
277
|
+
const mode = resolveHermesTransportMode(dotEnv);
|
|
278
|
+
// auto: stdio first; once a resident gateway's ownership refusal is seen,
|
|
279
|
+
// the whole process flips to attach (sticky — later clients skip the
|
|
280
|
+
// doomed stdio attempt entirely). Explicit stdio/attach never flip.
|
|
281
|
+
const transport = mode === "attach" || (mode === "auto" && gatewayOwnershipDetected) ? "attach" : "stdio";
|
|
282
|
+
this.#transport = transport;
|
|
275
283
|
const env = { ...(opts.env ?? process.env) };
|
|
276
284
|
if (this.#hubSession !== undefined)
|
|
277
285
|
env.SUPERD_HUB_SESSION = this.#hubSession;
|
|
@@ -953,6 +961,10 @@ export class HermesGatewayClient {
|
|
|
953
961
|
this.#fail(error);
|
|
954
962
|
return;
|
|
955
963
|
}
|
|
964
|
+
if (!gatewayOwnershipDetected && this.#transport === "stdio" && GATEWAY_OWNERSHIP_REFUSAL.test(error.message)) {
|
|
965
|
+
gatewayOwnershipDetected = true;
|
|
966
|
+
this.#trace("gateway ownership refusal in spawn failure — auto-attach engaged for future spawns");
|
|
967
|
+
}
|
|
956
968
|
if (this.#spawnAttempts < HERMES_SPAWN_ATTEMPTS) {
|
|
957
969
|
this.#trace(`pre-ready failure on spawn attempt ${this.#spawnAttempts}/${HERMES_SPAWN_ATTEMPTS} — respawning: ${error.message.slice(0, 200)}`);
|
|
958
970
|
this.#discardChildQuietly();
|
|
@@ -1048,6 +1060,10 @@ export class HermesGatewayClient {
|
|
|
1048
1060
|
if (this.#stderrLines.length > STDERR_RING)
|
|
1049
1061
|
this.#stderrLines.shift();
|
|
1050
1062
|
this.#trace(`stderr: ${trimmed.slice(0, 240)}`);
|
|
1063
|
+
if (!gatewayOwnershipDetected && this.#transport === "stdio" && GATEWAY_OWNERSHIP_REFUSAL.test(trimmed)) {
|
|
1064
|
+
gatewayOwnershipDetected = true;
|
|
1065
|
+
this.#trace("gateway ownership refusal on stderr — auto-attach engaged for future spawns");
|
|
1066
|
+
}
|
|
1051
1067
|
}
|
|
1052
1068
|
#stderrTail() {
|
|
1053
1069
|
return this.#stderrLines.slice(-20).join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.3.4-
|
|
3
|
+
"version": "0.3.4-c",
|
|
4
4
|
"description": "Super D for the DeepSeek Harness (DSH) — one dsh plugin that mounts the superd hub (runtime selector, roster, per-runtime agents) and joins local foreign-agent runtimes (OMP, Codex, Claude, Pi, Hermes, Antigravity) into the DSH Web UI. Each runtime keeps its native home and session store; the hub only bridges.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,10 @@ export interface ProvisionWorldOptions {
|
|
|
23
23
|
worldHome: string;
|
|
24
24
|
/** Profile directory name under `<worldHome>/profiles`. Default `'web'`. */
|
|
25
25
|
profileName?: string;
|
|
26
|
+
/** Host surface (the caller's ctx0 context) — carries `profileContext` for CTX0 mirrors. */
|
|
27
|
+
host?: {
|
|
28
|
+
get(name: string): unknown;
|
|
29
|
+
};
|
|
26
30
|
}
|
|
27
31
|
/**
|
|
28
32
|
* Provision one world's nested profile (idempotent: rewrites profile files,
|
|
@@ -104,6 +104,36 @@ function resolveHostPackageDir(spec) {
|
|
|
104
104
|
}
|
|
105
105
|
throw new Error(`super-dsh: cannot resolve ${spec} from the running host (set SUPERD_DSH_ANCHOR)`);
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Copy the CTX0 profile patch's `ui-settings-general` row (id..next-row) verbatim
|
|
109
|
+
* into a world's patch template. Best-effort: no CTX0 facts or no row → no lines.
|
|
110
|
+
*/
|
|
111
|
+
function mirrorCtx0SettingsGeneralRow(host) {
|
|
112
|
+
try {
|
|
113
|
+
const launcher = host?.get('profileContext');
|
|
114
|
+
if (launcher === undefined || typeof launcher.home !== 'string' || typeof launcher.name !== 'string')
|
|
115
|
+
return [];
|
|
116
|
+
const patch = readFileSync(join(launcher.home, 'profiles', launcher.name, 'cordis.patch.yml'), 'utf8');
|
|
117
|
+
const lines = patch.split('\n');
|
|
118
|
+
const start = lines.findIndex((line) => line.trim() === '- id: ui-settings-general');
|
|
119
|
+
if (start < 0)
|
|
120
|
+
return [];
|
|
121
|
+
let end = start + 1;
|
|
122
|
+
while (end < lines.length && !lines[end].startsWith('- id:'))
|
|
123
|
+
end++;
|
|
124
|
+
while (end > start && lines[end - 1].trim() === '')
|
|
125
|
+
end--;
|
|
126
|
+
return [
|
|
127
|
+
'# Mirror of the CTX0 profile patch (ui-settings-general): worlds project the',
|
|
128
|
+
'# same settings namespace values — the welcome-notice ack rides this row.',
|
|
129
|
+
...lines.slice(start, end),
|
|
130
|
+
'',
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return []; // best-effort mirror: the notice degrades to today's behavior
|
|
135
|
+
}
|
|
136
|
+
}
|
|
107
137
|
/**
|
|
108
138
|
* Provision one world's nested profile (idempotent: rewrites profile files,
|
|
109
139
|
* re-links packages — lstat-safe against dangling links). Returns whether
|
|
@@ -171,6 +201,13 @@ function provisionWorldProfileUnchecked(opts) {
|
|
|
171
201
|
' disabled: true',
|
|
172
202
|
'- id: client-hmr',
|
|
173
203
|
' disabled: true',
|
|
204
|
+
// Native settings namespaces project their values from the composition
|
|
205
|
+
// row, not a mutable store: without this mirror every world reads
|
|
206
|
+
// `ui-settings-general` as UNACKNOWLEDGED and the native Preview Notice
|
|
207
|
+
// mounts (its Continue cannot save — the read-back projects the same
|
|
208
|
+
// composition). Mirror the CTX0 row verbatim so worlds see what ctx0
|
|
209
|
+
// sees; upstream version bumps flow through automatically.
|
|
210
|
+
...mirrorCtx0SettingsGeneralRow(opts.host),
|
|
174
211
|
'',
|
|
175
212
|
].join('\n'));
|
|
176
213
|
// @pgmi-builds links, plugin-add shape: adapter + hub into the world
|