@getpaseo/client 0.1.97-beta.3 → 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.
@@ -544,7 +544,9 @@ export declare class DaemonClient {
544
544
  private lastServerInfoMessage;
545
545
  private runtimeMetricsInterval;
546
546
  private runtimeMetrics;
547
- private livenessProbe;
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
- checkLiveness(params?: {
618
+ measureLatency(params?: {
616
619
  timeoutMs?: number;
617
- }): Promise<{
618
- rttMs: number;
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 resolveLivenessProbe;
908
- private clearLivenessProbe;
909
- private rejectLivenessProbe;
916
+ private resolvePingProbe;
917
+ private clearPingProbe;
918
+ private rejectPingProbe;
910
919
  private recordLivenessFailure;
911
920
  private handleSessionMessage;
912
921
  private resolveWaiters;
@@ -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.livenessProbe = null;
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.rejectLivenessProbe(new Error("Daemon client closed"));
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
- checkLiveness(params) {
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.livenessProbe) {
773
- return this.livenessProbe.promise;
809
+ if (this.pingProbe) {
810
+ return this.pingProbe.promise;
774
811
  }
775
812
  const startedAt = perfNow();
776
- const timeoutMs = Math.max(1, params?.timeoutMs ?? DEFAULT_LIVENESS_TIMEOUT_MS);
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.livenessProbe !== probe) {
825
+ if (this.pingProbe !== probe) {
789
826
  return;
790
827
  }
791
- this.livenessProbe = null;
792
- const error = new Error(`Liveness check timed out (${timeoutMs}ms)`);
828
+ this.pingProbe = null;
829
+ const error = new PingTimeoutError(timeoutMs);
793
830
  probe.reject(error);
794
- this.recordLivenessFailure(error);
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.livenessProbe = probe;
838
+ this.pingProbe = probe;
799
839
  try {
800
840
  this.transport.send(JSON.stringify({ type: "ping" }));
801
841
  }
802
842
  catch (error) {
803
- this.clearLivenessProbe();
804
- const err = error instanceof Error ? error : new Error(String(error));
805
- this.recordLivenessFailure(err);
806
- return Promise.reject(err);
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.resolveLivenessProbe();
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.rejectLivenessProbe(new Error(reason ?? "Connection lost"));
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
- resolveLivenessProbe() {
3386
- const probe = this.livenessProbe;
3469
+ resolvePingProbe() {
3470
+ const probe = this.pingProbe;
3387
3471
  if (!probe) {
3388
3472
  return;
3389
3473
  }
3390
- this.livenessProbe = null;
3474
+ this.pingProbe = null;
3391
3475
  clearTimeout(probe.timeoutHandle);
3392
- probe.resolve({ rttMs: perfNow() - probe.startedAt });
3476
+ probe.resolve(perfNow() - probe.startedAt);
3393
3477
  }
3394
- clearLivenessProbe() {
3395
- const probe = this.livenessProbe;
3478
+ clearPingProbe() {
3479
+ const probe = this.pingProbe;
3396
3480
  if (!probe) {
3397
3481
  return;
3398
3482
  }
3399
- this.livenessProbe = null;
3483
+ this.pingProbe = null;
3400
3484
  clearTimeout(probe.timeoutHandle);
3401
3485
  }
3402
- rejectLivenessProbe(error) {
3403
- const probe = this.livenessProbe;
3486
+ rejectPingProbe(error) {
3487
+ const probe = this.pingProbe;
3404
3488
  if (!probe) {
3405
3489
  return;
3406
3490
  }
3407
- this.livenessProbe = null;
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-beta.3",
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-beta.3",
39
- "@getpaseo/relay": "0.1.97-beta.3",
40
- "zod": "^3.23.8"
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",