super-subagents 1.3.13 → 1.3.14
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/build/services/sdk-client-manager.d.ts +28 -11
- package/build/services/sdk-client-manager.d.ts.map +1 -1
- package/build/services/sdk-client-manager.js +209 -84
- package/build/services/sdk-client-manager.js.map +1 -1
- package/package.json +3 -2
- package/scripts/mcp-stdio-smoke.mjs +300 -0
- package/src/services/sdk-client-manager.ts +225 -107
|
@@ -19,22 +19,12 @@ declare class SDKClientManager {
|
|
|
19
19
|
private pendingClients;
|
|
20
20
|
private isShuttingDown;
|
|
21
21
|
private staleSessionTimer;
|
|
22
|
+
private sweepCycle;
|
|
22
23
|
/**
|
|
23
24
|
* Initialize the client manager (call on MCP connect).
|
|
24
25
|
* Initializes the account manager and resets to first token.
|
|
25
26
|
*/
|
|
26
27
|
initialize(): void;
|
|
27
|
-
/**
|
|
28
|
-
* Start periodic sweeper that detects and destroys orphaned sessions.
|
|
29
|
-
* A session is orphaned if it's tracked in a client entry but its corresponding
|
|
30
|
-
* task is in a terminal state or no longer exists.
|
|
31
|
-
*/
|
|
32
|
-
private startStaleSessionSweeper;
|
|
33
|
-
/**
|
|
34
|
-
* Sweep and destroy orphaned sessions across all client entries.
|
|
35
|
-
* This is the safety net that catches sessions missed by normal lifecycle cleanup.
|
|
36
|
-
*/
|
|
37
|
-
private sweepStaleSessions;
|
|
38
28
|
/**
|
|
39
29
|
* Reset the client manager (call on MCP reconnect).
|
|
40
30
|
* Clears all clients and resets account rotation to first token.
|
|
@@ -89,6 +79,33 @@ declare class SDKClientManager {
|
|
|
89
79
|
* Prevents gradual resource leak from accumulated stale clients after rotation.
|
|
90
80
|
*/
|
|
91
81
|
private cleanupStaleClients;
|
|
82
|
+
/**
|
|
83
|
+
* Start the periodic stale-session sweeper. Timer is unref()'d so it
|
|
84
|
+
* doesn't prevent the process from exiting.
|
|
85
|
+
*/
|
|
86
|
+
private startStaleSessionSweeper;
|
|
87
|
+
/**
|
|
88
|
+
* Ping each client to detect dead/crashed CLI processes. Removes dead
|
|
89
|
+
* clients from the pool and force-stops them.
|
|
90
|
+
*/
|
|
91
|
+
private checkClientHealth;
|
|
92
|
+
/**
|
|
93
|
+
* Sweep all tracked sessions. If the corresponding task (looked up by
|
|
94
|
+
* sessionId which equals taskId by convention) is terminal or missing,
|
|
95
|
+
* destroy the session with a timeout. Then recycle PTY leakers.
|
|
96
|
+
*/
|
|
97
|
+
private sweepStaleSessions;
|
|
98
|
+
/**
|
|
99
|
+
* Detect server-side sessions that we're not tracking locally (orphaned from
|
|
100
|
+
* crashes). Uses listSessions() and deletes any not in our tracking map.
|
|
101
|
+
*/
|
|
102
|
+
private detectOrphanedSessions;
|
|
103
|
+
/**
|
|
104
|
+
* For each client entry, check PTY FD count via lsof. If count exceeds
|
|
105
|
+
* threshold and no active sessions remain, recycle the client (stop it
|
|
106
|
+
* and remove from map so the next getClient() creates a fresh one).
|
|
107
|
+
*/
|
|
108
|
+
private recyclePtyLeakers;
|
|
92
109
|
/**
|
|
93
110
|
* Check if rotation should happen based on error.
|
|
94
111
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-client-manager.d.ts","sourceRoot":"","sources":["../../src/services/sdk-client-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAA6B,KAAK,cAAc,EAAE,KAAK,aAAa,EAAiD,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"sdk-client-manager.d.ts","sourceRoot":"","sources":["../../src/services/sdk-client-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,28 +14,50 @@
|
|
|
14
14
|
* - Forwards questions to QuestionRegistry for MCP client handling
|
|
15
15
|
*/
|
|
16
16
|
import { CopilotClient } from '@github/copilot-sdk';
|
|
17
|
+
import { execSync } from 'node:child_process';
|
|
17
18
|
import { accountManager } from './account-manager.js';
|
|
18
19
|
import { questionRegistry } from './question-registry.js';
|
|
19
20
|
import { createSessionHooks } from './session-hooks.js';
|
|
20
|
-
import { taskManager } from './task-manager.js';
|
|
21
|
-
import { TERMINAL_STATUSES } from '../types.js';
|
|
21
|
+
import { taskManager, TERMINAL_STATUSES } from './task-manager.js';
|
|
22
22
|
const COPILOT_PATH = process.env.COPILOT_PATH || '/opt/homebrew/bin/copilot';
|
|
23
|
-
|
|
23
|
+
const PTY_RECYCLE_THRESHOLD = 80;
|
|
24
24
|
const DESTROY_SESSION_TIMEOUT_MS = 10_000;
|
|
25
25
|
const CLIENT_START_TIMEOUT_MS = 30_000;
|
|
26
|
-
const
|
|
26
|
+
const CLIENT_HEALTH_CHECK_TIMEOUT_MS = 5_000;
|
|
27
|
+
const RECYCLE_STOP_TIMEOUT_MS = 10_000;
|
|
28
|
+
const SHUTDOWN_STOP_TIMEOUT_MS = 15_000;
|
|
29
|
+
const STALE_SESSION_CLEANUP_INTERVAL_MS = 60_000;
|
|
27
30
|
/**
|
|
28
|
-
*
|
|
29
|
-
* Clears the timeout when the promise resolves to prevent timer leaks.
|
|
31
|
+
* Generic timeout wrapper for SDK operations that can hang.
|
|
30
32
|
*/
|
|
31
33
|
function withTimeout(promise, ms, label) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
promise.then((v) => { clearTimeout(timer);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
36
|
+
promise.then((v) => { clearTimeout(timer); resolve(v); }, (e) => { clearTimeout(timer); reject(e); });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Retry-with-backoff wrapper for session.destroy(), matching the SDK's own
|
|
41
|
+
* 3-attempt exponential backoff pattern used in stop().
|
|
42
|
+
*/
|
|
43
|
+
async function destroySessionWithRetry(session, sessionId, totalTimeoutMs) {
|
|
44
|
+
return withTimeout((async () => {
|
|
45
|
+
const MAX_ATTEMPTS = 3;
|
|
46
|
+
let lastError = null;
|
|
47
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
await session.destroy();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
55
|
+
await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt - 1)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw lastError;
|
|
60
|
+
})(), totalTimeoutMs, `destroy session ${sessionId} (with retries)`);
|
|
39
61
|
}
|
|
40
62
|
/**
|
|
41
63
|
* Create a user input handler that forwards questions to the QuestionRegistry.
|
|
@@ -49,13 +71,12 @@ function createUserInputHandler(taskId) {
|
|
|
49
71
|
return response;
|
|
50
72
|
};
|
|
51
73
|
}
|
|
52
|
-
// Interval for periodic stale session cleanup (every 60 seconds)
|
|
53
|
-
const STALE_SESSION_CLEANUP_INTERVAL_MS = 60_000;
|
|
54
74
|
class SDKClientManager {
|
|
55
75
|
clients = new Map();
|
|
56
76
|
pendingClients = new Map(); // RC-3: Dedup concurrent getClient calls
|
|
57
77
|
isShuttingDown = false;
|
|
58
78
|
staleSessionTimer = null;
|
|
79
|
+
sweepCycle = 0;
|
|
59
80
|
/**
|
|
60
81
|
* Initialize the client manager (call on MCP connect).
|
|
61
82
|
* Initializes the account manager and resets to first token.
|
|
@@ -65,72 +86,28 @@ class SDKClientManager {
|
|
|
65
86
|
this.startStaleSessionSweeper();
|
|
66
87
|
console.error(`[sdk-client-manager] Initialized with ${accountManager.getTokenCount()} account(s)`);
|
|
67
88
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Start periodic sweeper that detects and destroys orphaned sessions.
|
|
70
|
-
* A session is orphaned if it's tracked in a client entry but its corresponding
|
|
71
|
-
* task is in a terminal state or no longer exists.
|
|
72
|
-
*/
|
|
73
|
-
startStaleSessionSweeper() {
|
|
74
|
-
if (this.staleSessionTimer) {
|
|
75
|
-
clearInterval(this.staleSessionTimer);
|
|
76
|
-
}
|
|
77
|
-
this.staleSessionTimer = setInterval(() => {
|
|
78
|
-
this.sweepStaleSessions();
|
|
79
|
-
}, STALE_SESSION_CLEANUP_INTERVAL_MS);
|
|
80
|
-
this.staleSessionTimer.unref();
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Sweep and destroy orphaned sessions across all client entries.
|
|
84
|
-
* This is the safety net that catches sessions missed by normal lifecycle cleanup.
|
|
85
|
-
*/
|
|
86
|
-
sweepStaleSessions() {
|
|
87
|
-
if (this.isShuttingDown)
|
|
88
|
-
return;
|
|
89
|
-
let destroyed = 0;
|
|
90
|
-
for (const [clientKey, entry] of this.clients) {
|
|
91
|
-
for (const [sessionId, session] of entry.sessions) {
|
|
92
|
-
// Check if any active (non-terminal) task references this session
|
|
93
|
-
const task = taskManager.getTask(sessionId); // sessionId === taskId by convention
|
|
94
|
-
const isOrphaned = !task || TERMINAL_STATUSES.has(task.status);
|
|
95
|
-
if (isOrphaned) {
|
|
96
|
-
// Remove from tracking first, then fire-and-forget destroy with timeout
|
|
97
|
-
entry.sessions.delete(sessionId);
|
|
98
|
-
withTimeout(session.destroy(), DESTROY_SESSION_TIMEOUT_MS, `sweeper:destroy(${sessionId})`).catch((err) => {
|
|
99
|
-
console.error(`[sdk-client-manager] Sweeper: failed to destroy session ${sessionId}:`, err);
|
|
100
|
-
});
|
|
101
|
-
destroyed++;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (destroyed > 0) {
|
|
106
|
-
console.error(`[sdk-client-manager] Sweeper: destroyed ${destroyed} orphaned session(s)`);
|
|
107
|
-
// Now that sessions are cleared, try to clean up stale clients too
|
|
108
|
-
this.cleanupStaleClients();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
89
|
/**
|
|
112
90
|
* Reset the client manager (call on MCP reconnect).
|
|
113
91
|
* Clears all clients and resets account rotation to first token.
|
|
114
92
|
*/
|
|
115
93
|
async reset() {
|
|
116
|
-
// Invalidate any in-flight client creations before stopping existing clients
|
|
117
94
|
this.pendingClients.clear();
|
|
118
|
-
//
|
|
119
|
-
for (const
|
|
120
|
-
// Destroy sessions in parallel to release PTY FDs (with timeout so hung sessions don't block reset)
|
|
121
|
-
await Promise.allSettled([...entry.sessions.entries()].map(([sessionId, session]) => withTimeout(session.destroy(), DESTROY_SESSION_TIMEOUT_MS, `reset:destroy(${sessionId})`)
|
|
122
|
-
.catch(err => console.error(`[sdk-client-manager] Error destroying session ${sessionId} during reset:`, err))));
|
|
95
|
+
// Clear our tracking first — let SDK stop() handle session cleanup
|
|
96
|
+
for (const entry of this.clients.values()) {
|
|
123
97
|
entry.sessions.clear();
|
|
124
|
-
|
|
98
|
+
}
|
|
99
|
+
for (const [key, entry] of this.clients) {
|
|
125
100
|
try {
|
|
126
|
-
await entry.client.stop();
|
|
101
|
+
await withTimeout(entry.client.stop(), SHUTDOWN_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
127
102
|
}
|
|
128
|
-
catch
|
|
129
|
-
|
|
103
|
+
catch {
|
|
104
|
+
try {
|
|
105
|
+
await entry.client.forceStop();
|
|
106
|
+
}
|
|
107
|
+
catch { /* ignore */ }
|
|
130
108
|
}
|
|
131
109
|
}
|
|
132
110
|
this.clients.clear();
|
|
133
|
-
// Reset account manager to first token
|
|
134
111
|
accountManager.reset();
|
|
135
112
|
console.error('[sdk-client-manager] Reset complete - all clients cleared, starting from first account');
|
|
136
113
|
}
|
|
@@ -190,7 +167,8 @@ class SDKClientManager {
|
|
|
190
167
|
cwd,
|
|
191
168
|
logLevel: 'error',
|
|
192
169
|
autoStart: true,
|
|
193
|
-
autoRestart: false,
|
|
170
|
+
autoRestart: false,
|
|
171
|
+
useStdio: false, // TCP mode: avoids macOS stdio pipe destruction race
|
|
194
172
|
};
|
|
195
173
|
if (githubToken) {
|
|
196
174
|
options.githubToken = githubToken;
|
|
@@ -271,10 +249,10 @@ class SDKClientManager {
|
|
|
271
249
|
for (const entry of this.clients.values()) {
|
|
272
250
|
const session = entry.sessions.get(sessionId);
|
|
273
251
|
if (session) {
|
|
274
|
-
//
|
|
252
|
+
// Delete from tracking first to prevent double-destroy race with sweeper
|
|
275
253
|
entry.sessions.delete(sessionId);
|
|
276
254
|
try {
|
|
277
|
-
await
|
|
255
|
+
await destroySessionWithRetry(session, sessionId, DESTROY_SESSION_TIMEOUT_MS);
|
|
278
256
|
}
|
|
279
257
|
catch (err) {
|
|
280
258
|
console.error(`[sdk-client-manager] Failed to destroy session ${sessionId}:`, err);
|
|
@@ -348,6 +326,152 @@ class SDKClientManager {
|
|
|
348
326
|
}
|
|
349
327
|
}
|
|
350
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Start the periodic stale-session sweeper. Timer is unref()'d so it
|
|
331
|
+
* doesn't prevent the process from exiting.
|
|
332
|
+
*/
|
|
333
|
+
startStaleSessionSweeper() {
|
|
334
|
+
if (this.staleSessionTimer)
|
|
335
|
+
return;
|
|
336
|
+
this.staleSessionTimer = setInterval(() => {
|
|
337
|
+
this.sweepStaleSessions().catch((err) => {
|
|
338
|
+
console.error('[sdk-client-manager] Sweeper error:', err);
|
|
339
|
+
});
|
|
340
|
+
}, STALE_SESSION_CLEANUP_INTERVAL_MS);
|
|
341
|
+
this.staleSessionTimer.unref();
|
|
342
|
+
console.error('[sdk-client-manager] Stale-session sweeper started');
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Ping each client to detect dead/crashed CLI processes. Removes dead
|
|
346
|
+
* clients from the pool and force-stops them.
|
|
347
|
+
*/
|
|
348
|
+
async checkClientHealth() {
|
|
349
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
350
|
+
try {
|
|
351
|
+
await withTimeout(entry.client.ping('health'), CLIENT_HEALTH_CHECK_TIMEOUT_MS, `ping client ${key}`);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
console.error(`[sdk-client-manager] Health check failed for client ${key}, removing dead client`);
|
|
355
|
+
entry.sessions.clear();
|
|
356
|
+
this.clients.delete(key);
|
|
357
|
+
try {
|
|
358
|
+
await entry.client.forceStop();
|
|
359
|
+
}
|
|
360
|
+
catch { /* already dead */ }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Sweep all tracked sessions. If the corresponding task (looked up by
|
|
366
|
+
* sessionId which equals taskId by convention) is terminal or missing,
|
|
367
|
+
* destroy the session with a timeout. Then recycle PTY leakers.
|
|
368
|
+
*/
|
|
369
|
+
async sweepStaleSessions() {
|
|
370
|
+
// Remove dead clients first so we don't try to destroy sessions on them
|
|
371
|
+
await this.checkClientHealth();
|
|
372
|
+
let destroyed = 0;
|
|
373
|
+
for (const entry of this.clients.values()) {
|
|
374
|
+
for (const [sessionId, session] of entry.sessions) {
|
|
375
|
+
const task = taskManager.getTask(sessionId);
|
|
376
|
+
if (!task || TERMINAL_STATUSES.has(task.status)) {
|
|
377
|
+
entry.sessions.delete(sessionId);
|
|
378
|
+
try {
|
|
379
|
+
await withTimeout(entry.client.deleteSession(sessionId), DESTROY_SESSION_TIMEOUT_MS, `delete session ${sessionId}`);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Fall back to local destroy if deleteSession fails
|
|
383
|
+
try {
|
|
384
|
+
await destroySessionWithRetry(session, sessionId, DESTROY_SESSION_TIMEOUT_MS);
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
console.error(`[sdk-client-manager] Sweeper: destroy failed for ${sessionId}: ${err}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
destroyed++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (destroyed > 0) {
|
|
395
|
+
console.error(`[sdk-client-manager] Sweeper: destroyed ${destroyed} stale session(s)`);
|
|
396
|
+
}
|
|
397
|
+
// Every 5th sweep cycle, detect and clean up orphaned server-side sessions
|
|
398
|
+
this.sweepCycle++;
|
|
399
|
+
if (this.sweepCycle % 5 === 0) {
|
|
400
|
+
await this.detectOrphanedSessions();
|
|
401
|
+
}
|
|
402
|
+
await this.recyclePtyLeakers();
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Detect server-side sessions that we're not tracking locally (orphaned from
|
|
406
|
+
* crashes). Uses listSessions() and deletes any not in our tracking map.
|
|
407
|
+
*/
|
|
408
|
+
async detectOrphanedSessions() {
|
|
409
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
410
|
+
try {
|
|
411
|
+
const serverSessions = await withTimeout(entry.client.listSessions(), CLIENT_HEALTH_CHECK_TIMEOUT_MS, `listSessions ${key}`);
|
|
412
|
+
const trackedIds = new Set(entry.sessions.keys());
|
|
413
|
+
for (const meta of serverSessions) {
|
|
414
|
+
if (!trackedIds.has(meta.sessionId)) {
|
|
415
|
+
console.error(`[sdk-client-manager] Sweeper: deleting orphaned session ${meta.sessionId}`);
|
|
416
|
+
try {
|
|
417
|
+
await entry.client.deleteSession(meta.sessionId);
|
|
418
|
+
}
|
|
419
|
+
catch { /* best effort */ }
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch { /* listSessions failure is non-fatal */ }
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* For each client entry, check PTY FD count via lsof. If count exceeds
|
|
428
|
+
* threshold and no active sessions remain, recycle the client (stop it
|
|
429
|
+
* and remove from map so the next getClient() creates a fresh one).
|
|
430
|
+
*/
|
|
431
|
+
async recyclePtyLeakers() {
|
|
432
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
433
|
+
// Skip dead clients — health check will handle cleanup
|
|
434
|
+
try {
|
|
435
|
+
await withTimeout(entry.client.ping('recycle-check'), 3_000, `ping ${key}`);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
// SDK has no public API for process PID or FD count.
|
|
441
|
+
// cliProcess is private (client.ts:121). This cast is the only way
|
|
442
|
+
// to count ptmx FDs until the SDK adds a resource health endpoint.
|
|
443
|
+
const pid = entry.client.cliProcess?.pid;
|
|
444
|
+
if (!pid)
|
|
445
|
+
continue;
|
|
446
|
+
let ptmxCount = 0;
|
|
447
|
+
try {
|
|
448
|
+
const output = execSync(`lsof -p ${pid} 2>/dev/null | grep -c ptmx`, {
|
|
449
|
+
encoding: 'utf8',
|
|
450
|
+
timeout: 5_000,
|
|
451
|
+
});
|
|
452
|
+
ptmxCount = parseInt(output.trim(), 10) || 0;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
// grep returns exit code 1 when no matches → ptmxCount stays 0
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (ptmxCount > PTY_RECYCLE_THRESHOLD && entry.sessions.size === 0) {
|
|
459
|
+
console.error(`[sdk-client-manager] Sweeper: recycling client ${key} (${ptmxCount} ptmx FDs, 0 active sessions)`);
|
|
460
|
+
// Escalating shutdown: graceful → force
|
|
461
|
+
try {
|
|
462
|
+
await withTimeout(entry.client.stop(), RECYCLE_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
463
|
+
}
|
|
464
|
+
catch (stopErr) {
|
|
465
|
+
console.error(`[sdk-client-manager] Sweeper: graceful stop failed for ${key}, forcing: ${stopErr}`);
|
|
466
|
+
try {
|
|
467
|
+
await entry.client.forceStop();
|
|
468
|
+
}
|
|
469
|
+
catch { /* ignore */ }
|
|
470
|
+
}
|
|
471
|
+
this.clients.delete(key);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
351
475
|
/**
|
|
352
476
|
* Check if rotation should happen based on error.
|
|
353
477
|
*/
|
|
@@ -392,28 +516,29 @@ class SDKClientManager {
|
|
|
392
516
|
async shutdown() {
|
|
393
517
|
this.isShuttingDown = true;
|
|
394
518
|
this.pendingClients.clear();
|
|
395
|
-
// Stop the stale session sweeper
|
|
396
519
|
if (this.staleSessionTimer) {
|
|
397
520
|
clearInterval(this.staleSessionTimer);
|
|
398
521
|
this.staleSessionTimer = null;
|
|
399
522
|
}
|
|
400
523
|
const errors = [];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const results = await Promise.allSettled([...entry.sessions.entries()].map(([sessionId, session]) => withTimeout(session.destroy(), SHUTDOWN_DESTROY_TIMEOUT_MS, `shutdown:destroy(${sessionId})`)));
|
|
404
|
-
for (const r of results) {
|
|
405
|
-
if (r.status === 'rejected') {
|
|
406
|
-
errors.push(r.reason instanceof Error ? r.reason : new Error(String(r.reason)));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
524
|
+
// Clear our tracking first (prevents sweeper interference)
|
|
525
|
+
for (const entry of this.clients.values()) {
|
|
409
526
|
entry.sessions.clear();
|
|
410
|
-
|
|
527
|
+
}
|
|
528
|
+
// Let SDK stop() handle cleanup with its own retry+backoff, with forceStop fallback
|
|
529
|
+
for (const [key, entry] of this.clients) {
|
|
411
530
|
try {
|
|
412
|
-
const clientErrors = await entry.client.stop();
|
|
531
|
+
const clientErrors = await withTimeout(entry.client.stop(), SHUTDOWN_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
413
532
|
errors.push(...clientErrors);
|
|
414
533
|
}
|
|
415
534
|
catch (err) {
|
|
416
|
-
|
|
535
|
+
console.error(`[sdk-client-manager] Shutdown: stop timed out for ${key}, forcing`);
|
|
536
|
+
try {
|
|
537
|
+
await entry.client.forceStop();
|
|
538
|
+
}
|
|
539
|
+
catch (e) {
|
|
540
|
+
errors.push(new Error(`Failed to force-stop client ${key}: ${e}`));
|
|
541
|
+
}
|
|
417
542
|
}
|
|
418
543
|
}
|
|
419
544
|
this.clients.clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-client-manager.js","sourceRoot":"","sources":["../../src/services/sdk-client-manager.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdk-client-manager.js","sourceRoot":"","sources":["../../src/services/sdk-client-manager.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-subagents",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.14",
|
|
4
4
|
"description": "MCP server for spawning and managing GitHub Copilot CLI tasks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"build": "tsc --noEmitOnError false; cp src/templates/*.mdx build/templates/; mkdir -p build/templates/overlays && cp src/templates/overlays/*.mdx build/templates/overlays/",
|
|
12
12
|
"dev": "tsx watch src/index.ts",
|
|
13
13
|
"start": "node build/index.js",
|
|
14
|
-
"clean": "rm -rf build"
|
|
14
|
+
"clean": "rm -rf build",
|
|
15
|
+
"mcp:smoke": "node scripts/mcp-stdio-smoke.mjs"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"mcp",
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
11
|
+
|
|
12
|
+
const TERMINAL_STATUSES = new Set([
|
|
13
|
+
'completed',
|
|
14
|
+
'failed',
|
|
15
|
+
'cancelled',
|
|
16
|
+
'rate_limited',
|
|
17
|
+
'timed_out',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TASK_TIMEOUT_MS = 45_000;
|
|
21
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 180_000;
|
|
22
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
23
|
+
const DEFAULT_SCENARIO = 'basic';
|
|
24
|
+
|
|
25
|
+
function log(message) {
|
|
26
|
+
process.stdout.write(`[mcp-smoke] ${message}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assert(condition, message) {
|
|
30
|
+
if (!condition) {
|
|
31
|
+
throw new Error(message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractText(result) {
|
|
36
|
+
const chunks = Array.isArray(result?.content) ? result.content : [];
|
|
37
|
+
return chunks
|
|
38
|
+
.filter((item) => item && item.type === 'text' && typeof item.text === 'string')
|
|
39
|
+
.map((item) => item.text)
|
|
40
|
+
.join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function extractTaskId(toolResultText) {
|
|
44
|
+
const backtickMatch = toolResultText.match(/task_id:\s*`([^`]+)`/i);
|
|
45
|
+
if (backtickMatch) return backtickMatch[1];
|
|
46
|
+
|
|
47
|
+
const plainMatch = toolResultText.match(/task_id:\s*([A-Za-z0-9-]+)/i);
|
|
48
|
+
if (plainMatch) return plainMatch[1];
|
|
49
|
+
|
|
50
|
+
throw new Error(`Could not parse task_id from tool response:\n${toolResultText}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseResourceJson(readResult, uri) {
|
|
54
|
+
const first = readResult?.contents?.[0];
|
|
55
|
+
if (!first || typeof first.text !== 'string') {
|
|
56
|
+
throw new Error(`Resource ${uri} did not return text content`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(first.text);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(`Failed to parse JSON from ${uri}: ${error}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveProvider(task) {
|
|
66
|
+
return task?.provider ?? task?.sessionMetrics?.provider ?? 'n/a';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadPersistedTaskMap(cwd) {
|
|
70
|
+
const hash = createHash('md5').update(cwd).digest('hex');
|
|
71
|
+
const storagePath = path.join(homedir(), '.super-agents', `${hash}.json`);
|
|
72
|
+
let raw = '[]';
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
raw = await fs.readFile(storagePath, 'utf8');
|
|
76
|
+
} catch {
|
|
77
|
+
return new Map();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = JSON.parse(raw);
|
|
83
|
+
} catch {
|
|
84
|
+
return new Map();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const map = new Map();
|
|
88
|
+
if (Array.isArray(parsed)) {
|
|
89
|
+
for (const task of parsed) {
|
|
90
|
+
if (task && typeof task.id === 'string') {
|
|
91
|
+
map.set(task.id, task);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mergeTaskFromPersisted(task, persistedTask) {
|
|
99
|
+
if (!persistedTask) return task;
|
|
100
|
+
return {
|
|
101
|
+
...task,
|
|
102
|
+
provider: task?.provider ?? persistedTask.provider,
|
|
103
|
+
sessionMetrics: {
|
|
104
|
+
...(task?.sessionMetrics ?? {}),
|
|
105
|
+
...(persistedTask?.sessionMetrics ?? {}),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function waitForTerminalStatus(client, taskId, waitTimeoutMs) {
|
|
111
|
+
const startedAt = Date.now();
|
|
112
|
+
let lastStatus = '<unknown>';
|
|
113
|
+
|
|
114
|
+
while (Date.now() - startedAt < waitTimeoutMs) {
|
|
115
|
+
const taskRead = await client.readResource({ uri: `task:///${taskId}` });
|
|
116
|
+
const task = parseResourceJson(taskRead, `task:///${taskId}`);
|
|
117
|
+
const status = task?.status;
|
|
118
|
+
|
|
119
|
+
if (status !== lastStatus) {
|
|
120
|
+
log(`task ${taskId} status: ${status}`);
|
|
121
|
+
lastStatus = status;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
125
|
+
return task;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await sleep(POLL_INTERVAL_MS);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(`Timed out waiting for terminal status on task ${taskId}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function spawnAndWait(client, cwd, index, prompt, taskTimeoutMs, waitTimeoutMs) {
|
|
135
|
+
|
|
136
|
+
const spawnResult = await client.callTool({
|
|
137
|
+
name: 'spawn_task',
|
|
138
|
+
arguments: {
|
|
139
|
+
prompt,
|
|
140
|
+
task_type: 'super-coder',
|
|
141
|
+
cwd,
|
|
142
|
+
timeout: taskTimeoutMs,
|
|
143
|
+
autonomous: true,
|
|
144
|
+
model: 'claude-haiku-4.5',
|
|
145
|
+
labels: ['mcp-smoke', `case-${index}`],
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const spawnText = extractText(spawnResult);
|
|
150
|
+
const taskId = extractTaskId(spawnText);
|
|
151
|
+
log(`spawned task #${index}: ${taskId}`);
|
|
152
|
+
|
|
153
|
+
// Must be readable immediately in the same persistent session.
|
|
154
|
+
await client.readResource({ uri: `task:///${taskId}` });
|
|
155
|
+
|
|
156
|
+
const finalTask = await waitForTerminalStatus(client, taskId, waitTimeoutMs);
|
|
157
|
+
assert(finalTask.status, `Task ${taskId} returned without status`);
|
|
158
|
+
|
|
159
|
+
if (finalTask.timeoutReason === 'server_restart') {
|
|
160
|
+
throw new Error(`Task ${taskId} ended with server_restart (persistent session check failed)`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
log(`task ${taskId} terminal: ${finalTask.status}`);
|
|
164
|
+
return finalTask;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
const cwd = path.resolve(process.argv[2] || process.cwd());
|
|
169
|
+
const serverEntry = path.join(cwd, 'build', 'index.js');
|
|
170
|
+
const taskTimeoutMs = Number(process.env.MCP_SMOKE_TASK_TIMEOUT_MS || DEFAULT_TASK_TIMEOUT_MS);
|
|
171
|
+
const waitTimeoutMs = Number(process.env.MCP_SMOKE_WAIT_TIMEOUT_MS || DEFAULT_WAIT_TIMEOUT_MS);
|
|
172
|
+
const scenario = process.env.MCP_SMOKE_SCENARIO || DEFAULT_SCENARIO;
|
|
173
|
+
const requireCompleted = process.env.MCP_SMOKE_REQUIRE_COMPLETED === 'true';
|
|
174
|
+
const expectedProvider = process.env.MCP_SMOKE_EXPECT_PROVIDER;
|
|
175
|
+
const expectFallbackActivated = process.env.MCP_SMOKE_EXPECT_FALLBACK_ACTIVATED;
|
|
176
|
+
const loremPath = path.join(cwd, 'lorem.txt');
|
|
177
|
+
|
|
178
|
+
const client = new Client(
|
|
179
|
+
{ name: 'mcp-stdio-smoke-client', version: '1.0.0' },
|
|
180
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const transport = new StdioClientTransport({
|
|
184
|
+
command: process.execPath,
|
|
185
|
+
args: [serverEntry],
|
|
186
|
+
cwd,
|
|
187
|
+
env: process.env,
|
|
188
|
+
stderr: 'pipe',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (transport.stderr) {
|
|
192
|
+
transport.stderr.on('data', (chunk) => {
|
|
193
|
+
const text = String(chunk).trim();
|
|
194
|
+
if (text.length > 0) {
|
|
195
|
+
process.stderr.write(`[mcp-server] ${text}\n`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
log(`connecting to stdio server: ${serverEntry}`);
|
|
202
|
+
await client.connect(transport);
|
|
203
|
+
log('connected');
|
|
204
|
+
|
|
205
|
+
const tools = await client.listTools();
|
|
206
|
+
assert(Array.isArray(tools.tools), 'tools/list did not return a tools array');
|
|
207
|
+
assert(tools.tools.some((t) => t.name === 'spawn_task'), 'spawn_task tool not found');
|
|
208
|
+
assert(tools.tools.some((t) => t.name === 'cancel_task'), 'cancel_task tool not found');
|
|
209
|
+
log(`tools/list ok (${tools.tools.length} tools)`);
|
|
210
|
+
|
|
211
|
+
const resources = await client.listResources();
|
|
212
|
+
assert(Array.isArray(resources.resources), 'resources/list did not return resources array');
|
|
213
|
+
log(`resources/list ok (${resources.resources.length} resources)`);
|
|
214
|
+
|
|
215
|
+
const systemStatus = await client.readResource({ uri: 'system:///status' });
|
|
216
|
+
const systemJson = parseResourceJson(systemStatus, 'system:///status');
|
|
217
|
+
log(`system/status ok (accounts.total=${systemJson?.accounts?.total ?? 'n/a'})`);
|
|
218
|
+
|
|
219
|
+
// Start from a clean workspace state.
|
|
220
|
+
await client.callTool({
|
|
221
|
+
name: 'cancel_task',
|
|
222
|
+
arguments: { task_id: 'all', clear: true, confirm: true },
|
|
223
|
+
});
|
|
224
|
+
log('cleared previous tasks');
|
|
225
|
+
|
|
226
|
+
let firstPrompt =
|
|
227
|
+
'MCP smoke test #1. Print exactly one short line that contains MCP_SMOKE_OK and then stop.';
|
|
228
|
+
let secondPrompt =
|
|
229
|
+
'MCP smoke test #2. Print exactly one short line that contains MCP_SMOKE_OK and then stop.';
|
|
230
|
+
|
|
231
|
+
if (scenario === 'lorem') {
|
|
232
|
+
await fs.rm(loremPath, { force: true });
|
|
233
|
+
firstPrompt =
|
|
234
|
+
'Create a file named lorem.txt in the current working directory with exactly one line: LOREM_AGENT_1. Then stop.';
|
|
235
|
+
secondPrompt =
|
|
236
|
+
'Append a new line to lorem.txt in the current working directory with exactly: LOREM_AGENT_2. Then stop.';
|
|
237
|
+
log(`scenario=lorem (target=${loremPath})`);
|
|
238
|
+
} else {
|
|
239
|
+
log(`scenario=${scenario}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const first = await spawnAndWait(client, cwd, 1, firstPrompt, taskTimeoutMs, waitTimeoutMs);
|
|
243
|
+
const second = await spawnAndWait(client, cwd, 2, secondPrompt, taskTimeoutMs, waitTimeoutMs);
|
|
244
|
+
|
|
245
|
+
const persistedMap = await loadPersistedTaskMap(cwd);
|
|
246
|
+
const mergedFirst = mergeTaskFromPersisted(first, persistedMap.get(first.id));
|
|
247
|
+
const mergedSecond = mergeTaskFromPersisted(second, persistedMap.get(second.id));
|
|
248
|
+
|
|
249
|
+
const allTasksRead = await client.readResource({ uri: 'task:///all' });
|
|
250
|
+
const allTasks = parseResourceJson(allTasksRead, 'task:///all');
|
|
251
|
+
assert(Array.isArray(allTasks.tasks), 'task:///all did not return tasks array');
|
|
252
|
+
log(`task:///all visible in-session (count=${allTasks.count})`);
|
|
253
|
+
|
|
254
|
+
log('final task outcomes:');
|
|
255
|
+
log(`- ${mergedFirst.id}: ${mergedFirst.status}`);
|
|
256
|
+
log(`- ${mergedSecond.id}: ${mergedSecond.status}`);
|
|
257
|
+
const firstProvider = resolveProvider(mergedFirst);
|
|
258
|
+
const secondProvider = resolveProvider(mergedSecond);
|
|
259
|
+
log(`- ${mergedFirst.id} provider: ${firstProvider} fallbackActivated: ${mergedFirst.sessionMetrics?.fallbackActivated ?? false}`);
|
|
260
|
+
log(`- ${mergedSecond.id} provider: ${secondProvider} fallbackActivated: ${mergedSecond.sessionMetrics?.fallbackActivated ?? false}`);
|
|
261
|
+
|
|
262
|
+
if (requireCompleted) {
|
|
263
|
+
assert(mergedFirst.status === 'completed', `Task ${mergedFirst.id} not completed (status=${mergedFirst.status})`);
|
|
264
|
+
assert(mergedSecond.status === 'completed', `Task ${mergedSecond.id} not completed (status=${mergedSecond.status})`);
|
|
265
|
+
log('requireCompleted=true check passed');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (expectedProvider) {
|
|
269
|
+
assert(firstProvider === expectedProvider, `Task ${mergedFirst.id} provider mismatch: expected=${expectedProvider} actual=${firstProvider}`);
|
|
270
|
+
assert(secondProvider === expectedProvider, `Task ${mergedSecond.id} provider mismatch: expected=${expectedProvider} actual=${secondProvider}`);
|
|
271
|
+
log(`expectedProvider=${expectedProvider} check passed`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (expectFallbackActivated === 'true') {
|
|
275
|
+
assert(mergedFirst.sessionMetrics?.fallbackActivated === true, `Task ${mergedFirst.id} fallbackActivated was not true`);
|
|
276
|
+
assert(mergedSecond.sessionMetrics?.fallbackActivated === true, `Task ${mergedSecond.id} fallbackActivated was not true`);
|
|
277
|
+
log('expectFallbackActivated=true check passed');
|
|
278
|
+
} else if (expectFallbackActivated === 'false') {
|
|
279
|
+
assert(mergedFirst.sessionMetrics?.fallbackActivated !== true, `Task ${mergedFirst.id} fallbackActivated unexpectedly true`);
|
|
280
|
+
assert(mergedSecond.sessionMetrics?.fallbackActivated !== true, `Task ${mergedSecond.id} fallbackActivated unexpectedly true`);
|
|
281
|
+
log('expectFallbackActivated=false check passed');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (scenario === 'lorem') {
|
|
285
|
+
const lorem = await fs.readFile(loremPath, 'utf8');
|
|
286
|
+
assert(lorem.includes('LOREM_AGENT_1'), 'lorem.txt missing LOREM_AGENT_1');
|
|
287
|
+
assert(lorem.includes('LOREM_AGENT_2'), 'lorem.txt missing LOREM_AGENT_2');
|
|
288
|
+
log(`lorem.txt verification passed (${loremPath})`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
log('PASS: persistent MCP stdio session handled back-to-back task lifecycle without server_restart');
|
|
292
|
+
} finally {
|
|
293
|
+
await client.close().catch(() => {});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
main().catch((error) => {
|
|
298
|
+
process.stderr.write(`[mcp-smoke] FAIL: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
|
@@ -15,35 +15,21 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { CopilotClient, type CopilotClientOptions, type CopilotSession, type SessionConfig, type UserInputRequest, type UserInputResponse } from '@github/copilot-sdk';
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
18
19
|
import { accountManager } from './account-manager.js';
|
|
19
20
|
import { questionRegistry } from './question-registry.js';
|
|
20
21
|
import { createSessionHooks } from './session-hooks.js';
|
|
21
|
-
import { taskManager } from './task-manager.js';
|
|
22
|
-
import { TERMINAL_STATUSES } from '../types.js';
|
|
22
|
+
import { taskManager, TERMINAL_STATUSES } from './task-manager.js';
|
|
23
23
|
|
|
24
24
|
const COPILOT_PATH = process.env.COPILOT_PATH || '/opt/homebrew/bin/copilot';
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
const PTY_RECYCLE_THRESHOLD = 80;
|
|
27
27
|
const DESTROY_SESSION_TIMEOUT_MS = 10_000;
|
|
28
28
|
const CLIENT_START_TIMEOUT_MS = 30_000;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* Clears the timeout when the promise resolves to prevent timer leaks.
|
|
34
|
-
*/
|
|
35
|
-
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
36
|
-
let timer: NodeJS.Timeout;
|
|
37
|
-
return Promise.race([
|
|
38
|
-
promise.then(
|
|
39
|
-
(v) => { clearTimeout(timer); return v; },
|
|
40
|
-
(e) => { clearTimeout(timer); throw e; },
|
|
41
|
-
),
|
|
42
|
-
new Promise<never>((_, reject) => {
|
|
43
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
44
|
-
}),
|
|
45
|
-
]);
|
|
46
|
-
}
|
|
29
|
+
const CLIENT_HEALTH_CHECK_TIMEOUT_MS = 5_000;
|
|
30
|
+
const RECYCLE_STOP_TIMEOUT_MS = 10_000;
|
|
31
|
+
const SHUTDOWN_STOP_TIMEOUT_MS = 15_000;
|
|
32
|
+
const STALE_SESSION_CLEANUP_INTERVAL_MS = 60_000;
|
|
47
33
|
|
|
48
34
|
interface ClientEntry {
|
|
49
35
|
client: CopilotClient;
|
|
@@ -54,6 +40,50 @@ interface ClientEntry {
|
|
|
54
40
|
sessions: Map<string, CopilotSession>;
|
|
55
41
|
}
|
|
56
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Generic timeout wrapper for SDK operations that can hang.
|
|
45
|
+
*/
|
|
46
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
47
|
+
return new Promise<T>((resolve, reject) => {
|
|
48
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
49
|
+
promise.then(
|
|
50
|
+
(v) => { clearTimeout(timer); resolve(v); },
|
|
51
|
+
(e) => { clearTimeout(timer); reject(e); },
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Retry-with-backoff wrapper for session.destroy(), matching the SDK's own
|
|
58
|
+
* 3-attempt exponential backoff pattern used in stop().
|
|
59
|
+
*/
|
|
60
|
+
async function destroySessionWithRetry(
|
|
61
|
+
session: CopilotSession,
|
|
62
|
+
sessionId: string,
|
|
63
|
+
totalTimeoutMs: number,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
return withTimeout(
|
|
66
|
+
(async () => {
|
|
67
|
+
const MAX_ATTEMPTS = 3;
|
|
68
|
+
let lastError: Error | null = null;
|
|
69
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
70
|
+
try {
|
|
71
|
+
await session.destroy();
|
|
72
|
+
return;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
75
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
76
|
+
await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt - 1)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw lastError!;
|
|
81
|
+
})(),
|
|
82
|
+
totalTimeoutMs,
|
|
83
|
+
`destroy session ${sessionId} (with retries)`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
/**
|
|
58
88
|
* Create a user input handler that forwards questions to the QuestionRegistry.
|
|
59
89
|
* This enables SDK's ask_user tool to pause and wait for MCP client response.
|
|
@@ -75,14 +105,12 @@ function createUserInputHandler(taskId: string): (request: UserInputRequest, inv
|
|
|
75
105
|
};
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
// Interval for periodic stale session cleanup (every 60 seconds)
|
|
79
|
-
const STALE_SESSION_CLEANUP_INTERVAL_MS = 60_000;
|
|
80
|
-
|
|
81
108
|
class SDKClientManager {
|
|
82
109
|
private clients: Map<string, ClientEntry> = new Map();
|
|
83
110
|
private pendingClients: Map<string, Promise<CopilotClient>> = new Map(); // RC-3: Dedup concurrent getClient calls
|
|
84
111
|
private isShuttingDown = false;
|
|
85
|
-
private staleSessionTimer:
|
|
112
|
+
private staleSessionTimer: ReturnType<typeof setInterval> | null = null;
|
|
113
|
+
private sweepCycle = 0;
|
|
86
114
|
|
|
87
115
|
/**
|
|
88
116
|
* Initialize the client manager (call on MCP connect).
|
|
@@ -94,82 +122,27 @@ class SDKClientManager {
|
|
|
94
122
|
console.error(`[sdk-client-manager] Initialized with ${accountManager.getTokenCount()} account(s)`);
|
|
95
123
|
}
|
|
96
124
|
|
|
97
|
-
/**
|
|
98
|
-
* Start periodic sweeper that detects and destroys orphaned sessions.
|
|
99
|
-
* A session is orphaned if it's tracked in a client entry but its corresponding
|
|
100
|
-
* task is in a terminal state or no longer exists.
|
|
101
|
-
*/
|
|
102
|
-
private startStaleSessionSweeper(): void {
|
|
103
|
-
if (this.staleSessionTimer) {
|
|
104
|
-
clearInterval(this.staleSessionTimer);
|
|
105
|
-
}
|
|
106
|
-
this.staleSessionTimer = setInterval(() => {
|
|
107
|
-
this.sweepStaleSessions();
|
|
108
|
-
}, STALE_SESSION_CLEANUP_INTERVAL_MS);
|
|
109
|
-
this.staleSessionTimer.unref();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Sweep and destroy orphaned sessions across all client entries.
|
|
114
|
-
* This is the safety net that catches sessions missed by normal lifecycle cleanup.
|
|
115
|
-
*/
|
|
116
|
-
private sweepStaleSessions(): void {
|
|
117
|
-
if (this.isShuttingDown) return;
|
|
118
|
-
|
|
119
|
-
let destroyed = 0;
|
|
120
|
-
for (const [clientKey, entry] of this.clients) {
|
|
121
|
-
for (const [sessionId, session] of entry.sessions) {
|
|
122
|
-
// Check if any active (non-terminal) task references this session
|
|
123
|
-
const task = taskManager.getTask(sessionId); // sessionId === taskId by convention
|
|
124
|
-
const isOrphaned = !task || TERMINAL_STATUSES.has(task.status);
|
|
125
|
-
|
|
126
|
-
if (isOrphaned) {
|
|
127
|
-
// Remove from tracking first, then fire-and-forget destroy with timeout
|
|
128
|
-
entry.sessions.delete(sessionId);
|
|
129
|
-
withTimeout(session.destroy(), DESTROY_SESSION_TIMEOUT_MS, `sweeper:destroy(${sessionId})`).catch((err) => {
|
|
130
|
-
console.error(`[sdk-client-manager] Sweeper: failed to destroy session ${sessionId}:`, err);
|
|
131
|
-
});
|
|
132
|
-
destroyed++;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (destroyed > 0) {
|
|
138
|
-
console.error(`[sdk-client-manager] Sweeper: destroyed ${destroyed} orphaned session(s)`);
|
|
139
|
-
// Now that sessions are cleared, try to clean up stale clients too
|
|
140
|
-
this.cleanupStaleClients();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
125
|
/**
|
|
145
126
|
* Reset the client manager (call on MCP reconnect).
|
|
146
127
|
* Clears all clients and resets account rotation to first token.
|
|
147
128
|
*/
|
|
148
129
|
async reset(): Promise<void> {
|
|
149
|
-
// Invalidate any in-flight client creations before stopping existing clients
|
|
150
130
|
this.pendingClients.clear();
|
|
151
131
|
|
|
152
|
-
//
|
|
153
|
-
for (const
|
|
154
|
-
// Destroy sessions in parallel to release PTY FDs (with timeout so hung sessions don't block reset)
|
|
155
|
-
await Promise.allSettled(
|
|
156
|
-
[...entry.sessions.entries()].map(([sessionId, session]) =>
|
|
157
|
-
withTimeout(session.destroy(), DESTROY_SESSION_TIMEOUT_MS, `reset:destroy(${sessionId})`)
|
|
158
|
-
.catch(err => console.error(`[sdk-client-manager] Error destroying session ${sessionId} during reset:`, err))
|
|
159
|
-
)
|
|
160
|
-
);
|
|
132
|
+
// Clear our tracking first — let SDK stop() handle session cleanup
|
|
133
|
+
for (const entry of this.clients.values()) {
|
|
161
134
|
entry.sessions.clear();
|
|
135
|
+
}
|
|
162
136
|
|
|
163
|
-
|
|
137
|
+
for (const [key, entry] of this.clients) {
|
|
164
138
|
try {
|
|
165
|
-
await entry.client.stop();
|
|
166
|
-
} catch
|
|
167
|
-
|
|
139
|
+
await withTimeout(entry.client.stop(), SHUTDOWN_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
140
|
+
} catch {
|
|
141
|
+
try { await entry.client.forceStop(); } catch { /* ignore */ }
|
|
168
142
|
}
|
|
169
143
|
}
|
|
170
144
|
this.clients.clear();
|
|
171
145
|
|
|
172
|
-
// Reset account manager to first token
|
|
173
146
|
accountManager.reset();
|
|
174
147
|
console.error('[sdk-client-manager] Reset complete - all clients cleared, starting from first account');
|
|
175
148
|
}
|
|
@@ -236,7 +209,8 @@ class SDKClientManager {
|
|
|
236
209
|
cwd,
|
|
237
210
|
logLevel: 'error',
|
|
238
211
|
autoStart: true,
|
|
239
|
-
autoRestart: false,
|
|
212
|
+
autoRestart: false,
|
|
213
|
+
useStdio: false, // TCP mode: avoids macOS stdio pipe destruction race
|
|
240
214
|
};
|
|
241
215
|
|
|
242
216
|
if (githubToken) {
|
|
@@ -344,10 +318,10 @@ class SDKClientManager {
|
|
|
344
318
|
for (const entry of this.clients.values()) {
|
|
345
319
|
const session = entry.sessions.get(sessionId);
|
|
346
320
|
if (session) {
|
|
347
|
-
//
|
|
321
|
+
// Delete from tracking first to prevent double-destroy race with sweeper
|
|
348
322
|
entry.sessions.delete(sessionId);
|
|
349
323
|
try {
|
|
350
|
-
await
|
|
324
|
+
await destroySessionWithRetry(session, sessionId, DESTROY_SESSION_TIMEOUT_MS);
|
|
351
325
|
} catch (err) {
|
|
352
326
|
console.error(`[sdk-client-manager] Failed to destroy session ${sessionId}:`, err);
|
|
353
327
|
}
|
|
@@ -426,6 +400,157 @@ class SDKClientManager {
|
|
|
426
400
|
}
|
|
427
401
|
}
|
|
428
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Start the periodic stale-session sweeper. Timer is unref()'d so it
|
|
405
|
+
* doesn't prevent the process from exiting.
|
|
406
|
+
*/
|
|
407
|
+
private startStaleSessionSweeper(): void {
|
|
408
|
+
if (this.staleSessionTimer) return;
|
|
409
|
+
this.staleSessionTimer = setInterval(() => {
|
|
410
|
+
this.sweepStaleSessions().catch((err) => {
|
|
411
|
+
console.error('[sdk-client-manager] Sweeper error:', err);
|
|
412
|
+
});
|
|
413
|
+
}, STALE_SESSION_CLEANUP_INTERVAL_MS);
|
|
414
|
+
this.staleSessionTimer.unref();
|
|
415
|
+
console.error('[sdk-client-manager] Stale-session sweeper started');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Ping each client to detect dead/crashed CLI processes. Removes dead
|
|
420
|
+
* clients from the pool and force-stops them.
|
|
421
|
+
*/
|
|
422
|
+
private async checkClientHealth(): Promise<void> {
|
|
423
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
424
|
+
try {
|
|
425
|
+
await withTimeout(
|
|
426
|
+
entry.client.ping('health'),
|
|
427
|
+
CLIENT_HEALTH_CHECK_TIMEOUT_MS,
|
|
428
|
+
`ping client ${key}`,
|
|
429
|
+
);
|
|
430
|
+
} catch {
|
|
431
|
+
console.error(`[sdk-client-manager] Health check failed for client ${key}, removing dead client`);
|
|
432
|
+
entry.sessions.clear();
|
|
433
|
+
this.clients.delete(key);
|
|
434
|
+
try { await entry.client.forceStop(); } catch { /* already dead */ }
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Sweep all tracked sessions. If the corresponding task (looked up by
|
|
441
|
+
* sessionId which equals taskId by convention) is terminal or missing,
|
|
442
|
+
* destroy the session with a timeout. Then recycle PTY leakers.
|
|
443
|
+
*/
|
|
444
|
+
private async sweepStaleSessions(): Promise<void> {
|
|
445
|
+
// Remove dead clients first so we don't try to destroy sessions on them
|
|
446
|
+
await this.checkClientHealth();
|
|
447
|
+
|
|
448
|
+
let destroyed = 0;
|
|
449
|
+
for (const entry of this.clients.values()) {
|
|
450
|
+
for (const [sessionId, session] of entry.sessions) {
|
|
451
|
+
const task = taskManager.getTask(sessionId);
|
|
452
|
+
if (!task || TERMINAL_STATUSES.has(task.status as any)) {
|
|
453
|
+
entry.sessions.delete(sessionId);
|
|
454
|
+
try {
|
|
455
|
+
await withTimeout(
|
|
456
|
+
entry.client.deleteSession(sessionId),
|
|
457
|
+
DESTROY_SESSION_TIMEOUT_MS,
|
|
458
|
+
`delete session ${sessionId}`,
|
|
459
|
+
);
|
|
460
|
+
} catch {
|
|
461
|
+
// Fall back to local destroy if deleteSession fails
|
|
462
|
+
try {
|
|
463
|
+
await destroySessionWithRetry(session, sessionId, DESTROY_SESSION_TIMEOUT_MS);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
console.error(`[sdk-client-manager] Sweeper: destroy failed for ${sessionId}: ${err}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
destroyed++;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (destroyed > 0) {
|
|
473
|
+
console.error(`[sdk-client-manager] Sweeper: destroyed ${destroyed} stale session(s)`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Every 5th sweep cycle, detect and clean up orphaned server-side sessions
|
|
477
|
+
this.sweepCycle++;
|
|
478
|
+
if (this.sweepCycle % 5 === 0) {
|
|
479
|
+
await this.detectOrphanedSessions();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
await this.recyclePtyLeakers();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Detect server-side sessions that we're not tracking locally (orphaned from
|
|
487
|
+
* crashes). Uses listSessions() and deletes any not in our tracking map.
|
|
488
|
+
*/
|
|
489
|
+
private async detectOrphanedSessions(): Promise<void> {
|
|
490
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
491
|
+
try {
|
|
492
|
+
const serverSessions = await withTimeout(
|
|
493
|
+
entry.client.listSessions(),
|
|
494
|
+
CLIENT_HEALTH_CHECK_TIMEOUT_MS,
|
|
495
|
+
`listSessions ${key}`,
|
|
496
|
+
);
|
|
497
|
+
const trackedIds = new Set(entry.sessions.keys());
|
|
498
|
+
for (const meta of serverSessions) {
|
|
499
|
+
if (!trackedIds.has(meta.sessionId)) {
|
|
500
|
+
console.error(`[sdk-client-manager] Sweeper: deleting orphaned session ${meta.sessionId}`);
|
|
501
|
+
try { await entry.client.deleteSession(meta.sessionId); } catch { /* best effort */ }
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch { /* listSessions failure is non-fatal */ }
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* For each client entry, check PTY FD count via lsof. If count exceeds
|
|
510
|
+
* threshold and no active sessions remain, recycle the client (stop it
|
|
511
|
+
* and remove from map so the next getClient() creates a fresh one).
|
|
512
|
+
*/
|
|
513
|
+
private async recyclePtyLeakers(): Promise<void> {
|
|
514
|
+
for (const [key, entry] of this.clients.entries()) {
|
|
515
|
+
// Skip dead clients — health check will handle cleanup
|
|
516
|
+
try {
|
|
517
|
+
await withTimeout(entry.client.ping('recycle-check'), 3_000, `ping ${key}`);
|
|
518
|
+
} catch {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// SDK has no public API for process PID or FD count.
|
|
523
|
+
// cliProcess is private (client.ts:121). This cast is the only way
|
|
524
|
+
// to count ptmx FDs until the SDK adds a resource health endpoint.
|
|
525
|
+
const pid = (entry.client as any).cliProcess?.pid as number | undefined;
|
|
526
|
+
if (!pid) continue;
|
|
527
|
+
|
|
528
|
+
let ptmxCount = 0;
|
|
529
|
+
try {
|
|
530
|
+
const output = execSync(`lsof -p ${pid} 2>/dev/null | grep -c ptmx`, {
|
|
531
|
+
encoding: 'utf8',
|
|
532
|
+
timeout: 5_000,
|
|
533
|
+
});
|
|
534
|
+
ptmxCount = parseInt(output.trim(), 10) || 0;
|
|
535
|
+
} catch {
|
|
536
|
+
// grep returns exit code 1 when no matches → ptmxCount stays 0
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (ptmxCount > PTY_RECYCLE_THRESHOLD && entry.sessions.size === 0) {
|
|
541
|
+
console.error(`[sdk-client-manager] Sweeper: recycling client ${key} (${ptmxCount} ptmx FDs, 0 active sessions)`);
|
|
542
|
+
// Escalating shutdown: graceful → force
|
|
543
|
+
try {
|
|
544
|
+
await withTimeout(entry.client.stop(), RECYCLE_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
545
|
+
} catch (stopErr) {
|
|
546
|
+
console.error(`[sdk-client-manager] Sweeper: graceful stop failed for ${key}, forcing: ${stopErr}`);
|
|
547
|
+
try { await entry.client.forceStop(); } catch { /* ignore */ }
|
|
548
|
+
}
|
|
549
|
+
this.clients.delete(key);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
429
554
|
/**
|
|
430
555
|
* Check if rotation should happen based on error.
|
|
431
556
|
*/
|
|
@@ -472,7 +597,6 @@ class SDKClientManager {
|
|
|
472
597
|
this.isShuttingDown = true;
|
|
473
598
|
this.pendingClients.clear();
|
|
474
599
|
|
|
475
|
-
// Stop the stale session sweeper
|
|
476
600
|
if (this.staleSessionTimer) {
|
|
477
601
|
clearInterval(this.staleSessionTimer);
|
|
478
602
|
this.staleSessionTimer = null;
|
|
@@ -480,31 +604,25 @@ class SDKClientManager {
|
|
|
480
604
|
|
|
481
605
|
const errors: Error[] = [];
|
|
482
606
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const results = await Promise.allSettled(
|
|
486
|
-
[...entry.sessions.entries()].map(([sessionId, session]) =>
|
|
487
|
-
withTimeout(session.destroy(), SHUTDOWN_DESTROY_TIMEOUT_MS, `shutdown:destroy(${sessionId})`)
|
|
488
|
-
)
|
|
489
|
-
);
|
|
490
|
-
for (const r of results) {
|
|
491
|
-
if (r.status === 'rejected') {
|
|
492
|
-
errors.push(r.reason instanceof Error ? r.reason : new Error(String(r.reason)));
|
|
493
|
-
}
|
|
494
|
-
}
|
|
607
|
+
// Clear our tracking first (prevents sweeper interference)
|
|
608
|
+
for (const entry of this.clients.values()) {
|
|
495
609
|
entry.sessions.clear();
|
|
610
|
+
}
|
|
496
611
|
|
|
497
|
-
|
|
612
|
+
// Let SDK stop() handle cleanup with its own retry+backoff, with forceStop fallback
|
|
613
|
+
for (const [key, entry] of this.clients) {
|
|
498
614
|
try {
|
|
499
|
-
const clientErrors = await entry.client.stop();
|
|
615
|
+
const clientErrors = await withTimeout(entry.client.stop(), SHUTDOWN_STOP_TIMEOUT_MS, `stop client ${key}`);
|
|
500
616
|
errors.push(...clientErrors);
|
|
501
617
|
} catch (err) {
|
|
502
|
-
|
|
618
|
+
console.error(`[sdk-client-manager] Shutdown: stop timed out for ${key}, forcing`);
|
|
619
|
+
try { await entry.client.forceStop(); } catch (e) {
|
|
620
|
+
errors.push(new Error(`Failed to force-stop client ${key}: ${e}`));
|
|
621
|
+
}
|
|
503
622
|
}
|
|
504
623
|
}
|
|
505
624
|
|
|
506
625
|
this.clients.clear();
|
|
507
|
-
|
|
508
626
|
if (errors.length > 0) {
|
|
509
627
|
console.error('[sdk-client-manager] Shutdown errors:', errors);
|
|
510
628
|
}
|