function extensionName(config) { return String(config?.name ?? '').trim(); } async function readJson(response) { const text = await response.text(); if (!response.ok) { throw new Error(text || `upstream ${response.status}`); } return text ? JSON.parse(text) : null; } export function sessionStdioExtensionNames(extensions) { return [ ...new Set( (extensions ?? []) .filter((extension) => String(extension?.type ?? '').trim() === 'stdio') .map(extensionName) .filter(Boolean), ), ].sort(); } export async function cancelSessionActiveRequest(apiFetch, sessionId, requestId) { const normalizedSessionId = String(sessionId ?? '').trim(); const normalizedRequestId = String(requestId ?? '').trim(); if (!normalizedSessionId || !normalizedRequestId) { return { cancelled: false, skipped: true }; } const response = await apiFetch( `/sessions/${encodeURIComponent(normalizedSessionId)}/cancel`, { method: 'POST', body: JSON.stringify({ request_id: normalizedRequestId }), }, ); const text = await response.text(); if ( !response.ok && ![404, 409, 410].includes(Number(response.status)) && !/(?:not found|no active|already (?:finished|cancelled))/i.test(text) ) { throw new Error(text || `upstream ${response.status}`); } return { cancelled: response.ok, skipped: !response.ok, }; } /** * Stop per-session stdio MCP children while preserving the Goose conversation. * Session reconciliation restores the required extensions before the next turn. */ export async function quiesceSessionStdioExtensions(apiFetch, sessionId) { const normalizedSessionId = String(sessionId ?? '').trim(); if (!normalizedSessionId) return { removed: [], skipped: true }; const payload = await readJson( await apiFetch(`/sessions/${encodeURIComponent(normalizedSessionId)}/extensions`), ); const names = sessionStdioExtensionNames(payload?.extensions); const removed = []; for (const name of names) { await readJson( await apiFetch('/agent/remove_extension', { method: 'POST', body: JSON.stringify({ session_id: normalizedSessionId, name, }), }), ); removed.push(name); } return { removed, skipped: names.length === 0 }; }