@antonbabenko/deliberation-mcp 3.5.2 → 3.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +141 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1591,16 +1591,17 @@ var require_antigravity = __commonJS({
|
|
|
1591
1591
|
},
|
|
1592
1592
|
async ask(req) {
|
|
1593
1593
|
const started = Date.now();
|
|
1594
|
+
const includeDirs = (req.files || []).filter((f) => f.dir).map((f) => f.dir);
|
|
1594
1595
|
const args = bridge.buildAgyArgs({
|
|
1595
1596
|
prompt: req.prompt,
|
|
1596
1597
|
model,
|
|
1597
1598
|
sandbox: "read-only",
|
|
1598
1599
|
developerInstructions: req.developerInstructions,
|
|
1599
|
-
includeDirs
|
|
1600
|
+
includeDirs
|
|
1600
1601
|
});
|
|
1601
1602
|
try {
|
|
1602
|
-
const out = await bridge.runGemini(args, req.cwd, req.timeoutMs, void 0);
|
|
1603
|
-
return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started, reasoningEffort: null };
|
|
1603
|
+
const out = await bridge.runGemini(args, req.cwd, req.timeoutMs, void 0, { readOnly: true, includeDirs });
|
|
1604
|
+
return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started, reasoningEffort: null, ...out.workspaceMutated ? { workspaceMutated: true } : {} };
|
|
1604
1605
|
} catch (e) {
|
|
1605
1606
|
const err = (
|
|
1606
1607
|
/** @type {any} */
|
|
@@ -1634,9 +1635,12 @@ var require_codex = __commonJS({
|
|
|
1634
1635
|
if (s.includes("rate")) return { errorKind: "rate-limit", retryable: true };
|
|
1635
1636
|
return { errorKind: "unknown", retryable: false };
|
|
1636
1637
|
}
|
|
1638
|
+
function codexExecArgs() {
|
|
1639
|
+
return ["exec", "--sandbox", "read-only", "--skip-git-repo-check"];
|
|
1640
|
+
}
|
|
1637
1641
|
function defaultRun({ prompt, cwd, timeoutMs }) {
|
|
1638
1642
|
return new Promise((resolve) => {
|
|
1639
|
-
const child = spawn("codex",
|
|
1643
|
+
const child = spawn("codex", codexExecArgs(), { cwd: cwd || process.cwd() });
|
|
1640
1644
|
let stdout = "", stderr = "", settled = false;
|
|
1641
1645
|
const timer = timeoutMs ? setTimeout(() => child.kill("SIGKILL"), timeoutMs) : null;
|
|
1642
1646
|
if (timer) timer.unref();
|
|
@@ -1693,7 +1697,7 @@ ${req.prompt}` : req.prompt;
|
|
|
1693
1697
|
}
|
|
1694
1698
|
};
|
|
1695
1699
|
}
|
|
1696
|
-
module2.exports = { makeCodexProvider, classifyCodex };
|
|
1700
|
+
module2.exports = { makeCodexProvider, classifyCodex, codexExecArgs };
|
|
1697
1701
|
}
|
|
1698
1702
|
});
|
|
1699
1703
|
|
|
@@ -2085,18 +2089,102 @@ var require_gemini = __commonJS({
|
|
|
2085
2089
|
function goDuration(ms) {
|
|
2086
2090
|
return Math.ceil(ms / 1e3) + "s";
|
|
2087
2091
|
}
|
|
2092
|
+
var READ_ONLY_GUARD = "[ADVISORY MODE - READ-ONLY] You are being consulted for an advisory second opinion. Do NOT modify, create, or delete any files. Do NOT run commands that write to the workspace. Do NOT git add, commit, or push. Any such action is a critical failure - respond with analysis only.";
|
|
2093
|
+
function applyReadOnlyGuard(prompt, readOnly) {
|
|
2094
|
+
return readOnly ? `${READ_ONLY_GUARD}
|
|
2095
|
+
|
|
2096
|
+
${prompt}` : prompt;
|
|
2097
|
+
}
|
|
2088
2098
|
function buildAgyArgs(req) {
|
|
2089
2099
|
const args = [];
|
|
2100
|
+
const readOnly = req.sandbox !== "workspace-write";
|
|
2090
2101
|
if (req.sandbox === "workspace-write") args.push("--dangerously-skip-permissions");
|
|
2091
2102
|
else args.push("--sandbox");
|
|
2092
2103
|
for (const d of req.includeDirs || []) args.push("--add-dir", d);
|
|
2093
|
-
let prompt = req.prompt;
|
|
2104
|
+
let prompt = applyReadOnlyGuard(req.prompt, readOnly);
|
|
2094
2105
|
if (req.developerInstructions) prompt = `${req.developerInstructions}
|
|
2095
2106
|
|
|
2096
2107
|
${prompt}`;
|
|
2097
2108
|
args.push("-p", prompt);
|
|
2098
2109
|
return args;
|
|
2099
2110
|
}
|
|
2111
|
+
var ADVISORY_ENV_SCRUB = ["GITHUB_TOKEN", "GH_TOKEN", "GIT_ASKPASS", "SSH_AUTH_SOCK"];
|
|
2112
|
+
function advisoryEnv(env) {
|
|
2113
|
+
const out = { ...env };
|
|
2114
|
+
delete out.DELIBERATION_DISABLE_OS_SANDBOX;
|
|
2115
|
+
for (const k of ADVISORY_ENV_SCRUB) delete out[k];
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2118
|
+
function seatbeltLiteral(p) {
|
|
2119
|
+
return String(p).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2120
|
+
}
|
|
2121
|
+
function buildSeatbeltProfile(o) {
|
|
2122
|
+
const gem = seatbeltLiteral(path.join(o.home, ".gemini"));
|
|
2123
|
+
const tmp = seatbeltLiteral(o.tmpdir);
|
|
2124
|
+
return [
|
|
2125
|
+
"(version 1)",
|
|
2126
|
+
"(allow default)",
|
|
2127
|
+
"(deny file-write*)",
|
|
2128
|
+
"(allow file-write*",
|
|
2129
|
+
` (subpath "${gem}")`,
|
|
2130
|
+
` (subpath "${tmp}")`,
|
|
2131
|
+
' (subpath "/private/var/folders")',
|
|
2132
|
+
' (subpath "/private/tmp")',
|
|
2133
|
+
' (literal "/dev/null")',
|
|
2134
|
+
' (literal "/dev/dtracehelper")',
|
|
2135
|
+
' (regex #"^/dev/tty"))'
|
|
2136
|
+
].join("\n");
|
|
2137
|
+
}
|
|
2138
|
+
function buildSpawnCommand(o) {
|
|
2139
|
+
const platform = o.platform || process.platform;
|
|
2140
|
+
const unwrapped = { cmd: o.bin, argv: o.args, osSandbox: false };
|
|
2141
|
+
if (!o.readOnly || o.disabled === true || platform !== "darwin" || !o.sandboxExecPath) {
|
|
2142
|
+
return unwrapped;
|
|
2143
|
+
}
|
|
2144
|
+
const profile = buildSeatbeltProfile({ home: o.home, tmpdir: o.tmpdir });
|
|
2145
|
+
return { cmd: o.sandboxExecPath, argv: ["-p", profile, o.bin, ...o.args], osSandbox: true };
|
|
2146
|
+
}
|
|
2147
|
+
function resolveSandboxExecPath() {
|
|
2148
|
+
try {
|
|
2149
|
+
fs.accessSync("/usr/bin/sandbox-exec", fs.constants.X_OK);
|
|
2150
|
+
return "/usr/bin/sandbox-exec";
|
|
2151
|
+
} catch (_) {
|
|
2152
|
+
return null;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
function captureGitState(cwd) {
|
|
2156
|
+
const opts = { cwd, encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] };
|
|
2157
|
+
try {
|
|
2158
|
+
const head = execFileSync("git", ["rev-parse", "HEAD"], opts).trim();
|
|
2159
|
+
const status = execFileSync("git", ["status", "--porcelain"], opts);
|
|
2160
|
+
return { head, status };
|
|
2161
|
+
} catch (_) {
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
function diffGitState(pre, post) {
|
|
2166
|
+
if (!pre || !post) return false;
|
|
2167
|
+
return pre.head !== post.head || pre.status !== post.status;
|
|
2168
|
+
}
|
|
2169
|
+
function captureRoots(dirs) {
|
|
2170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2171
|
+
const states = [];
|
|
2172
|
+
for (const d of dirs) {
|
|
2173
|
+
if (!d || seen.has(d)) continue;
|
|
2174
|
+
seen.add(d);
|
|
2175
|
+
states.push([d, captureGitState(d)]);
|
|
2176
|
+
}
|
|
2177
|
+
return states;
|
|
2178
|
+
}
|
|
2179
|
+
function rootsMutated(preStates) {
|
|
2180
|
+
for (const [dir, pre] of preStates) {
|
|
2181
|
+
if (diffGitState(pre, captureGitState(dir))) return true;
|
|
2182
|
+
}
|
|
2183
|
+
return false;
|
|
2184
|
+
}
|
|
2185
|
+
function workspaceMutationWarning(dir) {
|
|
2186
|
+
return "WORKSPACE MUTATION DETECTED\nThis advisory (read-only) run changed the workspace at " + dir + " (git HEAD and/or working-tree status changed during the run). Review `git status` / `git log` before continuing - nothing was auto-reverted, and the result below should be treated as tainted.\n---\n";
|
|
2187
|
+
}
|
|
2100
2188
|
function sendResponse(id, result) {
|
|
2101
2189
|
process.stdout.write(JSON.stringify({
|
|
2102
2190
|
jsonrpc: "2.0",
|
|
@@ -2150,11 +2238,13 @@ ${prompt}`;
|
|
|
2150
2238
|
return null;
|
|
2151
2239
|
}
|
|
2152
2240
|
}
|
|
2153
|
-
async function runGemini(args, cwd, timeoutMs, recoveryGraceMs) {
|
|
2241
|
+
async function runGemini(args, cwd, timeoutMs, recoveryGraceMs, opts = {}) {
|
|
2154
2242
|
return new Promise((resolve, reject) => {
|
|
2155
2243
|
const t = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
2156
2244
|
const effCwd = cwd || process.cwd();
|
|
2157
2245
|
const spawnStartMs = Date.now();
|
|
2246
|
+
const readOnly = opts.readOnly === true;
|
|
2247
|
+
const preRoots = readOnly ? captureRoots([effCwd, ...opts.includeDirs || []]) : [];
|
|
2158
2248
|
const disableRecovery = process.env.GEMINI_DISABLE_TIMEOUT_RECOVERY === "1";
|
|
2159
2249
|
const grace = disableRecovery ? 0 : typeof recoveryGraceMs === "number" && recoveryGraceMs >= 0 ? recoveryGraceMs : DEFAULT_RECOVERY_GRACE_MS;
|
|
2160
2250
|
let killed = false;
|
|
@@ -2165,8 +2255,27 @@ ${prompt}`;
|
|
|
2165
2255
|
const head = ptIdx >= 0 ? args.slice(0, ptIdx) : args;
|
|
2166
2256
|
const tail = ptIdx >= 0 ? args.slice(ptIdx) : [];
|
|
2167
2257
|
const agyArgs = [...head, "--print-timeout", goDuration(t + grace + 3e4), ...tail];
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2258
|
+
const spawnPlan = buildSpawnCommand({
|
|
2259
|
+
bin: AGY_BIN,
|
|
2260
|
+
args: agyArgs,
|
|
2261
|
+
readOnly,
|
|
2262
|
+
platform: process.platform,
|
|
2263
|
+
home: os.homedir(),
|
|
2264
|
+
tmpdir: (() => {
|
|
2265
|
+
try {
|
|
2266
|
+
return fs.realpathSync(os.tmpdir());
|
|
2267
|
+
} catch (_) {
|
|
2268
|
+
return os.tmpdir();
|
|
2269
|
+
}
|
|
2270
|
+
})(),
|
|
2271
|
+
sandboxExecPath: readOnly ? resolveSandboxExecPath() : null,
|
|
2272
|
+
disabled: process.env.DELIBERATION_DISABLE_OS_SANDBOX === "1"
|
|
2273
|
+
});
|
|
2274
|
+
if (spawnPlan.osSandbox) {
|
|
2275
|
+
process.stderr.write("[deliberation] agy read-only run wrapped in sandbox-exec (workspace writes denied)\n");
|
|
2276
|
+
}
|
|
2277
|
+
const agyProcess = spawn(spawnPlan.cmd, spawnPlan.argv, {
|
|
2278
|
+
env: readOnly ? advisoryEnv(process.env) : process.env,
|
|
2170
2279
|
shell: false,
|
|
2171
2280
|
cwd: effCwd,
|
|
2172
2281
|
// agy -p (print mode) waits for stdin EOF before returning; if the stdin
|
|
@@ -2274,7 +2383,9 @@ ${prompt}`;
|
|
|
2274
2383
|
process.stderr.write(
|
|
2275
2384
|
"[deliberation] recovered agy answer via stdout drain after soft timeout (" + Math.round((Date.now() - spawnStartMs) / 1e3) + "s)\n"
|
|
2276
2385
|
);
|
|
2277
|
-
|
|
2386
|
+
const mutated = readOnly && rootsMutated(preRoots);
|
|
2387
|
+
const text = mutated ? workspaceMutationWarning(effCwd) + out : out;
|
|
2388
|
+
return resolve({ response: text, threadId: resolveConversationId(effCwd) || "unknown", recovered: true, ...mutated ? { workspaceMutated: true } : {} });
|
|
2278
2389
|
}
|
|
2279
2390
|
return finishTimeout();
|
|
2280
2391
|
}
|
|
@@ -2287,7 +2398,9 @@ ${prompt}`;
|
|
|
2287
2398
|
"[deliberation] no conversation id found for cwd " + effCwd + '; returning threadId:"unknown" (resume will be unavailable)\n'
|
|
2288
2399
|
);
|
|
2289
2400
|
}
|
|
2290
|
-
|
|
2401
|
+
const mutated = readOnly && rootsMutated(preRoots);
|
|
2402
|
+
const text = mutated ? workspaceMutationWarning(effCwd) + out : out;
|
|
2403
|
+
return resolve({ response: text, threadId: threadId || "unknown", ...mutated ? { workspaceMutated: true } : {} });
|
|
2291
2404
|
}
|
|
2292
2405
|
let message;
|
|
2293
2406
|
if (trimmedErr) message = trimmedErr;
|
|
@@ -2454,19 +2567,27 @@ ${prompt}`;
|
|
|
2454
2567
|
return;
|
|
2455
2568
|
}
|
|
2456
2569
|
agyArgs.push("--conversation", threadId2, ...sandboxFlags, ...addDirFlags);
|
|
2457
|
-
agyArgs.push("-p", args.prompt);
|
|
2570
|
+
agyArgs.push("-p", applyReadOnlyGuard(args.prompt, args.sandbox !== "workspace-write"));
|
|
2458
2571
|
} else {
|
|
2459
2572
|
if (shouldRespond) sendError(id, -32601, `Tool not found: ${name}`);
|
|
2460
2573
|
return;
|
|
2461
2574
|
}
|
|
2462
2575
|
const timeoutMs = typeof args.timeout === "number" && args.timeout > 0 ? args.timeout : DEFAULT_TIMEOUT_MS;
|
|
2463
2576
|
const recoveryGraceMs = typeof args["recovery-grace"] === "number" && args["recovery-grace"] >= 0 ? args["recovery-grace"] : DEFAULT_RECOVERY_GRACE_MS;
|
|
2464
|
-
const
|
|
2577
|
+
const readOnly = args.sandbox !== "workspace-write";
|
|
2578
|
+
const { response, threadId, recovered, workspaceMutated } = await runGemini(
|
|
2579
|
+
agyArgs,
|
|
2580
|
+
args.cwd,
|
|
2581
|
+
timeoutMs,
|
|
2582
|
+
recoveryGraceMs,
|
|
2583
|
+
{ readOnly, includeDirs: args["include-directories"] || [] }
|
|
2584
|
+
);
|
|
2465
2585
|
if (shouldRespond) {
|
|
2466
2586
|
sendResponse(id, {
|
|
2467
2587
|
content: [{ type: "text", text: response }],
|
|
2468
2588
|
threadId,
|
|
2469
|
-
...recovered ? { recovered: true } : {}
|
|
2589
|
+
...recovered ? { recovered: true } : {},
|
|
2590
|
+
...workspaceMutated ? { workspaceMutated: true } : {}
|
|
2470
2591
|
});
|
|
2471
2592
|
}
|
|
2472
2593
|
} catch (e) {
|
|
@@ -2532,6 +2653,12 @@ ${prompt}`;
|
|
|
2532
2653
|
module2.exports.stdoutIsError = stdoutIsError;
|
|
2533
2654
|
module2.exports.runGemini = runGemini;
|
|
2534
2655
|
module2.exports.buildAgyArgs = buildAgyArgs;
|
|
2656
|
+
module2.exports.READ_ONLY_GUARD = READ_ONLY_GUARD;
|
|
2657
|
+
module2.exports.applyReadOnlyGuard = applyReadOnlyGuard;
|
|
2658
|
+
module2.exports.advisoryEnv = advisoryEnv;
|
|
2659
|
+
module2.exports.buildSeatbeltProfile = buildSeatbeltProfile;
|
|
2660
|
+
module2.exports.buildSpawnCommand = buildSpawnCommand;
|
|
2661
|
+
module2.exports.diffGitState = diffGitState;
|
|
2535
2662
|
}
|
|
2536
2663
|
}
|
|
2537
2664
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antonbabenko/deliberation-mcp",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.4",
|
|
4
4
|
"description": "Deliberation for Claude Code and any MCP host - GPT, Gemini, Grok, and OpenRouter expert subagents.",
|
|
5
5
|
"mcpName": "io.github.antonbabenko/deliberation",
|
|
6
6
|
"repository": { "type": "git", "url": "git+https://github.com/antonbabenko/deliberation.git", "directory": "server/mcp" },
|