@adhdev/session-host-core 1.0.82-rc.25 → 1.0.82-rc.27
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 +20 -4
- package/dist/index.d.ts +20 -4
- package/dist/index.js +200 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +198 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli-child-priority.ts +1 -1
- package/src/index.ts +2 -0
- package/src/ipc.ts +198 -31
package/dist/index.mjs
CHANGED
|
@@ -733,16 +733,59 @@ function createLineParser(onEnvelope) {
|
|
|
733
733
|
return parser;
|
|
734
734
|
}
|
|
735
735
|
var establishedSockets = /* @__PURE__ */ new WeakSet();
|
|
736
|
+
var activeClientRefs = /* @__PURE__ */ new Set();
|
|
737
|
+
function disconnectSessionHostClients(endpoint, error) {
|
|
738
|
+
for (const ref of activeClientRefs) {
|
|
739
|
+
const client = ref.deref();
|
|
740
|
+
if (!client) {
|
|
741
|
+
activeClientRefs.delete(ref);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (!endpoint || client.endpoint.path === endpoint.path) {
|
|
745
|
+
client.disconnect(error);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function findConnectedSessionHostClient(endpoint) {
|
|
750
|
+
for (const ref of activeClientRefs) {
|
|
751
|
+
const client = ref.deref();
|
|
752
|
+
if (!client) {
|
|
753
|
+
activeClientRefs.delete(ref);
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if ((!endpoint || client.endpoint.path === endpoint.path) && client.isConnected()) {
|
|
757
|
+
return client;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
736
762
|
var SessionHostClient = class {
|
|
737
763
|
endpoint;
|
|
738
764
|
socket = null;
|
|
765
|
+
connectPromise = null;
|
|
766
|
+
requestTimeoutMs = 3e4;
|
|
739
767
|
requestWaiters = /* @__PURE__ */ new Map();
|
|
740
768
|
eventListeners = /* @__PURE__ */ new Set();
|
|
741
769
|
disconnectListeners = /* @__PURE__ */ new Set();
|
|
742
770
|
/** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */
|
|
743
771
|
disconnectedSockets = /* @__PURE__ */ new WeakSet();
|
|
772
|
+
/** Session subscriptions established via create_session/attach_session, replayed automatically on reconnect. */
|
|
773
|
+
attachedSessions = /* @__PURE__ */ new Map();
|
|
744
774
|
constructor(options = {}) {
|
|
745
775
|
this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || "adhdev");
|
|
776
|
+
if (typeof options.requestTimeoutMs === "number" && options.requestTimeoutMs > 0) {
|
|
777
|
+
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
778
|
+
}
|
|
779
|
+
if (options.registerAsShared !== false) activeClientRefs.add(new WeakRef(this));
|
|
780
|
+
}
|
|
781
|
+
static disconnectClients(endpoint, error) {
|
|
782
|
+
disconnectSessionHostClients(endpoint, error);
|
|
783
|
+
}
|
|
784
|
+
static findConnectedClient(endpoint) {
|
|
785
|
+
return findConnectedSessionHostClient(endpoint);
|
|
786
|
+
}
|
|
787
|
+
isConnected() {
|
|
788
|
+
return Boolean(this.socket && !this.socket.destroyed && establishedSockets.has(this.socket));
|
|
746
789
|
}
|
|
747
790
|
/**
|
|
748
791
|
* Fires when an established connection to the session host goes away.
|
|
@@ -785,6 +828,7 @@ var SessionHostClient = class {
|
|
|
785
828
|
this.requestWaiters.clear();
|
|
786
829
|
if (this.socket === socket) {
|
|
787
830
|
this.socket = null;
|
|
831
|
+
this.connectPromise = null;
|
|
788
832
|
}
|
|
789
833
|
try {
|
|
790
834
|
socket.destroy();
|
|
@@ -804,8 +848,47 @@ var SessionHostClient = class {
|
|
|
804
848
|
}
|
|
805
849
|
}
|
|
806
850
|
}
|
|
807
|
-
|
|
808
|
-
|
|
851
|
+
sendRawRequestOnSocket(socket, request, timeoutMs = this.requestTimeoutMs) {
|
|
852
|
+
const requestId = generateUUID();
|
|
853
|
+
const envelope = {
|
|
854
|
+
kind: "request",
|
|
855
|
+
requestId,
|
|
856
|
+
request
|
|
857
|
+
};
|
|
858
|
+
const timeoutLabel = timeoutMs % 1e3 === 0 ? `${timeoutMs / 1e3}s` : `${timeoutMs}ms`;
|
|
859
|
+
return new Promise((resolve3, reject) => {
|
|
860
|
+
const timeout = setTimeout(() => {
|
|
861
|
+
const timeoutError = Object.assign(new Error(`Session host request timed out after ${timeoutLabel} (${request.type})`), { code: "SESSION_HOST_REQUEST_TIMEOUT", requestType: request.type });
|
|
862
|
+
if (this.socket === socket) {
|
|
863
|
+
this.disconnect(timeoutError);
|
|
864
|
+
} else {
|
|
865
|
+
this.requestWaiters.delete(requestId);
|
|
866
|
+
this.disconnectedSockets.add(socket);
|
|
867
|
+
try {
|
|
868
|
+
socket.destroy();
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
reject(timeoutError);
|
|
872
|
+
}
|
|
873
|
+
}, timeoutMs);
|
|
874
|
+
this.requestWaiters.set(requestId, {
|
|
875
|
+
resolve: (value) => {
|
|
876
|
+
clearTimeout(timeout);
|
|
877
|
+
resolve3(value);
|
|
878
|
+
},
|
|
879
|
+
reject: (error) => {
|
|
880
|
+
clearTimeout(timeout);
|
|
881
|
+
reject(error);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
socket.write(serializeEnvelope(envelope));
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
async connect(timeoutMs = this.requestTimeoutMs) {
|
|
888
|
+
if (this.socket && !this.socket.destroyed) {
|
|
889
|
+
if (this.connectPromise) await this.connectPromise;
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
809
892
|
if (this.socket) {
|
|
810
893
|
try {
|
|
811
894
|
this.socket.destroy();
|
|
@@ -813,6 +896,7 @@ var SessionHostClient = class {
|
|
|
813
896
|
}
|
|
814
897
|
this.socket = null;
|
|
815
898
|
}
|
|
899
|
+
this.connectPromise = null;
|
|
816
900
|
const socket = net.createConnection(this.endpoint.path);
|
|
817
901
|
this.socket = socket;
|
|
818
902
|
const parser = createLineParser((envelope) => {
|
|
@@ -846,13 +930,54 @@ var SessionHostClient = class {
|
|
|
846
930
|
socket.on("end", () => {
|
|
847
931
|
this.handleDisconnect(socket, "ended", void 0, flushParser().length);
|
|
848
932
|
});
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
933
|
+
const connectTask = (async () => {
|
|
934
|
+
await new Promise((resolve3, reject) => {
|
|
935
|
+
const cleanup = () => {
|
|
936
|
+
clearTimeout(timer);
|
|
937
|
+
socket.off("connect", connected);
|
|
938
|
+
socket.off("error", failed);
|
|
939
|
+
socket.off("close", closed);
|
|
940
|
+
};
|
|
941
|
+
const connected = () => {
|
|
942
|
+
cleanup();
|
|
943
|
+
establishedSockets.add(socket);
|
|
944
|
+
resolve3();
|
|
945
|
+
};
|
|
946
|
+
const failed = (error) => {
|
|
947
|
+
cleanup();
|
|
948
|
+
reject(error);
|
|
949
|
+
};
|
|
950
|
+
const closed = () => failed(new Error("Session host connection closed before connect"));
|
|
951
|
+
const timer = setTimeout(() => {
|
|
952
|
+
const error = Object.assign(new Error(`Session host connect timed out after ${timeoutMs}ms`), { code: "SESSION_HOST_CONNECT_TIMEOUT" });
|
|
953
|
+
failed(error);
|
|
954
|
+
if (this.socket === socket) this.disconnect(error);
|
|
955
|
+
}, timeoutMs);
|
|
956
|
+
socket.once("connect", connected);
|
|
957
|
+
socket.once("error", failed);
|
|
958
|
+
socket.once("close", closed);
|
|
853
959
|
});
|
|
854
|
-
socket.
|
|
855
|
-
|
|
960
|
+
if (this.attachedSessions.size > 0 && this.socket === socket && !socket.destroyed) {
|
|
961
|
+
for (const [sessionId, sub] of [...this.attachedSessions.entries()]) {
|
|
962
|
+
if (this.socket !== socket || socket.destroyed) break;
|
|
963
|
+
const replayResponse = await this.sendRawRequestOnSocket(socket, {
|
|
964
|
+
type: "attach_session",
|
|
965
|
+
payload: sub
|
|
966
|
+
}).catch(() => null);
|
|
967
|
+
if (replayResponse && !replayResponse.success) {
|
|
968
|
+
this.attachedSessions.delete(sessionId);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
})();
|
|
973
|
+
this.connectPromise = connectTask;
|
|
974
|
+
try {
|
|
975
|
+
await connectTask;
|
|
976
|
+
} finally {
|
|
977
|
+
if (this.connectPromise === connectTask) {
|
|
978
|
+
this.connectPromise = null;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
856
981
|
}
|
|
857
982
|
onEvent(listener) {
|
|
858
983
|
this.eventListeners.add(listener);
|
|
@@ -860,35 +985,73 @@ var SessionHostClient = class {
|
|
|
860
985
|
this.eventListeners.delete(listener);
|
|
861
986
|
};
|
|
862
987
|
}
|
|
863
|
-
async request(request) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
988
|
+
async request(request, options = {}) {
|
|
989
|
+
const timeoutMs = Math.max(1, options.timeoutMs ?? this.requestTimeoutMs);
|
|
990
|
+
const deadline = Date.now() + timeoutMs;
|
|
991
|
+
const alreadyConnected = this.isConnected() && !this.connectPromise;
|
|
992
|
+
if (options.timeoutMs !== void 0) {
|
|
993
|
+
let timer;
|
|
994
|
+
try {
|
|
995
|
+
await Promise.race([
|
|
996
|
+
this.connect(timeoutMs),
|
|
997
|
+
new Promise((_resolve, reject) => {
|
|
998
|
+
timer = setTimeout(() => {
|
|
999
|
+
const error = Object.assign(new Error(`Session host connect timed out after ${timeoutMs}ms`), { code: "SESSION_HOST_CONNECT_TIMEOUT" });
|
|
1000
|
+
reject(error);
|
|
1001
|
+
this.disconnect(error);
|
|
1002
|
+
}, timeoutMs);
|
|
1003
|
+
})
|
|
1004
|
+
]);
|
|
1005
|
+
} finally {
|
|
1006
|
+
if (timer) clearTimeout(timer);
|
|
1007
|
+
}
|
|
1008
|
+
} else {
|
|
1009
|
+
await this.connect(timeoutMs);
|
|
1010
|
+
}
|
|
1011
|
+
const requestSocket = this.socket;
|
|
1012
|
+
if (!requestSocket) throw new Error("Session host socket unavailable");
|
|
1013
|
+
const response = await this.sendRawRequestOnSocket(requestSocket, request, options.timeoutMs === void 0 || alreadyConnected ? timeoutMs : Math.max(1, deadline - Date.now()));
|
|
1014
|
+
if (response.success) {
|
|
1015
|
+
if (request.type === "attach_session") {
|
|
1016
|
+
this.attachedSessions.set(request.payload.sessionId, {
|
|
1017
|
+
sessionId: request.payload.sessionId,
|
|
1018
|
+
clientId: request.payload.clientId,
|
|
1019
|
+
clientType: request.payload.clientType,
|
|
1020
|
+
readOnly: request.payload.readOnly === true
|
|
1021
|
+
});
|
|
1022
|
+
} else if (request.type === "create_session") {
|
|
1023
|
+
const createdId = response.result?.sessionId || request.payload.sessionId;
|
|
1024
|
+
const clientId = typeof request.payload.clientId === "string" ? request.payload.clientId.trim() : "";
|
|
1025
|
+
if (createdId && clientId) {
|
|
1026
|
+
this.attachedSessions.set(createdId, {
|
|
1027
|
+
sessionId: createdId,
|
|
1028
|
+
clientId,
|
|
1029
|
+
clientType: request.payload.clientType || "daemon",
|
|
1030
|
+
readOnly: false
|
|
1031
|
+
});
|
|
885
1032
|
}
|
|
886
|
-
})
|
|
887
|
-
|
|
888
|
-
|
|
1033
|
+
} else if (request.type === "detach_session" || request.type === "delete_session") {
|
|
1034
|
+
this.attachedSessions.delete(request.payload.sessionId);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
889
1037
|
return response;
|
|
890
1038
|
}
|
|
1039
|
+
disconnect(error) {
|
|
1040
|
+
const socket = this.socket;
|
|
1041
|
+
if (socket) {
|
|
1042
|
+
this.handleDisconnect(socket, error ? "error" : "closed", error, 0);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
this.connectPromise = null;
|
|
1046
|
+
const failure = error || new Error(`Session host client disconnected (${this.endpoint.path})`);
|
|
1047
|
+
for (const waiter of this.requestWaiters.values()) {
|
|
1048
|
+
waiter.reject(failure);
|
|
1049
|
+
}
|
|
1050
|
+
this.requestWaiters.clear();
|
|
1051
|
+
}
|
|
891
1052
|
async close() {
|
|
1053
|
+
this.attachedSessions.clear();
|
|
1054
|
+
this.connectPromise = null;
|
|
892
1055
|
if (!this.socket) return;
|
|
893
1056
|
const socket = this.socket;
|
|
894
1057
|
this.socket = null;
|
|
@@ -1105,7 +1268,9 @@ export {
|
|
|
1105
1268
|
createLineParser,
|
|
1106
1269
|
createResponseEnvelope,
|
|
1107
1270
|
createSessionHostControlPlane,
|
|
1271
|
+
disconnectSessionHostClients,
|
|
1108
1272
|
ensureNodePtySpawnHelperPermissions,
|
|
1273
|
+
findConnectedSessionHostClient,
|
|
1109
1274
|
formatRuntimeOwner,
|
|
1110
1275
|
getDefaultSessionHostEndpoint,
|
|
1111
1276
|
getSessionHostRecoveryLabel,
|