716ef407fe
Wire Portal and TKMind proxy to loopback Goose v1.49 via canary env blocks, with verification scripts, Phase 2/3 evidence baselines, and rollback runbooks so local upgrade stays isolated from stable 1.41 and production. Co-authored-by: Cursor <cursoragent@cursor.com>
131 lines
4.3 KiB
JavaScript
131 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Milestone 2 smoke: load sandbox-fs stdio MCP via extension_overrides on local Goose v1.49.
|
|
*/
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { Agent, fetch } from 'undici';
|
|
|
|
const memindRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const port = process.env.GOOSE_V149_PORT || '18049';
|
|
const host = process.env.GOOSE_V149_HOST || '127.0.0.1';
|
|
const secret = process.env.GOOSE_SERVER__SECRET_KEY || 'local-v149-dev-secret';
|
|
const workingDir = process.env.GOOSE_V149_WORKING_DIR || memindRoot;
|
|
const nodeExec = process.env.GOOSED_MCP_NODE_PATH || process.execPath;
|
|
const serverPath =
|
|
process.env.GOOSED_MCP_SERVER_PATH || path.join(memindRoot, 'mindspace-sandbox-mcp.mjs');
|
|
const pollMs = Number(process.env.GOOSE_V149_SANDBOX_POLL_MS || 500);
|
|
const timeoutMs = Number(process.env.GOOSE_V149_SANDBOX_TIMEOUT_MS || 30_000);
|
|
|
|
const blockedHosts = ['58.38.22.103', '120.26.184.105'];
|
|
if (blockedHosts.includes(host)) {
|
|
console.error(`GOOSE_V149_SANDBOX_FS_FAIL: refusing production host ${host}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!fs.existsSync(serverPath)) {
|
|
console.error(`GOOSE_V149_SANDBOX_FS_FAIL: MCP entry missing: ${serverPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const sandboxRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'goose-v149-sandbox-'));
|
|
fs.mkdirSync(path.join(sandboxRoot, 'public'), { recursive: true });
|
|
|
|
const base = `https://${host}:${port}`;
|
|
const dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
|
|
async function apiFetch(pathname, init = {}) {
|
|
const headers = {
|
|
...(init.headers ?? {}),
|
|
'X-Secret-Key': secret,
|
|
};
|
|
if (init.body && !headers['Content-Type']) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
return fetch(`${base}${pathname}`, {
|
|
...init,
|
|
headers,
|
|
dispatcher,
|
|
});
|
|
}
|
|
|
|
async function apiJson(pathname, body) {
|
|
const response = await apiFetch(pathname, {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
const text = await response.text();
|
|
if (!response.ok) {
|
|
throw new Error(`${pathname} ${response.status}: ${text.slice(0, 800)}`);
|
|
}
|
|
return text.trim() ? JSON.parse(text) : {};
|
|
}
|
|
|
|
async function waitForSandboxTools(sessionId) {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
const response = await apiFetch(
|
|
`/agent/tools?session_id=${encodeURIComponent(sessionId)}&extension_name=sandbox-fs`,
|
|
);
|
|
const text = await response.text();
|
|
if (!response.ok) {
|
|
throw new Error(`/agent/tools ${response.status}: ${text.slice(0, 400)}`);
|
|
}
|
|
const tools = JSON.parse(text);
|
|
const names = tools.map((tool) => tool.name);
|
|
const writeTool = names.find((name) => name === 'sandbox-fs__write_file');
|
|
if (writeTool) {
|
|
return names;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
}
|
|
throw new Error(`sandbox-fs tools not ready within ${timeoutMs}ms`);
|
|
}
|
|
|
|
try {
|
|
const session = await apiJson('/agent/start', {
|
|
working_dir: workingDir,
|
|
extension_overrides: [
|
|
{
|
|
type: 'stdio',
|
|
name: 'sandbox-fs',
|
|
description: 'MindSpace sandbox filesystem MCP (local v1.49 smoke)',
|
|
cmd: nodeExec,
|
|
args: [serverPath, sandboxRoot],
|
|
envs: {
|
|
ALLOWED_TOOLS: 'write_file,read_file,edit_file,create_dir,list_dir',
|
|
SANDBOX_ROOT: sandboxRoot,
|
|
},
|
|
available_tools: ['write_file', 'read_file', 'edit_file', 'create_dir', 'list_dir'],
|
|
},
|
|
{
|
|
type: 'platform',
|
|
name: 'skills',
|
|
description: 'skills',
|
|
available_tools: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
if (!session?.id) {
|
|
throw new Error('missing session id from /agent/start');
|
|
}
|
|
|
|
const extResponse = await apiFetch(`/sessions/${session.id}/extensions`);
|
|
const extBody = await extResponse.json();
|
|
const configured = (extBody.extensions ?? []).some((ext) => ext.name === 'sandbox-fs');
|
|
if (!configured) {
|
|
throw new Error('sandbox-fs not present in session extension_data');
|
|
}
|
|
|
|
const toolNames = await waitForSandboxTools(session.id);
|
|
console.log(
|
|
`GOOSE_V149_SANDBOX_FS_OK: session=${session.id} sandbox=${sandboxRoot} tools=${toolNames.length}`,
|
|
);
|
|
} catch (error) {
|
|
console.error(`GOOSE_V149_SANDBOX_FS_FAIL: ${error.message}`);
|
|
process.exit(1);
|
|
}
|