@adhdev/session-host-core 1.0.82-rc.25 → 1.0.82-rc.26

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/index.d.mts CHANGED
@@ -478,7 +478,7 @@ declare function createCliChildPriorityPolicy(deps?: PriorityDependencies): <T e
478
478
  * registering listeners/doing other work. There is a spawn-to-setPriority race:
479
479
  * a child can run (and fork) before this call. Later descendants inherit nice
480
480
  * on POSIX; early descendants are not retroactively changed. Windows creation
481
- * and ConPTY inheritance require verification on the MainPC.
481
+ * and ConPTY inheritance require verification on the DesktopA.
482
482
  * Optional darwin-bg is applied asynchronously by taskpolicy after nice; the
483
483
  * extra helper startup adds a further race before the background class takes
484
484
  * effect. It also throttles I/O, so nice alone remains the default.
@@ -662,6 +662,7 @@ declare function createLineParser<TEnvelope = SessionHostWireEnvelope>(onEnvelop
662
662
  interface SessionHostClientOptions {
663
663
  endpoint?: SessionHostEndpoint;
664
664
  appName?: string;
665
+ requestTimeoutMs?: number;
665
666
  }
666
667
  /**
667
668
  * Why an established session-host connection went away.
@@ -670,6 +671,7 @@ interface SessionHostClientOptions {
670
671
  * - `closed` — socket fully closed ('close') without a preceding error/end
671
672
  */
672
673
  type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';
674
+ declare function disconnectSessionHostClients(endpoint?: SessionHostEndpoint, error?: Error): void;
673
675
  interface SessionHostDisconnectInfo {
674
676
  reason: SessionHostDisconnectReason;
675
677
  endpointPath: string;
@@ -690,12 +692,17 @@ interface SessionHostDisconnectInfo {
690
692
  declare class SessionHostClient {
691
693
  readonly endpoint: SessionHostEndpoint;
692
694
  private socket;
695
+ private connectPromise;
696
+ private requestTimeoutMs;
693
697
  private requestWaiters;
694
698
  private eventListeners;
695
699
  private disconnectListeners;
696
700
  /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */
697
701
  private disconnectedSockets;
702
+ /** Session subscriptions established via create_session/attach_session, replayed automatically on reconnect. */
703
+ private attachedSessions;
698
704
  constructor(options?: SessionHostClientOptions);
705
+ static disconnectClients(endpoint?: SessionHostEndpoint, error?: Error): void;
699
706
  /**
700
707
  * Fires when an established connection to the session host goes away.
701
708
  *
@@ -710,9 +717,11 @@ declare class SessionHostClient {
710
717
  */
711
718
  onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void;
712
719
  private handleDisconnect;
720
+ private sendRawRequestOnSocket;
713
721
  connect(): Promise<void>;
714
722
  onEvent(listener: (event: SessionHostEvent) => void): () => void;
715
723
  request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>>;
724
+ disconnect(error?: Error): void;
716
725
  close(): Promise<void>;
717
726
  }
718
727
  declare function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope;
@@ -871,4 +880,4 @@ interface SessionHostControlTransport {
871
880
  */
872
881
  declare function createSessionHostControlPlane(transport: SessionHostControlTransport): SessionHostControlPlane;
873
882
 
874
- export { type AcquireWritePayload, type AttachSessionPayload, CLI_CHILD_NICE_ENV, CLI_CHILD_NICE_INCREMENT, CLI_CHILD_SCHEDULING_ENV, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type HostUpgradeLeasePayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDisconnectInfo, type SessionHostDisconnectReason, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEndpointOptions, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSnapshot, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, canonicalizeInstancePath, classifyTermination, createCliChildPriorityPolicy, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, hiddenSpawn as hiddenSpawnProcess, isDefaultInstanceConfigDir, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, isTerminalRecord, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, replayLaunchEnv, resolveAttachableRuntimeRecord, resolveInstanceConfigDir, resolveRuntimeRecord, resolveSessionHostIpcKey, sanitizeSpawnEnv, spawnCliChild, writeEnvelope };
883
+ export { type AcquireWritePayload, type AttachSessionPayload, CLI_CHILD_NICE_ENV, CLI_CHILD_NICE_INCREMENT, CLI_CHILD_SCHEDULING_ENV, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type HostUpgradeLeasePayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDisconnectInfo, type SessionHostDisconnectReason, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEndpointOptions, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSnapshot, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, canonicalizeInstancePath, classifyTermination, createCliChildPriorityPolicy, createLineParser, createResponseEnvelope, createSessionHostControlPlane, disconnectSessionHostClients, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, hiddenSpawn as hiddenSpawnProcess, isDefaultInstanceConfigDir, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, isTerminalRecord, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, replayLaunchEnv, resolveAttachableRuntimeRecord, resolveInstanceConfigDir, resolveRuntimeRecord, resolveSessionHostIpcKey, sanitizeSpawnEnv, spawnCliChild, writeEnvelope };
package/dist/index.d.ts CHANGED
@@ -478,7 +478,7 @@ declare function createCliChildPriorityPolicy(deps?: PriorityDependencies): <T e
478
478
  * registering listeners/doing other work. There is a spawn-to-setPriority race:
479
479
  * a child can run (and fork) before this call. Later descendants inherit nice
480
480
  * on POSIX; early descendants are not retroactively changed. Windows creation
481
- * and ConPTY inheritance require verification on the MainPC.
481
+ * and ConPTY inheritance require verification on the DesktopA.
482
482
  * Optional darwin-bg is applied asynchronously by taskpolicy after nice; the
483
483
  * extra helper startup adds a further race before the background class takes
484
484
  * effect. It also throttles I/O, so nice alone remains the default.
@@ -662,6 +662,7 @@ declare function createLineParser<TEnvelope = SessionHostWireEnvelope>(onEnvelop
662
662
  interface SessionHostClientOptions {
663
663
  endpoint?: SessionHostEndpoint;
664
664
  appName?: string;
665
+ requestTimeoutMs?: number;
665
666
  }
666
667
  /**
667
668
  * Why an established session-host connection went away.
@@ -670,6 +671,7 @@ interface SessionHostClientOptions {
670
671
  * - `closed` — socket fully closed ('close') without a preceding error/end
671
672
  */
672
673
  type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';
674
+ declare function disconnectSessionHostClients(endpoint?: SessionHostEndpoint, error?: Error): void;
673
675
  interface SessionHostDisconnectInfo {
674
676
  reason: SessionHostDisconnectReason;
675
677
  endpointPath: string;
@@ -690,12 +692,17 @@ interface SessionHostDisconnectInfo {
690
692
  declare class SessionHostClient {
691
693
  readonly endpoint: SessionHostEndpoint;
692
694
  private socket;
695
+ private connectPromise;
696
+ private requestTimeoutMs;
693
697
  private requestWaiters;
694
698
  private eventListeners;
695
699
  private disconnectListeners;
696
700
  /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */
697
701
  private disconnectedSockets;
702
+ /** Session subscriptions established via create_session/attach_session, replayed automatically on reconnect. */
703
+ private attachedSessions;
698
704
  constructor(options?: SessionHostClientOptions);
705
+ static disconnectClients(endpoint?: SessionHostEndpoint, error?: Error): void;
699
706
  /**
700
707
  * Fires when an established connection to the session host goes away.
701
708
  *
@@ -710,9 +717,11 @@ declare class SessionHostClient {
710
717
  */
711
718
  onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void;
712
719
  private handleDisconnect;
720
+ private sendRawRequestOnSocket;
713
721
  connect(): Promise<void>;
714
722
  onEvent(listener: (event: SessionHostEvent) => void): () => void;
715
723
  request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>>;
724
+ disconnect(error?: Error): void;
716
725
  close(): Promise<void>;
717
726
  }
718
727
  declare function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope;
@@ -871,4 +880,4 @@ interface SessionHostControlTransport {
871
880
  */
872
881
  declare function createSessionHostControlPlane(transport: SessionHostControlTransport): SessionHostControlPlane;
873
882
 
874
- export { type AcquireWritePayload, type AttachSessionPayload, CLI_CHILD_NICE_ENV, CLI_CHILD_NICE_INCREMENT, CLI_CHILD_SCHEDULING_ENV, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type HostUpgradeLeasePayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDisconnectInfo, type SessionHostDisconnectReason, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEndpointOptions, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSnapshot, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, canonicalizeInstancePath, classifyTermination, createCliChildPriorityPolicy, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, hiddenSpawn as hiddenSpawnProcess, isDefaultInstanceConfigDir, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, isTerminalRecord, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, replayLaunchEnv, resolveAttachableRuntimeRecord, resolveInstanceConfigDir, resolveRuntimeRecord, resolveSessionHostIpcKey, sanitizeSpawnEnv, spawnCliChild, writeEnvelope };
883
+ export { type AcquireWritePayload, type AttachSessionPayload, CLI_CHILD_NICE_ENV, CLI_CHILD_NICE_INCREMENT, CLI_CHILD_SCHEDULING_ENV, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_CONFIG_DIR_NAME, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type HostUpgradeLeasePayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDisconnectInfo, type SessionHostDisconnectReason, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEndpointOptions, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSnapshot, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, canonicalizeInstancePath, classifyTermination, createCliChildPriorityPolicy, createLineParser, createResponseEnvelope, createSessionHostControlPlane, disconnectSessionHostClients, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, hiddenSpawn as hiddenSpawnProcess, isDefaultInstanceConfigDir, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, isTerminalRecord, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, replayLaunchEnv, resolveAttachableRuntimeRecord, resolveInstanceConfigDir, resolveRuntimeRecord, resolveSessionHostIpcKey, sanitizeSpawnEnv, spawnCliChild, writeEnvelope };
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  createLineParser: () => createLineParser,
51
51
  createResponseEnvelope: () => createResponseEnvelope,
52
52
  createSessionHostControlPlane: () => createSessionHostControlPlane,
53
+ disconnectSessionHostClients: () => disconnectSessionHostClients,
53
54
  ensureNodePtySpawnHelperPermissions: () => ensureNodePtySpawnHelperPermissions,
54
55
  formatRuntimeOwner: () => formatRuntimeOwner,
55
56
  getDefaultSessionHostEndpoint: () => getDefaultSessionHostEndpoint,
@@ -804,16 +805,40 @@ function createLineParser(onEnvelope) {
804
805
  return parser;
805
806
  }
806
807
  var establishedSockets = /* @__PURE__ */ new WeakSet();
808
+ var activeClientRefs = /* @__PURE__ */ new Set();
809
+ function disconnectSessionHostClients(endpoint, error) {
810
+ for (const ref of activeClientRefs) {
811
+ const client = ref.deref();
812
+ if (!client) {
813
+ activeClientRefs.delete(ref);
814
+ continue;
815
+ }
816
+ if (!endpoint || client.endpoint.path === endpoint.path) {
817
+ client.disconnect(error);
818
+ }
819
+ }
820
+ }
807
821
  var SessionHostClient = class {
808
822
  endpoint;
809
823
  socket = null;
824
+ connectPromise = null;
825
+ requestTimeoutMs = 3e4;
810
826
  requestWaiters = /* @__PURE__ */ new Map();
811
827
  eventListeners = /* @__PURE__ */ new Set();
812
828
  disconnectListeners = /* @__PURE__ */ new Set();
813
829
  /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */
814
830
  disconnectedSockets = /* @__PURE__ */ new WeakSet();
831
+ /** Session subscriptions established via create_session/attach_session, replayed automatically on reconnect. */
832
+ attachedSessions = /* @__PURE__ */ new Map();
815
833
  constructor(options = {}) {
816
834
  this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || "adhdev");
835
+ if (typeof options.requestTimeoutMs === "number" && options.requestTimeoutMs > 0) {
836
+ this.requestTimeoutMs = options.requestTimeoutMs;
837
+ }
838
+ activeClientRefs.add(new WeakRef(this));
839
+ }
840
+ static disconnectClients(endpoint, error) {
841
+ disconnectSessionHostClients(endpoint, error);
817
842
  }
818
843
  /**
819
844
  * Fires when an established connection to the session host goes away.
@@ -856,6 +881,7 @@ var SessionHostClient = class {
856
881
  this.requestWaiters.clear();
857
882
  if (this.socket === socket) {
858
883
  this.socket = null;
884
+ this.connectPromise = null;
859
885
  }
860
886
  try {
861
887
  socket.destroy();
@@ -875,8 +901,48 @@ var SessionHostClient = class {
875
901
  }
876
902
  }
877
903
  }
904
+ sendRawRequestOnSocket(socket, request) {
905
+ const requestId = generateUUID();
906
+ const envelope = {
907
+ kind: "request",
908
+ requestId,
909
+ request
910
+ };
911
+ const timeoutMs = this.requestTimeoutMs > 0 ? this.requestTimeoutMs : 3e4;
912
+ const timeoutLabel = timeoutMs % 1e3 === 0 ? `${timeoutMs / 1e3}s` : `${timeoutMs}ms`;
913
+ return new Promise((resolve3, reject) => {
914
+ const timeout = setTimeout(() => {
915
+ const timeoutError = new Error(`Session host request timed out after ${timeoutLabel} (${request.type})`);
916
+ if (this.socket === socket) {
917
+ this.disconnect(timeoutError);
918
+ } else {
919
+ this.requestWaiters.delete(requestId);
920
+ this.disconnectedSockets.add(socket);
921
+ try {
922
+ socket.destroy();
923
+ } catch {
924
+ }
925
+ reject(timeoutError);
926
+ }
927
+ }, timeoutMs);
928
+ this.requestWaiters.set(requestId, {
929
+ resolve: (value) => {
930
+ clearTimeout(timeout);
931
+ resolve3(value);
932
+ },
933
+ reject: (error) => {
934
+ clearTimeout(timeout);
935
+ reject(error);
936
+ }
937
+ });
938
+ socket.write(serializeEnvelope(envelope));
939
+ });
940
+ }
878
941
  async connect() {
879
- if (this.socket && !this.socket.destroyed) return;
942
+ if (this.socket && !this.socket.destroyed) {
943
+ if (this.connectPromise) await this.connectPromise;
944
+ return;
945
+ }
880
946
  if (this.socket) {
881
947
  try {
882
948
  this.socket.destroy();
@@ -884,6 +950,7 @@ var SessionHostClient = class {
884
950
  }
885
951
  this.socket = null;
886
952
  }
953
+ this.connectPromise = null;
887
954
  const socket = net.createConnection(this.endpoint.path);
888
955
  this.socket = socket;
889
956
  const parser = createLineParser((envelope) => {
@@ -917,13 +984,35 @@ var SessionHostClient = class {
917
984
  socket.on("end", () => {
918
985
  this.handleDisconnect(socket, "ended", void 0, flushParser().length);
919
986
  });
920
- await new Promise((resolve3, reject) => {
921
- socket.once("connect", () => {
922
- establishedSockets.add(socket);
923
- resolve3();
987
+ const connectTask = (async () => {
988
+ await new Promise((resolve3, reject) => {
989
+ socket.once("connect", () => {
990
+ establishedSockets.add(socket);
991
+ resolve3();
992
+ });
993
+ socket.once("error", reject);
924
994
  });
925
- socket.once("error", reject);
926
- });
995
+ if (this.attachedSessions.size > 0 && this.socket === socket && !socket.destroyed) {
996
+ for (const [sessionId, sub] of [...this.attachedSessions.entries()]) {
997
+ if (this.socket !== socket || socket.destroyed) break;
998
+ const replayResponse = await this.sendRawRequestOnSocket(socket, {
999
+ type: "attach_session",
1000
+ payload: sub
1001
+ }).catch(() => null);
1002
+ if (replayResponse && !replayResponse.success) {
1003
+ this.attachedSessions.delete(sessionId);
1004
+ }
1005
+ }
1006
+ }
1007
+ })();
1008
+ this.connectPromise = connectTask;
1009
+ try {
1010
+ await connectTask;
1011
+ } finally {
1012
+ if (this.connectPromise === connectTask) {
1013
+ this.connectPromise = null;
1014
+ }
1015
+ }
927
1016
  }
928
1017
  onEvent(listener) {
929
1018
  this.eventListeners.add(listener);
@@ -933,33 +1022,50 @@ var SessionHostClient = class {
933
1022
  }
934
1023
  async request(request) {
935
1024
  await this.connect();
936
- if (!this.socket) throw new Error("Session host socket unavailable");
937
- const requestId = generateUUID();
938
- const envelope = {
939
- kind: "request",
940
- requestId,
941
- request
942
- };
943
- const response = await new Promise((resolve3, reject) => {
944
- const timeout = setTimeout(() => {
945
- this.requestWaiters.delete(requestId);
946
- reject(new Error(`Session host request timed out after 30s (${request.type})`));
947
- }, 3e4);
948
- this.requestWaiters.set(requestId, {
949
- resolve: (value) => {
950
- clearTimeout(timeout);
951
- resolve3(value);
952
- },
953
- reject: (error) => {
954
- clearTimeout(timeout);
955
- reject(error);
1025
+ const requestSocket = this.socket;
1026
+ if (!requestSocket) throw new Error("Session host socket unavailable");
1027
+ const response = await this.sendRawRequestOnSocket(requestSocket, request);
1028
+ if (response.success) {
1029
+ if (request.type === "attach_session") {
1030
+ this.attachedSessions.set(request.payload.sessionId, {
1031
+ sessionId: request.payload.sessionId,
1032
+ clientId: request.payload.clientId,
1033
+ clientType: request.payload.clientType,
1034
+ readOnly: request.payload.readOnly === true
1035
+ });
1036
+ } else if (request.type === "create_session") {
1037
+ const createdId = response.result?.sessionId || request.payload.sessionId;
1038
+ const clientId = typeof request.payload.clientId === "string" ? request.payload.clientId.trim() : "";
1039
+ if (createdId && clientId) {
1040
+ this.attachedSessions.set(createdId, {
1041
+ sessionId: createdId,
1042
+ clientId,
1043
+ clientType: request.payload.clientType || "daemon",
1044
+ readOnly: false
1045
+ });
956
1046
  }
957
- });
958
- this.socket?.write(serializeEnvelope(envelope));
959
- });
1047
+ } else if (request.type === "detach_session" || request.type === "delete_session") {
1048
+ this.attachedSessions.delete(request.payload.sessionId);
1049
+ }
1050
+ }
960
1051
  return response;
961
1052
  }
1053
+ disconnect(error) {
1054
+ const socket = this.socket;
1055
+ if (socket) {
1056
+ this.handleDisconnect(socket, error ? "error" : "closed", error, 0);
1057
+ return;
1058
+ }
1059
+ this.connectPromise = null;
1060
+ const failure = error || new Error(`Session host client disconnected (${this.endpoint.path})`);
1061
+ for (const waiter of this.requestWaiters.values()) {
1062
+ waiter.reject(failure);
1063
+ }
1064
+ this.requestWaiters.clear();
1065
+ }
962
1066
  async close() {
1067
+ this.attachedSessions.clear();
1068
+ this.connectPromise = null;
963
1069
  if (!this.socket) return;
964
1070
  const socket = this.socket;
965
1071
  this.socket = null;
@@ -1177,6 +1283,7 @@ function createSessionHostControlPlane(transport) {
1177
1283
  createLineParser,
1178
1284
  createResponseEnvelope,
1179
1285
  createSessionHostControlPlane,
1286
+ disconnectSessionHostClients,
1180
1287
  ensureNodePtySpawnHelperPermissions,
1181
1288
  formatRuntimeOwner,
1182
1289
  getDefaultSessionHostEndpoint,