super-dsh 0.3.3 → 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.
Files changed (42) hide show
  1. package/README.md +15 -14
  2. package/agent-agy/dist/world-plugin.js +1 -0
  3. package/agent-agy/package.json +6 -6
  4. package/agent-agy-sdk/dist/world-plugin.js +1 -0
  5. package/agent-agy-sdk/package.json +5 -5
  6. package/agent-claude/package.json +8 -8
  7. package/agent-codex/dist/world-plugin.js +1 -0
  8. package/agent-codex/package.json +7 -7
  9. package/agent-codex-sdk/dist/world-plugin.js +1 -0
  10. package/agent-codex-sdk/package.json +7 -7
  11. package/agent-hermes/bridge/hermes-ws-bridge.py +273 -0
  12. package/agent-hermes/dist/agent.js +27 -4
  13. package/agent-hermes/dist/hermes-client.js +55 -10
  14. package/agent-hermes/dist/index.js +12 -3
  15. package/agent-hermes/dist/world-plugin.js +1 -0
  16. package/agent-hermes/package.json +7 -7
  17. package/agent-omp/dist/world-plugin.js +1 -0
  18. package/agent-omp/package.json +8 -8
  19. package/agent-omp-sdk/dist/world-plugin.js +1 -0
  20. package/agent-omp-sdk/package.json +8 -8
  21. package/agent-pi/dist/world-plugin.js +1 -0
  22. package/agent-pi/package.json +7 -7
  23. package/client/index.js +106 -249
  24. package/cordis.patch.yml +0 -6
  25. package/package.json +23 -27
  26. package/remote/plugin.d.ts +93 -20
  27. package/remote/plugin.js +921 -452
  28. package/remote/provenance.json +15 -13
  29. package/superd-hub/dist/fusion-transport.js +0 -1
  30. package/superd-hub/dist/world-join.js +1 -0
  31. package/superd-hub/dist/world-provision.d.ts +4 -0
  32. package/superd-hub/dist/world-provision.js +37 -0
  33. package/superd-hub/lib/client/index.js +106 -249
  34. package/superd-hub/package.json +11 -11
  35. package/superd-hub/dist/bundle-setup.d.ts +0 -27
  36. package/superd-hub/dist/bundle-setup.js +0 -94
  37. package/superd-hub/dist/client/companion-api.d.ts +0 -5
  38. package/superd-hub/dist/client/companion-api.js +0 -30
  39. package/superd-hub/dist/companion-contract.d.ts +0 -9
  40. package/superd-hub/dist/companion-contract.js +0 -1
  41. package/superd-hub/dist/companions.d.ts +0 -30
  42. package/superd-hub/dist/companions.js +0 -211
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * HermesGatewayClient — the reusable TUI-gateway client seam (AW-H Task 3).
3
3
  *
4
- * One client = one spawned gateway child (`python -m tui_gateway.entry`,
5
- * stdio ndjson JSON-RPC) = one bound gateway session. Spawn/env/ready patterns
4
+ * One client = one gateway child bound to one gateway session. Transports:
5
+ * `stdio` spawns `python -m tui_gateway.entry` (owns the Hermes home
6
+ * exclusively); `attach` (SUPERD_HERMES_TRANSPORT=attach) spawns the
7
+ * WS-attach bridge — same stdio ndjson JSON-RPC contract, frames relayed to
8
+ * the profile's canonical gateway WebSocket; `auto` (default) = stdio first,
9
+ * flips to attach on the gateway ownership refusal (resolveHermesTransportMode).
6
10
  * follow the upstream reference client `ui-tui/src/gatewayClient.ts` and the
7
11
  * Task 1 spike (`src/spike.ts`, live-pinned protocol facts):
8
12
  *
@@ -64,6 +68,7 @@ import { spawn, execFile } from "node:child_process";
64
68
  import { existsSync, readFileSync } from "node:fs";
65
69
  import { homedir } from "node:os";
66
70
  import { delimiter, resolve, join } from "node:path";
71
+ import { fileURLToPath } from "node:url";
67
72
  import { promisify } from "node:util";
68
73
  import { prepareHermesNativePlugin } from "@pgmi-builds/superd-hub/foreign-native";
69
74
  import { createInterface } from "node:readline";
@@ -166,6 +171,28 @@ export function loadHermesDotEnv() {
166
171
  return {};
167
172
  }
168
173
  }
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;
188
+ }
189
+ /** The WS-attach bridge script, shipped at the package root (`bridge/`). */
190
+ export function hermesBridgeScriptPath() {
191
+ const path = fileURLToPath(new URL("../bridge/hermes-ws-bridge.py", import.meta.url));
192
+ if (!existsSync(path))
193
+ throw new Error(`hermes ws-attach bridge missing: ${path}`);
194
+ return path;
195
+ }
169
196
  function asRecord(value) {
170
197
  if (value === null || typeof value !== "object" || Array.isArray(value))
171
198
  return null;
@@ -196,6 +223,8 @@ export class HermesGatewayClient {
196
223
  #readySettled = false;
197
224
  /** Pre-ready respawn attempts (full-lazy ruling: bounded at SPAWN_ATTEMPTS). */
198
225
  #spawnAttempts = 0;
226
+ /** Transport used by the CURRENT child attempt (drives the auto-attach flip). */
227
+ #transport = "stdio";
199
228
  #readyTimer = null;
200
229
  #killTimer = null;
201
230
  #failure = null;
@@ -244,6 +273,13 @@ export class HermesGatewayClient {
244
273
  const attempt = this.#spawnAttempts;
245
274
  const root = resolveHermesRoot();
246
275
  const python = resolveHermesPython(root);
276
+ const dotEnv = loadHermesDotEnv();
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;
247
283
  const env = { ...(opts.env ?? process.env) };
248
284
  if (this.#hubSession !== undefined)
249
285
  env.SUPERD_HUB_SESSION = this.#hubSession;
@@ -253,7 +289,7 @@ export class HermesGatewayClient {
253
289
  // pi runtimes load their own native `.env`; the hermes gateway child does
254
290
  // not, and the harness env carries no provider keys — without this fill
255
291
  // every provider request dies as quota/auth regardless of model.
256
- for (const [dotKey, dotValue] of Object.entries(loadHermesDotEnv())) {
292
+ for (const [dotKey, dotValue] of Object.entries(dotEnv)) {
257
293
  if (env[dotKey] === undefined || env[dotKey] === "")
258
294
  env[dotKey] = dotValue;
259
295
  }
@@ -266,12 +302,11 @@ export class HermesGatewayClient {
266
302
  delete env.HERMES_CWD;
267
303
  delete env.HERMES_TUI_GATEWAY_URL;
268
304
  delete env.HERMES_TUI_SIDECAR_URL;
269
- this.#child = gatewayFactory({
270
- command: python,
271
- args: ["-m", "tui_gateway.entry"],
272
- cwd: opts.cwd ?? root,
273
- env,
274
- });
305
+ const childSpec = transport === "attach"
306
+ ? { command: python, args: [hermesBridgeScriptPath()], cwd: opts.cwd ?? root, env }
307
+ : { command: python, args: ["-m", "tui_gateway.entry"], cwd: opts.cwd ?? root, env };
308
+ this.#trace(`spawn transport=${transport} cmd=${childSpec.command} args=${JSON.stringify(childSpec.args)}`);
309
+ this.#child = gatewayFactory(childSpec);
275
310
  // The ready gate is created ONCE and survives every pre-ready respawn —
276
311
  // replacing it would orphan callers awaiting the earlier object.
277
312
  if (this.#readyPromise === null) {
@@ -283,7 +318,9 @@ export class HermesGatewayClient {
283
318
  // surface an unhandledRejection when this gate rejects (timeout/close).
284
319
  this.#readyPromise.catch(() => { });
285
320
  }
286
- const readyTimeoutMs = opts.readyTimeoutMs ?? READY_TIMEOUT_MS;
321
+ // attach: gateway discovery (5s) or an idle-exit daemon start (ensure
322
+ // timeout 30s) can exceed the stdio boot budget before gateway.ready.
323
+ const readyTimeoutMs = opts.readyTimeoutMs ?? (transport === "attach" ? 45_000 : READY_TIMEOUT_MS);
287
324
  this.#readyTimer = setTimeout(() => {
288
325
  this.#preReadyFailure(new Error(`timed out waiting for gateway.ready after ${readyTimeoutMs}ms (spawn attempt ${attempt}/${HERMES_SPAWN_ATTEMPTS}); stderr tail:\n${this.#stderrTail()}`));
289
326
  }, readyTimeoutMs);
@@ -924,6 +961,10 @@ export class HermesGatewayClient {
924
961
  this.#fail(error);
925
962
  return;
926
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
+ }
927
968
  if (this.#spawnAttempts < HERMES_SPAWN_ATTEMPTS) {
928
969
  this.#trace(`pre-ready failure on spawn attempt ${this.#spawnAttempts}/${HERMES_SPAWN_ATTEMPTS} — respawning: ${error.message.slice(0, 200)}`);
929
970
  this.#discardChildQuietly();
@@ -1019,6 +1060,10 @@ export class HermesGatewayClient {
1019
1060
  if (this.#stderrLines.length > STDERR_RING)
1020
1061
  this.#stderrLines.shift();
1021
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
+ }
1022
1067
  }
1023
1068
  #stderrTail() {
1024
1069
  return this.#stderrLines.slice(-20).join("\n");
@@ -364,9 +364,18 @@ export class HermesProvider extends Service {
364
364
  throw new Error(`cannot resume session "${id}": no Hermes gateway session is recorded for this Dash session id`);
365
365
  }
366
366
  const persistence = this.requirePersistence("resume");
367
- if ((record.gatewayStoredSessionId === null || record.gatewayStoredSessionId === undefined)
368
- && (await readStoredEvents(persistence, id, options.signal)).some(event => event.type === "turn/start" || event.type === "user/message" || event.type === "assistant/message")) {
369
- throw new Error(`cannot resume session "${id}": no durable gateway session id is recorded (this record predates durable-id capture; re-create the session)`);
367
+ if (record.gatewayStoredSessionId === null || record.gatewayStoredSessionId === undefined) {
368
+ const events = await readStoredEvents(persistence, id, options.signal);
369
+ if (events.some(event => event.type === "turn/start" || event.type === "user/message" || event.type === "assistant/message")) {
370
+ if (events.some(event => event.type === "assistant/message")) {
371
+ // Completed turns but no durable id: a record from before the adapter captured ids.
372
+ throw new Error(`cannot resume session "${id}": no durable gateway session id is recorded (this record predates durable-id capture; re-create the session)`);
373
+ }
374
+ // Turn content with no assistant output and no durable id: the gateway never produced a
375
+ // session for this record (cold-start failure — e.g. the native home was owned by a
376
+ // running gateway). Nothing in it reached the model, so resume has nothing to attach to.
377
+ throw new Error(`cannot resume session "${id}": no durable gateway session id is recorded (the Hermes gateway failed before this session was created; its recorded turn never reached the model — re-create the session)`);
378
+ }
370
379
  }
371
380
  // Spawn cwd comes from the recorded pairing — re-realpath and re-verify.
372
381
  const spawnCwd = validatedCwd(record.cwd);
@@ -46,6 +46,7 @@ export function apply(ctx) {
46
46
  key: KEY,
47
47
  adapterPkg: '@pgmi-builds/agent-hermes',
48
48
  worldHome,
49
+ host: ctx,
49
50
  });
50
51
  const world = ownWorld(ctx, { key: KEY, label: 'Hermes' }, (ownerId) => spawnWorld({
51
52
  appName: `superd-${KEY}`,
@@ -26,13 +26,13 @@
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@deepseek-ai/cordis": "^4.0.1",
29
- "@deepseek-ai/dsh-agent": ">=0.2.2-0 <1.0.0-0",
30
- "@deepseek-ai/dsh-agent-presets": ">=0.2.2-0 <1.0.0-0",
31
- "@deepseek-ai/dsh-llm": ">=0.2.2-0 <1.0.0-0",
32
- "@deepseek-ai/dsh-scope": ">=0.2.2-0 <1.0.0-0",
33
- "@deepseek-ai/dsh-session": ">=0.2.2-0 <1.0.0-0",
34
- "@deepseek-ai/dsh-session-persistence": ">=0.2.2-0 <1.0.0-0",
35
- "@deepseek-ai/dsh-typert-protocol": ">=0.2.2-0 <1.0.0-0",
29
+ "@deepseek-ai/dsh-agent": ">=0.2.0-0 <1.0.0-0",
30
+ "@deepseek-ai/dsh-agent-presets": ">=0.2.0-0 <1.0.0-0",
31
+ "@deepseek-ai/dsh-llm": ">=0.2.0-0 <1.0.0-0",
32
+ "@deepseek-ai/dsh-scope": ">=0.2.0-0 <1.0.0-0",
33
+ "@deepseek-ai/dsh-session": ">=0.2.0-0 <1.0.0-0",
34
+ "@deepseek-ai/dsh-session-persistence": ">=0.2.0-0 <1.0.0-0",
35
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-0 <1.0.0-0",
36
36
  "@pgmi-builds/superd-hub": "*"
37
37
  },
38
38
  "peerDependenciesMeta": {
@@ -62,6 +62,7 @@ export function apply(ctx) {
62
62
  key: KEY,
63
63
  adapterPkg: '@pgmi-builds/agent-omp',
64
64
  worldHome,
65
+ host: ctx,
65
66
  });
66
67
  const world = ownWorld(ctx, { key: KEY, label: 'OMP' }, (ownerId) => spawnWorld({
67
68
  appName: `superd-${KEY}`,
@@ -34,14 +34,14 @@
34
34
  "dependencies": {},
35
35
  "peerDependencies": {
36
36
  "@deepseek-ai/cordis": "^4.0.1",
37
- "@deepseek-ai/dsh-agent": ">=0.2.2-0 <1.0.0-0",
38
- "@deepseek-ai/dsh-agent-presets": ">=0.2.2-0 <1.0.0-0",
39
- "@deepseek-ai/dsh-llm": ">=0.2.2-0 <1.0.0-0",
40
- "@deepseek-ai/dsh-scope": ">=0.2.2-0 <1.0.0-0",
41
- "@deepseek-ai/dsh-session": ">=0.2.2-0 <1.0.0-0",
42
- "@deepseek-ai/dsh-session-persistence": ">=0.2.2-0 <1.0.0-0",
43
- "@deepseek-ai/dsh-typert-protocol": ">=0.2.2-0 <1.0.0-0",
44
- "@deepseek-ai/dsh-home-paths": ">=0.2.2-0 <1.0.0-0",
37
+ "@deepseek-ai/dsh-agent": ">=0.2.0-0 <1.0.0-0",
38
+ "@deepseek-ai/dsh-agent-presets": ">=0.2.0-0 <1.0.0-0",
39
+ "@deepseek-ai/dsh-llm": ">=0.2.0-0 <1.0.0-0",
40
+ "@deepseek-ai/dsh-scope": ">=0.2.0-0 <1.0.0-0",
41
+ "@deepseek-ai/dsh-session": ">=0.2.0-0 <1.0.0-0",
42
+ "@deepseek-ai/dsh-session-persistence": ">=0.2.0-0 <1.0.0-0",
43
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-0 <1.0.0-0",
44
+ "@deepseek-ai/dsh-home-paths": ">=0.2.0-0 <1.0.0-0",
45
45
  "@pgmi-builds/superd-hub": "*"
46
46
  },
47
47
  "peerDependenciesMeta": {
@@ -79,6 +79,7 @@ export function apply(ctx) {
79
79
  key: KEY,
80
80
  adapterPkg: '@pgmi-builds/agent-omp-sdk',
81
81
  worldHome,
82
+ host: ctx,
82
83
  });
83
84
  const world = ownWorld(ctx, { key: KEY, label: 'OMP (SDK)' }, (ownerId) => spawnWorld({
84
85
  appName: `superd-${KEY}`,
@@ -26,14 +26,14 @@
26
26
  "dependencies": {},
27
27
  "peerDependencies": {
28
28
  "@deepseek-ai/cordis": "^4.0.1",
29
- "@deepseek-ai/dsh-agent": ">=0.2.2-0 <1.0.0-0",
30
- "@deepseek-ai/dsh-agent-presets": ">=0.2.2-0 <1.0.0-0",
31
- "@deepseek-ai/dsh-llm": ">=0.2.2-0 <1.0.0-0",
32
- "@deepseek-ai/dsh-scope": ">=0.2.2-0 <1.0.0-0",
33
- "@deepseek-ai/dsh-session": ">=0.2.2-0 <1.0.0-0",
34
- "@deepseek-ai/dsh-session-persistence": ">=0.2.2-0 <1.0.0-0",
35
- "@deepseek-ai/dsh-typert-protocol": ">=0.2.2-0 <1.0.0-0",
36
- "@deepseek-ai/dsh-home-paths": ">=0.2.2-0 <1.0.0-0",
29
+ "@deepseek-ai/dsh-agent": ">=0.2.0-0 <1.0.0-0",
30
+ "@deepseek-ai/dsh-agent-presets": ">=0.2.0-0 <1.0.0-0",
31
+ "@deepseek-ai/dsh-llm": ">=0.2.0-0 <1.0.0-0",
32
+ "@deepseek-ai/dsh-scope": ">=0.2.0-0 <1.0.0-0",
33
+ "@deepseek-ai/dsh-session": ">=0.2.0-0 <1.0.0-0",
34
+ "@deepseek-ai/dsh-session-persistence": ">=0.2.0-0 <1.0.0-0",
35
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-0 <1.0.0-0",
36
+ "@deepseek-ai/dsh-home-paths": ">=0.2.0-0 <1.0.0-0",
37
37
  "@pgmi-builds/superd-hub": "*"
38
38
  },
39
39
  "peerDependenciesMeta": {
@@ -43,6 +43,7 @@ export function apply(ctx) {
43
43
  key: KEY,
44
44
  adapterPkg: '@pgmi-builds/agent-pi',
45
45
  worldHome,
46
+ host: ctx,
46
47
  });
47
48
  const world = ownWorld(ctx, { key: KEY, label: 'Pi' }, (ownerId) => spawnWorld({
48
49
  appName: `superd-${KEY}`,
@@ -27,13 +27,13 @@
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@deepseek-ai/cordis": "^4.0.1",
30
- "@deepseek-ai/dsh-agent": ">=0.2.2-0 <1.0.0-0",
31
- "@deepseek-ai/dsh-agent-presets": ">=0.2.2-0 <1.0.0-0",
32
- "@deepseek-ai/dsh-llm": ">=0.2.2-0 <1.0.0-0",
33
- "@deepseek-ai/dsh-scope": ">=0.2.2-0 <1.0.0-0",
34
- "@deepseek-ai/dsh-session": ">=0.2.2-0 <1.0.0-0",
35
- "@deepseek-ai/dsh-session-persistence": ">=0.2.2-0 <1.0.0-0",
36
- "@deepseek-ai/dsh-typert-protocol": ">=0.2.2-0 <1.0.0-0",
30
+ "@deepseek-ai/dsh-agent": ">=0.2.0-0 <1.0.0-0",
31
+ "@deepseek-ai/dsh-agent-presets": ">=0.2.0-0 <1.0.0-0",
32
+ "@deepseek-ai/dsh-llm": ">=0.2.0-0 <1.0.0-0",
33
+ "@deepseek-ai/dsh-scope": ">=0.2.0-0 <1.0.0-0",
34
+ "@deepseek-ai/dsh-session": ">=0.2.0-0 <1.0.0-0",
35
+ "@deepseek-ai/dsh-session-persistence": ">=0.2.0-0 <1.0.0-0",
36
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-0 <1.0.0-0",
37
37
  "@pgmi-builds/superd-hub": "*"
38
38
  },
39
39
  "peerDependenciesMeta": {