fix: address 5 edge cases from review-work findings
- C3: include command args in auto-slash-command dedup key - H2: track completed task summaries for ALL COMPLETE message - H9: increment tmux close retry count on re-mark - H8: detect stale MCP connections after disconnect+reconnect race - H8: guard disconnectedSessions growth for non-MCP sessions - C1: await tmux cleanup in plugin dispose lifecycle
This commit is contained in:
@@ -87,7 +87,16 @@ async function cleanupIdleClients(state: SkillMcpManagerState): Promise<void> {
|
||||
}
|
||||
|
||||
export async function disconnectSession(state: SkillMcpManagerState, sessionID: string): Promise<void> {
|
||||
state.disconnectedSessions.add(sessionID)
|
||||
let hasPendingForSession = false
|
||||
for (const key of state.pendingConnections.keys()) {
|
||||
if (key.startsWith(`${sessionID}:`)) {
|
||||
hasPendingForSession = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasPendingForSession) {
|
||||
state.disconnectedSessions.add(sessionID)
|
||||
}
|
||||
const keysToRemove: string[] = []
|
||||
|
||||
for (const [key, managed] of state.clients.entries()) {
|
||||
|
||||
@@ -154,11 +154,11 @@ describe("getOrCreateClient disconnect race", () => {
|
||||
expect(createdClients[0]?.close).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("#given no pending connections #when disconnectSession is called #then no errors occur and the session is added to disconnectedSessions", async () => {
|
||||
it("#given no pending connections #when disconnectSession is called #then no errors occur and session is not added to disconnectedSessions", async () => {
|
||||
const state = createState()
|
||||
|
||||
await expect(disconnectSession(state, "session-a")).resolves.toBeUndefined()
|
||||
expect(state.disconnectedSessions.has("session-a")).toBe(true)
|
||||
expect(state.disconnectedSessions.has("session-a")).toBe(false)
|
||||
expect(state.pendingConnections.size).toBe(0)
|
||||
expect(state.clients.size).toBe(0)
|
||||
})
|
||||
|
||||
@@ -29,9 +29,16 @@ export async function getOrCreateClient(params: {
|
||||
}
|
||||
|
||||
const expandedConfig = expandEnvVarsInObject(config)
|
||||
const connectionPromise = (async () => {
|
||||
let currentConnectionPromise!: Promise<Client>
|
||||
currentConnectionPromise = (async () => {
|
||||
const client = await createClient({ state, clientKey, info, config: expandedConfig })
|
||||
|
||||
const isStale = state.pendingConnections.has(clientKey) && state.pendingConnections.get(clientKey) !== currentConnectionPromise
|
||||
if (isStale) {
|
||||
try { await client.close() } catch {}
|
||||
throw new Error(`Connection for "${info.sessionID}" was superseded by a newer connection attempt.`)
|
||||
}
|
||||
|
||||
if (state.disconnectedSessions.has(info.sessionID)) {
|
||||
await forceReconnect(state, clientKey)
|
||||
throw new Error(`Session "${info.sessionID}" disconnected during MCP connection setup.`)
|
||||
@@ -40,13 +47,13 @@ export async function getOrCreateClient(params: {
|
||||
return client
|
||||
})()
|
||||
|
||||
state.pendingConnections.set(clientKey, connectionPromise)
|
||||
state.pendingConnections.set(clientKey, currentConnectionPromise)
|
||||
|
||||
try {
|
||||
const client = await connectionPromise
|
||||
const client = await currentConnectionPromise
|
||||
return client
|
||||
} finally {
|
||||
if (state.pendingConnections.get(clientKey) === connectionPromise) {
|
||||
if (state.pendingConnections.get(clientKey) === currentConnectionPromise) {
|
||||
state.pendingConnections.delete(clientKey)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user