@dsiloed/silo-link 1.0.0

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/api/dsiloed-client.d.ts +32 -0
  4. package/dist/api/dsiloed-client.d.ts.map +1 -0
  5. package/dist/api/dsiloed-client.js +240 -0
  6. package/dist/api/dsiloed-client.js.map +1 -0
  7. package/dist/cable/action-cable-client.d.ts +28 -0
  8. package/dist/cable/action-cable-client.d.ts.map +1 -0
  9. package/dist/cable/action-cable-client.js +194 -0
  10. package/dist/cable/action-cable-client.js.map +1 -0
  11. package/dist/cable/subscription-manager.d.ts +29 -0
  12. package/dist/cable/subscription-manager.d.ts.map +1 -0
  13. package/dist/cable/subscription-manager.js +125 -0
  14. package/dist/cable/subscription-manager.js.map +1 -0
  15. package/dist/cli/commands.d.ts +4 -0
  16. package/dist/cli/commands.d.ts.map +1 -0
  17. package/dist/cli/commands.js +144 -0
  18. package/dist/cli/commands.js.map +1 -0
  19. package/dist/cli/daemon.d.ts +5 -0
  20. package/dist/cli/daemon.d.ts.map +1 -0
  21. package/dist/cli/daemon.js +35 -0
  22. package/dist/cli/daemon.js.map +1 -0
  23. package/dist/config/config-manager.d.ts +7 -0
  24. package/dist/config/config-manager.d.ts.map +1 -0
  25. package/dist/config/config-manager.js +76 -0
  26. package/dist/config/config-manager.js.map +1 -0
  27. package/dist/config/jwt-generator.d.ts +15 -0
  28. package/dist/config/jwt-generator.d.ts.map +1 -0
  29. package/dist/config/jwt-generator.js +54 -0
  30. package/dist/config/jwt-generator.js.map +1 -0
  31. package/dist/core/bridge.d.ts +37 -0
  32. package/dist/core/bridge.d.ts.map +1 -0
  33. package/dist/core/bridge.js +247 -0
  34. package/dist/core/bridge.js.map +1 -0
  35. package/dist/core/claude-launcher.d.ts +78 -0
  36. package/dist/core/claude-launcher.d.ts.map +1 -0
  37. package/dist/core/claude-launcher.js +352 -0
  38. package/dist/core/claude-launcher.js.map +1 -0
  39. package/dist/core/message-queue.d.ts +47 -0
  40. package/dist/core/message-queue.d.ts.map +1 -0
  41. package/dist/core/message-queue.js +139 -0
  42. package/dist/core/message-queue.js.map +1 -0
  43. package/dist/core/session-manager.d.ts +24 -0
  44. package/dist/core/session-manager.d.ts.map +1 -0
  45. package/dist/core/session-manager.js +111 -0
  46. package/dist/core/session-manager.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/mcp/server.d.ts +27 -0
  52. package/dist/mcp/server.d.ts.map +1 -0
  53. package/dist/mcp/server.js +109 -0
  54. package/dist/mcp/server.js.map +1 -0
  55. package/dist/mcp/tools/register-tools.d.ts +19 -0
  56. package/dist/mcp/tools/register-tools.d.ts.map +1 -0
  57. package/dist/mcp/tools/register-tools.js +385 -0
  58. package/dist/mcp/tools/register-tools.js.map +1 -0
  59. package/dist/types/index.d.ts +74 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +2 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/package.json +57 -0
@@ -0,0 +1,352 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Manages Claude Code child processes.
4
+ *
5
+ * Strategy:
6
+ * - For SiloLink-spawned sessions: nudge via stdin when idle (preserves context)
7
+ * - Respawn ONLY when the process actually exits
8
+ * - On respawn, use `claude --resume <id>` to restore context
9
+ * - Store Claude session ID in DSiloed agent_session metadata
10
+ * - Kill all child processes on SiloLink shutdown
11
+ */
12
+ export class ClaudeLauncher {
13
+ config;
14
+ sessionManager;
15
+ dsiloedClient;
16
+ childProcess = null;
17
+ tmuxSessions = new Map(); // tmuxName -> conversationId
18
+ tmuxMonitorTimer = null;
19
+ idleCheckTimer = null;
20
+ launchingConversations = new Set();
21
+ messageQueue = null;
22
+ enabled;
23
+ lastNudgeAt = 0;
24
+ nudgeCount = 0; // Consecutive nudges without activity change
25
+ lastActivitySnapshot = new Map(); // sessionId -> lastActivity timestamp
26
+ // Last Claude Code session ID (for --resume)
27
+ lastClaudeSessionId = null;
28
+ constructor(config, sessionManager, dsiloedClient, messageQueue) {
29
+ this.config = config;
30
+ this.sessionManager = sessionManager;
31
+ this.dsiloedClient = dsiloedClient || null;
32
+ this.messageQueue = messageQueue || null;
33
+ this.enabled = !!(config.claude_command && config.claude_working_directory);
34
+ }
35
+ isEnabled() {
36
+ return this.enabled;
37
+ }
38
+ isAutoRespawnEnabled() {
39
+ return this.enabled && !!this.config.claude_auto_respawn;
40
+ }
41
+ hasLiveProcess() {
42
+ return this.tmuxSessions.size > 0;
43
+ }
44
+ getLastClaudeSessionId() {
45
+ return this.lastClaudeSessionId;
46
+ }
47
+ setLastResumeId(resumeId) {
48
+ this.lastClaudeSessionId = resumeId;
49
+ console.log(`[ClaudeLauncher] Restored resume ID from previous session: ${resumeId}`);
50
+ }
51
+ /**
52
+ * Start the idle monitor that nudges idle sessions or respawns dead ones.
53
+ */
54
+ startIdleMonitor() {
55
+ if (!this.isAutoRespawnEnabled())
56
+ return;
57
+ const checkInterval = Math.max((this.config.claude_idle_timeout_ms || 30000) / 2, 5000);
58
+ this.idleCheckTimer = setInterval(() => this.checkIdle(), checkInterval);
59
+ console.log(` Claude auto-manage enabled (idle timeout: ${this.config.claude_idle_timeout_ms || 30000}ms)`);
60
+ }
61
+ stopIdleMonitor() {
62
+ if (this.idleCheckTimer) {
63
+ clearInterval(this.idleCheckTimer);
64
+ this.idleCheckTimer = null;
65
+ }
66
+ }
67
+ /**
68
+ * Launch a new Claude Code session. Uses --resume if a previous session ID is available.
69
+ */
70
+ async launch(reason = 'manual', conversationId) {
71
+ if (!this.enabled) {
72
+ console.log('[ClaudeLauncher] Not configured — set claude_command and claude_working_directory in config');
73
+ return false;
74
+ }
75
+ const convKey = conversationId || 0;
76
+ if (this.launchingConversations.has(convKey)) {
77
+ console.log(`[ClaudeLauncher] Already launching for conversation ${convKey}, skipping`);
78
+ return false;
79
+ }
80
+ this.launchingConversations.add(convKey);
81
+ try {
82
+ const command = this.config.claude_command || 'claude';
83
+ const cwd = this.config.claude_working_directory || process.cwd();
84
+ const args = ['--dangerously-skip-permissions'];
85
+ let prompt;
86
+ // Try to resume previous session if we have an ID
87
+ if (this.lastClaudeSessionId && (reason === 'process-exit' || reason === 'inbound-message')) {
88
+ args.push('--resume', this.lastClaudeSessionId);
89
+ console.log(`[ClaudeLauncher] Resuming session: ${this.lastClaudeSessionId}`);
90
+ prompt = 'Continue polling SiloLink for messages. Check memory for context if needed.';
91
+ }
92
+ else {
93
+ if (reason === 'process-exit' || reason === 'inbound-message') {
94
+ const registerParams = conversationId
95
+ ? `{ "conversation_id": ${conversationId} }`
96
+ : `{}`;
97
+ prompt = [
98
+ `Call mcp__silolink__remote_register with these exact parameters: ${registerParams}. After registering, IMMEDIATELY send a remote_notify message telling the user you are ready. Then enter the poll loop using remote_poll. See CLAUDE.md for the poll loop pattern.`,
99
+ '',
100
+ 'Context: This is a new session replacing a previous one that ended.',
101
+ 'Before entering the poll loop:',
102
+ '1. Check your memory files for context on recent work',
103
+ '2. Briefly review what was being worked on',
104
+ '3. Save any important context from the conversation history to memory',
105
+ '4. Send a remote_notify telling the user you are ready and what context you loaded',
106
+ '5. Then enter the poll loop and process any pending messages',
107
+ '',
108
+ 'CRITICAL: The user is NOT watching the terminal. You MUST send a remote_notify progress update after EVERY major step (file edits, commits, merges, deploys, errors). Do not go silent while working.',
109
+ ].join('\n');
110
+ }
111
+ else {
112
+ prompt = this.config.claude_session_prompt ||
113
+ 'Register with SiloLink as "portablemind" and enter the poll loop.';
114
+ }
115
+ }
116
+ console.log(`[ClaudeLauncher] Launching Claude Code via tmux (reason: ${reason})`);
117
+ console.log(` Command: ${command} ${args.join(' ')}`);
118
+ console.log(` Working directory: ${cwd}`);
119
+ console.log(` Prompt: ${prompt.substring(0, 100)}...`);
120
+ const tmuxSession = `silolink-claude-${Date.now()}`;
121
+ // Create a new tmux session running Claude Code in a real TTY
122
+ const claudeCmd = `cd ${cwd} && ${command} ${args.join(' ')}`;
123
+ const tmuxCreate = spawn('tmux', [
124
+ 'new-session', '-d', '-s', tmuxSession, '-x', '200', '-y', '50', claudeCmd,
125
+ ], {
126
+ stdio: 'ignore',
127
+ detached: true,
128
+ env: {
129
+ ...process.env,
130
+ SILOLINK_MCP_URL: `http://localhost:${this.config.mcp_port}/mcp`,
131
+ },
132
+ });
133
+ tmuxCreate.unref();
134
+ // Wait for tmux session to start, then send the prompt
135
+ await new Promise(resolve => setTimeout(resolve, 3000));
136
+ // Check if tmux session exists
137
+ const checkResult = spawn('tmux', ['has-session', '-t', tmuxSession], { stdio: 'ignore' });
138
+ const sessionExists = await new Promise(resolve => {
139
+ checkResult.on('exit', code => resolve(code === 0));
140
+ });
141
+ if (!sessionExists) {
142
+ console.error(`[ClaudeLauncher] tmux session ${tmuxSession} failed to start`);
143
+ return false;
144
+ }
145
+ console.log(`[ClaudeLauncher] tmux session "${tmuxSession}" created`);
146
+ this.tmuxSessions.set(tmuxSession, conversationId || 0);
147
+ // Send the prompt to Claude via tmux send-keys after MCP servers initialize
148
+ const sessionToPrompt = tmuxSession;
149
+ const promptToSend = prompt;
150
+ setTimeout(() => {
151
+ const escapedPrompt = promptToSend.replace(/"/g, '\\"').replace(/\n/g, ' ');
152
+ const sendKeys = spawn('tmux', ['send-keys', '-t', sessionToPrompt, escapedPrompt], { stdio: 'pipe' });
153
+ sendKeys.on('exit', (code) => {
154
+ if (code === 0) {
155
+ console.log(`[ClaudeLauncher] Sent prompt to tmux session "${sessionToPrompt}" (${promptToSend.length} chars)`);
156
+ // Send Enter separately after a short delay to avoid bracketed paste swallowing it
157
+ setTimeout(() => {
158
+ spawn('tmux', ['send-keys', '-t', sessionToPrompt, 'Enter'], { stdio: 'pipe' });
159
+ }, 500);
160
+ }
161
+ else {
162
+ console.error(`[ClaudeLauncher] Failed to send prompt to tmux session "${sessionToPrompt}" (exit code: ${code})`);
163
+ }
164
+ });
165
+ sendKeys.on('error', (err) => {
166
+ console.error(`[ClaudeLauncher] Error sending prompt to "${sessionToPrompt}": ${err.message}`);
167
+ });
168
+ }, 8000); // Wait 8s for MCP to initialize
169
+ // Monitor the tmux session for exit
170
+ this.monitorTmuxSession(tmuxSession);
171
+ return true;
172
+ }
173
+ finally {
174
+ setTimeout(() => { this.launchingConversations.delete(convKey); }, 10000);
175
+ }
176
+ }
177
+ /**
178
+ * Store the Claude resume ID in the DSiloed agent session metadata.
179
+ */
180
+ async storeResumeId(resumeId) {
181
+ if (!this.dsiloedClient)
182
+ return;
183
+ const sessions = this.sessionManager.getAll();
184
+ for (const session of sessions) {
185
+ if (session.agentSessionId) {
186
+ session.claudeResumeId = resumeId;
187
+ await this.dsiloedClient.updateAgentSession(session.agentSessionId, {
188
+ metadata: { claude_resume_id: resumeId },
189
+ });
190
+ console.log(`[ClaudeLauncher] Stored resume ID in agent session ${session.agentSessionId}`);
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Called when an inbound message arrives and no active session can handle it.
196
+ */
197
+ async launchOnMessage(conversationId) {
198
+ if (!this.enabled)
199
+ return false;
200
+ // Check if there's already a session registered for this conversation
201
+ if (conversationId) {
202
+ const existingSession = this.sessionManager.getByConversationId(conversationId);
203
+ if (existingSession) {
204
+ console.log(`[ClaudeLauncher] Session already exists for conversation ${conversationId} — skipping`);
205
+ return false;
206
+ }
207
+ }
208
+ return this.launch('inbound-message', conversationId);
209
+ }
210
+ /**
211
+ * Check for idle sessions — nudge if process alive, respawn if dead.
212
+ * If a session has been nudged multiple times without responding and has
213
+ * pending messages, kill and relaunch it (hung session recovery).
214
+ */
215
+ async checkIdle() {
216
+ const sessions = this.sessionManager.getAll();
217
+ const idleTimeout = this.config.claude_idle_timeout_ms || 30000;
218
+ const maxNudges = 3; // Kill after 3 failed nudges (~90s with no response)
219
+ const now = Date.now();
220
+ if (sessions.length === 0)
221
+ return;
222
+ const allIdle = sessions.every(s => now - s.lastActivity.getTime() > idleTimeout);
223
+ if (!allIdle) {
224
+ // Activity detected — reset nudge counter and update snapshots
225
+ this.nudgeCount = 0;
226
+ for (const s of sessions) {
227
+ this.lastActivitySnapshot.set(s.sessionId, s.lastActivity.getTime());
228
+ }
229
+ return;
230
+ }
231
+ if (this.hasLiveProcess()) {
232
+ // Check if activity changed since last nudge
233
+ const activityChanged = sessions.some(s => {
234
+ const prev = this.lastActivitySnapshot.get(s.sessionId) || 0;
235
+ return s.lastActivity.getTime() > prev;
236
+ });
237
+ if (activityChanged) {
238
+ this.nudgeCount = 0;
239
+ }
240
+ // Update snapshots
241
+ for (const s of sessions) {
242
+ this.lastActivitySnapshot.set(s.sessionId, s.lastActivity.getTime());
243
+ }
244
+ // Check if any session has pending messages in the queue
245
+ const hasPendingMessages = this.messageQueue && sessions.some(s => this.messageQueue.queueSize(s.sessionId) > 0);
246
+ if (this.nudgeCount >= maxNudges && hasPendingMessages) {
247
+ // Session is hung with undelivered messages — kill and relaunch
248
+ console.log(`[ClaudeLauncher] Session hung (${this.nudgeCount} nudges with no response, messages pending) — killing and relaunching`);
249
+ const hungSessions = sessions.filter(s => this.messageQueue.queueSize(s.sessionId) > 0);
250
+ for (const s of hungSessions) {
251
+ this.killByConversation(s.conversationId);
252
+ this.sessionManager.unregister(s.sessionId);
253
+ // Relaunch for this conversation
254
+ await this.launch('inbound-message', s.conversationId);
255
+ }
256
+ this.nudgeCount = 0;
257
+ return;
258
+ }
259
+ // Process alive but idle — nudge via stdin
260
+ this.nudge();
261
+ this.nudgeCount++;
262
+ return;
263
+ }
264
+ else if (this.launchingConversations.size === 0) {
265
+ const lastConvId = sessions[0]?.conversationId;
266
+ if (!this.lastClaudeSessionId) {
267
+ this.lastClaudeSessionId = sessions[0]?.claudeResumeId || null;
268
+ }
269
+ console.log(`[ClaudeLauncher] Sessions orphaned (process dead), respawning...`);
270
+ this.nudgeCount = 0;
271
+ await this.launch('process-exit', lastConvId);
272
+ }
273
+ }
274
+ /**
275
+ * Monitor a tmux session and detect when it exits.
276
+ */
277
+ monitorTmuxSession(sessionName) {
278
+ // Start monitor if not already running
279
+ if (!this.tmuxMonitorTimer) {
280
+ this.tmuxMonitorTimer = setInterval(() => this.checkTmuxSessions(), 5000);
281
+ }
282
+ }
283
+ checkTmuxSessions() {
284
+ for (const [name] of this.tmuxSessions) {
285
+ const check = spawn('tmux', ['has-session', '-t', name], { stdio: 'ignore' });
286
+ check.on('exit', (code) => {
287
+ if (code !== 0) {
288
+ console.log(`[ClaudeLauncher] tmux session "${name}" ended`);
289
+ this.tmuxSessions.delete(name);
290
+ if (this.tmuxSessions.size === 0 && this.tmuxMonitorTimer) {
291
+ clearInterval(this.tmuxMonitorTimer);
292
+ this.tmuxMonitorTimer = null;
293
+ }
294
+ }
295
+ });
296
+ }
297
+ }
298
+ /**
299
+ * Send a message to all running Claude sessions via tmux send-keys.
300
+ */
301
+ nudge() {
302
+ if (this.tmuxSessions.size === 0)
303
+ return false;
304
+ const now = Date.now();
305
+ if (now - this.lastNudgeAt < 10000)
306
+ return false;
307
+ this.lastNudgeAt = now;
308
+ const message = 'Continue polling SiloLink for messages. Do not stop the poll loop.';
309
+ for (const [name] of this.tmuxSessions) {
310
+ spawn('tmux', ['send-keys', '-t', name, message, 'Enter'], { stdio: 'ignore' });
311
+ }
312
+ console.log(`[ClaudeLauncher] Nudged ${this.tmuxSessions.size} Claude session(s) via tmux`);
313
+ return true;
314
+ }
315
+ /**
316
+ * Kill the Claude session for a specific conversation.
317
+ */
318
+ killByConversation(conversationId) {
319
+ for (const [name, convId] of this.tmuxSessions) {
320
+ if (convId === conversationId) {
321
+ console.log(`[ClaudeLauncher] Killing tmux session "${name}" (conversation ${conversationId})`);
322
+ spawn('tmux', ['kill-session', '-t', name], { stdio: 'ignore' });
323
+ this.tmuxSessions.delete(name);
324
+ return;
325
+ }
326
+ }
327
+ console.log(`[ClaudeLauncher] No tmux session found for conversation ${conversationId}`);
328
+ }
329
+ /**
330
+ * Kill all running Claude Code sessions.
331
+ */
332
+ kill() {
333
+ for (const [name] of this.tmuxSessions) {
334
+ console.log(`[ClaudeLauncher] Killing tmux session "${name}"`);
335
+ spawn('tmux', ['kill-session', '-t', name], { stdio: 'ignore' });
336
+ }
337
+ this.tmuxSessions.clear();
338
+ if (this.tmuxMonitorTimer) {
339
+ clearInterval(this.tmuxMonitorTimer);
340
+ this.tmuxMonitorTimer = null;
341
+ }
342
+ if (this.childProcess) {
343
+ this.childProcess.kill('SIGTERM');
344
+ this.childProcess = null;
345
+ }
346
+ }
347
+ destroy() {
348
+ this.stopIdleMonitor();
349
+ this.kill();
350
+ }
351
+ }
352
+ //# sourceMappingURL=claude-launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-launcher.js","sourceRoot":"","sources":["../../src/core/claude-launcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAM9D;;;;;;;;;GASG;AACH,MAAM,OAAO,cAAc;IACjB,MAAM,CAAiB;IACvB,cAAc,CAAiB;IAC/B,aAAa,CAAuB;IACpC,YAAY,GAAwB,IAAI,CAAC;IACzC,YAAY,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,6BAA6B;IAC5E,gBAAgB,GAA0C,IAAI,CAAC;IAC/D,cAAc,GAA0C,IAAI,CAAC;IAC7D,sBAAsB,GAAgB,IAAI,GAAG,EAAE,CAAC;IAChD,YAAY,GAAwB,IAAI,CAAC;IACzC,OAAO,CAAU;IACjB,WAAW,GAAG,CAAC,CAAC;IAChB,UAAU,GAAG,CAAC,CAAC,CAAC,6CAA6C;IAC7D,oBAAoB,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,sCAAsC;IACrG,6CAA6C;IACrC,mBAAmB,GAAkB,IAAI,CAAC;IAElD,YACE,MAAsB,EACtB,cAA8B,EAC9B,aAA6B,EAC7B,YAA2B;QAE3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,IAAI,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,YAAY,IAAI,IAAI,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC9E,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,oBAAoB;QAClB,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAC3D,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,sBAAsB;QACpB,OAAO,IAAI,CAAC,mBAAmB,CAAC;IAClC,CAAC;IAED,eAAe,CAAC,QAAgB;QAC9B,IAAI,CAAC,mBAAmB,GAAG,QAAQ,CAAC;QACpC,OAAO,CAAC,GAAG,CAAC,8DAA8D,QAAQ,EAAE,CAAC,CAAC;IACxF,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAAE,OAAO;QAEzC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QACxF,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,+CAA+C,IAAI,CAAC,MAAM,CAAC,sBAAsB,IAAI,KAAK,KAAK,CAAC,CAAC;IAC/G,CAAC;IAED,eAAe;QACb,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAGD;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,SAAiB,QAAQ,EAAE,cAAuB;QAC7D,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAC;YAC3G,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,OAAO,GAAG,cAAc,IAAI,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,uDAAuD,OAAO,YAAY,CAAC,CAAC;YACxF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEzC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,QAAQ,CAAC;YACvD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,wBAAwB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAElE,MAAM,IAAI,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAEhD,IAAI,MAAc,CAAC;YAEnB,kDAAkD;YAClD,IAAI,IAAI,CAAC,mBAAmB,IAAI,CAAC,MAAM,KAAK,cAAc,IAAI,MAAM,KAAK,iBAAiB,CAAC,EAAE,CAAC;gBAC5F,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;gBAChD,OAAO,CAAC,GAAG,CAAC,sCAAsC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;gBAC9E,MAAM,GAAG,6EAA6E,CAAC;YACzF,CAAC;iBAAM,CAAC;gBACN,IAAI,MAAM,KAAK,cAAc,IAAI,MAAM,KAAK,iBAAiB,EAAE,CAAC;oBAC9D,MAAM,cAAc,GAAG,cAAc;wBACnC,CAAC,CAAC,wBAAwB,cAAc,IAAI;wBAC5C,CAAC,CAAC,IAAI,CAAC;oBACT,MAAM,GAAG;wBACP,oEAAoE,cAAc,oLAAoL;wBACtQ,EAAE;wBACF,qEAAqE;wBACrE,gCAAgC;wBAChC,uDAAuD;wBACvD,4CAA4C;wBAC5C,uEAAuE;wBACvE,oFAAoF;wBACpF,8DAA8D;wBAC9D,EAAE;wBACF,uMAAuM;qBACxM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACf,CAAC;qBAAM,CAAC;oBACN,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB;wBACxC,mEAAmE,CAAC;gBACxE,CAAC;YACH,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,4DAA4D,MAAM,GAAG,CAAC,CAAC;YACnF,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YAExD,MAAM,WAAW,GAAG,mBAAmB,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAEpD,8DAA8D;YAC9D,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE;gBAC/B,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS;aAC3E,EAAE;gBACD,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,IAAI;gBACd,GAAG,EAAE;oBACH,GAAG,OAAO,CAAC,GAAG;oBACd,gBAAgB,EAAE,oBAAoB,IAAI,CAAC,MAAM,CAAC,QAAQ,MAAM;iBACjE;aACF,CAAC,CAAC;YACH,UAAU,CAAC,KAAK,EAAE,CAAC;YAEnB,uDAAuD;YACvD,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAExD,+BAA+B;YAC/B,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC3F,MAAM,aAAa,GAAG,MAAM,IAAI,OAAO,CAAU,OAAO,CAAC,EAAE;gBACzD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;YACtD,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,OAAO,CAAC,KAAK,CAAC,iCAAiC,WAAW,kBAAkB,CAAC,CAAC;gBAC9E,OAAO,KAAK,CAAC;YACf,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,kCAAkC,WAAW,WAAW,CAAC,CAAC;YACtE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,IAAI,CAAC,CAAC,CAAC;YAExD,4EAA4E;YAC5E,MAAM,eAAe,GAAG,WAAW,CAAC;YACpC,MAAM,YAAY,GAAG,MAAM,CAAC;YAC5B,UAAU,CAAC,GAAG,EAAE;gBACd,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC5E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;gBACvG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBAC3B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;wBACf,OAAO,CAAC,GAAG,CAAC,iDAAiD,eAAe,MAAM,YAAY,CAAC,MAAM,SAAS,CAAC,CAAC;wBAChH,mFAAmF;wBACnF,UAAU,CAAC,GAAG,EAAE;4BACd,KAAK,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;wBAClF,CAAC,EAAE,GAAG,CAAC,CAAC;oBACV,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,KAAK,CAAC,2DAA2D,eAAe,iBAAiB,IAAI,GAAG,CAAC,CAAC;oBACpH,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBAC3B,OAAO,CAAC,KAAK,CAAC,6CAA6C,eAAe,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACjG,CAAC,CAAC,CAAC;YACL,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,gCAAgC;YAE1C,oCAAoC;YACpC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;YAErC,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CAAC,QAAgB;QAC1C,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO;QAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC3B,OAAO,CAAC,cAAc,GAAG,QAAQ,CAAC;gBAClC,MAAM,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,cAAc,EAAE;oBAClE,QAAQ,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE;iBACzC,CAAC,CAAC;gBACH,OAAO,CAAC,GAAG,CAAC,sDAAsD,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;YAC9F,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CAAC,cAAuB;QAC3C,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAEhC,sEAAsE;QACtE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC;YAChF,IAAI,eAAe,EAAE,CAAC;gBACpB,OAAO,CAAC,GAAG,CAAC,4DAA4D,cAAc,aAAa,CAAC,CAAC;gBACrG,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,SAAS;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;QAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,sBAAsB,IAAI,KAAK,CAAC;QAChE,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,qDAAqD;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAElC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CACjC,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,WAAW,CAC7C,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,+DAA+D;YAC/D,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC;YACvE,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1B,6CAA6C;YAC7C,MAAM,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;gBACxC,MAAM,IAAI,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC7D,OAAO,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;YACzC,CAAC,CAAC,CAAC;YAEH,IAAI,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;YACtB,CAAC;YAED,mBAAmB;YACnB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,yDAAyD;YACzD,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAChE,IAAI,CAAC,YAAa,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAC9C,CAAC;YAEF,IAAI,IAAI,CAAC,UAAU,IAAI,SAAS,IAAI,kBAAkB,EAAE,CAAC;gBACvD,gEAAgE;gBAChE,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,CAAC,UAAU,uEAAuE,CAAC,CAAC;gBACtI,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACvC,IAAI,CAAC,YAAa,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAC9C,CAAC;gBACF,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;oBAC7B,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;oBAC1C,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;oBAC5C,iCAAiC;oBACjC,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC;gBACzD,CAAC;gBACD,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;gBACpB,OAAO;YACT,CAAC;YAED,2CAA2C;YAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;aAAM,IAAI,IAAI,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC9B,IAAI,CAAC,mBAAmB,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,IAAI,IAAI,CAAC;YACjE,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;YAChF,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;YACpB,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,WAAmB;QAC5C,uCAAuC;QACvC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC9E,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACxB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,SAAS,CAAC,CAAC;oBAC7D,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC/B,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;wBAC1D,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBACrC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;oBAC/B,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAE/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,GAAG,KAAK;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAEvB,MAAM,OAAO,GAAG,oEAAoE,CAAC;QACrF,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvC,KAAK,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,YAAY,CAAC,IAAI,6BAA6B,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,cAAsB;QACvC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,0CAA0C,IAAI,mBAAmB,cAAc,GAAG,CAAC,CAAC;gBAChG,KAAK,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACjE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,2DAA2D,cAAc,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;OAEG;IACH,IAAI;QACF,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,0CAA0C,IAAI,GAAG,CAAC,CAAC;YAC/D,KAAK,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACrC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ import type { InboundMessage } from '../types/index.js';
2
+ /**
3
+ * Message queue with delivery acknowledgment.
4
+ *
5
+ * Messages are NEVER removed from the queue until the next successful
6
+ * interaction confirms the prior message was delivered. This ensures
7
+ * messages survive MCP transport failures, tool rejections, and
8
+ * connection drops.
9
+ *
10
+ * Flow:
11
+ * 1. Message arrives → pushed to queue, waiter notified (if waiting)
12
+ * 2. waitForMessage returns the message → message stays in queue, marked as "pending delivery"
13
+ * 3. Next waitForMessage/checkMessages call → acknowledges prior delivery, removes from queue
14
+ * 4. If no acknowledgment (connection dropped) → message stays in queue for retry
15
+ */
16
+ export declare class MessageQueue {
17
+ private queues;
18
+ private waiters;
19
+ private pendingAck;
20
+ enqueue(sessionId: string, message: InboundMessage): void;
21
+ /**
22
+ * Get the next message, acknowledging any previously returned message.
23
+ * The returned message stays in the queue until the NEXT call acknowledges it.
24
+ */
25
+ waitForMessage(sessionId: string, timeoutMs: number): Promise<InboundMessage>;
26
+ /**
27
+ * Non-blocking check for all queued messages. Acknowledges any pending delivery.
28
+ */
29
+ dequeueAll(sessionId: string): InboundMessage[];
30
+ /**
31
+ * Non-blocking peek: returns the first message without consuming it.
32
+ * Acknowledges any previously delivered message first.
33
+ * Returns null if no messages are queued.
34
+ */
35
+ poll(sessionId: string): InboundMessage | null;
36
+ /**
37
+ * Acknowledge that the previously returned message was successfully delivered.
38
+ * Removes it from the queue.
39
+ */
40
+ private acknowledge;
41
+ clearSession(sessionId: string): void;
42
+ /**
43
+ * Get queue size for a session (for debugging)
44
+ */
45
+ queueSize(sessionId: string): number;
46
+ }
47
+ //# sourceMappingURL=message-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-queue.d.ts","sourceRoot":"","sources":["../../src/core/message-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQxD;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,OAAO,CAAkC;IAEjD,OAAO,CAAC,UAAU,CAAkC;IAEpD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAoBzD;;;OAGG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuC7E;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE;IAU/C;;;;OAIG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAW9C;;;OAGG;IACH,OAAO,CAAC,WAAW;IAgBnB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWrC;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAGrC"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Message queue with delivery acknowledgment.
3
+ *
4
+ * Messages are NEVER removed from the queue until the next successful
5
+ * interaction confirms the prior message was delivered. This ensures
6
+ * messages survive MCP transport failures, tool rejections, and
7
+ * connection drops.
8
+ *
9
+ * Flow:
10
+ * 1. Message arrives → pushed to queue, waiter notified (if waiting)
11
+ * 2. waitForMessage returns the message → message stays in queue, marked as "pending delivery"
12
+ * 3. Next waitForMessage/checkMessages call → acknowledges prior delivery, removes from queue
13
+ * 4. If no acknowledgment (connection dropped) → message stays in queue for retry
14
+ */
15
+ export class MessageQueue {
16
+ queues = new Map();
17
+ waiters = new Map();
18
+ // Track the last message ID returned to a caller — acknowledged on next call
19
+ pendingAck = new Map();
20
+ enqueue(sessionId, message) {
21
+ let queue = this.queues.get(sessionId);
22
+ if (!queue) {
23
+ queue = [];
24
+ this.queues.set(sessionId, queue);
25
+ }
26
+ queue.push(message);
27
+ console.log(`[MessageQueue] Enqueued message ${message.id} for session ${sessionId} (queue size: ${queue.length})`);
28
+ // Wake up waiter if one exists
29
+ const waiter = this.waiters.get(sessionId);
30
+ if (waiter) {
31
+ clearTimeout(waiter.timer);
32
+ this.waiters.delete(sessionId);
33
+ // Don't consume — just notify. The waiter's handler will call peek/ack.
34
+ waiter.resolve(message);
35
+ }
36
+ }
37
+ /**
38
+ * Get the next message, acknowledging any previously returned message.
39
+ * The returned message stays in the queue until the NEXT call acknowledges it.
40
+ */
41
+ waitForMessage(sessionId, timeoutMs) {
42
+ // Acknowledge previously returned message
43
+ this.acknowledge(sessionId);
44
+ // Check queue for pending messages
45
+ const queue = this.queues.get(sessionId);
46
+ if (queue && queue.length > 0) {
47
+ const message = queue[0]; // Peek, don't shift
48
+ this.pendingAck.set(sessionId, message.id);
49
+ console.log(`[MessageQueue] Returning queued message ${message.id} (pending ack, queue size: ${queue.length})`);
50
+ return Promise.resolve(message);
51
+ }
52
+ // Cancel any existing waiter
53
+ const existing = this.waiters.get(sessionId);
54
+ if (existing) {
55
+ clearTimeout(existing.timer);
56
+ existing.reject(new Error('Superseded by new waiter'));
57
+ }
58
+ return new Promise((resolve, reject) => {
59
+ const timer = setTimeout(() => {
60
+ this.waiters.delete(sessionId);
61
+ reject(new Error('Timeout waiting for message'));
62
+ }, timeoutMs);
63
+ this.waiters.set(sessionId, {
64
+ resolve: (message) => {
65
+ // Message was just enqueued — peek it without removing
66
+ this.pendingAck.set(sessionId, message.id);
67
+ console.log(`[MessageQueue] Waiter resolved with message ${message.id} (pending ack)`);
68
+ resolve(message);
69
+ },
70
+ reject,
71
+ timer
72
+ });
73
+ });
74
+ }
75
+ /**
76
+ * Non-blocking check for all queued messages. Acknowledges any pending delivery.
77
+ */
78
+ dequeueAll(sessionId) {
79
+ this.acknowledge(sessionId);
80
+ const queue = this.queues.get(sessionId);
81
+ if (!queue || queue.length === 0)
82
+ return [];
83
+ // Return copies and clear queue (check_messages consumes all)
84
+ const messages = [...queue];
85
+ queue.length = 0;
86
+ return messages;
87
+ }
88
+ /**
89
+ * Non-blocking peek: returns the first message without consuming it.
90
+ * Acknowledges any previously delivered message first.
91
+ * Returns null if no messages are queued.
92
+ */
93
+ poll(sessionId) {
94
+ this.acknowledge(sessionId);
95
+ const queue = this.queues.get(sessionId);
96
+ if (!queue || queue.length === 0)
97
+ return null;
98
+ const message = queue[0];
99
+ this.pendingAck.set(sessionId, message.id);
100
+ console.log(`[MessageQueue] Poll returned message ${message.id} (pending ack, queue size: ${queue.length})`);
101
+ return message;
102
+ }
103
+ /**
104
+ * Acknowledge that the previously returned message was successfully delivered.
105
+ * Removes it from the queue.
106
+ */
107
+ acknowledge(sessionId) {
108
+ const ackId = this.pendingAck.get(sessionId);
109
+ if (ackId === undefined)
110
+ return;
111
+ const queue = this.queues.get(sessionId);
112
+ if (queue) {
113
+ const index = queue.findIndex(m => m.id === ackId);
114
+ if (index !== -1) {
115
+ queue.splice(index, 1);
116
+ console.log(`[MessageQueue] Acknowledged and removed message ${ackId} (queue size: ${queue.length})`);
117
+ }
118
+ }
119
+ // Delete pendingAck AFTER removing from queue to prevent stuck messages
120
+ this.pendingAck.delete(sessionId);
121
+ }
122
+ clearSession(sessionId) {
123
+ this.queues.delete(sessionId);
124
+ this.pendingAck.delete(sessionId);
125
+ const waiter = this.waiters.get(sessionId);
126
+ if (waiter) {
127
+ clearTimeout(waiter.timer);
128
+ waiter.reject(new Error('Session cleared'));
129
+ this.waiters.delete(sessionId);
130
+ }
131
+ }
132
+ /**
133
+ * Get queue size for a session (for debugging)
134
+ */
135
+ queueSize(sessionId) {
136
+ return this.queues.get(sessionId)?.length || 0;
137
+ }
138
+ }
139
+ //# sourceMappingURL=message-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-queue.js","sourceRoot":"","sources":["../../src/core/message-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Session } from '../types/index.js';
2
+ export declare class SessionManager {
3
+ private sessions;
4
+ private mcpToSession;
5
+ private conversationToSession;
6
+ private cleanupTimer;
7
+ private onSessionCleaned?;
8
+ constructor(onSessionCleaned?: (session: Session) => void);
9
+ register(session: Session): void;
10
+ unregister(sessionId: string): Session | undefined;
11
+ get(sessionId: string): Session | undefined;
12
+ getByMcpSessionId(mcpSessionId: string): Session | undefined;
13
+ getByConversationId(conversationId: number): Session | undefined;
14
+ getAll(): Session[];
15
+ touchActivity(sessionId: string): void;
16
+ /**
17
+ * Checks that the 3 internal maps are consistent with each other.
18
+ * Returns true if everything is in sync, false otherwise.
19
+ */
20
+ validate(): boolean;
21
+ private cleanupIdle;
22
+ destroy(): void;
23
+ }
24
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,qBAAqB,CAAkC;IAC/D,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,gBAAgB,CAAC,CAA6B;gBAE1C,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;IAKzD,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAMhC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAUlD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI3C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAK5D,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAKhE,MAAM,IAAI,OAAO,EAAE;IAInB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAOtC;;;OAGG;IACH,QAAQ,IAAI,OAAO;IAyDnB,OAAO,CAAC,WAAW;IAcnB,OAAO,IAAI,IAAI;CAShB"}