@getpaseo/client 0.1.97-beta.2 → 0.1.97
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/dist/daemon-client.d.ts +17 -8
- package/dist/daemon-client.js +112 -27
- package/package.json +4 -4
package/dist/daemon-client.d.ts
CHANGED
|
@@ -544,7 +544,9 @@ export declare class DaemonClient {
|
|
|
544
544
|
private lastServerInfoMessage;
|
|
545
545
|
private runtimeMetricsInterval;
|
|
546
546
|
private runtimeMetrics;
|
|
547
|
-
private
|
|
547
|
+
private pingProbe;
|
|
548
|
+
private livenessHeartbeatTimer;
|
|
549
|
+
private lastLivenessRttMs;
|
|
548
550
|
private consecutiveLivenessFailures;
|
|
549
551
|
constructor(config: DaemonClientConfig);
|
|
550
552
|
connect(): Promise<void>;
|
|
@@ -558,6 +560,7 @@ export declare class DaemonClient {
|
|
|
558
560
|
get isConnected(): boolean;
|
|
559
561
|
get isConnecting(): boolean;
|
|
560
562
|
get lastError(): string | null;
|
|
563
|
+
getLastLivenessRttMs(): number | null;
|
|
561
564
|
subscribe(handler: DaemonEventHandler): () => void;
|
|
562
565
|
subscribeRawMessages(handler: (message: SessionOutboundMessage) => void): () => void;
|
|
563
566
|
on<TType extends SessionOutboundMessage["type"]>(type: TType, handler: (message: Extract<SessionOutboundMessage, {
|
|
@@ -612,11 +615,14 @@ export declare class DaemonClient {
|
|
|
612
615
|
serverSentAt: number;
|
|
613
616
|
rttMs: number;
|
|
614
617
|
}>;
|
|
615
|
-
|
|
618
|
+
measureLatency(params?: {
|
|
616
619
|
timeoutMs?: number;
|
|
617
|
-
}): Promise<
|
|
618
|
-
|
|
619
|
-
|
|
620
|
+
}): Promise<number>;
|
|
621
|
+
private livenessPing;
|
|
622
|
+
private sendPingAwaitRtt;
|
|
623
|
+
private startLivenessHeartbeat;
|
|
624
|
+
private stopLivenessHeartbeat;
|
|
625
|
+
private scheduleNextLivenessHeartbeat;
|
|
620
626
|
fetchAgents(options?: FetchAgentsOptions): Promise<FetchAgentsPayload>;
|
|
621
627
|
fetchAgentHistory(options?: FetchAgentHistoryOptions): Promise<FetchAgentHistoryPayload>;
|
|
622
628
|
fetchRecentProviderSessions(options?: FetchRecentProviderSessionsOptions): Promise<FetchRecentProviderSessionsPayload>;
|
|
@@ -642,6 +648,9 @@ export declare class DaemonClient {
|
|
|
642
648
|
renameProject(projectId: string, customName: string | null, requestId?: string): Promise<{
|
|
643
649
|
customName: string | null;
|
|
644
650
|
}>;
|
|
651
|
+
removeProject(projectId: string, requestId?: string): Promise<{
|
|
652
|
+
removedWorkspaceIds: string[];
|
|
653
|
+
}>;
|
|
645
654
|
setWorkspaceTitle(workspaceId: string, title: string | null, requestId?: string): Promise<{
|
|
646
655
|
title: string | null;
|
|
647
656
|
}>;
|
|
@@ -904,9 +913,9 @@ export declare class DaemonClient {
|
|
|
904
913
|
private scheduleReconnect;
|
|
905
914
|
private emitDisconnectedStateForReconnect;
|
|
906
915
|
private armReconnectTimer;
|
|
907
|
-
private
|
|
908
|
-
private
|
|
909
|
-
private
|
|
916
|
+
private resolvePingProbe;
|
|
917
|
+
private clearPingProbe;
|
|
918
|
+
private rejectPingProbe;
|
|
910
919
|
private recordLivenessFailure;
|
|
911
920
|
private handleSessionMessage;
|
|
912
921
|
private resolveWaiters;
|
package/dist/daemon-client.js
CHANGED
|
@@ -35,10 +35,25 @@ class DaemonRpcError extends Error {
|
|
|
35
35
|
this.code = params.code;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
class PingTimeoutError extends Error {
|
|
39
|
+
constructor(timeoutMs) {
|
|
40
|
+
super(`Ping timed out (${timeoutMs}ms)`);
|
|
41
|
+
this.timeoutMs = timeoutMs;
|
|
42
|
+
this.name = "PingTimeoutError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function toTimeoutError(error, label, timeoutMs) {
|
|
46
|
+
if (error instanceof PingTimeoutError) {
|
|
47
|
+
return new Error(`${label} timed out (${timeoutMs}ms)`);
|
|
48
|
+
}
|
|
49
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
50
|
+
}
|
|
38
51
|
const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
|
|
39
52
|
const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
|
|
40
53
|
const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
|
|
41
54
|
const DEFAULT_LIVENESS_TIMEOUT_MS = 5000;
|
|
55
|
+
const LIVENESS_HEARTBEAT_INTERVAL_MS = 10000;
|
|
56
|
+
const LIVENESS_HEARTBEAT_TIMEOUT_MS = 15000;
|
|
42
57
|
const LIVENESS_FAILURE_RECONNECT_THRESHOLD = 2;
|
|
43
58
|
/** Default timeout for waiting for connection before sending queued messages */
|
|
44
59
|
const DEFAULT_SEND_QUEUE_TIMEOUT_MS = 10000;
|
|
@@ -161,7 +176,9 @@ export class DaemonClient {
|
|
|
161
176
|
this.lastServerInfoMessage = null;
|
|
162
177
|
this.runtimeMetricsInterval = null;
|
|
163
178
|
this.runtimeMetrics = null;
|
|
164
|
-
this.
|
|
179
|
+
this.pingProbe = null;
|
|
180
|
+
this.livenessHeartbeatTimer = null;
|
|
181
|
+
this.lastLivenessRttMs = null;
|
|
165
182
|
this.consecutiveLivenessFailures = 0;
|
|
166
183
|
this.logger = config.logger ?? consoleLogger;
|
|
167
184
|
this.logConnectionPath = isRelayClientWebSocketUrl(this.config.url) ? "relay" : "direct";
|
|
@@ -397,7 +414,7 @@ export class DaemonClient {
|
|
|
397
414
|
this.disposeTransport(1000, "Client closed");
|
|
398
415
|
this.clearWaiters(new Error("Daemon client closed"));
|
|
399
416
|
this.rejectPendingSendQueue(new Error("Daemon client closed"));
|
|
400
|
-
this.
|
|
417
|
+
this.rejectPingProbe(new Error("Daemon client closed"));
|
|
401
418
|
this.terminalStreams.clearSlots();
|
|
402
419
|
this.lastServerInfoMessage = null;
|
|
403
420
|
if (this.runtimeMetricsInterval) {
|
|
@@ -440,6 +457,9 @@ export class DaemonClient {
|
|
|
440
457
|
get lastError() {
|
|
441
458
|
return this.lastErrorValue;
|
|
442
459
|
}
|
|
460
|
+
getLastLivenessRttMs() {
|
|
461
|
+
return this.lastLivenessRttMs;
|
|
462
|
+
}
|
|
443
463
|
// ============================================================================
|
|
444
464
|
// Message Subscription
|
|
445
465
|
// ============================================================================
|
|
@@ -765,15 +785,32 @@ export class DaemonClient {
|
|
|
765
785
|
rttMs: Date.now() - clientSentAt,
|
|
766
786
|
};
|
|
767
787
|
}
|
|
768
|
-
|
|
788
|
+
measureLatency(params) {
|
|
789
|
+
const timeoutMs = Math.max(1, params?.timeoutMs ?? DEFAULT_LIVENESS_TIMEOUT_MS);
|
|
790
|
+
return this.sendPingAwaitRtt({ timeoutMs, drivesLivenessFailure: false }).catch((error) => {
|
|
791
|
+
throw toTimeoutError(error, "Latency measurement", timeoutMs);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
async livenessPing(params) {
|
|
795
|
+
const timeoutMs = Math.max(1, params?.timeoutMs ?? DEFAULT_LIVENESS_TIMEOUT_MS);
|
|
796
|
+
try {
|
|
797
|
+
const rttMs = await this.sendPingAwaitRtt({ timeoutMs, drivesLivenessFailure: true });
|
|
798
|
+
this.lastLivenessRttMs = rttMs;
|
|
799
|
+
return rttMs;
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
throw toTimeoutError(error, "Liveness check", timeoutMs);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
sendPingAwaitRtt(params) {
|
|
769
806
|
if (this.connectionState.status !== "connected" || !this.transport) {
|
|
770
807
|
return Promise.reject(new Error(`Transport not connected (status: ${this.connectionState.status})`));
|
|
771
808
|
}
|
|
772
|
-
if (this.
|
|
773
|
-
return this.
|
|
809
|
+
if (this.pingProbe) {
|
|
810
|
+
return this.pingProbe.promise;
|
|
774
811
|
}
|
|
775
812
|
const startedAt = perfNow();
|
|
776
|
-
const timeoutMs =
|
|
813
|
+
const timeoutMs = params.timeoutMs;
|
|
777
814
|
let resolveProbe = null;
|
|
778
815
|
let rejectProbe = null;
|
|
779
816
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -785,28 +822,58 @@ export class DaemonClient {
|
|
|
785
822
|
resolve: (value) => resolveProbe?.(value),
|
|
786
823
|
reject: (error) => rejectProbe?.(error),
|
|
787
824
|
timeoutHandle: setTimeout(() => {
|
|
788
|
-
if (this.
|
|
825
|
+
if (this.pingProbe !== probe) {
|
|
789
826
|
return;
|
|
790
827
|
}
|
|
791
|
-
this.
|
|
792
|
-
const error = new
|
|
828
|
+
this.pingProbe = null;
|
|
829
|
+
const error = new PingTimeoutError(timeoutMs);
|
|
793
830
|
probe.reject(error);
|
|
794
|
-
|
|
831
|
+
if (probe.drivesLivenessFailure) {
|
|
832
|
+
this.recordLivenessFailure(toTimeoutError(error, "Liveness check", timeoutMs));
|
|
833
|
+
}
|
|
795
834
|
}, timeoutMs),
|
|
796
835
|
startedAt,
|
|
836
|
+
drivesLivenessFailure: params.drivesLivenessFailure,
|
|
797
837
|
};
|
|
798
|
-
this.
|
|
838
|
+
this.pingProbe = probe;
|
|
799
839
|
try {
|
|
800
840
|
this.transport.send(JSON.stringify({ type: "ping" }));
|
|
801
841
|
}
|
|
802
842
|
catch (error) {
|
|
803
|
-
this.
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
843
|
+
this.clearPingProbe();
|
|
844
|
+
const sendError = error instanceof Error ? error : new Error(String(error));
|
|
845
|
+
if (probe.drivesLivenessFailure) {
|
|
846
|
+
this.recordLivenessFailure(sendError);
|
|
847
|
+
}
|
|
848
|
+
return Promise.reject(sendError);
|
|
807
849
|
}
|
|
808
850
|
return promise;
|
|
809
851
|
}
|
|
852
|
+
startLivenessHeartbeat() {
|
|
853
|
+
this.stopLivenessHeartbeat();
|
|
854
|
+
this.lastLivenessRttMs = null;
|
|
855
|
+
this.scheduleNextLivenessHeartbeat();
|
|
856
|
+
}
|
|
857
|
+
stopLivenessHeartbeat() {
|
|
858
|
+
if (!this.livenessHeartbeatTimer) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
clearTimeout(this.livenessHeartbeatTimer);
|
|
862
|
+
this.livenessHeartbeatTimer = null;
|
|
863
|
+
}
|
|
864
|
+
scheduleNextLivenessHeartbeat() {
|
|
865
|
+
if (this.connectionState.status !== "connected" || this.livenessHeartbeatTimer) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
this.livenessHeartbeatTimer = setTimeout(() => {
|
|
869
|
+
this.livenessHeartbeatTimer = null;
|
|
870
|
+
this.livenessPing({ timeoutMs: LIVENESS_HEARTBEAT_TIMEOUT_MS })
|
|
871
|
+
.catch(() => { })
|
|
872
|
+
.finally(() => {
|
|
873
|
+
this.scheduleNextLivenessHeartbeat();
|
|
874
|
+
});
|
|
875
|
+
}, LIVENESS_HEARTBEAT_INTERVAL_MS);
|
|
876
|
+
}
|
|
810
877
|
// ============================================================================
|
|
811
878
|
// Agent RPCs (requestId-correlated)
|
|
812
879
|
// ============================================================================
|
|
@@ -1163,6 +1230,20 @@ export class DaemonClient {
|
|
|
1163
1230
|
}
|
|
1164
1231
|
return { customName: payload.customName };
|
|
1165
1232
|
}
|
|
1233
|
+
async removeProject(projectId, requestId) {
|
|
1234
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
1235
|
+
requestId,
|
|
1236
|
+
message: {
|
|
1237
|
+
type: "project.remove.request",
|
|
1238
|
+
projectId,
|
|
1239
|
+
},
|
|
1240
|
+
timeout: 10000,
|
|
1241
|
+
});
|
|
1242
|
+
if (!payload.accepted) {
|
|
1243
|
+
throw new Error(payload.error ?? "removeProject rejected");
|
|
1244
|
+
}
|
|
1245
|
+
return { removedWorkspaceIds: payload.removedWorkspaceIds };
|
|
1246
|
+
}
|
|
1166
1247
|
async setWorkspaceTitle(workspaceId, title, requestId) {
|
|
1167
1248
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
1168
1249
|
requestId,
|
|
@@ -3134,6 +3215,7 @@ export class DaemonClient {
|
|
|
3134
3215
|
}
|
|
3135
3216
|
}
|
|
3136
3217
|
disposeTransport(code = 1001, reason = "Reconnecting") {
|
|
3218
|
+
this.stopLivenessHeartbeat();
|
|
3137
3219
|
this.cleanupTransport();
|
|
3138
3220
|
if (this.transport) {
|
|
3139
3221
|
try {
|
|
@@ -3217,7 +3299,7 @@ export class DaemonClient {
|
|
|
3217
3299
|
}
|
|
3218
3300
|
this.consecutiveLivenessFailures = 0;
|
|
3219
3301
|
if (parsed.data.type === "pong") {
|
|
3220
|
-
this.
|
|
3302
|
+
this.resolvePingProbe();
|
|
3221
3303
|
this.runtimeMetrics?.recordMessage("pong", bytes, perfNow() - startMs);
|
|
3222
3304
|
return;
|
|
3223
3305
|
}
|
|
@@ -3231,6 +3313,7 @@ export class DaemonClient {
|
|
|
3231
3313
|
tryHandleBinaryFrame(rawBytes) {
|
|
3232
3314
|
const fileFrame = decodeFileTransferFrame(rawBytes);
|
|
3233
3315
|
if (fileFrame) {
|
|
3316
|
+
this.consecutiveLivenessFailures = 0;
|
|
3234
3317
|
this.handleFileTransferFrame(fileFrame);
|
|
3235
3318
|
this.runtimeMetrics?.recordBinaryFrame("other", rawBytes.byteLength, 0);
|
|
3236
3319
|
return true;
|
|
@@ -3239,6 +3322,7 @@ export class DaemonClient {
|
|
|
3239
3322
|
if (!frame) {
|
|
3240
3323
|
return false;
|
|
3241
3324
|
}
|
|
3325
|
+
this.consecutiveLivenessFailures = 0;
|
|
3242
3326
|
const binaryStartMs = perfNow();
|
|
3243
3327
|
this.terminalStreams.handleFrame(frame);
|
|
3244
3328
|
let frameKind = "other";
|
|
@@ -3344,7 +3428,7 @@ export class DaemonClient {
|
|
|
3344
3428
|
// and responses from the previous connection will never arrive.
|
|
3345
3429
|
this.clearWaiters(new Error(reason ?? "Connection lost"));
|
|
3346
3430
|
this.rejectPendingSendQueue(new Error(reason ?? "Connection lost"));
|
|
3347
|
-
this.
|
|
3431
|
+
this.rejectPingProbe(new Error(reason ?? "Connection lost"));
|
|
3348
3432
|
this.terminalStreams.clearSlots();
|
|
3349
3433
|
this.lastServerInfoMessage = null;
|
|
3350
3434
|
if (wasDisposed) {
|
|
@@ -3382,29 +3466,29 @@ export class DaemonClient {
|
|
|
3382
3466
|
this.attemptConnect();
|
|
3383
3467
|
}, delay);
|
|
3384
3468
|
}
|
|
3385
|
-
|
|
3386
|
-
const probe = this.
|
|
3469
|
+
resolvePingProbe() {
|
|
3470
|
+
const probe = this.pingProbe;
|
|
3387
3471
|
if (!probe) {
|
|
3388
3472
|
return;
|
|
3389
3473
|
}
|
|
3390
|
-
this.
|
|
3474
|
+
this.pingProbe = null;
|
|
3391
3475
|
clearTimeout(probe.timeoutHandle);
|
|
3392
|
-
probe.resolve(
|
|
3476
|
+
probe.resolve(perfNow() - probe.startedAt);
|
|
3393
3477
|
}
|
|
3394
|
-
|
|
3395
|
-
const probe = this.
|
|
3478
|
+
clearPingProbe() {
|
|
3479
|
+
const probe = this.pingProbe;
|
|
3396
3480
|
if (!probe) {
|
|
3397
3481
|
return;
|
|
3398
3482
|
}
|
|
3399
|
-
this.
|
|
3483
|
+
this.pingProbe = null;
|
|
3400
3484
|
clearTimeout(probe.timeoutHandle);
|
|
3401
3485
|
}
|
|
3402
|
-
|
|
3403
|
-
const probe = this.
|
|
3486
|
+
rejectPingProbe(error) {
|
|
3487
|
+
const probe = this.pingProbe;
|
|
3404
3488
|
if (!probe) {
|
|
3405
3489
|
return;
|
|
3406
3490
|
}
|
|
3407
|
-
this.
|
|
3491
|
+
this.pingProbe = null;
|
|
3408
3492
|
clearTimeout(probe.timeoutHandle);
|
|
3409
3493
|
probe.reject(error);
|
|
3410
3494
|
}
|
|
@@ -3431,6 +3515,7 @@ export class DaemonClient {
|
|
|
3431
3515
|
this.resetConnectTimeout();
|
|
3432
3516
|
this.reconnectAttempt = 0;
|
|
3433
3517
|
this.updateConnectionState({ status: "connected" }, { event: "HELLO_SERVER_INFO" });
|
|
3518
|
+
this.startLivenessHeartbeat();
|
|
3434
3519
|
this.resubscribeCheckoutDiffSubscriptions();
|
|
3435
3520
|
this.resubscribeTerminalDirectorySubscriptions();
|
|
3436
3521
|
this.flushPendingSendQueue();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/client",
|
|
3
|
-
"version": "0.1.97
|
|
3
|
+
"version": "0.1.97",
|
|
4
4
|
"description": "Paseo client SDK package",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test": "vitest run"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@getpaseo/protocol": "0.1.97
|
|
39
|
-
"@getpaseo/relay": "0.1.97
|
|
40
|
-
"zod": "^
|
|
38
|
+
"@getpaseo/protocol": "0.1.97",
|
|
39
|
+
"@getpaseo/relay": "0.1.97",
|
|
40
|
+
"zod": "^4.4.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.9.0",
|