bedrock-agentcore 0.2.4 → 0.3.0
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 +1 -1
- package/dist/src/memory/integrations/strands/factory.d.ts +78 -0
- package/dist/src/memory/integrations/strands/factory.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/factory.js +108 -0
- package/dist/src/memory/integrations/strands/factory.js.map +1 -0
- package/dist/src/memory/integrations/strands/format.d.ts +17 -0
- package/dist/src/memory/integrations/strands/format.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/format.js +29 -0
- package/dist/src/memory/integrations/strands/format.js.map +1 -0
- package/dist/src/memory/integrations/strands/index.d.ts +11 -0
- package/dist/src/memory/integrations/strands/index.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/index.js +6 -0
- package/dist/src/memory/integrations/strands/index.js.map +1 -0
- package/dist/src/memory/integrations/strands/logger.d.ts +19 -0
- package/dist/src/memory/integrations/strands/logger.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/logger.js +27 -0
- package/dist/src/memory/integrations/strands/logger.js.map +1 -0
- package/dist/src/memory/integrations/strands/sender.d.ts +79 -0
- package/dist/src/memory/integrations/strands/sender.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/sender.js +170 -0
- package/dist/src/memory/integrations/strands/sender.js.map +1 -0
- package/dist/src/memory/integrations/strands/store.d.ts +28 -0
- package/dist/src/memory/integrations/strands/store.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/store.js +154 -0
- package/dist/src/memory/integrations/strands/store.js.map +1 -0
- package/dist/src/memory/integrations/strands/types.d.ts +122 -0
- package/dist/src/memory/integrations/strands/types.d.ts.map +1 -0
- package/dist/src/memory/integrations/strands/types.js +73 -0
- package/dist/src/memory/integrations/strands/types.js.map +1 -0
- package/dist/src/runtime/client.d.ts +79 -11
- package/dist/src/runtime/client.d.ts.map +1 -1
- package/dist/src/runtime/client.js +230 -79
- package/dist/src/runtime/client.js.map +1 -1
- package/dist/src/runtime/index.d.ts +5 -0
- package/dist/src/runtime/index.d.ts.map +1 -1
- package/dist/src/runtime/index.js +3 -0
- package/dist/src/runtime/index.js.map +1 -1
- package/dist/src/runtime/shell/config.d.ts +81 -0
- package/dist/src/runtime/shell/config.d.ts.map +1 -0
- package/dist/src/runtime/shell/config.js +15 -0
- package/dist/src/runtime/shell/config.js.map +1 -0
- package/dist/src/runtime/shell/index.d.ts +9 -0
- package/dist/src/runtime/shell/index.d.ts.map +1 -0
- package/dist/src/runtime/shell/index.js +6 -0
- package/dist/src/runtime/shell/index.js.map +1 -0
- package/dist/src/runtime/shell/protocol.d.ts +45 -0
- package/dist/src/runtime/shell/protocol.d.ts.map +1 -0
- package/dist/src/runtime/shell/protocol.js +99 -0
- package/dist/src/runtime/shell/protocol.js.map +1 -0
- package/dist/src/runtime/shell/session.d.ts +240 -0
- package/dist/src/runtime/shell/session.d.ts.map +1 -0
- package/dist/src/runtime/shell/session.js +880 -0
- package/dist/src/runtime/shell/session.js.map +1 -0
- package/dist/src/runtime/shell/validation.d.ts +8 -0
- package/dist/src/runtime/shell/validation.d.ts.map +1 -0
- package/dist/src/runtime/shell/validation.js +17 -0
- package/dist/src/runtime/shell/validation.js.map +1 -0
- package/dist/src/runtime/types.d.ts +89 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/package.json +13 -4
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Binary channel-prefix framer for InvokeAgentRuntimeCommandShell.
|
|
3
|
+
*
|
|
4
|
+
* Wire format (identical to Kubernetes v5.channel.k8s.io):
|
|
5
|
+
* [1-byte channel ID][payload bytes]
|
|
6
|
+
*
|
|
7
|
+
* Channels:
|
|
8
|
+
* 0x00 STDIN Raw bytes, client → shell
|
|
9
|
+
* 0x01 STDOUT Raw bytes, shell → client
|
|
10
|
+
* 0x02 STDERR UTF-8 text (platform diagnostics), shell → client
|
|
11
|
+
* 0x03 STATUS metav1.Status JSON (shell → client):
|
|
12
|
+
* Connection confirmation: metadata.shellId present
|
|
13
|
+
* {"kind":"Status","apiVersion":"v1",
|
|
14
|
+
* "metadata":{"shellId":"…","reconnected":bool},"status":"Success"}
|
|
15
|
+
* Shell exit (code 0):
|
|
16
|
+
* {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}
|
|
17
|
+
* Shell exit (non-zero):
|
|
18
|
+
* {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure",
|
|
19
|
+
* "reason":"NonZeroExitCode","details":{"causes":[{"reason":"ExitCode","message":"N"}]}}
|
|
20
|
+
* 0x04 RESIZE JSON {"width":N,"height":N}, client → shell
|
|
21
|
+
* 0x05 HEARTBEAT Empty payload, bidirectional keepalive
|
|
22
|
+
* 0xFF CLOSE Empty payload. Client → server: explicit session kill (SIGHUP → SIGKILL).
|
|
23
|
+
* Server echoes [0xFF] back, kills the shell, closes WebSocket with code 1000.
|
|
24
|
+
* Unlike disconnect (which detaches), 0xFF is permanent — no reconnection possible.
|
|
25
|
+
*/
|
|
26
|
+
import { Buffer } from 'buffer';
|
|
27
|
+
/** Wire channel identifiers for the binary channel-prefix protocol. */
|
|
28
|
+
export var ShellChannel;
|
|
29
|
+
(function (ShellChannel) {
|
|
30
|
+
ShellChannel[ShellChannel["STDIN"] = 0] = "STDIN";
|
|
31
|
+
ShellChannel[ShellChannel["STDOUT"] = 1] = "STDOUT";
|
|
32
|
+
ShellChannel[ShellChannel["STDERR"] = 2] = "STDERR";
|
|
33
|
+
ShellChannel[ShellChannel["STATUS"] = 3] = "STATUS";
|
|
34
|
+
ShellChannel[ShellChannel["RESIZE"] = 4] = "RESIZE";
|
|
35
|
+
ShellChannel[ShellChannel["HEARTBEAT"] = 5] = "HEARTBEAT";
|
|
36
|
+
ShellChannel[ShellChannel["CLOSE"] = 255] = "CLOSE";
|
|
37
|
+
/** Sentinel for unrecognised channel bytes — not a wire value. */
|
|
38
|
+
ShellChannel[ShellChannel["UNKNOWN"] = -1] = "UNKNOWN";
|
|
39
|
+
})(ShellChannel || (ShellChannel = {}));
|
|
40
|
+
// Pre-built set of known wire channel bytes (excludes UNKNOWN sentinel).
|
|
41
|
+
const KNOWN_CHANNEL_BYTES = new Set(Object.values(ShellChannel).filter((v) => typeof v === 'number' && v !== ShellChannel.UNKNOWN));
|
|
42
|
+
/** Maximum frame size — matches DP WebSocketFlowController limit. */
|
|
43
|
+
export const MAX_FRAME_SIZE = 64 * 1024;
|
|
44
|
+
function makeFrame(channel, rawChannelByte, payload) {
|
|
45
|
+
return {
|
|
46
|
+
channel,
|
|
47
|
+
rawChannelByte,
|
|
48
|
+
payload,
|
|
49
|
+
get text() {
|
|
50
|
+
return payload.toString('utf8');
|
|
51
|
+
},
|
|
52
|
+
json() {
|
|
53
|
+
return JSON.parse(payload.toString('utf8'));
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Encodes and decodes binary channel-prefix WebSocket frames.
|
|
59
|
+
* Stateless — a single instance is safe to reuse across frames.
|
|
60
|
+
*/
|
|
61
|
+
export class ShellFramer {
|
|
62
|
+
/** Decode one raw WebSocket binary message into a ShellFrame. */
|
|
63
|
+
decode(frame) {
|
|
64
|
+
if (frame.length === 0) {
|
|
65
|
+
throw new Error('Cannot decode empty frame');
|
|
66
|
+
}
|
|
67
|
+
const rawByte = frame[0];
|
|
68
|
+
const channel = KNOWN_CHANNEL_BYTES.has(rawByte) ? rawByte : ShellChannel.UNKNOWN;
|
|
69
|
+
return makeFrame(channel, rawByte, frame.slice(1));
|
|
70
|
+
}
|
|
71
|
+
/** Encode keyboard input or paste data as a STDIN frame. */
|
|
72
|
+
encodeStdin(data) {
|
|
73
|
+
const bytes = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
74
|
+
if (bytes.length > MAX_FRAME_SIZE - 1) {
|
|
75
|
+
throw new Error(`Payload ${bytes.length} bytes exceeds the 64 KB frame limit. Split large pastes into multiple encodeStdin() calls.`);
|
|
76
|
+
}
|
|
77
|
+
return Buffer.concat([Buffer.from([ShellChannel.STDIN]), bytes]);
|
|
78
|
+
}
|
|
79
|
+
/** Encode a terminal resize event as a RESIZE frame. */
|
|
80
|
+
encodeResize(width, height) {
|
|
81
|
+
if (!Number.isInteger(width) || !Number.isInteger(height)) {
|
|
82
|
+
throw new Error(`width and height must be integers, got ${typeof width} and ${typeof height}`);
|
|
83
|
+
}
|
|
84
|
+
if (width <= 0 || height <= 0) {
|
|
85
|
+
throw new Error(`width and height must be positive integers, got width=${width}, height=${height}`);
|
|
86
|
+
}
|
|
87
|
+
const payload = Buffer.from(JSON.stringify({ width, height }), 'utf8');
|
|
88
|
+
return Buffer.concat([Buffer.from([ShellChannel.RESIZE]), payload]);
|
|
89
|
+
}
|
|
90
|
+
/** Encode an app-level heartbeat frame (channel 0x05, empty payload). */
|
|
91
|
+
encodeHeartbeat() {
|
|
92
|
+
return Buffer.from([ShellChannel.HEARTBEAT]);
|
|
93
|
+
}
|
|
94
|
+
/** Encode a graceful-shutdown CLOSE frame (empty payload). */
|
|
95
|
+
encodeClose() {
|
|
96
|
+
return Buffer.from([ShellChannel.CLOSE]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../../src/runtime/shell/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShellSession — async-iterable interactive PTY WebSocket session.
|
|
3
|
+
*
|
|
4
|
+
* Connects on `connect()`, reads the initial STATUS confirmation frame, and exposes
|
|
5
|
+
* typed `send()` / `resize()` / `[Symbol.asyncIterator]()` / `close()`.
|
|
6
|
+
*
|
|
7
|
+
* When `reconnectConfig` is provided, transparently reconnects on unexpected disconnects
|
|
8
|
+
* using the same `shellId` so the shell's working directory, environment, background jobs,
|
|
9
|
+
* and up to 256 KB of buffered output are preserved on the server.
|
|
10
|
+
*
|
|
11
|
+
* Reconnect restores the *connection* on its own (it is driven by the socket close event,
|
|
12
|
+
* not by your read loop, and `send()`/`resize()` wait for it). However, on reattach the
|
|
13
|
+
* server replays the buffered output as inbound frames — to receive that replay (and to see
|
|
14
|
+
* `bytesDropped` updated and `exitCode` set) you must be consuming the session with
|
|
15
|
+
* `for await (const frame of shell)`. A write-only caller that never iterates stays
|
|
16
|
+
* connected across drops but will not observe the replayed output. Keep a `for await` loop
|
|
17
|
+
* running for the life of the session.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const shell = await client.openShell({ runtimeArn })
|
|
22
|
+
* try {
|
|
23
|
+
* await shell.send('cat /etc/os-release\n')
|
|
24
|
+
* for await (const frame of shell) {
|
|
25
|
+
* if (frame.channel === ShellChannel.STDOUT) process.stdout.write(frame.text)
|
|
26
|
+
* }
|
|
27
|
+
* } finally {
|
|
28
|
+
* await shell.close()
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import WebSocket from 'ws';
|
|
33
|
+
import type { ClientOptions } from 'ws';
|
|
34
|
+
import { Buffer } from 'buffer';
|
|
35
|
+
import { type ShellFrame } from './protocol.js';
|
|
36
|
+
import { type Logger, type ReconnectConfig } from './config.js';
|
|
37
|
+
/**
|
|
38
|
+
* Callback that produces connection params for a new WebSocket.
|
|
39
|
+
* Receives the current `shellId` and `sessionId` so both can be embedded in the
|
|
40
|
+
* signed URL/headers. Both values are server-confirmed and may differ from the
|
|
41
|
+
* values originally passed to `openShell` after the first connection.
|
|
42
|
+
*/
|
|
43
|
+
export type ConnectFn = (shellId: string, sessionId: string) => Promise<{
|
|
44
|
+
url: string;
|
|
45
|
+
headers: Record<string, string>;
|
|
46
|
+
/** WebSocket subprotocols — used by OAuth auth (base64UrlBearerAuthorization). */
|
|
47
|
+
protocols?: string[];
|
|
48
|
+
}>;
|
|
49
|
+
/** Options for constructing a ShellSession. */
|
|
50
|
+
export interface ShellSessionOptions {
|
|
51
|
+
connectFn: ConnectFn;
|
|
52
|
+
shellId?: string | undefined;
|
|
53
|
+
sessionId?: string | undefined;
|
|
54
|
+
reconnectConfig?: ReconnectConfig | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Interval in milliseconds between RFC 6455 Ping frames sent to keep the connection
|
|
57
|
+
* alive through the KARP proxy (~60s idle timeout). Defaults to 30000ms.
|
|
58
|
+
* Set to 0 to disable keepalive (e.g. when the caller manages pings externally).
|
|
59
|
+
*/
|
|
60
|
+
keepaliveIntervalMs?: number | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Optional logger for diagnostic output. When omitted, all logging is silent.
|
|
63
|
+
* Pass `console` to enable, or any object implementing `{ debug, info, warn }`.
|
|
64
|
+
*/
|
|
65
|
+
logger?: Logger | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Optional WebSocket factory for testing — overrides `new WebSocket(...)`.
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
_wsFactory?: ((url: string, protocols?: string[], options?: ClientOptions) => WebSocket) | undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Async-iterable shell session wrapping a live PTY WebSocket.
|
|
74
|
+
*
|
|
75
|
+
* Read-only observable attributes (updated by the session as events arrive):
|
|
76
|
+
* - `shellId` — Server-confirmed shell identifier. Preserve to reconnect to the same PTY.
|
|
77
|
+
* - `sessionId` — Runtime session ID routing to the VM.
|
|
78
|
+
* - `reconnected` — True when the most recent connect reattached an existing PTY.
|
|
79
|
+
* - `kicked` — True when another client connected with the same shellId (close 4000).
|
|
80
|
+
* Check this after the `for await` loop exits to distinguish a kick from
|
|
81
|
+
* a clean shell exit.
|
|
82
|
+
* - `bytesDropped` — PTY ring-buffer bytes lost during the most recent disconnect, as
|
|
83
|
+
* reported by the server in the reconnect confirmation frame.
|
|
84
|
+
* Zero if no overflow occurred or on a fresh connection.
|
|
85
|
+
* - `exitCode` — Shell process exit code. `null` until the shell exits; `0` for a clean
|
|
86
|
+
* exit. Check this after the `for await` loop exits alongside `kicked`.
|
|
87
|
+
*/
|
|
88
|
+
export declare class ShellSession implements AsyncIterable<ShellFrame> {
|
|
89
|
+
private _shellId;
|
|
90
|
+
private _sessionId;
|
|
91
|
+
private _reconnected;
|
|
92
|
+
private _kicked;
|
|
93
|
+
private _bytesDropped;
|
|
94
|
+
private _exitCode;
|
|
95
|
+
/** Server-confirmed shell identifier. */
|
|
96
|
+
get shellId(): string;
|
|
97
|
+
/** Runtime session ID routing to the VM. */
|
|
98
|
+
get sessionId(): string;
|
|
99
|
+
/** True when the most recent connect reattached an existing PTY. */
|
|
100
|
+
get reconnected(): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* True when another client connected with the same shellId (close 4000).
|
|
103
|
+
* Check after the `for await` loop exits to distinguish a kick from a clean exit.
|
|
104
|
+
*/
|
|
105
|
+
get kicked(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* PTY ring-buffer bytes lost during the most recent disconnect.
|
|
108
|
+
* Zero when no overflow occurred or on a fresh connection.
|
|
109
|
+
*/
|
|
110
|
+
get bytesDropped(): number;
|
|
111
|
+
/**
|
|
112
|
+
* Shell process exit code. `null` until the shell exits; `0` for a clean exit.
|
|
113
|
+
* Check after the `for await` loop exits alongside `kicked`.
|
|
114
|
+
*/
|
|
115
|
+
get exitCode(): number | null;
|
|
116
|
+
private readonly connectFn;
|
|
117
|
+
private readonly reconnectConfig;
|
|
118
|
+
private readonly keepaliveIntervalMs;
|
|
119
|
+
private readonly log;
|
|
120
|
+
private readonly framer;
|
|
121
|
+
private readonly _wsFactory;
|
|
122
|
+
private _state;
|
|
123
|
+
private _abortController;
|
|
124
|
+
private readonly _sessionController;
|
|
125
|
+
private _closeError;
|
|
126
|
+
/**
|
|
127
|
+
* Set while a reconnect is in flight, cleared when it settles. Shared so that the
|
|
128
|
+
* iterator, the close/dead-detection handler, and send()/resize() all await the same
|
|
129
|
+
* attempt rather than racing or each starting their own. Resolves to the reconnect
|
|
130
|
+
* outcome (true = recovered, false = gave up). This is what makes *connection* recovery
|
|
131
|
+
* iterator-independent — the socket is restored without a `for await` loop. Consuming the
|
|
132
|
+
* replayed output still requires an active iterator (see the class docstring).
|
|
133
|
+
*/
|
|
134
|
+
private _reconnectPromise;
|
|
135
|
+
constructor(opts: ShellSessionOptions);
|
|
136
|
+
/** Connect and read the initial STATUS metadata frame. */
|
|
137
|
+
connect(): Promise<this>;
|
|
138
|
+
/**
|
|
139
|
+
* Send text or raw bytes to the shell's stdin.
|
|
140
|
+
* Pass a string for text commands; pass a Buffer for binary/escape sequences.
|
|
141
|
+
*
|
|
142
|
+
* If a reconnect is in flight, this waits for it and sends on the recovered
|
|
143
|
+
* connection. Throws a descriptive `Error` (never the raw `ws` "readyState 3"
|
|
144
|
+
* error) when the session is closed or could not be recovered.
|
|
145
|
+
*/
|
|
146
|
+
send(data: string | Buffer): Promise<void>;
|
|
147
|
+
/** Send a HEARTBEAT frame (0x05) to the server. */
|
|
148
|
+
sendHeartbeat(): Promise<void>;
|
|
149
|
+
/** Resize the terminal PTY. */
|
|
150
|
+
resize(width: number, height: number): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the live socket for a write, healing first if needed. Awaits an in-flight
|
|
153
|
+
* reconnect (transparent recovery) and validates the *real* socket
|
|
154
|
+
* readyState — not just the `_state` flag, which can lag a silently-dropped socket.
|
|
155
|
+
* Throws a descriptive `Error` instead of leaking the raw `ws`
|
|
156
|
+
* "readyState 3 (CLOSED)" error.
|
|
157
|
+
*/
|
|
158
|
+
private _writableSocket;
|
|
159
|
+
/** Send a CLOSE frame (0xFF) to permanently kill the shell, then close the WebSocket.
|
|
160
|
+
* The server kills the shell process (SIGHUP → SIGKILL) and responds with its own [0xFF].
|
|
161
|
+
* Unlike dropping the WebSocket (which detaches and allows reconnection), this is permanent. */
|
|
162
|
+
close(): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Forcibly terminates the underlying WebSocket without a clean handshake.
|
|
165
|
+
* Useful in tests to simulate an abrupt network drop and trigger the reconnect path.
|
|
166
|
+
* Has no effect if the session is not currently open.
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
_terminateConnection(): void;
|
|
170
|
+
/**
|
|
171
|
+
* Async iterator — yields inbound ShellFrames, reconnecting on drop if configured.
|
|
172
|
+
*
|
|
173
|
+
* The loop exits silently (no throw) in three cases: shell exit, kicked by a new
|
|
174
|
+
* client, or reconnect budget exhausted. Check `exitCode`, `kicked`, and
|
|
175
|
+
* `bytesDropped` after the loop to distinguish them:
|
|
176
|
+
*
|
|
177
|
+
* ```typescript
|
|
178
|
+
* for await (const frame of shell) { ... }
|
|
179
|
+
* if (shell.kicked) { ... } // another client took over
|
|
180
|
+
* if (shell.exitCode !== null) { ... } // shell process exited
|
|
181
|
+
* if (shell.bytesDropped > 0) { ... } // ring-buffer overflow on reconnect
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
[Symbol.asyncIterator](): AsyncIterator<ShellFrame>;
|
|
185
|
+
private _startKeepalive;
|
|
186
|
+
private _stopKeepalive;
|
|
187
|
+
private _wsSend;
|
|
188
|
+
/** Open WebSocket, capture 101 upgrade headers, then read metadata frame. */
|
|
189
|
+
private _connectWithUpgrade;
|
|
190
|
+
/** Receive one raw binary message from the WebSocket. */
|
|
191
|
+
private _recvRaw;
|
|
192
|
+
/**
|
|
193
|
+
* Consume frames until a STATUS confirmation is found, stashing others in pendingFrames.
|
|
194
|
+
* Returns the accumulated pending frames to be stored in the 'open' state.
|
|
195
|
+
*/
|
|
196
|
+
private _readMetadataFrame;
|
|
197
|
+
private _isConfirmationStatus;
|
|
198
|
+
/**
|
|
199
|
+
* Record `bytesDropped` from a reconnection confirmation frame's metadata, if present.
|
|
200
|
+
* `bytesDropped` reports PTY output lost from ring-buffer overflow during THIS disconnect
|
|
201
|
+
* (per-disconnect, not session-cumulative — assign, don't accumulate). Present
|
|
202
|
+
* only when greater than 0; absent on a clean reconnect.
|
|
203
|
+
*/
|
|
204
|
+
private _recordBytesDropped;
|
|
205
|
+
private _isTerminationStatus;
|
|
206
|
+
private _parseExitCode;
|
|
207
|
+
private _iterate;
|
|
208
|
+
/** Returns true when close() has been called. Used after await points to guard
|
|
209
|
+
* against close() firing while the method was suspended. A method call prevents
|
|
210
|
+
* TypeScript from narrowing away 'closed' comparisons after state assignments. */
|
|
211
|
+
private _isClosed;
|
|
212
|
+
private _extractCloseCode;
|
|
213
|
+
/**
|
|
214
|
+
* Wait for the socket's authoritative 'close' to land after an 'error' woke the read
|
|
215
|
+
* loop early. The 'close' handler sets `_closeError` (carrying the real close code) and
|
|
216
|
+
* makes the reconnect decision, so this resolves as soon as `_closeError` is populated.
|
|
217
|
+
* Bounded by a short timeout in case 'close' never follows (it always does on a
|
|
218
|
+
* connected `ws`, but we must not hang the iterator on a misbehaving socket).
|
|
219
|
+
*/
|
|
220
|
+
private _waitForClose;
|
|
221
|
+
/**
|
|
222
|
+
* Decide whether a close code is auto-reconnectable, then start-or-join a reconnect.
|
|
223
|
+
*
|
|
224
|
+
* This is the single entry point for triggering reconnection. It is called from
|
|
225
|
+
* wherever a disconnect is observed — the iterator's read error, the `ws.on('close')`
|
|
226
|
+
* handler (dead-detection / silent drop while no one is iterating), or the keepalive
|
|
227
|
+
* pong-timeout — so restoring the *connection* no longer depends on an active `for await`
|
|
228
|
+
* loop. (Receiving the server's replayed output after reattach still requires iterating;
|
|
229
|
+
* see the class docstring.)
|
|
230
|
+
*
|
|
231
|
+
* Returns a promise that resolves to the reconnect outcome (true = recovered).
|
|
232
|
+
* Returns immediately with `false` for terminal close codes (4000 kicked, 1003 text,
|
|
233
|
+
* 1000 normal) or when no `reconnectConfig` is set. Concurrent callers share one
|
|
234
|
+
* in-flight attempt via `_reconnectPromise`.
|
|
235
|
+
*/
|
|
236
|
+
private _ensureReconnect;
|
|
237
|
+
private _reconnectWithBackoff;
|
|
238
|
+
private _runInnerRetryLoop;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../src/runtime/shell/session.ts"],"names":[],"mappings":"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"}
|