@chrysb/alphaclaw 0.9.0-beta.5 → 0.9.0-beta.7

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.
@@ -28,33 +28,55 @@ const resolveGatewayWsUrl = ({ openclawDir, gatewayPort }) => {
28
28
 
29
29
  const sessions = new Map();
30
30
  let activeTransport = null;
31
- const kSessionGraceMs = 15_000;
32
31
  const kSseKeepAliveMs = 15_000;
32
+ const kMaxSessions = 8;
33
33
 
34
- const closeSession = (sessionId) => {
35
- const t = sessions.get(sessionId);
36
- if (!t) return;
37
- sessions.delete(sessionId);
38
- if (activeTransport === t) activeTransport = null;
39
- t.close().catch(() => {});
34
+ let nextBridgeId = 1;
35
+ const pendingRequests = new Map();
36
+
37
+ const adoptSession = (sessionId, transport) => {
38
+ sessions.set(sessionId, transport);
39
+ activeTransport = transport;
40
+
41
+ if (sessions.size > kMaxSessions) {
42
+ const oldestId = sessions.keys().next().value;
43
+ if (oldestId !== sessionId) {
44
+ const old = sessions.get(oldestId);
45
+ sessions.delete(oldestId);
46
+ old.close().catch(() => {});
47
+ console.log(`[mcp] Evicted oldest session: ${oldestId}`);
48
+ }
49
+ }
40
50
  };
41
51
 
42
- const closeAllSessions = () => {
43
- for (const [id] of sessions) closeSession(id);
44
- activeTransport = null;
52
+ const forwardToBridge = (message, transport) => {
53
+ if (message.id != null) {
54
+ const bridgeId = nextBridgeId++;
55
+ pendingRequests.set(bridgeId, { originalId: message.id, transport });
56
+ writeToMcpBridge({ ...message, id: bridgeId });
57
+ } else {
58
+ writeToMcpBridge(message);
59
+ }
45
60
  };
46
61
 
47
- const retireStaleSessions = (keepId) => {
48
- const staleIds = [...sessions.keys()].filter((id) => id !== keepId);
49
- if (staleIds.length === 0) return;
50
- setTimeout(() => {
51
- for (const id of staleIds) {
52
- if (sessions.has(id) && sessions.get(id) !== activeTransport) {
53
- console.log(`[mcp] Cleaning up stale session: ${id}`);
54
- closeSession(id);
55
- }
62
+ const cleanupTransport = (transport) => {
63
+ for (const [id, t] of sessions) {
64
+ if (t === transport) {
65
+ sessions.delete(id);
66
+ break;
56
67
  }
57
- }, kSessionGraceMs);
68
+ }
69
+ for (const [bridgeId, pending] of pendingRequests) {
70
+ if (pending.transport === transport) pendingRequests.delete(bridgeId);
71
+ }
72
+ if (activeTransport === transport) activeTransport = null;
73
+ };
74
+
75
+ const closeAllSessions = () => {
76
+ for (const [, t] of sessions) t.close().catch(() => {});
77
+ sessions.clear();
78
+ pendingRequests.clear();
79
+ activeTransport = null;
58
80
  };
59
81
 
60
82
  const registerMcpRoutes = ({
@@ -65,10 +87,21 @@ const registerMcpRoutes = ({
65
87
  openclawDir,
66
88
  }) => {
67
89
  setOnMcpMessage((message) => {
68
- if (!activeTransport) return;
69
- activeTransport.send(message).catch((err) => {
70
- console.error("[mcp] Failed to forward to transport:", err?.message);
71
- });
90
+ if (message.id != null) {
91
+ const pending = pendingRequests.get(message.id);
92
+ if (pending) {
93
+ pendingRequests.delete(message.id);
94
+ pending.transport
95
+ .send({ ...message, id: pending.originalId })
96
+ .catch((err) => {
97
+ console.error("[mcp] Failed to forward response:", err?.message);
98
+ });
99
+ }
100
+ return;
101
+ }
102
+ if (activeTransport) {
103
+ activeTransport.send(message).catch(() => {});
104
+ }
72
105
  });
73
106
 
74
107
  // ── Internal API (session auth) ────────────────────────────────
@@ -156,7 +189,7 @@ const registerMcpRoutes = ({
156
189
  const transport = sessions.get(sessionId);
157
190
  if (transport) {
158
191
  console.log(
159
- `[mcp] ${req.method} sessionId=${sessionId} → routed to transport (sessions=${sessions.size})`,
192
+ `[mcp] ${req.method} sessionId=${sessionId} → routed (sessions=${sessions.size})`,
160
193
  );
161
194
  try {
162
195
  await transport.handleRequest(req, res, req.body);
@@ -171,7 +204,7 @@ const registerMcpRoutes = ({
171
204
  }
172
205
  } else {
173
206
  console.log(
174
- `[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (known=[${[...sessions.keys()].join(", ")}])`,
207
+ `[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (sessions=${sessions.size})`,
175
208
  );
176
209
  res.status(404).json({
177
210
  jsonrpc: "2.0",
@@ -191,27 +224,19 @@ const registerMcpRoutes = ({
191
224
  sessionIdGenerator: () => randomUUID(),
192
225
  enableJsonResponse: true,
193
226
  onsessioninitialized: (newSessionId) => {
194
- sessions.set(newSessionId, transport);
195
- activeTransport = transport;
196
- retireStaleSessions(newSessionId);
227
+ adoptSession(newSessionId, transport);
197
228
  console.log(
198
- `[mcp] Session registered: ${newSessionId} (sessions=${sessions.size})`,
229
+ `[mcp] Session adopted: ${newSessionId} (sessions=${sessions.size})`,
199
230
  );
200
231
  },
201
232
  });
202
233
 
203
234
  transport.onmessage = (message) => {
204
- writeToMcpBridge(message);
235
+ forwardToBridge(message, transport);
205
236
  };
206
237
 
207
238
  transport.onclose = () => {
208
- for (const [id, t] of sessions) {
209
- if (t === transport) {
210
- sessions.delete(id);
211
- break;
212
- }
213
- }
214
- if (activeTransport === transport) activeTransport = null;
239
+ cleanupTransport(transport);
215
240
  console.log(`[mcp] Transport closed (sessions=${sessions.size})`);
216
241
  };
217
242
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.9.0-beta.5",
3
+ "version": "0.9.0-beta.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },