super-dsh 0.3.2 → 0.3.3-b
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/agent.d.ts +4 -1
- package/agent-agy/dist/agent.js +15 -1
- package/agent-agy/dist/index.js +5 -8
- package/agent-agy/package.json +6 -6
- package/agent-agy-sdk/dist/agent.d.ts +4 -1
- package/agent-agy-sdk/dist/agent.js +15 -1
- package/agent-agy-sdk/dist/index.js +5 -8
- package/agent-agy-sdk/package.json +5 -5
- package/agent-claude/dist/agent.js +15 -1
- package/agent-claude/package.json +8 -8
- package/agent-codex/dist/agent.js +15 -1
- package/agent-codex/package.json +7 -7
- package/agent-codex-sdk/dist/agent.js +15 -1
- package/agent-codex-sdk/dist/index.js +5 -11
- package/agent-codex-sdk/package.json +7 -7
- package/agent-hermes/dist/agent.js +15 -1
- package/agent-hermes/dist/index.js +5 -8
- package/agent-hermes/package.json +7 -7
- package/agent-omp/dist/agent.js +26 -2
- package/agent-omp/package.json +8 -8
- package/agent-omp-sdk/dist/agent.d.ts +4 -1
- package/agent-omp-sdk/dist/agent.js +15 -1
- package/agent-omp-sdk/package.json +8 -8
- package/agent-pi/dist/agent.js +20 -2
- package/agent-pi/dist/index.js +5 -11
- package/agent-pi/package.json +7 -7
- package/package.json +21 -21
- package/remote/hub-pty-pump.mjs +97 -0
- package/remote/plugin.d.ts +476 -200
- package/remote/plugin.js +1445 -664
- package/remote/provenance.json +22 -17
- package/superd-hub/dist/agent-model-face.d.ts +93 -0
- package/superd-hub/dist/agent-model-face.js +113 -0
- package/superd-hub/dist/exec.d.ts +3 -2
- package/superd-hub/dist/exec.js +6 -5
- package/superd-hub/dist/hub-tool.d.ts +1 -1
- package/superd-hub/dist/hub-tool.js +6 -6
- package/superd-hub/dist/index.d.ts +1 -0
- package/superd-hub/dist/index.js +1 -0
- package/superd-hub/dist/remote-provider.js +70 -36
- package/superd-hub/dist/world-entry.js +30 -0
- package/superd-hub/package.json +14 -10
- package/superd-hub/dist/exec-pty-channel.d.ts +0 -58
- package/superd-hub/dist/exec-pty-channel.js +0 -162
- package/superd-hub/dist/exec-pty-framing.d.ts +0 -34
- package/superd-hub/dist/exec-pty-framing.js +0 -89
- package/superd-hub/dist/exec-pty-session.d.ts +0 -78
- package/superd-hub/dist/exec-pty-session.js +0 -228
|
@@ -4,7 +4,7 @@ import { LlmAttemptId, ToolCallId, QUOTA_EXCEEDED_CODE, createAssistantMessage,
|
|
|
4
4
|
import { createScope } from "@deepseek-ai/dsh-scope";
|
|
5
5
|
import { ompPermissionCommand } from "./permission.js";
|
|
6
6
|
import { getBridgeStore } from "./store/index.js";
|
|
7
|
-
import { withSystemHead } from "../../superd-hub/dist/index.js";
|
|
7
|
+
import { createAgentModelFace, withSystemHead } from "../../superd-hub/dist/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* v2 live assistant-stream publication for the OMP bridge — the same frame
|
|
10
10
|
* protocol the reference loop's AssistantStreamAttempt speaks (start marker,
|
|
@@ -254,6 +254,9 @@ export class OmpAgent {
|
|
|
254
254
|
session;
|
|
255
255
|
inbox;
|
|
256
256
|
ctx;
|
|
257
|
+
/** Durable model selection (0.2.2 Agent face): the shared hub face over
|
|
258
|
+
* `model/selection` events — see superd-hub agent-model-face.ts. */
|
|
259
|
+
model;
|
|
257
260
|
#rpc;
|
|
258
261
|
#loopCtx;
|
|
259
262
|
#scope;
|
|
@@ -304,6 +307,17 @@ export class OmpAgent {
|
|
|
304
307
|
this.id = id;
|
|
305
308
|
this.options = options;
|
|
306
309
|
this.session = withSystemHead(session, () => createSystemMessage(""));
|
|
310
|
+
this.model = createAgentModelFace({
|
|
311
|
+
session: this.session,
|
|
312
|
+
options: this.options,
|
|
313
|
+
appendSelection: (selection) => {
|
|
314
|
+
this.session.append("model/selection", selection);
|
|
315
|
+
},
|
|
316
|
+
readDefault: () => {
|
|
317
|
+
const service = this.ctx.get("agentDefaultModel");
|
|
318
|
+
return service?.currentSelection?.();
|
|
319
|
+
},
|
|
320
|
+
});
|
|
307
321
|
this.#rpc = rpc;
|
|
308
322
|
this.#onIdleExit = onIdleExit;
|
|
309
323
|
this.#dispatch = agentEvents(loopCtx, this);
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"dependencies": {},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
29
|
-
"@deepseek-ai/dsh-agent": ">=0.
|
|
30
|
-
"@deepseek-ai/dsh-agent-presets": ">=0.
|
|
31
|
-
"@deepseek-ai/dsh-llm": ">=0.
|
|
32
|
-
"@deepseek-ai/dsh-scope": ">=0.
|
|
33
|
-
"@deepseek-ai/dsh-session": ">=0.
|
|
34
|
-
"@deepseek-ai/dsh-session-persistence": ">=0.
|
|
35
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.
|
|
36
|
-
"@deepseek-ai/dsh-home-paths": ">=0.
|
|
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",
|
|
37
37
|
"@pgmi-builds/superd-hub": "*"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
package/agent-pi/dist/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { agentEvents } from "@deepseek-ai/dsh-agent";
|
|
4
4
|
import { Inbox } from "./inbox.js";
|
|
5
5
|
import { LlmAttemptId, ToolCallId, QUOTA_EXCEEDED_CODE, createAssistantMessage, createSystemMessage, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
|
-
import { withSystemHead } from "../../superd-hub/dist/index.js";
|
|
6
|
+
import { createAgentModelFace, recordedModelSelection, withSystemHead } from "../../superd-hub/dist/index.js";
|
|
7
7
|
import { createScope } from "@deepseek-ai/dsh-scope";
|
|
8
8
|
import { PI_IDLE_EXIT_MS } from "./knobs.js";
|
|
9
9
|
import { trace } from "./knobs.js";
|
|
@@ -176,6 +176,9 @@ export class PiAgent {
|
|
|
176
176
|
session;
|
|
177
177
|
inbox;
|
|
178
178
|
ctx;
|
|
179
|
+
/** Durable model selection (0.2.2 Agent face): the shared hub face over
|
|
180
|
+
* `model/selection` events — see superd-hub agent-model-face.ts. */
|
|
181
|
+
model;
|
|
179
182
|
#client;
|
|
180
183
|
#loopCtx;
|
|
181
184
|
#scope;
|
|
@@ -237,6 +240,17 @@ export class PiAgent {
|
|
|
237
240
|
this.id = id;
|
|
238
241
|
this.options = options;
|
|
239
242
|
this.session = withSystemHead(session, () => createSystemMessage(""));
|
|
243
|
+
this.model = createAgentModelFace({
|
|
244
|
+
session: this.session,
|
|
245
|
+
options: this.options,
|
|
246
|
+
appendSelection: (selection) => {
|
|
247
|
+
this.session.append("model/selection", selection);
|
|
248
|
+
},
|
|
249
|
+
readDefault: () => {
|
|
250
|
+
const service = this.ctx.get("agentDefaultModel");
|
|
251
|
+
return service?.currentSelection?.();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
240
254
|
this.#client = client;
|
|
241
255
|
this.#onIdleExit = onIdleExit;
|
|
242
256
|
this.#runtimeInfo = runtimeInfo;
|
|
@@ -522,7 +536,11 @@ export class PiAgent {
|
|
|
522
536
|
* LIVE (an upgrade over the codex SDK line's next-turn switch).
|
|
523
537
|
*/
|
|
524
538
|
async #syncModelSelection() {
|
|
525
|
-
|
|
539
|
+
// 0.2.2: the recorded-selection fold (pending ?? last-used) leads the
|
|
540
|
+
// waterfall, so a `model.select` switch lands on the NEXT turn — the
|
|
541
|
+
// native precedence (pending retires only on a matching request header).
|
|
542
|
+
const recorded = recordedModelSelection(this.session.snapshotEvents());
|
|
543
|
+
const persisted = recorded ?? this.session.requestHeader()?.config;
|
|
526
544
|
const service = this.ctx.get("agentDefaultModel");
|
|
527
545
|
const selection = service?.currentSelection();
|
|
528
546
|
const target = persisted !== undefined && persisted.provider !== "" && persisted.model !== ""
|
package/agent-pi/dist/index.js
CHANGED
|
@@ -29,7 +29,6 @@ import { bindForeignHub } from '@pgmi-builds/superd-hub/foreign-hub';
|
|
|
29
29
|
import { realpathSync, statSync } from "node:fs";
|
|
30
30
|
import { join } from "node:path";
|
|
31
31
|
import { Service } from "@deepseek-ai/cordis";
|
|
32
|
-
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
33
32
|
import { SessionLogOffset, SessionPreparation, interruptedTurnClosers, } from "@deepseek-ai/dsh-session";
|
|
34
33
|
import { PiSessionClient } from "./pi-client.js";
|
|
35
34
|
import { PiAgent } from "./agent.js";
|
|
@@ -514,16 +513,11 @@ async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, sessi
|
|
|
514
513
|
const route = runtimeInfo?.routeContext(agentOptions);
|
|
515
514
|
if (route !== undefined) {
|
|
516
515
|
const selection = { provider: route.provider, model: route.model };
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
});
|
|
523
|
-
// `model/selection` is contributed to `SessionEventMap` by the upstream
|
|
524
|
-
// session-controller package (declaration merging); this adapter does
|
|
525
|
-
// not depend on it, so the append goes through a narrow local cast.
|
|
526
|
-
session.append("model/selection", selection);
|
|
516
|
+
// 0.2.2: installModelSelection dropped its injected-ref parameter — the
|
|
517
|
+
// durable face (`agent.model`, the same mechanism session/selectModel
|
|
518
|
+
// uses) carries the pin: select() appends the typed, deduped
|
|
519
|
+
// `model/selection` event.
|
|
520
|
+
agent.model.select(selection);
|
|
527
521
|
trace(`session route pinned: ${route.provider}/${route.model}`);
|
|
528
522
|
}
|
|
529
523
|
}
|
package/agent-pi/package.json
CHANGED
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
30
|
-
"@deepseek-ai/dsh-agent": ">=0.
|
|
31
|
-
"@deepseek-ai/dsh-agent-presets": ">=0.
|
|
32
|
-
"@deepseek-ai/dsh-llm": ">=0.
|
|
33
|
-
"@deepseek-ai/dsh-scope": ">=0.
|
|
34
|
-
"@deepseek-ai/dsh-session": ">=0.
|
|
35
|
-
"@deepseek-ai/dsh-session-persistence": ">=0.
|
|
36
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.
|
|
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",
|
|
37
37
|
"@pgmi-builds/superd-hub": "*"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3-b",
|
|
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",
|
|
@@ -93,26 +93,26 @@
|
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
95
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
96
|
-
"@deepseek-ai/dsh-agent": ">=0.
|
|
97
|
-
"@deepseek-ai/dsh-agent-presets": ">=0.
|
|
98
|
-
"@deepseek-ai/dsh-api-gateway": ">=0.
|
|
99
|
-
"@deepseek-ai/dsh-commands": ">=0.
|
|
100
|
-
"@deepseek-ai/dsh-home-paths": ">=0.
|
|
101
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.
|
|
102
|
-
"@deepseek-ai/dsh-llm": ">=0.
|
|
103
|
-
"@deepseek-ai/dsh-scope": ">=0.
|
|
104
|
-
"@deepseek-ai/dsh-session": ">=0.
|
|
105
|
-
"@deepseek-ai/dsh-session-persistence": ">=0.
|
|
106
|
-
"@deepseek-ai/dsh-tools": ">=0.
|
|
107
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.
|
|
108
|
-
"@deepseek-ai/dsh-user-approval": ">=0.
|
|
109
|
-
"@deepseek-ai/dsh-user-questions": ">=0.
|
|
110
|
-
"@deepseek-ai/dsh-client-connection": ">=0.
|
|
111
|
-
"@deepseek-ai/dsh-app-boot": ">=0.
|
|
112
|
-
"@deepseek-ai/dsh-atomic-write": ">=0.
|
|
113
|
-
"@deepseek-ai/dsh-cmdline": ">=0.
|
|
114
|
-
"@deepseek-ai/dsh-plugin-manager": ">=0.
|
|
115
|
-
"@deepseek-ai/dsh-jobs": ">=0.
|
|
96
|
+
"@deepseek-ai/dsh-agent": ">=0.2.2-0 <1.0.0-0",
|
|
97
|
+
"@deepseek-ai/dsh-agent-presets": ">=0.2.2-0 <1.0.0-0",
|
|
98
|
+
"@deepseek-ai/dsh-api-gateway": ">=0.2.2-0 <1.0.0-0",
|
|
99
|
+
"@deepseek-ai/dsh-commands": ">=0.2.2-0 <1.0.0-0",
|
|
100
|
+
"@deepseek-ai/dsh-home-paths": ">=0.2.2-0 <1.0.0-0",
|
|
101
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.2.2-0 <1.0.0-0",
|
|
102
|
+
"@deepseek-ai/dsh-llm": ">=0.2.2-0 <1.0.0-0",
|
|
103
|
+
"@deepseek-ai/dsh-scope": ">=0.2.2-0 <1.0.0-0",
|
|
104
|
+
"@deepseek-ai/dsh-session": ">=0.2.2-0 <1.0.0-0",
|
|
105
|
+
"@deepseek-ai/dsh-session-persistence": ">=0.2.2-0 <1.0.0-0",
|
|
106
|
+
"@deepseek-ai/dsh-tools": ">=0.2.2-0 <1.0.0-0",
|
|
107
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.2.2-0 <1.0.0-0",
|
|
108
|
+
"@deepseek-ai/dsh-user-approval": ">=0.2.2-0 <1.0.0-0",
|
|
109
|
+
"@deepseek-ai/dsh-user-questions": ">=0.2.2-0 <1.0.0-0",
|
|
110
|
+
"@deepseek-ai/dsh-client-connection": ">=0.2.2-0 <1.0.0-0",
|
|
111
|
+
"@deepseek-ai/dsh-app-boot": ">=0.2.2-0 <1.0.0-0",
|
|
112
|
+
"@deepseek-ai/dsh-atomic-write": ">=0.2.2-0 <1.0.0-0",
|
|
113
|
+
"@deepseek-ai/dsh-cmdline": ">=0.2.2-0 <1.0.0-0",
|
|
114
|
+
"@deepseek-ai/dsh-plugin-manager": ">=0.2.2-0 <1.0.0-0",
|
|
115
|
+
"@deepseek-ai/dsh-jobs": ">=0.2.2-0 <1.0.0-0"
|
|
116
116
|
},
|
|
117
117
|
"peerDependenciesMeta": {
|
|
118
118
|
"@deepseek-ai/cordis": {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Hub PTY pump — the DUMB child of the canonical PTY session over a Hub
|
|
3
|
+
// machine (0.3.2-c). The session (better-dsh/src/remote/pty-session.ts)
|
|
4
|
+
// spawns `script -qfec 'stty -echo; … exec node <this file>' /dev/null`, so
|
|
5
|
+
// this process's stdin/stdout ride a host-side PTY; we forward those bytes
|
|
6
|
+
// over one WebSocket to the peer hub's dumb PTY executor
|
|
7
|
+
// (/api/superd/exec/pty: one WS = one `script -qfec bash /dev/null`, text
|
|
8
|
+
// frames = stream bytes, {"op":…} JSON frames = transport control, WS close
|
|
9
|
+
// = peer group-kill). All session intelligence (nonce framing, init
|
|
10
|
+
// handshake, env E-frames, two-stage interrupt, idle TTL) is in-band bytes
|
|
11
|
+
// produced by the SESSION — this pump never parses the stream, only the
|
|
12
|
+
// peer's control frames.
|
|
13
|
+
//
|
|
14
|
+
// Connection facts arrive via environment (never argv — credentials must not
|
|
15
|
+
// show up in `ps`):
|
|
16
|
+
// SUPERD_HUB_PTY_URL wss://…/api/superd/exec/pty (peer executor)
|
|
17
|
+
// SUPERD_HUB_PTY_COOKIE pairing cookie for the upgrade request
|
|
18
|
+
// SUPERD_HUB_PTY_WS ABSOLUTE path to a usable `ws` module (resolved by
|
|
19
|
+
// the caller before spawning; no resolution gamble
|
|
20
|
+
// here — a missing module is a creation-time error)
|
|
21
|
+
//
|
|
22
|
+
// Verbatim-asset discipline (helper-bin precedent): this file is copied
|
|
23
|
+
// byte-for-byte into lib/remote/ and the Super closure; no build step
|
|
24
|
+
// transforms it. Crash-proof by construction (mesh-gateway lesson): the
|
|
25
|
+
// 'error' listener is registered before anything can reject, and post-close
|
|
26
|
+
// sends are swallowed — the session's death path owns the teardown.
|
|
27
|
+
const PUMP_ENV_PREFIX = 'SUPERD_HUB_PTY_'
|
|
28
|
+
const [url, cookie, wsPath] = ['URL', 'COOKIE', 'WS'].map((k) => process.env[`SUPERD_HUB_PTY_${k}`])
|
|
29
|
+
|
|
30
|
+
// cookie may be legitimately empty (an unauthenticated test lane sends no header);
|
|
31
|
+
// the endpoint URL and the ws module path are the hard requirements.
|
|
32
|
+
if (url === undefined || url === '' || wsPath === undefined || wsPath === '') {
|
|
33
|
+
process.stderr.write(`hub-pty-pump: missing environment (${PUMP_ENV_PREFIX}URL/WS)\n`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let WebSocket
|
|
38
|
+
try {
|
|
39
|
+
const require = (await import('node:module')).createRequire(import.meta.url)
|
|
40
|
+
const mod = require(wsPath)
|
|
41
|
+
WebSocket = typeof mod === 'function' ? mod : mod.default
|
|
42
|
+
} catch (err) {
|
|
43
|
+
process.stderr.write(`hub-pty-pump: cannot load ws module at ${wsPath} — ${err instanceof Error ? err.message : String(err)}\n`)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
if (typeof WebSocket !== 'function') {
|
|
47
|
+
process.stderr.write('hub-pty-pump: the ws module did not yield a WebSocket constructor\n')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const socket = new WebSocket(url, { headers: cookie ? { cookie } : {} })
|
|
52
|
+
let opened = false
|
|
53
|
+
let forward = true
|
|
54
|
+
|
|
55
|
+
socket.on('error', (err) => {
|
|
56
|
+
// Always-on listener: an ECONNREFUSED or a rejected 401 upgrade must be a
|
|
57
|
+
// reported death, never an uncaught exception.
|
|
58
|
+
process.stderr.write(`hub-pty-pump: ${opened ? 'socket error' : 'connect failed'} — ${err instanceof Error ? err.message : String(err)}\n`)
|
|
59
|
+
})
|
|
60
|
+
socket.on('open', () => { opened = true; pumpStdin() })
|
|
61
|
+
socket.on('message', (data) => {
|
|
62
|
+
if (!forward) return
|
|
63
|
+
const text = typeof data === 'string' ? data : Array.isArray(data) ? Buffer.concat(data).toString('utf8') : Buffer.from(data).toString('utf8')
|
|
64
|
+
// Transport control frames (peer: {"op":"exit"} / {"op":"spawn-error"}) are
|
|
65
|
+
// addressed to THIS pump, not to the shell stream — the exit frame is
|
|
66
|
+
// followed by a socket close, which settles us below.
|
|
67
|
+
if (text.startsWith('{"op":')) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(text)
|
|
70
|
+
if (typeof parsed.op === 'string') {
|
|
71
|
+
if (parsed.op === 'spawn-error' && typeof parsed.error === 'string') process.stderr.write(`hub-pty-pump: peer could not spawn the terminal — ${parsed.error}\n`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
} catch { /* not JSON after all: fall through and forward */ }
|
|
75
|
+
}
|
|
76
|
+
process.stdout.write(text)
|
|
77
|
+
})
|
|
78
|
+
socket.on('close', () => {
|
|
79
|
+
forward = false
|
|
80
|
+
// Before open this is a failed connection (diag already on stderr); after
|
|
81
|
+
// open it is the peer session ending — either way the child exiting is the
|
|
82
|
+
// session's dead signal.
|
|
83
|
+
process.exit(opened ? 0 : 1)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function pumpStdin() {
|
|
87
|
+
process.stdin.setEncoding('utf8')
|
|
88
|
+
process.stdin.on('data', (chunk) => {
|
|
89
|
+
if (!opened) return
|
|
90
|
+
try { socket.send(chunk) } catch { /* closing: the close handler exits us */ }
|
|
91
|
+
})
|
|
92
|
+
// The session's kill path tears down the process group; a plain stdin end
|
|
93
|
+
// (no kill) still means nobody will type more — close politely so the peer
|
|
94
|
+
// reaps its side.
|
|
95
|
+
process.stdin.on('end', () => { try { socket.close() } catch { /* already closing */ } })
|
|
96
|
+
process.stdin.on('error', () => { /* EPIPE on teardown: the close path owns the exit */ })
|
|
97
|
+
}
|