@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.
Files changed (2) hide show
  1. package/dist/index.js +219 -35
  2. 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
- async function callProvider(provider, req, logger, tool, cache) {
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: (req.files || []).filter((f) => f.dir).map((f) => f.dir)
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", ["exec", "--skip-git-repo-check"], { cwd: cwd || process.cwd() });
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 agyProcess = spawn(AGY_BIN, agyArgs, {
2169
- env: process.env,
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
- return resolve({ response: out, threadId: resolveConversationId(effCwd) || "unknown", recovered: true });
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
- return resolve({ response: out, threadId: threadId || "unknown" });
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 { response, threadId, recovered } = await runGemini(agyArgs, args.cwd, timeoutMs, recoveryGraceMs);
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.5.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" },