super-dsh 0.1.6 → 0.1.10-e
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 +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1883 -287
- package/cordis.patch.yml +80 -0
- package/package.json +23 -9
- package/superd-hub/dist/a2a-delivery.d.ts +189 -0
- package/superd-hub/dist/a2a-delivery.js +240 -0
- package/superd-hub/dist/a2a-host.d.ts +76 -0
- package/superd-hub/dist/a2a-host.js +145 -0
- package/superd-hub/dist/a2a-ingest.d.ts +31 -0
- package/superd-hub/dist/a2a-ingest.js +636 -0
- package/superd-hub/dist/a2a-log.d.ts +17 -0
- package/superd-hub/dist/a2a-log.js +42 -0
- package/superd-hub/dist/a2a-row.d.ts +65 -0
- package/superd-hub/dist/a2a-row.js +162 -0
- package/superd-hub/dist/a2a-service.d.ts +357 -0
- package/superd-hub/dist/a2a-service.js +980 -0
- package/superd-hub/dist/a2a-wire.d.ts +220 -0
- package/superd-hub/dist/a2a-wire.js +305 -0
- package/superd-hub/dist/agent-ids.d.ts +10 -0
- package/superd-hub/dist/agent-ids.js +68 -0
- package/superd-hub/dist/agent-roster.d.ts +29 -18
- package/superd-hub/dist/agent-roster.js +54 -15
- package/superd-hub/dist/agent-status.d.ts +119 -0
- package/superd-hub/dist/agent-status.js +386 -0
- package/superd-hub/dist/client/agent-settings.d.ts +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -0
- package/superd-hub/dist/client/mesh-api.d.ts +134 -0
- package/superd-hub/dist/client/mesh-api.js +219 -0
- package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
- package/superd-hub/dist/client/mesh-groups.js +66 -0
- package/superd-hub/dist/client/roster.d.ts +48 -0
- package/superd-hub/dist/client/roster.js +102 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
- package/superd-hub/dist/exec-pty-channel.js +162 -0
- package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
- package/superd-hub/dist/exec-pty-framing.js +81 -0
- package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
- package/superd-hub/dist/exec-pty-rpc.js +128 -0
- package/superd-hub/dist/exec-pty-session.d.ts +76 -0
- package/superd-hub/dist/exec-pty-session.js +234 -0
- package/superd-hub/dist/exec.d.ts +56 -0
- package/superd-hub/dist/exec.js +225 -0
- package/superd-hub/dist/gateway.d.ts +39 -2
- package/superd-hub/dist/gateway.js +62 -3
- package/superd-hub/dist/hub-tool.d.ts +174 -0
- package/superd-hub/dist/hub-tool.js +464 -0
- package/superd-hub/dist/index.d.ts +15 -1
- package/superd-hub/dist/index.js +16 -0
- package/superd-hub/dist/machines.d.ts +45 -0
- package/superd-hub/dist/machines.js +125 -0
- package/superd-hub/dist/mesh-federate.d.ts +138 -0
- package/superd-hub/dist/mesh-federate.js +223 -0
- package/superd-hub/dist/mesh-gateway.d.ts +67 -0
- package/superd-hub/dist/mesh-gateway.js +408 -0
- package/superd-hub/dist/mesh-link.d.ts +274 -0
- package/superd-hub/dist/mesh-link.js +441 -0
- package/superd-hub/dist/mesh-rpc.d.ts +123 -0
- package/superd-hub/dist/mesh-rpc.js +250 -0
- package/superd-hub/dist/mux-client.d.ts +71 -0
- package/superd-hub/dist/mux-client.js +281 -0
- package/superd-hub/dist/pack-version.d.ts +2 -0
- package/superd-hub/dist/pack-version.js +30 -0
- package/superd-hub/dist/roster.d.ts +6 -0
- package/superd-hub/dist/rpc.d.ts +18 -2
- package/superd-hub/dist/rpc.js +34 -4
- package/superd-hub/dist/spawn-world.js +3 -1
- package/superd-hub/dist/switch-core.d.ts +62 -25
- package/superd-hub/dist/switch-core.js +53 -30
- package/superd-hub/dist/system-head.d.ts +22 -12
- package/superd-hub/dist/system-head.js +100 -84
- package/superd-hub/dist/wire-endpoints.d.ts +264 -0
- package/superd-hub/dist/wire-endpoints.js +180 -0
- package/superd-hub/lib/client/index.js +1883 -287
- package/superd-hub/package.json +11 -2
- 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/models.js +0 -152
- 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-omp/dist/sidecar-client.js +0 -195
- 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
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// PTY channels — the ONE seam spec §8.2 carves out of better-dsh's
|
|
2
|
+
// PtySession: the session machinery (nonce injection, framing, two-stage
|
|
3
|
+
// interrupt, idle TTL) is transport-agnostic; only {open, write, kill} differ
|
|
4
|
+
// between "a local `script -qfec` subprocess" (this machine as target) and
|
|
5
|
+
// "a WebSocket to the peer's dumb PTY executor" (a paired machine as target).
|
|
6
|
+
//
|
|
7
|
+
// The WS client follows mesh-gateway's crash-proofing lesson: an 'error'
|
|
8
|
+
// listener is ALWAYS registered (ECONNREFUSED / a rejected 401 upgrade would
|
|
9
|
+
// otherwise be an uncaught exception), and sends before 'open' queue in
|
|
10
|
+
// order while post-close sends are swallowed (the session's death path owns
|
|
11
|
+
// the teardown).
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
/** Local target: `script -qfec bash /dev/null` — host-side PTY, no node-pty (spec §8.2 / D12). */
|
|
15
|
+
export class LocalScriptChannel {
|
|
16
|
+
argv;
|
|
17
|
+
diag = '';
|
|
18
|
+
proc = null;
|
|
19
|
+
deadCb;
|
|
20
|
+
constructor(argv = ['script', '-qfec', 'bash', '/dev/null']) {
|
|
21
|
+
this.argv = argv;
|
|
22
|
+
}
|
|
23
|
+
async open(onData) {
|
|
24
|
+
const proc = spawn(this.argv[0], this.argv.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], detached: true });
|
|
25
|
+
this.proc = proc;
|
|
26
|
+
proc.stdout.setEncoding('utf8');
|
|
27
|
+
proc.stdout.on('data', (d) => onData(d));
|
|
28
|
+
proc.stderr.setEncoding('utf8').on('data', (d) => { this.diag = (this.diag + d).slice(-2_000); });
|
|
29
|
+
proc.once('close', () => this.deadCb?.());
|
|
30
|
+
proc.once('error', (err) => { this.diag = (this.diag + `\n[pty: spawn failed: ${err.message}]`).slice(-2_000); this.deadCb?.(); });
|
|
31
|
+
}
|
|
32
|
+
write(s) {
|
|
33
|
+
if (this.proc?.stdin?.writable !== true)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
this.proc.stdin.write(s);
|
|
37
|
+
}
|
|
38
|
+
catch { /* dead: the death callback settles the session */ }
|
|
39
|
+
}
|
|
40
|
+
close() {
|
|
41
|
+
this.write('exit\n');
|
|
42
|
+
const pid = this.proc?.pid;
|
|
43
|
+
this.proc = null;
|
|
44
|
+
if (pid === undefined)
|
|
45
|
+
return;
|
|
46
|
+
setTimeout(() => { try {
|
|
47
|
+
process.kill(-pid, 'SIGKILL');
|
|
48
|
+
}
|
|
49
|
+
catch { /* already gone */ } }, 1_000).unref();
|
|
50
|
+
}
|
|
51
|
+
kill() {
|
|
52
|
+
const pid = this.proc?.pid;
|
|
53
|
+
this.proc = null;
|
|
54
|
+
if (pid === undefined)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
process.kill(-pid, 'SIGTERM');
|
|
58
|
+
}
|
|
59
|
+
catch { /* already gone */ }
|
|
60
|
+
setTimeout(() => { try {
|
|
61
|
+
process.kill(-pid, 'SIGKILL');
|
|
62
|
+
}
|
|
63
|
+
catch { /* already gone */ } }, 2_000).unref();
|
|
64
|
+
}
|
|
65
|
+
onDead(callback) { this.deadCb = callback; }
|
|
66
|
+
}
|
|
67
|
+
const WS_MODULE = 'ws';
|
|
68
|
+
let wsConstructor;
|
|
69
|
+
function loadWs() {
|
|
70
|
+
if (wsConstructor !== undefined)
|
|
71
|
+
return wsConstructor;
|
|
72
|
+
const require = createRequire(import.meta.url);
|
|
73
|
+
const mod = require(WS_MODULE);
|
|
74
|
+
const boxed = mod;
|
|
75
|
+
wsConstructor = (typeof mod === 'function' ? mod : boxed.default);
|
|
76
|
+
return wsConstructor;
|
|
77
|
+
}
|
|
78
|
+
/** Remote target: a WebSocket to the peer hub's dumb PTY executor, pairing Cookie riding the upgrade. */
|
|
79
|
+
export class WsPtyChannel {
|
|
80
|
+
url;
|
|
81
|
+
headers;
|
|
82
|
+
wsCtor;
|
|
83
|
+
diag = '';
|
|
84
|
+
socket = null;
|
|
85
|
+
deadCb;
|
|
86
|
+
pending = [];
|
|
87
|
+
opened = false;
|
|
88
|
+
constructor(url, headers = {}, wsCtor = undefined) {
|
|
89
|
+
this.url = url;
|
|
90
|
+
this.headers = headers;
|
|
91
|
+
this.wsCtor = wsCtor;
|
|
92
|
+
}
|
|
93
|
+
open(onData) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
let settled = false;
|
|
96
|
+
let socket;
|
|
97
|
+
try {
|
|
98
|
+
socket = new (this.wsCtor ?? loadWs())(this.url, { headers: this.headers });
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.socket = socket;
|
|
105
|
+
// crash-proof by construction (mesh-gateway fix-round-1 lesson): 'error'
|
|
106
|
+
// ALWAYS has a listener; 'close' is guaranteed to follow and the dead
|
|
107
|
+
// path owns the teardown.
|
|
108
|
+
socket.on('error', (err) => { this.diag = (this.diag + `\n[pty-ws: ${err instanceof Error ? err.message : String(err)}]`).slice(-2_000); });
|
|
109
|
+
socket.on('open', () => {
|
|
110
|
+
if (settled)
|
|
111
|
+
return;
|
|
112
|
+
settled = true;
|
|
113
|
+
this.opened = true;
|
|
114
|
+
for (const chunk of this.pending.splice(0))
|
|
115
|
+
socket.send(chunk);
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
socket.on('message', (data) => {
|
|
119
|
+
const text = typeof data === 'string'
|
|
120
|
+
? data
|
|
121
|
+
: Array.isArray(data)
|
|
122
|
+
? Buffer.concat(data).toString('utf8')
|
|
123
|
+
: Buffer.from(data).toString('utf8');
|
|
124
|
+
onData(text);
|
|
125
|
+
});
|
|
126
|
+
socket.on('close', () => {
|
|
127
|
+
if (!settled) {
|
|
128
|
+
settled = true;
|
|
129
|
+
reject(new Error(`pty-ws: socket closed before opening${this.diag !== '' ? `; ${this.diag}` : ''}`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.opened = false;
|
|
133
|
+
this.deadCb?.();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
write(s) {
|
|
138
|
+
if (this.socket === null)
|
|
139
|
+
return;
|
|
140
|
+
if (!this.opened) {
|
|
141
|
+
this.pending.push(s);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
this.socket.send(s);
|
|
146
|
+
}
|
|
147
|
+
catch { /* dead: the close path settles the session */ }
|
|
148
|
+
}
|
|
149
|
+
close() {
|
|
150
|
+
try {
|
|
151
|
+
this.socket?.close();
|
|
152
|
+
}
|
|
153
|
+
catch { /* already closing */ }
|
|
154
|
+
}
|
|
155
|
+
kill() {
|
|
156
|
+
try {
|
|
157
|
+
this.socket?.terminate();
|
|
158
|
+
}
|
|
159
|
+
catch { /* already gone */ }
|
|
160
|
+
}
|
|
161
|
+
onDead(callback) { this.deadCb = callback; }
|
|
162
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** 收帧帧体:退出码 + 只读回传的 cwd(Non-Goal ②:报告不重建)。 */
|
|
2
|
+
export interface PtyFrame {
|
|
3
|
+
exit: number;
|
|
4
|
+
cwd: string | null;
|
|
5
|
+
}
|
|
6
|
+
/** 8 字节随机串的 hex 形(spec §五.2:每次下发前生成)。 */
|
|
7
|
+
export declare function genNonce(bytes?: number): string;
|
|
8
|
+
/** 命令整行:花括号复合体 + Nonce 收帧尾(spec §五.2 的 printf 逐字落地 + cwd 第三字段)。 */
|
|
9
|
+
export declare function wrapPtyCommand(cmd: string, nonce: string): string;
|
|
10
|
+
/** init 整行:哑终端(echo/PS1/PS2/TERM)+ 固定 40×120(Non-Goal ③)+ INIT 帧。 */
|
|
11
|
+
export declare function buildInitCommand(nonce: string): string;
|
|
12
|
+
export declare function stripAnsi(s: string): string;
|
|
13
|
+
export declare function normalizePtyText(s: string): string;
|
|
14
|
+
export declare function tailWindow(text: string, cap: number): {
|
|
15
|
+
text: string;
|
|
16
|
+
truncated?: number;
|
|
17
|
+
};
|
|
18
|
+
export interface FrameParserEvents {
|
|
19
|
+
/** 输出文本:feed 路径经 normalize(剥 ANSI/规整换行);flush 路径为**裸放出**(残余字节原样,勿假设已剥洗)。 */
|
|
20
|
+
onOutput?(text: string): void;
|
|
21
|
+
onFrame(frame: PtyFrame): void;
|
|
22
|
+
}
|
|
23
|
+
export interface NonceFrameParser {
|
|
24
|
+
feed(chunk: string): void;
|
|
25
|
+
flush(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 单 nonce、单帧解析器:一轮 dispatch 一个实例。只认 `\x1b]133;D;<nonce>;`,
|
|
29
|
+
* 错误 nonce 的静态/伪造 marker 永不匹配,按普通输出流过(再经 ANSI 剥洗)。
|
|
30
|
+
* 缓冲尾若可能是 marker 前缀则扣留,flush() 时作为输出放出。
|
|
31
|
+
*/
|
|
32
|
+
export declare function createNonceFrameParser(nonce: string, events: FrameParserEvents): NonceFrameParser;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// PTY nonce framing — PORTED VERBATIM from better-dsh src/remote/nonce-framing.ts
|
|
2
|
+
// (spec §8.2 / D12 "原样照抄"; only the import specifiers were adapted to this
|
|
3
|
+
// package's ESM convention). Command wrap = brace compound + OSC-133;D nonce
|
|
4
|
+
// tail carrying exit + cwd; parser holds back possible marker prefixes and
|
|
5
|
+
// flushes leftovers raw.
|
|
6
|
+
import { randomBytes } from 'node:crypto';
|
|
7
|
+
/** 8 字节随机串的 hex 形(spec §五.2:每次下发前生成)。 */
|
|
8
|
+
export function genNonce(bytes = 8) {
|
|
9
|
+
return randomBytes(bytes).toString('hex');
|
|
10
|
+
}
|
|
11
|
+
/** 命令整行:花括号复合体 + Nonce 收帧尾(spec §五.2 的 printf 逐字落地 + cwd 第三字段)。 */
|
|
12
|
+
export function wrapPtyCommand(cmd, nonce) {
|
|
13
|
+
return `{\n${cmd}\n} ; printf '\\033]133;D;${nonce};%s;%s\\007' "$?" "$PWD"\n`;
|
|
14
|
+
}
|
|
15
|
+
/** init 整行:哑终端(echo/PS1/PS2/TERM)+ 固定 40×120(Non-Goal ③)+ INIT 帧。 */
|
|
16
|
+
export function buildInitCommand(nonce) {
|
|
17
|
+
return `stty -echo 2>/dev/null ; stty rows 40 cols 120 2>/dev/null ; PS1='' ; PS2='' ; export TERM=dumb ; printf '\\033]133;D;${nonce};0;%s\\007' "$PWD"\n`;
|
|
18
|
+
}
|
|
19
|
+
const ANSI_RE = /\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)?|\[[0-9;?]*[ -/]*[@-~]|[@-Z\\-_])/g;
|
|
20
|
+
export function stripAnsi(s) {
|
|
21
|
+
return s.replace(ANSI_RE, '');
|
|
22
|
+
}
|
|
23
|
+
export function normalizePtyText(s) {
|
|
24
|
+
return stripAnsi(s).replace(/\r\n/g, '\n').replace(/\r/g, '');
|
|
25
|
+
}
|
|
26
|
+
export function tailWindow(text, cap) {
|
|
27
|
+
if (text.length <= cap)
|
|
28
|
+
return { text };
|
|
29
|
+
return { text: `[truncated: showing last ${cap} of ${text.length} chars]\n${text.slice(text.length - cap)}`, truncated: text.length };
|
|
30
|
+
}
|
|
31
|
+
const FRAME_BODY_RE = /^(\d+);([^\x07\x1b]*)(?:\x07|\x1b\\)/; // cwd 是尾字段:合法路径可含 `;`,只有 BEL/ESC 终止它
|
|
32
|
+
/**
|
|
33
|
+
* 单 nonce、单帧解析器:一轮 dispatch 一个实例。只认 `\x1b]133;D;<nonce>;`,
|
|
34
|
+
* 错误 nonce 的静态/伪造 marker 永不匹配,按普通输出流过(再经 ANSI 剥洗)。
|
|
35
|
+
* 缓冲尾若可能是 marker 前缀则扣留,flush() 时作为输出放出。
|
|
36
|
+
*/
|
|
37
|
+
export function createNonceFrameParser(nonce, events) {
|
|
38
|
+
const prefix = `\x1b]133;D;${nonce};`;
|
|
39
|
+
let buf = '';
|
|
40
|
+
let done = false;
|
|
41
|
+
const emit = (raw) => {
|
|
42
|
+
const text = normalizePtyText(raw);
|
|
43
|
+
if (text.length > 0)
|
|
44
|
+
events.onOutput?.(text);
|
|
45
|
+
};
|
|
46
|
+
const holdback = () => {
|
|
47
|
+
const max = Math.min(buf.length, prefix.length - 1);
|
|
48
|
+
for (let k = max; k > 0; k--)
|
|
49
|
+
if (buf.endsWith(prefix.slice(0, k)))
|
|
50
|
+
return k;
|
|
51
|
+
return 0;
|
|
52
|
+
};
|
|
53
|
+
const consume = () => {
|
|
54
|
+
if (done)
|
|
55
|
+
return;
|
|
56
|
+
const i = buf.indexOf(prefix);
|
|
57
|
+
if (i < 0) {
|
|
58
|
+
const keep = holdback();
|
|
59
|
+
emit(buf.slice(0, buf.length - keep));
|
|
60
|
+
buf = buf.slice(buf.length - keep);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
emit(buf.slice(0, i));
|
|
64
|
+
const rest = buf.slice(i + prefix.length);
|
|
65
|
+
const m = FRAME_BODY_RE.exec(rest);
|
|
66
|
+
if (m === null) {
|
|
67
|
+
buf = buf.slice(i);
|
|
68
|
+
return;
|
|
69
|
+
} // 帧体未到齐,整段扣留
|
|
70
|
+
events.onFrame({ exit: Number(m[1]), cwd: m[2].length > 0 ? m[2] : null });
|
|
71
|
+
done = true; // 帧后字节一律丢弃(下一轮是新解析器)
|
|
72
|
+
buf = '';
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
feed(chunk) { buf += chunk; consume(); },
|
|
76
|
+
// 流结束 = 裸放出扣留的残余(含未完成的 marker 前缀):不经过 ANSI 剥洗——
|
|
77
|
+
// stripAnsi 会把未闭合 OSC 整段吃掉,扣留字节就此蒸发,违背 flush 语义。
|
|
78
|
+
flush() { if (buf.length > 0)
|
|
79
|
+
events.onOutput?.(buf); buf = ''; },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { A2aLogFn } from './a2a-log.js';
|
|
2
|
+
/** The exact upgrade path this executor claims. */
|
|
3
|
+
export declare const PTY_EXEC_PATH = "/api/superd/exec/pty";
|
|
4
|
+
export interface PtyExecutorOptions {
|
|
5
|
+
/** The ctx0 auth gate (connection.authorizeIndex) — cookie auth comes first. */
|
|
6
|
+
authorize(req: unknown, res: unknown): boolean;
|
|
7
|
+
/** The ctx0 browser-trust fence (connection.requestRejection): Host/Origin policy. */
|
|
8
|
+
requestRejection?(req: unknown): number | undefined;
|
|
9
|
+
log?: A2aLogFn;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register the PTY executor's upgrade route on the REAL webserver face
|
|
13
|
+
* (ctx0 only — gateway.ts's `webServer` inject scope, the /hub family's
|
|
14
|
+
* sibling). @returns the dispose fn.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerPtyExecutor(real: {
|
|
17
|
+
registerUpgrade(route: {
|
|
18
|
+
path: string;
|
|
19
|
+
handler: (req: unknown, socket: unknown, head: Buffer) => void;
|
|
20
|
+
}): unknown;
|
|
21
|
+
}, opts: PtyExecutorOptions): Promise<() => void>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The remote PTY executor — the DUMB side of spec §8.2 (~a hundred lines by
|
|
2
|
+
// design): one accepted WebSocket = one `script -qfec bash /dev/null` (host
|
|
3
|
+
// PTY, no node-pty) with a bidirectional byte pump. All session intelligence
|
|
4
|
+
// (nonce init/framing, two-stage interrupt, idle TTL) lives on the INITIATOR
|
|
5
|
+
// side (exec-pty-session.ts over exec-pty-channel.ts) — the executor never
|
|
6
|
+
// parses the stream. Protocol: text frames carry stdin/stdout bytes; the
|
|
7
|
+
// child's exit is announced as one JSON control frame `{"op":"exit","code":N}`
|
|
8
|
+
// before the socket closes; WS close (any reason) group-kills the child.
|
|
9
|
+
//
|
|
10
|
+
// Auth/trust: the same gate as the /hub family — connection.authorizeIndex
|
|
11
|
+
// (cookie) FIRST, then the browser-trust fence (requestRejection) — an
|
|
12
|
+
// upgrade is never weaker than its sibling HTTP routes (hub-route precedent).
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
/** The exact upgrade path this executor claims. */
|
|
16
|
+
export const PTY_EXEC_PATH = '/api/superd/exec/pty';
|
|
17
|
+
const WS_MODULE = 'ws';
|
|
18
|
+
async function loadWsServer() {
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const mod = require(WS_MODULE);
|
|
21
|
+
const boxed = mod;
|
|
22
|
+
return typeof mod.WebSocketServer === 'function' ? mod : boxed.default;
|
|
23
|
+
}
|
|
24
|
+
const REASON_PHRASE = { 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found' };
|
|
25
|
+
function rejectUpgrade(socket, status, body) {
|
|
26
|
+
const writable = socket;
|
|
27
|
+
writable.write?.(`HTTP/1.1 ${status} ${REASON_PHRASE[status] ?? 'Rejected'}\r\n`
|
|
28
|
+
+ 'Connection: close\r\n'
|
|
29
|
+
+ `Content-Type: application/json\r\n`
|
|
30
|
+
+ `Content-Length: ${String(Buffer.byteLength(body, 'utf8'))}\r\n\r\n`
|
|
31
|
+
+ body);
|
|
32
|
+
writable.destroy?.();
|
|
33
|
+
}
|
|
34
|
+
/** One connection = one dumb PTY pump. */
|
|
35
|
+
function runPump(client, log) {
|
|
36
|
+
let child = null;
|
|
37
|
+
let closed = false;
|
|
38
|
+
const killTree = () => {
|
|
39
|
+
const pid = child?.pid;
|
|
40
|
+
child = null;
|
|
41
|
+
if (pid === undefined)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
process.kill(-pid, 'SIGTERM');
|
|
45
|
+
}
|
|
46
|
+
catch { /* already gone */ }
|
|
47
|
+
setTimeout(() => { try {
|
|
48
|
+
process.kill(-pid, 'SIGKILL');
|
|
49
|
+
}
|
|
50
|
+
catch { /* already gone */ } }, 2_000).unref();
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
child = spawn('script', ['-qfec', 'bash', '/dev/null'], { stdio: ['pipe', 'pipe', 'pipe'], detached: true });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
log?.('warn', 'exec.pty.spawn-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
57
|
+
try {
|
|
58
|
+
client.send(JSON.stringify({ op: 'spawn-error', error: 'script spawn failed' }));
|
|
59
|
+
client.close();
|
|
60
|
+
}
|
|
61
|
+
catch { /* closing */ }
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const started = Date.now();
|
|
65
|
+
log?.('info', 'exec.pty.start', { pid: child.pid ?? 'none' });
|
|
66
|
+
child.stdout?.setEncoding('utf8');
|
|
67
|
+
child.stdout?.on('data', (d) => { if (!closed) {
|
|
68
|
+
try {
|
|
69
|
+
client.send(d);
|
|
70
|
+
}
|
|
71
|
+
catch { /* closing */ }
|
|
72
|
+
} });
|
|
73
|
+
child.stderr?.setEncoding('utf8');
|
|
74
|
+
child.once('close', (code) => {
|
|
75
|
+
if (closed)
|
|
76
|
+
return;
|
|
77
|
+
closed = true;
|
|
78
|
+
log?.('info', 'exec.pty.exit', { code: code ?? 'none', durationMs: Date.now() - started });
|
|
79
|
+
try {
|
|
80
|
+
client.send(JSON.stringify({ op: 'exit', code }));
|
|
81
|
+
client.close();
|
|
82
|
+
}
|
|
83
|
+
catch { /* already closing */ }
|
|
84
|
+
killTree();
|
|
85
|
+
});
|
|
86
|
+
client.on('message', (data) => {
|
|
87
|
+
const text = typeof data === 'string' ? data : Buffer.from(data).toString('utf8');
|
|
88
|
+
if (child?.stdin?.writable === true) {
|
|
89
|
+
try {
|
|
90
|
+
child.stdin.write(text);
|
|
91
|
+
}
|
|
92
|
+
catch { /* dead */ }
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const shutdown = () => { closed = true; killTree(); };
|
|
96
|
+
client.on('close', shutdown);
|
|
97
|
+
client.on('error', shutdown);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Register the PTY executor's upgrade route on the REAL webserver face
|
|
101
|
+
* (ctx0 only — gateway.ts's `webServer` inject scope, the /hub family's
|
|
102
|
+
* sibling). @returns the dispose fn.
|
|
103
|
+
*/
|
|
104
|
+
export async function registerPtyExecutor(real, opts) {
|
|
105
|
+
const ws = await loadWsServer();
|
|
106
|
+
const server = new ws.WebSocketServer({ noServer: true });
|
|
107
|
+
const handler = (req, socket, head) => {
|
|
108
|
+
// auth first, then trust fence — identical ordering to the /hub family
|
|
109
|
+
let status = 401;
|
|
110
|
+
let body = '';
|
|
111
|
+
const admitted = opts.authorize(req, {
|
|
112
|
+
writeHead: (answered) => { status = answered; },
|
|
113
|
+
end: (chunk) => { if (chunk !== undefined && chunk !== null)
|
|
114
|
+
body = String(chunk); },
|
|
115
|
+
});
|
|
116
|
+
if (!admitted) {
|
|
117
|
+
rejectUpgrade(socket, status, body !== '' ? body : JSON.stringify({ error: 'unauthorized' }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const rejection = opts.requestRejection?.(req);
|
|
121
|
+
if (rejection !== undefined) {
|
|
122
|
+
rejectUpgrade(socket, rejection, JSON.stringify({ error: rejection === 401 ? 'unauthorized' : 'forbidden' }));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
server.handleUpgrade(req, socket, head, (client) => runPump(client, opts.log));
|
|
126
|
+
};
|
|
127
|
+
return real.registerUpgrade({ path: PTY_EXEC_PATH, handler });
|
|
128
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PtyChannel } from './exec-pty-channel.js';
|
|
2
|
+
/** spec §五.2 逐字。 */
|
|
3
|
+
export declare const RECONNECT_NOTICE = "[remote: session reconnected to fresh shell; cwd reset to default]";
|
|
4
|
+
export interface PtySessionOptions {
|
|
5
|
+
key: string;
|
|
6
|
+
channel: PtyChannel;
|
|
7
|
+
idleTtlSec: number;
|
|
8
|
+
initTimeoutSec?: number;
|
|
9
|
+
/** 池回调:会话彻底出池(dispose)时触发;进程意外死亡不出池(Ruling 7)。 */
|
|
10
|
+
onDead?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface PtyDispatchOptions {
|
|
13
|
+
stdin?: string;
|
|
14
|
+
timeoutSec?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PtyDispatchResult {
|
|
17
|
+
output: string;
|
|
18
|
+
exit: number | null;
|
|
19
|
+
cwd: string | null;
|
|
20
|
+
timedOut: boolean;
|
|
21
|
+
reconnected: boolean;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
}
|
|
24
|
+
export type PtyState = 'cold' | 'starting' | 'ready' | 'dead' | 'disposed';
|
|
25
|
+
/** 会话运行时快照(status 面消费,Ruling 16 词汇源)。 */
|
|
26
|
+
export interface SessionSnapshot {
|
|
27
|
+
state: PtyState;
|
|
28
|
+
busy: boolean;
|
|
29
|
+
idleMs: number | null;
|
|
30
|
+
}
|
|
31
|
+
export declare class PtySession {
|
|
32
|
+
private readonly opts;
|
|
33
|
+
private state;
|
|
34
|
+
private startPromise;
|
|
35
|
+
private queue;
|
|
36
|
+
private idleTimer;
|
|
37
|
+
private onChunk;
|
|
38
|
+
private deathWaiters;
|
|
39
|
+
private inFlight;
|
|
40
|
+
private lastSettleAt;
|
|
41
|
+
constructor(opts: PtySessionOptions);
|
|
42
|
+
get sessionState(): PtyState;
|
|
43
|
+
/** status 面快照:state + busy(有命令在跑)+ idleMs(上次 settle 至今)。 */
|
|
44
|
+
get snapshot(): SessionSnapshot;
|
|
45
|
+
/** 同一会话命令严格串行(FIFO);死亡/冷态先重启。 */
|
|
46
|
+
dispatch(cmd: string, dOpts?: PtyDispatchOptions): Promise<PtyDispatchResult>;
|
|
47
|
+
/** TTL / row teardown:尽力 exit,1s 后杀组,出池。 */
|
|
48
|
+
dispose(): Promise<void>;
|
|
49
|
+
private write;
|
|
50
|
+
private start;
|
|
51
|
+
private doStart;
|
|
52
|
+
private diag;
|
|
53
|
+
private markDead;
|
|
54
|
+
private resetIdleTimer;
|
|
55
|
+
private clearIdleTimer;
|
|
56
|
+
}
|
|
57
|
+
export interface PtyPoolDefaults {
|
|
58
|
+
idleTtlSec: number;
|
|
59
|
+
initTimeoutSec?: number;
|
|
60
|
+
}
|
|
61
|
+
/** 会话池:键 → 会话。意外死亡的会话留在池内(下次 dispatch 带 reconnect 提示)。 */
|
|
62
|
+
export declare class PtyPool {
|
|
63
|
+
private readonly defaults;
|
|
64
|
+
private readonly sessions;
|
|
65
|
+
constructor(defaults: PtyPoolDefaults);
|
|
66
|
+
get size(): number;
|
|
67
|
+
/** status 面:键上的会话快照(无会话 = undefined → "none — dials on first exec")。 */
|
|
68
|
+
inspect(key: string): SessionSnapshot | undefined;
|
|
69
|
+
/** roster 面:池内全部会话。 */
|
|
70
|
+
list(): Array<{
|
|
71
|
+
key: string;
|
|
72
|
+
snapshot: SessionSnapshot;
|
|
73
|
+
}>;
|
|
74
|
+
getOrCreate(key: string, channelOf: () => PtyChannel): PtySession;
|
|
75
|
+
disposeAll(): Promise<void>;
|
|
76
|
+
}
|