d523b9595d
Agent runs quiesced stdio MCP extensions including sandbox-fs, but reconcile skipped re-adding missing stdio extensions and treated absent listings as OK. Preserve sandbox-fs across quiesce and re-add other required stdio extensions on the next reconcile so generate_image and write_file stay available. Co-authored-by: Cursor <cursoragent@cursor.com>
85 lines
2.6 KiB
JavaScript
85 lines
2.6 KiB
JavaScript
function extensionName(config) {
|
|
return String(config?.name ?? '').trim();
|
|
}
|
|
|
|
/** Stdio MCP extensions that must survive agent-run quiesce (page write + image gen). */
|
|
export const PRESERVED_STDIO_EXTENSIONS = new Set(['sandbox-fs']);
|
|
|
|
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.
|
|
* Critical page tools (sandbox-fs) stay attached so the next turn is not tool-less.
|
|
*/
|
|
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).filter(
|
|
(name) => !PRESERVED_STDIO_EXTENSIONS.has(name),
|
|
);
|
|
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 };
|
|
}
|