@termwright/probe-runtime 0.2.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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +363 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gorce-ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @termwright/probe-runtime
|
|
2
|
+
|
|
3
|
+
Framework-neutral, fail-closed socket transport shared by zero-config
|
|
4
|
+
JavaScript probes. It owns handshake validation, full-snapshot publication,
|
|
5
|
+
revision commits, marker authentication, `get-tree`, limits and channel
|
|
6
|
+
shutdown.
|
|
7
|
+
|
|
8
|
+
It intentionally does not observe framework state or write markers. Only the
|
|
9
|
+
framework probe knows when its render bytes have drained, so marker placement
|
|
10
|
+
stays in `@termwright/probe-ink` and `@termwright/probe-opentui`.
|
|
11
|
+
|
|
12
|
+
## Debug performance metrics
|
|
13
|
+
|
|
14
|
+
`ProbeChannel.performanceMetrics()` returns immutable transport telemetry.
|
|
15
|
+
Collection turns on automatically with `TERMWRIGHT_DEBUG=1`/`all` or
|
|
16
|
+
`TERMWRIGHT_DEBUG_FILE`, and can be forced with `connectProbe({
|
|
17
|
+
performanceMetrics: true, ... })`. Disabled sessions do not call the timer.
|
|
18
|
+
|
|
19
|
+
The snapshot reports full snapshots, deltas, encoded semantic bytes, node and
|
|
20
|
+
generic-node counts, failed publications, requested markers and serialization
|
|
21
|
+
time, together with per-frame averages. Framework sessions also supply their
|
|
22
|
+
Probe IR fact count and report publications superseded in their marker queue.
|
|
23
|
+
Fields the shared transport cannot see stay `null`: parent normalization and
|
|
24
|
+
whether the framework-specific sink actually drained a marker.
|
|
25
|
+
|
|
26
|
+
Injected applications do not need a channel handle to inspect these values:
|
|
27
|
+
set `TERMWRIGHT_DEBUG_FILE=/path/to/adapter.log`. The runtime appends a
|
|
28
|
+
`termwright-probe-performance` JSONL record after each publication and
|
|
29
|
+
coalescing update. Records contain counters and timings only, never semantic
|
|
30
|
+
text or the authentication token. Debug-file failures are fail-open and cannot
|
|
31
|
+
stop the application.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Socket } from 'node:net';
|
|
2
|
+
import { ProtocolId, ProtocolLimits, ProbeInfo, AdapterCapability, FrameDecoder, SemanticSnapshot } from '@termwright/protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared process-to-driver transport for zero-config JavaScript probes.
|
|
6
|
+
*
|
|
7
|
+
* Framework probes deliberately keep observation and marker placement in
|
|
8
|
+
* their own packages. This module owns only the framework-neutral handshake,
|
|
9
|
+
* full-snapshot publication and fail-closed socket lifecycle.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** What the driver decided for this session. */
|
|
13
|
+
interface ChannelSession {
|
|
14
|
+
readonly protocol: ProtocolId;
|
|
15
|
+
readonly sessionId: string;
|
|
16
|
+
readonly limits: ProtocolLimits;
|
|
17
|
+
readonly markerEnabled: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Connection parameters. */
|
|
20
|
+
interface ConnectOptions {
|
|
21
|
+
readonly endpoint: string;
|
|
22
|
+
readonly token: string;
|
|
23
|
+
readonly probe: ProbeInfo;
|
|
24
|
+
readonly capabilities: readonly AdapterCapability[];
|
|
25
|
+
readonly adapterName: string;
|
|
26
|
+
readonly adapterVersion: string;
|
|
27
|
+
readonly protocol?: ProtocolId;
|
|
28
|
+
/** Milliseconds for connect plus handshake. Default 1000. */
|
|
29
|
+
readonly handshakeTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Collect per-publication counters and timings.
|
|
32
|
+
*
|
|
33
|
+
* Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an
|
|
34
|
+
* adapter debug file is configured. Explicit false keeps even the timer off.
|
|
35
|
+
*/
|
|
36
|
+
readonly performanceMetrics?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Append machine-readable performance snapshots here. Defaults to
|
|
39
|
+
* `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.
|
|
40
|
+
*/
|
|
41
|
+
readonly performanceMetricsFile?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Debug-only facts the shared transport can observe without guessing. */
|
|
44
|
+
interface ProbePerformanceMetrics {
|
|
45
|
+
readonly enabled: boolean;
|
|
46
|
+
readonly fullSnapshots: number;
|
|
47
|
+
readonly deltas: number;
|
|
48
|
+
readonly semanticBytes: number;
|
|
49
|
+
readonly semanticNodes: number;
|
|
50
|
+
readonly unknownFrameworkNodes: number;
|
|
51
|
+
readonly droppedEvents: number;
|
|
52
|
+
readonly markerRequests: number;
|
|
53
|
+
readonly serializationMicroseconds: number;
|
|
54
|
+
readonly averageBytesPerFrame: number | null;
|
|
55
|
+
readonly averageSemanticNodesPerFrame: number | null;
|
|
56
|
+
readonly averageUnknownFrameworkNodesPerFrame: number | null;
|
|
57
|
+
readonly averageSerializationMicrosecondsPerFrame: number | null;
|
|
58
|
+
/** Present when the framework session supplies its pre-normalized fact count. */
|
|
59
|
+
readonly probeEventsPerFrame: number | null;
|
|
60
|
+
/** Superseded publications reported by a framework-owned marker queue. */
|
|
61
|
+
readonly coalescedEvents: number;
|
|
62
|
+
/** Only the framework-specific sink knows whether a returned marker drained. */
|
|
63
|
+
readonly renderCorrelationRate: null;
|
|
64
|
+
/** Normalization currently happens before the shared transport is called. */
|
|
65
|
+
readonly parentNormalizationMicrosecondsPerFrame: null;
|
|
66
|
+
}
|
|
67
|
+
interface ProbePublicationMetrics {
|
|
68
|
+
/** Probe IR objects plus render/layout operations observed for this frame. */
|
|
69
|
+
readonly probeEvents?: number;
|
|
70
|
+
}
|
|
71
|
+
/** One JSONL record written by an injected probe in debug-file mode. */
|
|
72
|
+
interface ProbePerformanceRecord {
|
|
73
|
+
readonly kind: 'termwright-probe-performance';
|
|
74
|
+
readonly adapter: string;
|
|
75
|
+
readonly framework: string;
|
|
76
|
+
readonly sessionId: string;
|
|
77
|
+
readonly metrics: ProbePerformanceMetrics;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A connected channel.
|
|
81
|
+
*
|
|
82
|
+
* `publish` returns the marker rather than writing it. Only a framework probe
|
|
83
|
+
* knows when its frame bytes have drained, and emitting earlier silently pairs
|
|
84
|
+
* the tree with the wrong screen.
|
|
85
|
+
*/
|
|
86
|
+
declare class ProbeChannel {
|
|
87
|
+
#private;
|
|
88
|
+
readonly session: ChannelSession;
|
|
89
|
+
/** @internal Built by {@link connectProbe} once the handshake succeeded. */
|
|
90
|
+
constructor(socket: Socket, session: ChannelSession, token: string, decoder: FrameDecoder, pending?: readonly unknown[], performanceMetrics?: boolean, performanceSink?: (metrics: ProbePerformanceMetrics) => void);
|
|
91
|
+
get isOpen(): boolean;
|
|
92
|
+
/** Revisions published so far. */
|
|
93
|
+
get revision(): number;
|
|
94
|
+
/**
|
|
95
|
+
* A stable, immutable debug snapshot. Disabled collection reports no
|
|
96
|
+
* fabricated zeros for averages that were never observed.
|
|
97
|
+
*/
|
|
98
|
+
performanceMetrics(): ProbePerformanceMetrics;
|
|
99
|
+
/**
|
|
100
|
+
* Send a full tree and commit its revision.
|
|
101
|
+
*
|
|
102
|
+
* Full snapshots satisfy the producer side of dropped-frame recovery by
|
|
103
|
+
* construction. Diff subscription can be added later without weakening this
|
|
104
|
+
* initial correctness floor.
|
|
105
|
+
*/
|
|
106
|
+
publish(snapshot: SemanticSnapshot, publication?: ProbePublicationMetrics): string | undefined;
|
|
107
|
+
/** Report a framework publication superseded before its marker could drain. */
|
|
108
|
+
recordCoalescedEvent(): void;
|
|
109
|
+
/** Disable the channel and release the socket. Idempotent. */
|
|
110
|
+
close(): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Connect and complete the handshake.
|
|
114
|
+
*
|
|
115
|
+
* Any failure resolves to `null`. A refused socket, malformed acknowledgement
|
|
116
|
+
* or disappearing driver must be indistinguishable from disabled semantics to
|
|
117
|
+
* the application.
|
|
118
|
+
*/
|
|
119
|
+
declare function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null>;
|
|
120
|
+
|
|
121
|
+
export { type ChannelSession, type ConnectOptions, ProbeChannel, type ProbePerformanceMetrics, type ProbePerformanceRecord, type ProbePublicationMetrics, connectProbe };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createConnection } from "net";
|
|
3
|
+
import { appendFileSync } from "fs";
|
|
4
|
+
import { performance } from "perf_hooks";
|
|
5
|
+
import {
|
|
6
|
+
createFrameDecoder,
|
|
7
|
+
encodeFrame,
|
|
8
|
+
encodeMarker,
|
|
9
|
+
parseDriverMessage,
|
|
10
|
+
DEFAULT_LIMITS,
|
|
11
|
+
PROTOCOL_ID
|
|
12
|
+
} from "@termwright/protocol";
|
|
13
|
+
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e3;
|
|
14
|
+
var ProbeChannel = class {
|
|
15
|
+
#socket;
|
|
16
|
+
session;
|
|
17
|
+
#token;
|
|
18
|
+
#open = true;
|
|
19
|
+
#revision = 0;
|
|
20
|
+
#latest;
|
|
21
|
+
#performance;
|
|
22
|
+
#performanceSink;
|
|
23
|
+
/** @internal Built by {@link connectProbe} once the handshake succeeded. */
|
|
24
|
+
constructor(socket, session, token, decoder, pending = [], performanceMetrics = false, performanceSink) {
|
|
25
|
+
this.#socket = socket;
|
|
26
|
+
this.session = session;
|
|
27
|
+
this.#token = token;
|
|
28
|
+
this.#performance = performanceMetrics ? {
|
|
29
|
+
fullSnapshots: 0,
|
|
30
|
+
semanticBytes: 0,
|
|
31
|
+
semanticNodes: 0,
|
|
32
|
+
unknownFrameworkNodes: 0,
|
|
33
|
+
droppedEvents: 0,
|
|
34
|
+
markerRequests: 0,
|
|
35
|
+
serializationMicroseconds: 0,
|
|
36
|
+
probeEvents: 0,
|
|
37
|
+
framesWithProbeEvents: 0,
|
|
38
|
+
coalescedEvents: 0
|
|
39
|
+
} : null;
|
|
40
|
+
this.#performanceSink = performanceSink === void 0 ? null : () => performanceSink(this.performanceMetrics());
|
|
41
|
+
socket.on("data", (chunk) => {
|
|
42
|
+
if (!this.#open) return;
|
|
43
|
+
try {
|
|
44
|
+
for (const value of decoder.push(chunk)) this.#dispatch(value);
|
|
45
|
+
} catch {
|
|
46
|
+
this.close();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
socket.on("error", () => this.close());
|
|
50
|
+
socket.on("close", () => this.close());
|
|
51
|
+
for (const value of pending) this.#dispatch(value);
|
|
52
|
+
}
|
|
53
|
+
get isOpen() {
|
|
54
|
+
return this.#open;
|
|
55
|
+
}
|
|
56
|
+
/** Revisions published so far. */
|
|
57
|
+
get revision() {
|
|
58
|
+
return this.#revision;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A stable, immutable debug snapshot. Disabled collection reports no
|
|
62
|
+
* fabricated zeros for averages that were never observed.
|
|
63
|
+
*/
|
|
64
|
+
performanceMetrics() {
|
|
65
|
+
const metrics = this.#performance;
|
|
66
|
+
if (metrics === null) {
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
enabled: false,
|
|
69
|
+
fullSnapshots: 0,
|
|
70
|
+
deltas: 0,
|
|
71
|
+
semanticBytes: 0,
|
|
72
|
+
semanticNodes: 0,
|
|
73
|
+
unknownFrameworkNodes: 0,
|
|
74
|
+
droppedEvents: 0,
|
|
75
|
+
markerRequests: 0,
|
|
76
|
+
serializationMicroseconds: 0,
|
|
77
|
+
averageBytesPerFrame: null,
|
|
78
|
+
averageSemanticNodesPerFrame: null,
|
|
79
|
+
averageUnknownFrameworkNodesPerFrame: null,
|
|
80
|
+
averageSerializationMicrosecondsPerFrame: null,
|
|
81
|
+
probeEventsPerFrame: null,
|
|
82
|
+
coalescedEvents: 0,
|
|
83
|
+
renderCorrelationRate: null,
|
|
84
|
+
parentNormalizationMicrosecondsPerFrame: null
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const frames = metrics.fullSnapshots;
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
enabled: true,
|
|
90
|
+
fullSnapshots: frames,
|
|
91
|
+
// ProbeChannel deliberately does not advertise tree-diffs.
|
|
92
|
+
deltas: 0,
|
|
93
|
+
semanticBytes: metrics.semanticBytes,
|
|
94
|
+
semanticNodes: metrics.semanticNodes,
|
|
95
|
+
unknownFrameworkNodes: metrics.unknownFrameworkNodes,
|
|
96
|
+
droppedEvents: metrics.droppedEvents,
|
|
97
|
+
markerRequests: metrics.markerRequests,
|
|
98
|
+
serializationMicroseconds: metrics.serializationMicroseconds,
|
|
99
|
+
averageBytesPerFrame: average(metrics.semanticBytes, frames),
|
|
100
|
+
averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),
|
|
101
|
+
averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),
|
|
102
|
+
averageSerializationMicrosecondsPerFrame: average(
|
|
103
|
+
metrics.serializationMicroseconds,
|
|
104
|
+
frames
|
|
105
|
+
),
|
|
106
|
+
probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),
|
|
107
|
+
coalescedEvents: metrics.coalescedEvents,
|
|
108
|
+
renderCorrelationRate: null,
|
|
109
|
+
parentNormalizationMicrosecondsPerFrame: null
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Send a full tree and commit its revision.
|
|
114
|
+
*
|
|
115
|
+
* Full snapshots satisfy the producer side of dropped-frame recovery by
|
|
116
|
+
* construction. Diff subscription can be added later without weakening this
|
|
117
|
+
* initial correctness floor.
|
|
118
|
+
*/
|
|
119
|
+
publish(snapshot, publication = {}) {
|
|
120
|
+
if (!this.#open) {
|
|
121
|
+
if (this.#performance !== null) this.#performance.droppedEvents += 1;
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
const sent = this.#send(
|
|
125
|
+
{ type: "snapshot", snapshot },
|
|
126
|
+
this.#performance !== null
|
|
127
|
+
);
|
|
128
|
+
if (sent === void 0) {
|
|
129
|
+
if (this.#performance !== null) this.#performance.droppedEvents += 1;
|
|
130
|
+
this.#emitPerformance();
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
this.#revision = snapshot.revision;
|
|
134
|
+
this.#latest = snapshot;
|
|
135
|
+
if (this.#performance !== null) {
|
|
136
|
+
this.#performance.fullSnapshots += 1;
|
|
137
|
+
this.#performance.semanticBytes += sent.bytes;
|
|
138
|
+
this.#performance.semanticNodes += snapshot.nodes.length;
|
|
139
|
+
this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(
|
|
140
|
+
(node) => node.role === "generic"
|
|
141
|
+
).length;
|
|
142
|
+
this.#performance.serializationMicroseconds += sent.serializationMicroseconds;
|
|
143
|
+
if (publication.probeEvents !== void 0 && Number.isSafeInteger(publication.probeEvents) && publication.probeEvents >= 0) {
|
|
144
|
+
this.#performance.probeEvents += publication.probeEvents;
|
|
145
|
+
this.#performance.framesWithProbeEvents += 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (this.#send({ type: "revision-commit", revision: snapshot.revision }) === void 0) {
|
|
149
|
+
if (this.#performance !== null) this.#performance.droppedEvents += 1;
|
|
150
|
+
this.#emitPerformance();
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
if (!this.session.markerEnabled) {
|
|
154
|
+
this.#emitPerformance();
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);
|
|
159
|
+
if (this.#performance !== null) this.#performance.markerRequests += 1;
|
|
160
|
+
this.#emitPerformance();
|
|
161
|
+
return marker;
|
|
162
|
+
} catch {
|
|
163
|
+
this.#emitPerformance();
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Report a framework publication superseded before its marker could drain. */
|
|
168
|
+
recordCoalescedEvent() {
|
|
169
|
+
if (this.#performance !== null) {
|
|
170
|
+
this.#performance.coalescedEvents += 1;
|
|
171
|
+
this.#emitPerformance();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Disable the channel and release the socket. Idempotent. */
|
|
175
|
+
close() {
|
|
176
|
+
if (!this.#open) return;
|
|
177
|
+
this.#open = false;
|
|
178
|
+
this.#latest = void 0;
|
|
179
|
+
this.#socket.removeAllListeners("data");
|
|
180
|
+
this.#socket.destroy();
|
|
181
|
+
}
|
|
182
|
+
#dispatch(value) {
|
|
183
|
+
const parsed = parseDriverMessage(value, this.session.limits);
|
|
184
|
+
if (!parsed.ok) {
|
|
185
|
+
this.close();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const message = parsed.message;
|
|
189
|
+
if (message.type === "error") {
|
|
190
|
+
this.close();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (message.type !== "get-tree") return;
|
|
194
|
+
const held = this.#latest;
|
|
195
|
+
const usable = held !== void 0 && (message.revision === void 0 || message.revision === held.revision);
|
|
196
|
+
this.#send(
|
|
197
|
+
usable ? { type: "get-tree-result", requestId: message.requestId, snapshot: held } : { type: "get-tree-result", requestId: message.requestId, error: "revision not retained" }
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
#send(message, measureSerialization = false) {
|
|
201
|
+
if (!this.#open) return void 0;
|
|
202
|
+
let frame;
|
|
203
|
+
let serializationMicroseconds;
|
|
204
|
+
try {
|
|
205
|
+
const started = measureSerialization ? performance.now() : 0;
|
|
206
|
+
frame = encodeFrame(message, this.session.limits.maxFrameBytes);
|
|
207
|
+
serializationMicroseconds = measureSerialization ? (performance.now() - started) * 1e3 : 0;
|
|
208
|
+
} catch {
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.#socket.write(frame);
|
|
213
|
+
return { bytes: frame.byteLength, serializationMicroseconds };
|
|
214
|
+
} catch {
|
|
215
|
+
this.close();
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
#emitPerformance() {
|
|
220
|
+
try {
|
|
221
|
+
this.#performanceSink?.();
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
function average(total, count) {
|
|
227
|
+
return count === 0 ? null : total / count;
|
|
228
|
+
}
|
|
229
|
+
function debugMetricsEnabled(env) {
|
|
230
|
+
if ((env["TERMWRIGHT_DEBUG_FILE"] ?? "").trim().length > 0) return true;
|
|
231
|
+
const value = (env["TERMWRIGHT_DEBUG"] ?? "").trim().toLowerCase();
|
|
232
|
+
return value === "1" || value === "true" || value === "api" || value === "all" || value === "on";
|
|
233
|
+
}
|
|
234
|
+
function performanceFile(env) {
|
|
235
|
+
const path = (env["TERMWRIGHT_DEBUG_FILE"] ?? "").trim();
|
|
236
|
+
return path.length === 0 ? void 0 : path;
|
|
237
|
+
}
|
|
238
|
+
function filePerformanceSink(path, context) {
|
|
239
|
+
if (path === void 0) return void 0;
|
|
240
|
+
return (metrics) => {
|
|
241
|
+
const record = {
|
|
242
|
+
kind: "termwright-probe-performance",
|
|
243
|
+
adapter: context.adapter,
|
|
244
|
+
framework: context.framework,
|
|
245
|
+
sessionId: context.sessionId,
|
|
246
|
+
metrics
|
|
247
|
+
};
|
|
248
|
+
appendFileSync(path, `${JSON.stringify(record)}
|
|
249
|
+
`, "utf8");
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async function connectProbe(options) {
|
|
253
|
+
const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
let settled = false;
|
|
256
|
+
let socket;
|
|
257
|
+
const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);
|
|
258
|
+
const timer = setTimeout(() => abandon(), timeoutMs);
|
|
259
|
+
timer.unref();
|
|
260
|
+
const detach = () => {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
for (const event of ["connect", "data", "error", "close"]) socket.removeAllListeners(event);
|
|
263
|
+
};
|
|
264
|
+
const abandon = () => {
|
|
265
|
+
if (settled) return;
|
|
266
|
+
settled = true;
|
|
267
|
+
detach();
|
|
268
|
+
socket.destroy();
|
|
269
|
+
resolve(null);
|
|
270
|
+
};
|
|
271
|
+
const accept = (session, pending) => {
|
|
272
|
+
if (settled) return;
|
|
273
|
+
settled = true;
|
|
274
|
+
detach();
|
|
275
|
+
const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);
|
|
276
|
+
resolve(new ProbeChannel(
|
|
277
|
+
socket,
|
|
278
|
+
session,
|
|
279
|
+
options.token,
|
|
280
|
+
decoder,
|
|
281
|
+
pending,
|
|
282
|
+
options.performanceMetrics ?? (metricsPath !== void 0 || debugMetricsEnabled(process.env)),
|
|
283
|
+
filePerformanceSink(metricsPath, {
|
|
284
|
+
adapter: options.adapterName,
|
|
285
|
+
framework: options.probe.framework,
|
|
286
|
+
sessionId: session.sessionId
|
|
287
|
+
})
|
|
288
|
+
));
|
|
289
|
+
};
|
|
290
|
+
try {
|
|
291
|
+
socket = createConnection(options.endpoint);
|
|
292
|
+
socket.unref();
|
|
293
|
+
} catch {
|
|
294
|
+
clearTimeout(timer);
|
|
295
|
+
resolve(null);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
socket.on("error", abandon);
|
|
299
|
+
socket.on("close", abandon);
|
|
300
|
+
socket.on("connect", () => {
|
|
301
|
+
try {
|
|
302
|
+
socket.write(
|
|
303
|
+
encodeFrame(
|
|
304
|
+
{
|
|
305
|
+
type: "hello",
|
|
306
|
+
protocol: options.protocol ?? PROTOCOL_ID,
|
|
307
|
+
token: options.token,
|
|
308
|
+
adapter: { name: options.adapterName, version: options.adapterVersion },
|
|
309
|
+
capabilities: options.capabilities,
|
|
310
|
+
probe: options.probe
|
|
311
|
+
},
|
|
312
|
+
DEFAULT_LIMITS.maxFrameBytes
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
} catch {
|
|
316
|
+
abandon();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
socket.on("data", (chunk) => {
|
|
320
|
+
if (settled) return;
|
|
321
|
+
let messages;
|
|
322
|
+
try {
|
|
323
|
+
messages = decoder.push(chunk);
|
|
324
|
+
} catch {
|
|
325
|
+
abandon();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (messages.length === 0) return;
|
|
329
|
+
const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);
|
|
330
|
+
if (!parsed.ok || parsed.message.type !== "hello-ack") {
|
|
331
|
+
abandon();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const ack = parsed.message;
|
|
335
|
+
if (ack.protocol !== (options.protocol ?? PROTOCOL_ID)) {
|
|
336
|
+
abandon();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
accept(
|
|
340
|
+
{
|
|
341
|
+
protocol: ack.protocol,
|
|
342
|
+
sessionId: ack.sessionId,
|
|
343
|
+
limits: narrowLimits(ack.limits),
|
|
344
|
+
markerEnabled: ack.marker.enabled
|
|
345
|
+
},
|
|
346
|
+
messages.slice(1)
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function narrowLimits(offered) {
|
|
352
|
+
const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {
|
|
353
|
+
const value = offered[key];
|
|
354
|
+
const usable = typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
355
|
+
return [key, usable ? Math.min(value, fallback) : fallback];
|
|
356
|
+
});
|
|
357
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
358
|
+
}
|
|
359
|
+
export {
|
|
360
|
+
ProbeChannel,
|
|
361
|
+
connectProbe
|
|
362
|
+
};
|
|
363
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Shared process-to-driver transport for zero-config JavaScript probes.\n *\n * Framework probes deliberately keep observation and marker placement in\n * their own packages. This module owns only the framework-neutral handshake,\n * full-snapshot publication and fail-closed socket lifecycle.\n */\n\nimport { createConnection, type Socket } from 'node:net';\nimport { appendFileSync } from 'node:fs';\nimport { performance } from 'node:perf_hooks';\nimport {\n createFrameDecoder,\n encodeFrame,\n encodeMarker,\n parseDriverMessage,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n type ProtocolId,\n type AdapterCapability,\n type AdapterToDriverMessage,\n type FrameDecoder,\n type ProbeInfo,\n type ProtocolLimits,\n type SemanticSnapshot,\n} from '@termwright/protocol';\n\n/** What the driver decided for this session. */\nexport interface ChannelSession {\n readonly protocol: ProtocolId;\n readonly sessionId: string;\n readonly limits: ProtocolLimits;\n readonly markerEnabled: boolean;\n}\n\n/** Connection parameters. */\nexport interface ConnectOptions {\n readonly endpoint: string;\n readonly token: string;\n readonly probe: ProbeInfo;\n readonly capabilities: readonly AdapterCapability[];\n readonly adapterName: string;\n readonly adapterVersion: string;\n readonly protocol?: ProtocolId;\n /** Milliseconds for connect plus handshake. Default 1000. */\n readonly handshakeTimeoutMs?: number;\n /**\n * Collect per-publication counters and timings.\n *\n * Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an\n * adapter debug file is configured. Explicit false keeps even the timer off.\n */\n readonly performanceMetrics?: boolean;\n /**\n * Append machine-readable performance snapshots here. Defaults to\n * `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.\n */\n readonly performanceMetricsFile?: string;\n}\n\n/** Debug-only facts the shared transport can observe without guessing. */\nexport interface ProbePerformanceMetrics {\n readonly enabled: boolean;\n readonly fullSnapshots: number;\n readonly deltas: number;\n readonly semanticBytes: number;\n readonly semanticNodes: number;\n readonly unknownFrameworkNodes: number;\n readonly droppedEvents: number;\n readonly markerRequests: number;\n readonly serializationMicroseconds: number;\n readonly averageBytesPerFrame: number | null;\n readonly averageSemanticNodesPerFrame: number | null;\n readonly averageUnknownFrameworkNodesPerFrame: number | null;\n readonly averageSerializationMicrosecondsPerFrame: number | null;\n /** Present when the framework session supplies its pre-normalized fact count. */\n readonly probeEventsPerFrame: number | null;\n /** Superseded publications reported by a framework-owned marker queue. */\n readonly coalescedEvents: number;\n /** Only the framework-specific sink knows whether a returned marker drained. */\n readonly renderCorrelationRate: null;\n /** Normalization currently happens before the shared transport is called. */\n readonly parentNormalizationMicrosecondsPerFrame: null;\n}\n\ninterface MutableProbePerformanceMetrics {\n fullSnapshots: number;\n semanticBytes: number;\n semanticNodes: number;\n unknownFrameworkNodes: number;\n droppedEvents: number;\n markerRequests: number;\n serializationMicroseconds: number;\n probeEvents: number;\n framesWithProbeEvents: number;\n coalescedEvents: number;\n}\n\nexport interface ProbePublicationMetrics {\n /** Probe IR objects plus render/layout operations observed for this frame. */\n readonly probeEvents?: number;\n}\n\n/** One JSONL record written by an injected probe in debug-file mode. */\nexport interface ProbePerformanceRecord {\n readonly kind: 'termwright-probe-performance';\n readonly adapter: string;\n readonly framework: string;\n readonly sessionId: string;\n readonly metrics: ProbePerformanceMetrics;\n}\n\ninterface SendResult {\n readonly bytes: number;\n readonly serializationMicroseconds: number;\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 1_000;\n\n/**\n * A connected channel.\n *\n * `publish` returns the marker rather than writing it. Only a framework probe\n * knows when its frame bytes have drained, and emitting earlier silently pairs\n * the tree with the wrong screen.\n */\nexport class ProbeChannel {\n readonly #socket: Socket;\n readonly session: ChannelSession;\n readonly #token: string;\n #open = true;\n #revision = 0;\n #latest: SemanticSnapshot | undefined;\n readonly #performance: MutableProbePerformanceMetrics | null;\n readonly #performanceSink: (() => void) | null;\n\n /** @internal Built by {@link connectProbe} once the handshake succeeded. */\n constructor(\n socket: Socket,\n session: ChannelSession,\n token: string,\n decoder: FrameDecoder,\n pending: readonly unknown[] = [],\n performanceMetrics = false,\n performanceSink?: (metrics: ProbePerformanceMetrics) => void,\n ) {\n this.#socket = socket;\n this.session = session;\n this.#token = token;\n this.#performance = performanceMetrics\n ? {\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n probeEvents: 0,\n framesWithProbeEvents: 0,\n coalescedEvents: 0,\n }\n : null;\n this.#performanceSink = performanceSink === undefined\n ? null\n : () => performanceSink(this.performanceMetrics());\n\n socket.on('data', (chunk: Buffer) => {\n if (!this.#open) return;\n try {\n for (const value of decoder.push(chunk)) this.#dispatch(value);\n } catch {\n this.close();\n }\n });\n socket.on('error', () => this.close());\n socket.on('close', () => this.close());\n\n // The ACK and the driver's first request may share one pipe/TCP chunk. The\n // handshake decoder already removed every complete frame from its buffer,\n // so values after the ACK must be handed over explicitly or they vanish.\n for (const value of pending) this.#dispatch(value);\n }\n\n get isOpen(): boolean {\n return this.#open;\n }\n\n /** Revisions published so far. */\n get revision(): number {\n return this.#revision;\n }\n\n /**\n * A stable, immutable debug snapshot. Disabled collection reports no\n * fabricated zeros for averages that were never observed.\n */\n performanceMetrics(): ProbePerformanceMetrics {\n const metrics = this.#performance;\n if (metrics === null) {\n return Object.freeze({\n enabled: false,\n fullSnapshots: 0,\n deltas: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n averageBytesPerFrame: null,\n averageSemanticNodesPerFrame: null,\n averageUnknownFrameworkNodesPerFrame: null,\n averageSerializationMicrosecondsPerFrame: null,\n probeEventsPerFrame: null,\n coalescedEvents: 0,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n const frames = metrics.fullSnapshots;\n return Object.freeze({\n enabled: true,\n fullSnapshots: frames,\n // ProbeChannel deliberately does not advertise tree-diffs.\n deltas: 0,\n semanticBytes: metrics.semanticBytes,\n semanticNodes: metrics.semanticNodes,\n unknownFrameworkNodes: metrics.unknownFrameworkNodes,\n droppedEvents: metrics.droppedEvents,\n markerRequests: metrics.markerRequests,\n serializationMicroseconds: metrics.serializationMicroseconds,\n averageBytesPerFrame: average(metrics.semanticBytes, frames),\n averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),\n averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),\n averageSerializationMicrosecondsPerFrame: average(\n metrics.serializationMicroseconds,\n frames,\n ),\n probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),\n coalescedEvents: metrics.coalescedEvents,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n\n /**\n * Send a full tree and commit its revision.\n *\n * Full snapshots satisfy the producer side of dropped-frame recovery by\n * construction. Diff subscription can be added later without weakening this\n * initial correctness floor.\n */\n publish(\n snapshot: SemanticSnapshot,\n publication: ProbePublicationMetrics = {},\n ): string | undefined {\n if (!this.#open) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n return undefined;\n }\n const sent = this.#send(\n { type: 'snapshot', snapshot },\n this.#performance !== null,\n );\n if (sent === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n this.#revision = snapshot.revision;\n this.#latest = snapshot;\n if (this.#performance !== null) {\n this.#performance.fullSnapshots += 1;\n this.#performance.semanticBytes += sent.bytes;\n this.#performance.semanticNodes += snapshot.nodes.length;\n this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(\n (node) => node.role === 'generic',\n ).length;\n this.#performance.serializationMicroseconds += sent.serializationMicroseconds;\n if (\n publication.probeEvents !== undefined\n && Number.isSafeInteger(publication.probeEvents)\n && publication.probeEvents >= 0\n ) {\n this.#performance.probeEvents += publication.probeEvents;\n this.#performance.framesWithProbeEvents += 1;\n }\n }\n if (this.#send({ type: 'revision-commit', revision: snapshot.revision }) === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n\n if (!this.session.markerEnabled) {\n this.#emitPerformance();\n return undefined;\n }\n try {\n const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);\n if (this.#performance !== null) this.#performance.markerRequests += 1;\n this.#emitPerformance();\n return marker;\n } catch {\n this.#emitPerformance();\n return undefined;\n }\n }\n\n /** Report a framework publication superseded before its marker could drain. */\n recordCoalescedEvent(): void {\n if (this.#performance !== null) {\n this.#performance.coalescedEvents += 1;\n this.#emitPerformance();\n }\n }\n\n /** Disable the channel and release the socket. Idempotent. */\n close(): void {\n if (!this.#open) return;\n this.#open = false;\n this.#latest = undefined;\n this.#socket.removeAllListeners('data');\n this.#socket.destroy();\n }\n\n #dispatch(value: unknown): void {\n const parsed = parseDriverMessage(value, this.session.limits);\n if (!parsed.ok) {\n this.close();\n return;\n }\n const message = parsed.message;\n if (message.type === 'error') {\n this.close();\n return;\n }\n if (message.type !== 'get-tree') return;\n\n const held = this.#latest;\n const usable = held !== undefined\n && (message.revision === undefined || message.revision === held.revision);\n this.#send(\n usable\n ? { type: 'get-tree-result', requestId: message.requestId, snapshot: held }\n : { type: 'get-tree-result', requestId: message.requestId, error: 'revision not retained' },\n );\n }\n\n #send(message: AdapterToDriverMessage, measureSerialization = false): SendResult | undefined {\n if (!this.#open) return undefined;\n let frame: Uint8Array;\n let serializationMicroseconds: number;\n try {\n const started = measureSerialization ? performance.now() : 0;\n frame = encodeFrame(message, this.session.limits.maxFrameBytes);\n serializationMicroseconds = measureSerialization\n ? (performance.now() - started) * 1_000\n : 0;\n } catch {\n // This is locally produced data and encodeFrame writes nothing before it\n // rejects. Drop only this publication: a later, smaller framework frame\n // can still be valid and must not lose the already negotiated channel.\n return undefined;\n }\n try {\n this.#socket.write(frame);\n return { bytes: frame.byteLength, serializationMicroseconds };\n } catch {\n this.close();\n return undefined;\n }\n }\n\n #emitPerformance(): void {\n try {\n this.#performanceSink?.();\n } catch {\n // Debug output is strictly fail-open: a bad path cannot break rendering.\n }\n }\n}\n\nfunction average(total: number, count: number): number | null {\n return count === 0 ? null : total / count;\n}\n\nfunction debugMetricsEnabled(env: NodeJS.ProcessEnv): boolean {\n if ((env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim().length > 0) return true;\n const value = (env['TERMWRIGHT_DEBUG'] ?? '').trim().toLowerCase();\n return value === '1' || value === 'true' || value === 'api' || value === 'all' || value === 'on';\n}\n\nfunction performanceFile(env: NodeJS.ProcessEnv): string | undefined {\n const path = (env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim();\n return path.length === 0 ? undefined : path;\n}\n\nfunction filePerformanceSink(\n path: string | undefined,\n context: { readonly adapter: string; readonly framework: string; readonly sessionId: string },\n): ((metrics: ProbePerformanceMetrics) => void) | undefined {\n if (path === undefined) return undefined;\n return (metrics) => {\n const record: ProbePerformanceRecord = {\n kind: 'termwright-probe-performance',\n adapter: context.adapter,\n framework: context.framework,\n sessionId: context.sessionId,\n metrics,\n };\n appendFileSync(path, `${JSON.stringify(record)}\\n`, 'utf8');\n };\n}\n\n/**\n * Connect and complete the handshake.\n *\n * Any failure resolves to `null`. A refused socket, malformed acknowledgement\n * or disappearing driver must be indistinguishable from disabled semantics to\n * the application.\n */\nexport async function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null> {\n const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n return new Promise<ProbeChannel | null>((resolve) => {\n let settled = false;\n let socket: Socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n const timer = setTimeout(() => abandon(), timeoutMs);\n timer.unref();\n\n const detach = (): void => {\n clearTimeout(timer);\n for (const event of ['connect', 'data', 'error', 'close']) socket.removeAllListeners(event);\n };\n const abandon = (): void => {\n if (settled) return;\n settled = true;\n detach();\n socket.destroy();\n resolve(null);\n };\n const accept = (session: ChannelSession, pending: readonly unknown[]): void => {\n if (settled) return;\n settled = true;\n detach();\n const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);\n resolve(new ProbeChannel(\n socket,\n session,\n options.token,\n decoder,\n pending,\n options.performanceMetrics ?? (metricsPath !== undefined || debugMetricsEnabled(process.env)),\n filePerformanceSink(metricsPath, {\n adapter: options.adapterName,\n framework: options.probe.framework,\n sessionId: session.sessionId,\n }),\n ));\n };\n\n try {\n socket = createConnection(options.endpoint);\n socket.unref();\n } catch {\n clearTimeout(timer);\n resolve(null);\n return;\n }\n\n socket.on('error', abandon);\n socket.on('close', abandon);\n socket.on('connect', () => {\n try {\n socket.write(\n encodeFrame(\n {\n type: 'hello',\n protocol: options.protocol ?? PROTOCOL_ID,\n token: options.token,\n adapter: { name: options.adapterName, version: options.adapterVersion },\n capabilities: options.capabilities,\n probe: options.probe,\n },\n DEFAULT_LIMITS.maxFrameBytes,\n ),\n );\n } catch {\n abandon();\n }\n });\n\n socket.on('data', (chunk: Buffer) => {\n if (settled) return;\n let messages: readonly unknown[];\n try {\n messages = decoder.push(chunk);\n } catch {\n abandon();\n return;\n }\n if (messages.length === 0) return;\n\n const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);\n if (!parsed.ok || parsed.message.type !== 'hello-ack') {\n abandon();\n return;\n }\n const ack = parsed.message;\n if (ack.protocol !== (options.protocol ?? PROTOCOL_ID)) {\n abandon();\n return;\n }\n accept(\n {\n protocol: ack.protocol,\n sessionId: ack.sessionId,\n limits: narrowLimits(ack.limits),\n markerEnabled: ack.marker.enabled,\n },\n messages.slice(1),\n );\n });\n });\n}\n\n/** Adopt the driver's limits, never above our own: it may tighten, not widen. */\nfunction narrowLimits(offered: ProtocolLimits): ProtocolLimits {\n const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {\n const value = (offered as unknown as Record<string, unknown>)[key];\n const usable = typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n return [key, usable ? Math.min(value, fallback as number) : fallback];\n });\n return Object.freeze(Object.fromEntries(entries)) as ProtocolLimits;\n}\n"],"mappings":";AAQA,SAAS,wBAAqC;AAC9C,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AA4FP,IAAM,+BAA+B;AAS9B,IAAM,eAAN,MAAmB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ;AAAA,EACS;AAAA,EACA;AAAA;AAAA,EAGT,YACE,QACA,SACA,OACA,SACA,UAA8B,CAAC,GAC/B,qBAAqB,OACrB,iBACA;AACA,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,eAAe,qBAChB;AAAA,MACE,eAAe;AAAA,MACf,eAAe;AAAA,MACf,eAAe;AAAA,MACf,uBAAuB;AAAA,MACvB,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,2BAA2B;AAAA,MAC3B,aAAa;AAAA,MACb,uBAAuB;AAAA,MACvB,iBAAiB;AAAA,IACnB,IACA;AACJ,SAAK,mBAAmB,oBAAoB,SACxC,OACA,MAAM,gBAAgB,KAAK,mBAAmB,CAAC;AAEnD,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,UAAI,CAAC,KAAK,MAAO;AACjB,UAAI;AACF,mBAAW,SAAS,QAAQ,KAAK,KAAK,EAAG,MAAK,UAAU,KAAK;AAAA,MAC/D,QAAQ;AACN,aAAK,MAAM;AAAA,MACb;AAAA,IACF,CAAC;AACD,WAAO,GAAG,SAAS,MAAM,KAAK,MAAM,CAAC;AACrC,WAAO,GAAG,SAAS,MAAM,KAAK,MAAM,CAAC;AAKrC,eAAW,SAAS,QAAS,MAAK,UAAU,KAAK;AAAA,EACnD;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAA8C;AAC5C,UAAM,UAAU,KAAK;AACrB,QAAI,YAAY,MAAM;AACpB,aAAO,OAAO,OAAO;AAAA,QACnB,SAAS;AAAA,QACT,eAAe;AAAA,QACf,QAAQ;AAAA,QACR,eAAe;AAAA,QACf,eAAe;AAAA,QACf,uBAAuB;AAAA,QACvB,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,2BAA2B;AAAA,QAC3B,sBAAsB;AAAA,QACtB,8BAA8B;AAAA,QAC9B,sCAAsC;AAAA,QACtC,0CAA0C;AAAA,QAC1C,qBAAqB;AAAA,QACrB,iBAAiB;AAAA,QACjB,uBAAuB;AAAA,QACvB,yCAAyC;AAAA,MAC3C,CAAC;AAAA,IACH;AACA,UAAM,SAAS,QAAQ;AACvB,WAAO,OAAO,OAAO;AAAA,MACnB,SAAS;AAAA,MACT,eAAe;AAAA;AAAA,MAEf,QAAQ;AAAA,MACR,eAAe,QAAQ;AAAA,MACvB,eAAe,QAAQ;AAAA,MACvB,uBAAuB,QAAQ;AAAA,MAC/B,eAAe,QAAQ;AAAA,MACvB,gBAAgB,QAAQ;AAAA,MACxB,2BAA2B,QAAQ;AAAA,MACnC,sBAAsB,QAAQ,QAAQ,eAAe,MAAM;AAAA,MAC3D,8BAA8B,QAAQ,QAAQ,eAAe,MAAM;AAAA,MACnE,sCAAsC,QAAQ,QAAQ,uBAAuB,MAAM;AAAA,MACnF,0CAA0C;AAAA,QACxC,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,MACA,qBAAqB,QAAQ,QAAQ,aAAa,QAAQ,qBAAqB;AAAA,MAC/E,iBAAiB,QAAQ;AAAA,MACzB,uBAAuB;AAAA,MACvB,yCAAyC;AAAA,IAC3C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QACE,UACA,cAAuC,CAAC,GACpB;AACpB,QAAI,CAAC,KAAK,OAAO;AACf,UAAI,KAAK,iBAAiB,KAAM,MAAK,aAAa,iBAAiB;AACnE,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK;AAAA,MAChB,EAAE,MAAM,YAAY,SAAS;AAAA,MAC7B,KAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,SAAS,QAAW;AACtB,UAAI,KAAK,iBAAiB,KAAM,MAAK,aAAa,iBAAiB;AACnE,WAAK,iBAAiB;AACtB,aAAO;AAAA,IACT;AACA,SAAK,YAAY,SAAS;AAC1B,SAAK,UAAU;AACf,QAAI,KAAK,iBAAiB,MAAM;AAC9B,WAAK,aAAa,iBAAiB;AACnC,WAAK,aAAa,iBAAiB,KAAK;AACxC,WAAK,aAAa,iBAAiB,SAAS,MAAM;AAClD,WAAK,aAAa,yBAAyB,SAAS,MAAM;AAAA,QACxD,CAAC,SAAS,KAAK,SAAS;AAAA,MAC1B,EAAE;AACF,WAAK,aAAa,6BAA6B,KAAK;AACpD,UACE,YAAY,gBAAgB,UACzB,OAAO,cAAc,YAAY,WAAW,KAC5C,YAAY,eAAe,GAC9B;AACA,aAAK,aAAa,eAAe,YAAY;AAC7C,aAAK,aAAa,yBAAyB;AAAA,MAC7C;AAAA,IACF;AACA,QAAI,KAAK,MAAM,EAAE,MAAM,mBAAmB,UAAU,SAAS,SAAS,CAAC,MAAM,QAAW;AACtF,UAAI,KAAK,iBAAiB,KAAM,MAAK,aAAa,iBAAiB;AACnE,WAAK,iBAAiB;AACtB,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,QAAQ,eAAe;AAC/B,WAAK,iBAAiB;AACtB,aAAO;AAAA,IACT;AACA,QAAI;AACF,YAAM,SAAS,aAAa,KAAK,QAAQ,KAAK,QAAQ,WAAW,SAAS,QAAQ;AAClF,UAAI,KAAK,iBAAiB,KAAM,MAAK,aAAa,kBAAkB;AACpE,WAAK,iBAAiB;AACtB,aAAO;AAAA,IACT,QAAQ;AACN,WAAK,iBAAiB;AACtB,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,uBAA6B;AAC3B,QAAI,KAAK,iBAAiB,MAAM;AAC9B,WAAK,aAAa,mBAAmB;AACrC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,QAAQ,mBAAmB,MAAM;AACtC,SAAK,QAAQ,QAAQ;AAAA,EACvB;AAAA,EAEA,UAAU,OAAsB;AAC9B,UAAM,SAAS,mBAAmB,OAAO,KAAK,QAAQ,MAAM;AAC5D,QAAI,CAAC,OAAO,IAAI;AACd,WAAK,MAAM;AACX;AAAA,IACF;AACA,UAAM,UAAU,OAAO;AACvB,QAAI,QAAQ,SAAS,SAAS;AAC5B,WAAK,MAAM;AACX;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,WAAY;AAEjC,UAAM,OAAO,KAAK;AAClB,UAAM,SAAS,SAAS,WAClB,QAAQ,aAAa,UAAa,QAAQ,aAAa,KAAK;AAClE,SAAK;AAAA,MACH,SACI,EAAE,MAAM,mBAAmB,WAAW,QAAQ,WAAW,UAAU,KAAK,IACxE,EAAE,MAAM,mBAAmB,WAAW,QAAQ,WAAW,OAAO,wBAAwB;AAAA,IAC9F;AAAA,EACF;AAAA,EAEA,MAAM,SAAiC,uBAAuB,OAA+B;AAC3F,QAAI,CAAC,KAAK,MAAO,QAAO;AACxB,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,uBAAuB,YAAY,IAAI,IAAI;AAC3D,cAAQ,YAAY,SAAS,KAAK,QAAQ,OAAO,aAAa;AAC9D,kCAA4B,wBACvB,YAAY,IAAI,IAAI,WAAW,MAChC;AAAA,IACN,QAAQ;AAIN,aAAO;AAAA,IACT;AACA,QAAI;AACF,WAAK,QAAQ,MAAM,KAAK;AACxB,aAAO,EAAE,OAAO,MAAM,YAAY,0BAA0B;AAAA,IAC9D,QAAQ;AACN,WAAK,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,mBAAyB;AACvB,QAAI;AACF,WAAK,mBAAmB;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,QAAQ,OAAe,OAA8B;AAC5D,SAAO,UAAU,IAAI,OAAO,QAAQ;AACtC;AAEA,SAAS,oBAAoB,KAAiC;AAC5D,OAAK,IAAI,uBAAuB,KAAK,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO;AACnE,QAAM,SAAS,IAAI,kBAAkB,KAAK,IAAI,KAAK,EAAE,YAAY;AACjE,SAAO,UAAU,OAAO,UAAU,UAAU,UAAU,SAAS,UAAU,SAAS,UAAU;AAC9F;AAEA,SAAS,gBAAgB,KAA4C;AACnE,QAAM,QAAQ,IAAI,uBAAuB,KAAK,IAAI,KAAK;AACvD,SAAO,KAAK,WAAW,IAAI,SAAY;AACzC;AAEA,SAAS,oBACP,MACA,SAC0D;AAC1D,MAAI,SAAS,OAAW,QAAO;AAC/B,SAAO,CAAC,YAAY;AAClB,UAAM,SAAiC;AAAA,MACrC,MAAM;AAAA,MACN,SAAS,QAAQ;AAAA,MACjB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB;AAAA,IACF;AACA,mBAAe,MAAM,GAAG,KAAK,UAAU,MAAM,CAAC;AAAA,GAAM,MAAM;AAAA,EAC5D;AACF;AASA,eAAsB,aAAa,SAAuD;AACxF,QAAM,YAAY,QAAQ,sBAAsB;AAEhD,SAAO,IAAI,QAA6B,CAAC,YAAY;AACnD,QAAI,UAAU;AACd,QAAI;AACJ,UAAM,UAAU,mBAAmB,eAAe,aAAa;AAC/D,UAAM,QAAQ,WAAW,MAAM,QAAQ,GAAG,SAAS;AACnD,UAAM,MAAM;AAEZ,UAAM,SAAS,MAAY;AACzB,mBAAa,KAAK;AAClB,iBAAW,SAAS,CAAC,WAAW,QAAQ,SAAS,OAAO,EAAG,QAAO,mBAAmB,KAAK;AAAA,IAC5F;AACA,UAAM,UAAU,MAAY;AAC1B,UAAI,QAAS;AACb,gBAAU;AACV,aAAO;AACP,aAAO,QAAQ;AACf,cAAQ,IAAI;AAAA,IACd;AACA,UAAM,SAAS,CAAC,SAAyB,YAAsC;AAC7E,UAAI,QAAS;AACb,gBAAU;AACV,aAAO;AACP,YAAM,cAAc,QAAQ,0BAA0B,gBAAgB,QAAQ,GAAG;AACjF,cAAQ,IAAI;AAAA,QACV;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,QAAQ,uBAAuB,gBAAgB,UAAa,oBAAoB,QAAQ,GAAG;AAAA,QAC3F,oBAAoB,aAAa;AAAA,UAC/B,SAAS,QAAQ;AAAA,UACjB,WAAW,QAAQ,MAAM;AAAA,UACzB,WAAW,QAAQ;AAAA,QACrB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,QAAI;AACF,eAAS,iBAAiB,QAAQ,QAAQ;AAC1C,aAAO,MAAM;AAAA,IACf,QAAQ;AACN,mBAAa,KAAK;AAClB,cAAQ,IAAI;AACZ;AAAA,IACF;AAEA,WAAO,GAAG,SAAS,OAAO;AAC1B,WAAO,GAAG,SAAS,OAAO;AAC1B,WAAO,GAAG,WAAW,MAAM;AACzB,UAAI;AACF,eAAO;AAAA,UACL;AAAA,YACE;AAAA,cACE,MAAM;AAAA,cACN,UAAU,QAAQ,YAAY;AAAA,cAC9B,OAAO,QAAQ;AAAA,cACf,SAAS,EAAE,MAAM,QAAQ,aAAa,SAAS,QAAQ,eAAe;AAAA,cACtE,cAAc,QAAQ;AAAA,cACtB,OAAO,QAAQ;AAAA,YACjB;AAAA,YACA,eAAe;AAAA,UACjB;AAAA,QACF;AAAA,MACF,QAAQ;AACN,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAED,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,UAAI,QAAS;AACb,UAAI;AACJ,UAAI;AACF,mBAAW,QAAQ,KAAK,KAAK;AAAA,MAC/B,QAAQ;AACN,gBAAQ;AACR;AAAA,MACF;AACA,UAAI,SAAS,WAAW,EAAG;AAE3B,YAAM,SAAS,mBAAmB,SAAS,CAAC,GAAG,cAAc;AAC7D,UAAI,CAAC,OAAO,MAAM,OAAO,QAAQ,SAAS,aAAa;AACrD,gBAAQ;AACR;AAAA,MACF;AACA,YAAM,MAAM,OAAO;AACnB,UAAI,IAAI,cAAc,QAAQ,YAAY,cAAc;AACtD,gBAAQ;AACR;AAAA,MACF;AACA;AAAA,QACE;AAAA,UACE,UAAU,IAAI;AAAA,UACd,WAAW,IAAI;AAAA,UACf,QAAQ,aAAa,IAAI,MAAM;AAAA,UAC/B,eAAe,IAAI,OAAO;AAAA,QAC5B;AAAA,QACA,SAAS,MAAM,CAAC;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAGA,SAAS,aAAa,SAAyC;AAC7D,QAAM,UAAU,OAAO,QAAQ,cAAc,EAAE,IAAI,CAAC,CAAC,KAAK,QAAQ,MAAM;AACtE,UAAM,QAAS,QAA+C,GAAG;AACjE,UAAM,SAAS,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AACnF,WAAO,CAAC,KAAK,SAAS,KAAK,IAAI,OAAO,QAAkB,IAAI,QAAQ;AAAA,EACtE,CAAC;AACD,SAAO,OAAO,OAAO,OAAO,YAAY,OAAO,CAAC;AAClD;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@termwright/probe-runtime",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "shared fail-closed transport for zero-config JavaScript probes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Gorce-AI/termwright.git",
|
|
9
|
+
"directory": "packages/probe-runtime"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@termwright/protocol": "0.2.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts --format esm --dts --sourcemap --clean",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "vitest run"
|
|
31
|
+
}
|
|
32
|
+
}
|