diff --git a/package-lock.json b/package-lock.json index a7c6df5..ab56d88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@github/copilot-sdk": "^0.1.25", + "@github/copilot-sdk": "^0.1.29", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "electron-log": "^5.4.3", @@ -1581,26 +1581,26 @@ } }, "node_modules/@github/copilot": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.411.tgz", - "integrity": "sha512-I3/7gw40Iu1O+kTyNPKJHNqDRyOebjsUW6wJsvSVrOpT0TNa3/lfm8xdS2XUuJWkp+PgEG/PRwF7u3DVNdP7bQ==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.420.tgz", + "integrity": "sha512-UpPuSjxUxQ+j02WjZEFffWf0scLb23LvuGHzMFtaSsweR+P/BdbtDUI5ZDIA6T0tVyyt6+X1/vgfsJiRqd6jig==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "0.0.411", - "@github/copilot-darwin-x64": "0.0.411", - "@github/copilot-linux-arm64": "0.0.411", - "@github/copilot-linux-x64": "0.0.411", - "@github/copilot-win32-arm64": "0.0.411", - "@github/copilot-win32-x64": "0.0.411" + "@github/copilot-darwin-arm64": "0.0.420", + "@github/copilot-darwin-x64": "0.0.420", + "@github/copilot-linux-arm64": "0.0.420", + "@github/copilot-linux-x64": "0.0.420", + "@github/copilot-win32-arm64": "0.0.420", + "@github/copilot-win32-x64": "0.0.420" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.411.tgz", - "integrity": "sha512-dtr+iHxTS4f8HlV2JT9Fp0FFoxuiPWCnU3XGmrHK+rY6bX5okPC2daU5idvs77WKUGcH8yHTZtfbKYUiMxKosw==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.420.tgz", + "integrity": "sha512-sj8Oxcf3oKDbeUotm2gtq5YU1lwCt3QIzbMZioFD/PMLOeqSX/wrecI+c0DDYXKofFhALb0+DxxnWgbEs0mnkQ==", "cpu": [ "arm64" ], @@ -1614,9 +1614,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.411.tgz", - "integrity": "sha512-zhdbQCbPi1L4iHClackSLx8POfklA+NX9RQLuS48HlKi/0KI/JlaDA/bdbIeMR79wjif5t9gnc/m+RTVmHlRtA==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.420.tgz", + "integrity": "sha512-2acA93IqXz1uuz3TVUm0Y7BVrBr0MySh1kQa8LqMILhTsG0YHRMm8ybzTp2HA7Mi1tl5CjqMSk163kkS7OzfUA==", "cpu": [ "x64" ], @@ -1630,9 +1630,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.411.tgz", - "integrity": "sha512-oZYZ7oX/7O+jzdTUcHkfD1A8YnNRW6mlUgdPjUg+5rXC43bwIdyatAnc0ObY21m9h8ghxGqholoLhm5WnGv1LQ==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.420.tgz", + "integrity": "sha512-h/IvEryTOYm1HzR2GNq8s2aDtN4lvT4MxldfZuS42CtWJDOfVG2jLLsoHWU1T3QV8j1++PmDgE//HX0JLpLMww==", "cpu": [ "arm64" ], @@ -1646,9 +1646,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.411.tgz", - "integrity": "sha512-nnXrKANmmGnkwa3ROlKdAhVNOx8daeMSE8Xh0o3ybKckFv4s38blhKdcxs0RJQRxgAk4p7XXGlDDKNRhurqF1g==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.420.tgz", + "integrity": "sha512-iL2NpZvXIDZ+3lw7sO2fo5T0nKmP5dZbU2gdYcv+SFBm/ONhCxIY5VRX4yN/9VkFaa9ePv5JzCnsl3vZINiDxg==", "cpu": [ "x64" ], @@ -1662,12 +1662,12 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.1.25.tgz", - "integrity": "sha512-hIgYLPXzWw9bNgrsD5BLKmgVH20ow5Or5UyVXfVe3YgeiaTgFxC4jWSAVHLGB6ufHZUrvbjppcq2dWK63FmDRA==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.1.29.tgz", + "integrity": "sha512-GdcN6bJTeesr1HP6IrhN2MznIf1B3ufqd3PX+uKbDLXNriOmP65Ai29/hxzTidNLHyOf6rW4NwmFfkMXiKfCBw==", "license": "MIT", "dependencies": { - "@github/copilot": "^0.0.411", + "@github/copilot": "^0.0.420", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -1676,9 +1676,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.411.tgz", - "integrity": "sha512-h+Bovb2YVCQSeELZOO7zxv8uht45XHcvAkFbRsc1gf9dl109sSUJIcB4KAhs8Aznk28qksxz7kvdSgUWyQBlIA==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.420.tgz", + "integrity": "sha512-Njlc2j9vYSBAL+lC6FIEhQ3C+VxO3xavwKnw0ecVRiNLcGLyPrTdzPfPQOmEjC63gpVCqLabikoDGv8fuLPA2w==", "cpu": [ "arm64" ], @@ -1692,9 +1692,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "0.0.411", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.411.tgz", - "integrity": "sha512-xmOgi1lGvUBHQJWmq5AK1EP95+Y8xR4TFoK9OCSOaGbQ+LFcX2jF7iavnMolfWwddabew/AMQjsEHlXvbgMG8Q==", + "version": "0.0.420", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.420.tgz", + "integrity": "sha512-rZlH35oNehAP2DvQbu4vQFVNeCh/1p3rUjafBYaEY0Nkhx7RmdrYBileL5U3PtRPPRsBPaq3Qp+pVIrGoCDLzQ==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 80ed951..fc0c484 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ } }, "dependencies": { - "@github/copilot-sdk": "^0.1.25", + "@github/copilot-sdk": "^0.1.29", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "electron-log": "^5.4.3", diff --git a/src/main/main.ts b/src/main/main.ts index 3cdaca2..b328244 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -853,6 +853,7 @@ function registerSessionEventForwarding(sessionId: string, session: CopilotSessi requestUserAttention(); } else if (event.type === 'tool.execution_start') { log.debug(`[${sessionId}] Tool start: ${event.data.toolName} (${event.data.toolCallId})`); + startToolStallTimer(sessionId, event.data.toolName); mainWindow.webContents.send('copilot:tool-start', { sessionId, toolCallId: event.data.toolCallId, @@ -861,6 +862,7 @@ function registerSessionEventForwarding(sessionId: string, session: CopilotSessi }); } else if (event.type === 'tool.execution_complete') { log.debug(`[${sessionId}] Tool end: ${event.data.toolCallId}`); + clearToolStallTimer(sessionId); const completeData = event.data as Record; mainWindow.webContents.send('copilot:tool-end', { sessionId, @@ -999,6 +1001,44 @@ function stopKeepAlive(): void { } } +// Tool execution stall detection - auto-abort sessions with hung tool calls +const TOOL_STALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes +const sessionToolStallTimers = new Map(); + +function startToolStallTimer(sessionId: string, toolName: string): void { + clearToolStallTimer(sessionId); + const timer = setTimeout(async () => { + sessionToolStallTimers.delete(sessionId); + const sessionState = sessions.get(sessionId); + if (!sessionState) return; + log.warn( + `[${sessionId}] Tool "${toolName}" stalled for ${TOOL_STALL_TIMEOUT_MS / 60000} minutes, aborting` + ); + try { + await sessionState.session.abort(); + } catch (err) { + log.error(`[${sessionId}] Failed to abort stalled session:`, err); + } + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('copilot:error', { + sessionId, + message: `Tool "${toolName}" timed out after ${TOOL_STALL_TIMEOUT_MS / 60000} minutes and was aborted.`, + }); + sessionState.isProcessing = false; + mainWindow.webContents.send('copilot:idle', { sessionId }); + } + }, TOOL_STALL_TIMEOUT_MS); + sessionToolStallTimers.set(sessionId, timer); +} + +function clearToolStallTimer(sessionId: string): void { + const timer = sessionToolStallTimers.get(sessionId); + if (timer) { + clearTimeout(timer); + sessionToolStallTimers.delete(sessionId); + } +} + // Resume a session that has been disconnected async function resumeDisconnectedSession( sessionId: string, @@ -1177,6 +1217,7 @@ async function startEarlySessionResumption(): Promise { log.debug( `[${sessionId}] Tool start: ${event.data.toolName} (${event.data.toolCallId})` ); + startToolStallTimer(sessionId, event.data.toolName); mainWindow.webContents.send('copilot:tool-start', { sessionId, toolCallId: event.data.toolCallId, @@ -1185,6 +1226,7 @@ async function startEarlySessionResumption(): Promise { }); } else if (event.type === 'tool.execution_complete') { log.debug(`[${sessionId}] Tool end: ${event.data.toolCallId}`); + clearToolStallTimer(sessionId); const completeData = event.data as Record; mainWindow.webContents.send('copilot:tool-end', { sessionId, @@ -2305,6 +2347,7 @@ async function initCopilot(): Promise { log.debug( `[${sessionId}] Tool start: ${event.data.toolName} (${event.data.toolCallId})` ); + startToolStallTimer(sessionId, event.data.toolName); mainWindow.webContents.send('copilot:tool-start', { sessionId, toolCallId: event.data.toolCallId, @@ -2313,6 +2356,7 @@ async function initCopilot(): Promise { }); } else if (event.type === 'tool.execution_complete') { log.debug(`[${sessionId}] Tool end: ${event.data.toolCallId}`); + clearToolStallTimer(sessionId); const completeData = event.data as Record; mainWindow.webContents.send('copilot:tool-end', { sessionId, @@ -4945,6 +4989,7 @@ ipcMain.handle( requestUserAttention(); } else if (event.type === 'tool.execution_start') { log.debug(`[${sessionId}] Tool start: ${event.data.toolName} (${event.data.toolCallId})`); + startToolStallTimer(sessionId, event.data.toolName); mainWindow.webContents.send('copilot:tool-start', { sessionId, toolCallId: event.data.toolCallId, @@ -4953,6 +4998,7 @@ ipcMain.handle( }); } else if (event.type === 'tool.execution_complete') { log.debug(`[${sessionId}] Tool end: ${event.data.toolCallId}`); + clearToolStallTimer(sessionId); const completeData = event.data as Record; mainWindow.webContents.send('copilot:tool-end', { sessionId,