@antonbabenko/deliberation-mcp 3.5.3 → 3.6.0
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 +219 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -520,7 +520,14 @@ var require_orchestrate = __commonJS({
|
|
|
520
520
|
} catch {
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
|
-
|
|
523
|
+
function withOrientation(provider, req, orientationFiles) {
|
|
524
|
+
if (Array.isArray(orientationFiles) && orientationFiles.length && !(Array.isArray(req.files) && req.files.length) && provider.capabilities && provider.capabilities.walksFilesystem === false) {
|
|
525
|
+
return { ...req, files: orientationFiles };
|
|
526
|
+
}
|
|
527
|
+
return req;
|
|
528
|
+
}
|
|
529
|
+
async function callProvider(provider, req, logger, tool, cache, orientationFiles) {
|
|
530
|
+
req = withOrientation(provider, req, orientationFiles);
|
|
524
531
|
const useCache = cache && !(Array.isArray(req.files) && req.files.length);
|
|
525
532
|
if (useCache) {
|
|
526
533
|
const hit = cache.get(provider.name, req);
|
|
@@ -553,7 +560,7 @@ var require_orchestrate = __commonJS({
|
|
|
553
560
|
const logger = opts.logger || NULL_LOGGER;
|
|
554
561
|
const tool = opts.tool || "ask-all";
|
|
555
562
|
const settled = await Promise.allSettled(
|
|
556
|
-
providers.map((p) => callProvider(p, req, logger, tool, opts.cache))
|
|
563
|
+
providers.map((p) => callProvider(p, req, logger, tool, opts.cache, opts.orientationFiles))
|
|
557
564
|
);
|
|
558
565
|
return settled.map(
|
|
559
566
|
(s, i) => s.status === "fulfilled" ? s.value : {
|
|
@@ -568,7 +575,7 @@ var require_orchestrate = __commonJS({
|
|
|
568
575
|
);
|
|
569
576
|
}
|
|
570
577
|
async function askOne2(provider, req, opts = {}) {
|
|
571
|
-
return callProvider(provider, req, opts.logger || NULL_LOGGER, opts.tool || "ask-one", opts.cache);
|
|
578
|
+
return callProvider(provider, req, opts.logger || NULL_LOGGER, opts.tool || "ask-one", opts.cache, opts.orientationFiles);
|
|
572
579
|
}
|
|
573
580
|
function buildArbiterPrompt(question, opinions) {
|
|
574
581
|
const blocks = opinions.map((o, i) => `### Opinion ${i + 1}
|
|
@@ -593,17 +600,17 @@ ${blocks}`,
|
|
|
593
600
|
// Promise.resolve().then(...) so even a SYNCHRONOUS throw in ask() is caught
|
|
594
601
|
// by the rejection handler (a bare arbiter.ask() could throw before awaiting).
|
|
595
602
|
Promise.resolve().then(
|
|
596
|
-
() => arbiter.ask({
|
|
603
|
+
() => arbiter.ask(withOrientation(arbiter, {
|
|
597
604
|
...req,
|
|
598
605
|
files: req.files ? req.files.map((f) => ({ ...f })) : void 0,
|
|
599
606
|
developerInstructions: opts.arbiterInstructions || req.developerInstructions
|
|
600
|
-
})
|
|
607
|
+
}, opts.orientationFiles))
|
|
601
608
|
).then((v) => v, () => null)
|
|
602
609
|
) : Promise.resolve(
|
|
603
610
|
/** @type {DelegationResult|null} */
|
|
604
611
|
null
|
|
605
612
|
);
|
|
606
|
-
const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req, { logger: opts.logger, tool: "consensus" }), blindPromise]);
|
|
613
|
+
const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req, { logger: opts.logger, tool: "consensus", orientationFiles: opts.orientationFiles }), blindPromise]);
|
|
607
614
|
const ok = (
|
|
608
615
|
/** @type {DelegationSuccess[]} */
|
|
609
616
|
opinions.filter((o) => !o.isError)
|
|
@@ -666,8 +673,8 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
|
|
|
666
673
|
const { peerPrompt, blindPrompt } = loop.prepareRound(state);
|
|
667
674
|
const roundNo = state.round;
|
|
668
675
|
const [blindRes, peerResults] = await Promise.all([
|
|
669
|
-
Promise.resolve().then(() => arbiter.ask({ ...req, prompt: blindPrompt })).then((r) => r, () => null),
|
|
670
|
-
askAll2(providers, { ...req, prompt: peerPrompt }, { logger, tool: "consensus" })
|
|
676
|
+
Promise.resolve().then(() => arbiter.ask(withOrientation(arbiter, { ...req, prompt: blindPrompt }, opts.orientationFiles))).then((r) => r, () => null),
|
|
677
|
+
askAll2(providers, { ...req, prompt: peerPrompt }, { logger, tool: "consensus", orientationFiles: opts.orientationFiles })
|
|
671
678
|
]);
|
|
672
679
|
state = loop.recordBlindVerdict(state, okText(blindRes) || "(blind pass unavailable)");
|
|
673
680
|
lastResults = peerResults.map(
|
|
@@ -732,6 +739,52 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
|
|
|
732
739
|
}
|
|
733
740
|
});
|
|
734
741
|
|
|
742
|
+
// ../../core/orientation.js
|
|
743
|
+
var require_orientation = __commonJS({
|
|
744
|
+
"../../core/orientation.js"(exports2, module2) {
|
|
745
|
+
"use strict";
|
|
746
|
+
var fs = require("node:fs");
|
|
747
|
+
var path = require("node:path");
|
|
748
|
+
var ORIENTATION_CANDIDATES = [
|
|
749
|
+
"CLAUDE.md",
|
|
750
|
+
"AGENTS.md",
|
|
751
|
+
"README.md",
|
|
752
|
+
"package.json",
|
|
753
|
+
"pyproject.toml",
|
|
754
|
+
"Cargo.toml",
|
|
755
|
+
"go.mod",
|
|
756
|
+
"tsconfig.json",
|
|
757
|
+
"main.tf"
|
|
758
|
+
];
|
|
759
|
+
var DEFAULT_MAX_FILES = 6;
|
|
760
|
+
function resolveOrientationFiles(cwd, opts = {}) {
|
|
761
|
+
const base = cwd || process.cwd();
|
|
762
|
+
const max = Number.isInteger(opts.maxFiles) && /** @type {number} */
|
|
763
|
+
opts.maxFiles > 0 ? (
|
|
764
|
+
/** @type {number} */
|
|
765
|
+
opts.maxFiles
|
|
766
|
+
) : DEFAULT_MAX_FILES;
|
|
767
|
+
const candidates = opts.candidates || ORIENTATION_CANDIDATES;
|
|
768
|
+
const out = [];
|
|
769
|
+
for (const name of candidates) {
|
|
770
|
+
if (out.length >= max) break;
|
|
771
|
+
const abs = path.join(base, name);
|
|
772
|
+
try {
|
|
773
|
+
if (fs.statSync(abs).isFile()) out.push({ path: abs });
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return out;
|
|
778
|
+
}
|
|
779
|
+
function orientationFilesFor2(config, cwd) {
|
|
780
|
+
const o = config && config.orientation;
|
|
781
|
+
if (!o || o.enabled !== true) return void 0;
|
|
782
|
+
return resolveOrientationFiles(cwd, { maxFiles: o.maxFiles });
|
|
783
|
+
}
|
|
784
|
+
module2.exports = { resolveOrientationFiles, orientationFilesFor: orientationFilesFor2, ORIENTATION_CANDIDATES, DEFAULT_MAX_FILES };
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
|
|
735
788
|
// ../../core/prompts/index.js
|
|
736
789
|
var require_prompts = __commonJS({
|
|
737
790
|
"../../core/prompts/index.js"(exports2, module2) {
|
|
@@ -1443,7 +1496,7 @@ var require_openai_compatible = __commonJS({
|
|
|
1443
1496
|
/** @type {any} */
|
|
1444
1497
|
{
|
|
1445
1498
|
name,
|
|
1446
|
-
capabilities: { canImplement: false, fileUpload: false, multiTurn: true },
|
|
1499
|
+
capabilities: { canImplement: false, fileUpload: false, multiTurn: true, walksFilesystem: false },
|
|
1447
1500
|
/** Test-only: current number of cached sessions. */
|
|
1448
1501
|
get __sessionCount() {
|
|
1449
1502
|
return sessions.size;
|
|
@@ -1525,7 +1578,7 @@ var require_grok = __commonJS({
|
|
|
1525
1578
|
name: "grok",
|
|
1526
1579
|
// multiTurn is not wired through Core (runGrok/runWithFiles return no threadId),
|
|
1527
1580
|
// so report false to match reality.
|
|
1528
|
-
capabilities: { canImplement: false, fileUpload: true, multiTurn: false },
|
|
1581
|
+
capabilities: { canImplement: false, fileUpload: true, multiTurn: false, walksFilesystem: false },
|
|
1529
1582
|
async health() {
|
|
1530
1583
|
return process.env.XAI_API_KEY ? { ok: true } : { ok: false, reason: "XAI_API_KEY unset" };
|
|
1531
1584
|
},
|
|
@@ -1585,22 +1638,23 @@ var require_antigravity = __commonJS({
|
|
|
1585
1638
|
const model = opts.model || process.env.GEMINI_DEFAULT_MODEL || "auto-gemini-3";
|
|
1586
1639
|
return {
|
|
1587
1640
|
name: "gemini",
|
|
1588
|
-
capabilities: { canImplement: true, fileUpload: false, multiTurn: true },
|
|
1641
|
+
capabilities: { canImplement: true, fileUpload: false, multiTurn: true, walksFilesystem: true },
|
|
1589
1642
|
async health() {
|
|
1590
1643
|
return typeof bridge.runGemini === "function" ? { ok: true } : { ok: false, reason: "agy bridge unavailable" };
|
|
1591
1644
|
},
|
|
1592
1645
|
async ask(req) {
|
|
1593
1646
|
const started = Date.now();
|
|
1647
|
+
const includeDirs = (req.files || []).filter((f) => f.dir).map((f) => f.dir);
|
|
1594
1648
|
const args = bridge.buildAgyArgs({
|
|
1595
1649
|
prompt: req.prompt,
|
|
1596
1650
|
model,
|
|
1597
1651
|
sandbox: "read-only",
|
|
1598
1652
|
developerInstructions: req.developerInstructions,
|
|
1599
|
-
includeDirs
|
|
1653
|
+
includeDirs
|
|
1600
1654
|
});
|
|
1601
1655
|
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 };
|
|
1656
|
+
const out = await bridge.runGemini(args, req.cwd, req.timeoutMs, void 0, { readOnly: true, includeDirs });
|
|
1657
|
+
return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started, reasoningEffort: null, ...out.workspaceMutated ? { workspaceMutated: true } : {} };
|
|
1604
1658
|
} catch (e) {
|
|
1605
1659
|
const err = (
|
|
1606
1660
|
/** @type {any} */
|
|
@@ -1634,9 +1688,12 @@ var require_codex = __commonJS({
|
|
|
1634
1688
|
if (s.includes("rate")) return { errorKind: "rate-limit", retryable: true };
|
|
1635
1689
|
return { errorKind: "unknown", retryable: false };
|
|
1636
1690
|
}
|
|
1691
|
+
function codexExecArgs() {
|
|
1692
|
+
return ["exec", "--sandbox", "read-only", "--skip-git-repo-check"];
|
|
1693
|
+
}
|
|
1637
1694
|
function defaultRun({ prompt, cwd, timeoutMs }) {
|
|
1638
1695
|
return new Promise((resolve) => {
|
|
1639
|
-
const child = spawn("codex",
|
|
1696
|
+
const child = spawn("codex", codexExecArgs(), { cwd: cwd || process.cwd() });
|
|
1640
1697
|
let stdout = "", stderr = "", settled = false;
|
|
1641
1698
|
const timer = timeoutMs ? setTimeout(() => child.kill("SIGKILL"), timeoutMs) : null;
|
|
1642
1699
|
if (timer) timer.unref();
|
|
@@ -1662,7 +1719,7 @@ var require_codex = __commonJS({
|
|
|
1662
1719
|
const model = opts.model || "default";
|
|
1663
1720
|
return {
|
|
1664
1721
|
name: "codex",
|
|
1665
|
-
capabilities: { canImplement: true, fileUpload: false, multiTurn: false },
|
|
1722
|
+
capabilities: { canImplement: true, fileUpload: false, multiTurn: false, walksFilesystem: true },
|
|
1666
1723
|
// Option A: no threadId continuity
|
|
1667
1724
|
async health() {
|
|
1668
1725
|
return { ok: true };
|
|
@@ -1693,7 +1750,7 @@ ${req.prompt}` : req.prompt;
|
|
|
1693
1750
|
}
|
|
1694
1751
|
};
|
|
1695
1752
|
}
|
|
1696
|
-
module2.exports = { makeCodexProvider, classifyCodex };
|
|
1753
|
+
module2.exports = { makeCodexProvider, classifyCodex, codexExecArgs };
|
|
1697
1754
|
}
|
|
1698
1755
|
});
|
|
1699
1756
|
|
|
@@ -2085,18 +2142,102 @@ var require_gemini = __commonJS({
|
|
|
2085
2142
|
function goDuration(ms) {
|
|
2086
2143
|
return Math.ceil(ms / 1e3) + "s";
|
|
2087
2144
|
}
|
|
2145
|
+
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.";
|
|
2146
|
+
function applyReadOnlyGuard(prompt, readOnly) {
|
|
2147
|
+
return readOnly ? `${READ_ONLY_GUARD}
|
|
2148
|
+
|
|
2149
|
+
${prompt}` : prompt;
|
|
2150
|
+
}
|
|
2088
2151
|
function buildAgyArgs(req) {
|
|
2089
2152
|
const args = [];
|
|
2153
|
+
const readOnly = req.sandbox !== "workspace-write";
|
|
2090
2154
|
if (req.sandbox === "workspace-write") args.push("--dangerously-skip-permissions");
|
|
2091
2155
|
else args.push("--sandbox");
|
|
2092
2156
|
for (const d of req.includeDirs || []) args.push("--add-dir", d);
|
|
2093
|
-
let prompt = req.prompt;
|
|
2157
|
+
let prompt = applyReadOnlyGuard(req.prompt, readOnly);
|
|
2094
2158
|
if (req.developerInstructions) prompt = `${req.developerInstructions}
|
|
2095
2159
|
|
|
2096
2160
|
${prompt}`;
|
|
2097
2161
|
args.push("-p", prompt);
|
|
2098
2162
|
return args;
|
|
2099
2163
|
}
|
|
2164
|
+
var ADVISORY_ENV_SCRUB = ["GITHUB_TOKEN", "GH_TOKEN", "GIT_ASKPASS", "SSH_AUTH_SOCK"];
|
|
2165
|
+
function advisoryEnv(env) {
|
|
2166
|
+
const out = { ...env };
|
|
2167
|
+
delete out.DELIBERATION_DISABLE_OS_SANDBOX;
|
|
2168
|
+
for (const k of ADVISORY_ENV_SCRUB) delete out[k];
|
|
2169
|
+
return out;
|
|
2170
|
+
}
|
|
2171
|
+
function seatbeltLiteral(p) {
|
|
2172
|
+
return String(p).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2173
|
+
}
|
|
2174
|
+
function buildSeatbeltProfile(o) {
|
|
2175
|
+
const gem = seatbeltLiteral(path.join(o.home, ".gemini"));
|
|
2176
|
+
const tmp = seatbeltLiteral(o.tmpdir);
|
|
2177
|
+
return [
|
|
2178
|
+
"(version 1)",
|
|
2179
|
+
"(allow default)",
|
|
2180
|
+
"(deny file-write*)",
|
|
2181
|
+
"(allow file-write*",
|
|
2182
|
+
` (subpath "${gem}")`,
|
|
2183
|
+
` (subpath "${tmp}")`,
|
|
2184
|
+
' (subpath "/private/var/folders")',
|
|
2185
|
+
' (subpath "/private/tmp")',
|
|
2186
|
+
' (literal "/dev/null")',
|
|
2187
|
+
' (literal "/dev/dtracehelper")',
|
|
2188
|
+
' (regex #"^/dev/tty"))'
|
|
2189
|
+
].join("\n");
|
|
2190
|
+
}
|
|
2191
|
+
function buildSpawnCommand(o) {
|
|
2192
|
+
const platform = o.platform || process.platform;
|
|
2193
|
+
const unwrapped = { cmd: o.bin, argv: o.args, osSandbox: false };
|
|
2194
|
+
if (!o.readOnly || o.disabled === true || platform !== "darwin" || !o.sandboxExecPath) {
|
|
2195
|
+
return unwrapped;
|
|
2196
|
+
}
|
|
2197
|
+
const profile = buildSeatbeltProfile({ home: o.home, tmpdir: o.tmpdir });
|
|
2198
|
+
return { cmd: o.sandboxExecPath, argv: ["-p", profile, o.bin, ...o.args], osSandbox: true };
|
|
2199
|
+
}
|
|
2200
|
+
function resolveSandboxExecPath() {
|
|
2201
|
+
try {
|
|
2202
|
+
fs.accessSync("/usr/bin/sandbox-exec", fs.constants.X_OK);
|
|
2203
|
+
return "/usr/bin/sandbox-exec";
|
|
2204
|
+
} catch (_) {
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function captureGitState(cwd) {
|
|
2209
|
+
const opts = { cwd, encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] };
|
|
2210
|
+
try {
|
|
2211
|
+
const head = execFileSync("git", ["rev-parse", "HEAD"], opts).trim();
|
|
2212
|
+
const status = execFileSync("git", ["status", "--porcelain"], opts);
|
|
2213
|
+
return { head, status };
|
|
2214
|
+
} catch (_) {
|
|
2215
|
+
return null;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
function diffGitState(pre, post) {
|
|
2219
|
+
if (!pre || !post) return false;
|
|
2220
|
+
return pre.head !== post.head || pre.status !== post.status;
|
|
2221
|
+
}
|
|
2222
|
+
function captureRoots(dirs) {
|
|
2223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2224
|
+
const states = [];
|
|
2225
|
+
for (const d of dirs) {
|
|
2226
|
+
if (!d || seen.has(d)) continue;
|
|
2227
|
+
seen.add(d);
|
|
2228
|
+
states.push([d, captureGitState(d)]);
|
|
2229
|
+
}
|
|
2230
|
+
return states;
|
|
2231
|
+
}
|
|
2232
|
+
function rootsMutated(preStates) {
|
|
2233
|
+
for (const [dir, pre] of preStates) {
|
|
2234
|
+
if (diffGitState(pre, captureGitState(dir))) return true;
|
|
2235
|
+
}
|
|
2236
|
+
return false;
|
|
2237
|
+
}
|
|
2238
|
+
function workspaceMutationWarning(dir) {
|
|
2239
|
+
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";
|
|
2240
|
+
}
|
|
2100
2241
|
function sendResponse(id, result) {
|
|
2101
2242
|
process.stdout.write(JSON.stringify({
|
|
2102
2243
|
jsonrpc: "2.0",
|
|
@@ -2150,11 +2291,13 @@ ${prompt}`;
|
|
|
2150
2291
|
return null;
|
|
2151
2292
|
}
|
|
2152
2293
|
}
|
|
2153
|
-
async function runGemini(args, cwd, timeoutMs, recoveryGraceMs) {
|
|
2294
|
+
async function runGemini(args, cwd, timeoutMs, recoveryGraceMs, opts = {}) {
|
|
2154
2295
|
return new Promise((resolve, reject) => {
|
|
2155
2296
|
const t = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
2156
2297
|
const effCwd = cwd || process.cwd();
|
|
2157
2298
|
const spawnStartMs = Date.now();
|
|
2299
|
+
const readOnly = opts.readOnly === true;
|
|
2300
|
+
const preRoots = readOnly ? captureRoots([effCwd, ...opts.includeDirs || []]) : [];
|
|
2158
2301
|
const disableRecovery = process.env.GEMINI_DISABLE_TIMEOUT_RECOVERY === "1";
|
|
2159
2302
|
const grace = disableRecovery ? 0 : typeof recoveryGraceMs === "number" && recoveryGraceMs >= 0 ? recoveryGraceMs : DEFAULT_RECOVERY_GRACE_MS;
|
|
2160
2303
|
let killed = false;
|
|
@@ -2165,8 +2308,27 @@ ${prompt}`;
|
|
|
2165
2308
|
const head = ptIdx >= 0 ? args.slice(0, ptIdx) : args;
|
|
2166
2309
|
const tail = ptIdx >= 0 ? args.slice(ptIdx) : [];
|
|
2167
2310
|
const agyArgs = [...head, "--print-timeout", goDuration(t + grace + 3e4), ...tail];
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2311
|
+
const spawnPlan = buildSpawnCommand({
|
|
2312
|
+
bin: AGY_BIN,
|
|
2313
|
+
args: agyArgs,
|
|
2314
|
+
readOnly,
|
|
2315
|
+
platform: process.platform,
|
|
2316
|
+
home: os.homedir(),
|
|
2317
|
+
tmpdir: (() => {
|
|
2318
|
+
try {
|
|
2319
|
+
return fs.realpathSync(os.tmpdir());
|
|
2320
|
+
} catch (_) {
|
|
2321
|
+
return os.tmpdir();
|
|
2322
|
+
}
|
|
2323
|
+
})(),
|
|
2324
|
+
sandboxExecPath: readOnly ? resolveSandboxExecPath() : null,
|
|
2325
|
+
disabled: process.env.DELIBERATION_DISABLE_OS_SANDBOX === "1"
|
|
2326
|
+
});
|
|
2327
|
+
if (spawnPlan.osSandbox) {
|
|
2328
|
+
process.stderr.write("[deliberation] agy read-only run wrapped in sandbox-exec (workspace writes denied)\n");
|
|
2329
|
+
}
|
|
2330
|
+
const agyProcess = spawn(spawnPlan.cmd, spawnPlan.argv, {
|
|
2331
|
+
env: readOnly ? advisoryEnv(process.env) : process.env,
|
|
2170
2332
|
shell: false,
|
|
2171
2333
|
cwd: effCwd,
|
|
2172
2334
|
// agy -p (print mode) waits for stdin EOF before returning; if the stdin
|
|
@@ -2274,7 +2436,9 @@ ${prompt}`;
|
|
|
2274
2436
|
process.stderr.write(
|
|
2275
2437
|
"[deliberation] recovered agy answer via stdout drain after soft timeout (" + Math.round((Date.now() - spawnStartMs) / 1e3) + "s)\n"
|
|
2276
2438
|
);
|
|
2277
|
-
|
|
2439
|
+
const mutated = readOnly && rootsMutated(preRoots);
|
|
2440
|
+
const text = mutated ? workspaceMutationWarning(effCwd) + out : out;
|
|
2441
|
+
return resolve({ response: text, threadId: resolveConversationId(effCwd) || "unknown", recovered: true, ...mutated ? { workspaceMutated: true } : {} });
|
|
2278
2442
|
}
|
|
2279
2443
|
return finishTimeout();
|
|
2280
2444
|
}
|
|
@@ -2287,7 +2451,9 @@ ${prompt}`;
|
|
|
2287
2451
|
"[deliberation] no conversation id found for cwd " + effCwd + '; returning threadId:"unknown" (resume will be unavailable)\n'
|
|
2288
2452
|
);
|
|
2289
2453
|
}
|
|
2290
|
-
|
|
2454
|
+
const mutated = readOnly && rootsMutated(preRoots);
|
|
2455
|
+
const text = mutated ? workspaceMutationWarning(effCwd) + out : out;
|
|
2456
|
+
return resolve({ response: text, threadId: threadId || "unknown", ...mutated ? { workspaceMutated: true } : {} });
|
|
2291
2457
|
}
|
|
2292
2458
|
let message;
|
|
2293
2459
|
if (trimmedErr) message = trimmedErr;
|
|
@@ -2454,19 +2620,27 @@ ${prompt}`;
|
|
|
2454
2620
|
return;
|
|
2455
2621
|
}
|
|
2456
2622
|
agyArgs.push("--conversation", threadId2, ...sandboxFlags, ...addDirFlags);
|
|
2457
|
-
agyArgs.push("-p", args.prompt);
|
|
2623
|
+
agyArgs.push("-p", applyReadOnlyGuard(args.prompt, args.sandbox !== "workspace-write"));
|
|
2458
2624
|
} else {
|
|
2459
2625
|
if (shouldRespond) sendError(id, -32601, `Tool not found: ${name}`);
|
|
2460
2626
|
return;
|
|
2461
2627
|
}
|
|
2462
2628
|
const timeoutMs = typeof args.timeout === "number" && args.timeout > 0 ? args.timeout : DEFAULT_TIMEOUT_MS;
|
|
2463
2629
|
const recoveryGraceMs = typeof args["recovery-grace"] === "number" && args["recovery-grace"] >= 0 ? args["recovery-grace"] : DEFAULT_RECOVERY_GRACE_MS;
|
|
2464
|
-
const
|
|
2630
|
+
const readOnly = args.sandbox !== "workspace-write";
|
|
2631
|
+
const { response, threadId, recovered, workspaceMutated } = await runGemini(
|
|
2632
|
+
agyArgs,
|
|
2633
|
+
args.cwd,
|
|
2634
|
+
timeoutMs,
|
|
2635
|
+
recoveryGraceMs,
|
|
2636
|
+
{ readOnly, includeDirs: args["include-directories"] || [] }
|
|
2637
|
+
);
|
|
2465
2638
|
if (shouldRespond) {
|
|
2466
2639
|
sendResponse(id, {
|
|
2467
2640
|
content: [{ type: "text", text: response }],
|
|
2468
2641
|
threadId,
|
|
2469
|
-
...recovered ? { recovered: true } : {}
|
|
2642
|
+
...recovered ? { recovered: true } : {},
|
|
2643
|
+
...workspaceMutated ? { workspaceMutated: true } : {}
|
|
2470
2644
|
});
|
|
2471
2645
|
}
|
|
2472
2646
|
} catch (e) {
|
|
@@ -2532,6 +2706,12 @@ ${prompt}`;
|
|
|
2532
2706
|
module2.exports.stdoutIsError = stdoutIsError;
|
|
2533
2707
|
module2.exports.runGemini = runGemini;
|
|
2534
2708
|
module2.exports.buildAgyArgs = buildAgyArgs;
|
|
2709
|
+
module2.exports.READ_ONLY_GUARD = READ_ONLY_GUARD;
|
|
2710
|
+
module2.exports.applyReadOnlyGuard = applyReadOnlyGuard;
|
|
2711
|
+
module2.exports.advisoryEnv = advisoryEnv;
|
|
2712
|
+
module2.exports.buildSeatbeltProfile = buildSeatbeltProfile;
|
|
2713
|
+
module2.exports.buildSpawnCommand = buildSpawnCommand;
|
|
2714
|
+
module2.exports.diffGitState = diffGitState;
|
|
2535
2715
|
}
|
|
2536
2716
|
}
|
|
2537
2717
|
});
|
|
@@ -4398,6 +4578,7 @@ var require_openrouter = __commonJS({
|
|
|
4398
4578
|
// index.js
|
|
4399
4579
|
var { makeRegistry, pinAlias } = require_registry();
|
|
4400
4580
|
var { askAll, askOne, consensus, runToConvergence } = require_orchestrate();
|
|
4581
|
+
var { orientationFilesFor } = require_orientation();
|
|
4401
4582
|
var { PROMPTS } = require_prompts();
|
|
4402
4583
|
var analyzeCore = require_analyze();
|
|
4403
4584
|
var ADVISORY = { readOnlyHint: true };
|
|
@@ -4674,6 +4855,9 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4674
4855
|
if (!persona) return request;
|
|
4675
4856
|
return { ...request, developerInstructions: persona };
|
|
4676
4857
|
}
|
|
4858
|
+
function orient(req) {
|
|
4859
|
+
return orientationFilesFor(getConfig(), req && req.cwd);
|
|
4860
|
+
}
|
|
4677
4861
|
function sessionsCfg() {
|
|
4678
4862
|
const c = getConfig() || {};
|
|
4679
4863
|
const s = c.sessions || {};
|
|
@@ -4771,7 +4955,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4771
4955
|
lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "ask-all", voices: selected.length });
|
|
4772
4956
|
} catch {
|
|
4773
4957
|
}
|
|
4774
|
-
const results = await askAll(selected, withPersona(req, expert), { logger: lg, tool: "ask-all", cache: opts.noCache ? void 0 : resultCache });
|
|
4958
|
+
const results = await askAll(selected, withPersona(req, expert), { logger: lg, tool: "ask-all", cache: opts.noCache ? void 0 : resultCache, orientationFiles: orient(req) });
|
|
4775
4959
|
return {
|
|
4776
4960
|
payload: { results, omitted },
|
|
4777
4961
|
parts: { opinions: results, blindVerdict: null, verdict: null, arbiter: null, warnings: [] }
|
|
@@ -4789,13 +4973,13 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4789
4973
|
const resolved = await resolveArbiter(arbiterSpec, selected, registry, getConfig);
|
|
4790
4974
|
if (resolved.warning) warnings.push(resolved.warning);
|
|
4791
4975
|
if (resolved.mode === "host") {
|
|
4792
|
-
const opinions = await askAll(selected, withPersona(req, expert), { logger: currentLogger(), tool: "consensus" });
|
|
4976
|
+
const opinions = await askAll(selected, withPersona(req, expert), { logger: currentLogger(), tool: "consensus", orientationFiles: orient(req) });
|
|
4793
4977
|
const arbiter2 = { mode: "host" };
|
|
4794
4978
|
const body = { opinions, blindVerdict: null, verdict: null, arbiter: arbiter2, warnings };
|
|
4795
4979
|
return { payload: body, parts: body };
|
|
4796
4980
|
}
|
|
4797
4981
|
if (!resolved.provider) {
|
|
4798
|
-
const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter, logger: currentLogger() });
|
|
4982
|
+
const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter, logger: currentLogger(), orientationFiles: orient(req) });
|
|
4799
4983
|
const arbiter2 = { mode: "server", provider: null };
|
|
4800
4984
|
return {
|
|
4801
4985
|
payload: { opinions: out2.opinions, blindVerdict: out2.blindVerdict, verdict: out2.verdict, error: out2.error, arbiter: arbiter2, warnings },
|
|
@@ -4808,7 +4992,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4808
4992
|
peers = selected;
|
|
4809
4993
|
warnings.push(`panel too small to exclude arbiter '${arbiterP.name}'; kept it in the peer panel (floor of 2)`);
|
|
4810
4994
|
}
|
|
4811
|
-
const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote, logger: currentLogger() });
|
|
4995
|
+
const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote, logger: currentLogger(), orientationFiles: orient(req) });
|
|
4812
4996
|
const arbiter = { mode: "server", provider: arbiterP.name };
|
|
4813
4997
|
return {
|
|
4814
4998
|
payload: { opinions: out.opinions, blindVerdict: out.blindVerdict, verdict: out.verdict, error: out.error, arbiter, warnings },
|
|
@@ -4838,7 +5022,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4838
5022
|
}
|
|
4839
5023
|
const maxRounds = Number.isInteger(maxRoundsOverride) && /** @type {number} */
|
|
4840
5024
|
maxRoundsOverride > 0 ? maxRoundsOverride : Number.isInteger(cc.maxRounds) && cc.maxRounds > 0 ? cc.maxRounds : void 0;
|
|
4841
|
-
const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds, logger: currentLogger() });
|
|
5025
|
+
const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds, logger: currentLogger(), orientationFiles: orient(req) });
|
|
4842
5026
|
const allWarnings = out.error ? warnings.concat([`loop: ${out.error}`]) : warnings;
|
|
4843
5027
|
const rounds = Array.isArray(out.rounds) ? out.rounds.length : 0;
|
|
4844
5028
|
const arbiter = { mode: "server", provider: arbiterP.name };
|
|
@@ -4979,7 +5163,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
4979
5163
|
lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "consensus", round: cur.round, voices: selected.length });
|
|
4980
5164
|
} catch {
|
|
4981
5165
|
}
|
|
4982
|
-
const peerResults = await askAll(selected, withPersona(peerReq, ex), { logger: lg, tool: "consensus" });
|
|
5166
|
+
const peerResults = await askAll(selected, withPersona(peerReq, ex), { logger: lg, tool: "consensus", orientationFiles: orient(peerReq) });
|
|
4983
5167
|
const results = peerResults.map(
|
|
4984
5168
|
(r) => r.isError ? { source: r.provider, isError: true, errorKind: r.errorKind, verdict: null, criticalIssues: [], model: r.model, reasoningEffort: r.reasoningEffort ?? null, ms: r.ms } : { ...parseReview(typeof r.text === "string" ? r.text : ""), source: r.provider, isError: false, model: r.model, reasoningEffort: r.reasoningEffort ?? null, ms: r.ms }
|
|
4985
5169
|
);
|
|
@@ -5103,7 +5287,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
5103
5287
|
if (!p) {
|
|
5104
5288
|
return jsonResult({ error: `provider "${want}" is not in the active panel`, panel: selected.map((x) => x.name) });
|
|
5105
5289
|
}
|
|
5106
|
-
const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
|
|
5290
|
+
const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache, orientationFiles: orient(req) });
|
|
5107
5291
|
return jsonResult({ result });
|
|
5108
5292
|
}
|
|
5109
5293
|
if (name === "analyze") {
|
|
@@ -5172,12 +5356,12 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify
|
|
|
5172
5356
|
if (Object.prototype.hasOwnProperty.call(ASK_PROVIDER, name)) {
|
|
5173
5357
|
const p = registry.get(ASK_PROVIDER[name]);
|
|
5174
5358
|
if (!p) return { content: [{ type: "text", text: JSON.stringify({ error: `provider ${ASK_PROVIDER[name]} not registered` }) }] };
|
|
5175
|
-
const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
|
|
5359
|
+
const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache, orientationFiles: orient(req) });
|
|
5176
5360
|
return { content: [{ type: "text", text: JSON.stringify({ result }) }] };
|
|
5177
5361
|
}
|
|
5178
5362
|
if (EXPERTS.includes(name)) {
|
|
5179
5363
|
const { providers: selected } = registry.selectForAskAll({ config: getConfig(), expert: name });
|
|
5180
|
-
const results = await askAll(selected, withPersona({ ...req, expert: name }, expert), { logger: currentLogger(), tool: name, cache: resultCache });
|
|
5364
|
+
const results = await askAll(selected, withPersona({ ...req, expert: name }, expert), { logger: currentLogger(), tool: name, cache: resultCache, orientationFiles: orient(req) });
|
|
5181
5365
|
return { content: [{ type: "text", text: JSON.stringify({ results }) }] };
|
|
5182
5366
|
}
|
|
5183
5367
|
throw new Error(`unknown tool: ${name}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antonbabenko/deliberation-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
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" },
|