@antonbabenko/deliberation-mcp 3.1.1 → 3.2.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 (3) hide show
  1. package/dist/index.js +495 -114
  2. package/dist/setup.js +10 -1
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -87,9 +87,9 @@ var require_registry = __commonJS({
87
87
  var require_provider = __commonJS({
88
88
  "../../core/provider.js"(exports2, module2) {
89
89
  "use strict";
90
- function toErrorResult(name, model, started, err, classify) {
90
+ function toErrorResult(name, model, started, err, classify, extra) {
91
91
  const { errorKind, retryable } = classify(err && err.status, err && err.code);
92
- return { provider: name, model, isError: true, errorKind, retryable, ms: Date.now() - started };
92
+ return { ...extra && typeof extra === "object" ? extra : {}, provider: name, model, isError: true, errorKind, retryable, ms: Date.now() - started };
93
93
  }
94
94
  function deepFreeze(o) {
95
95
  if (o && typeof o === "object" && !Object.isFrozen(o)) {
@@ -430,17 +430,130 @@ ${state.currentPlan}`
430
430
  }
431
431
  });
432
432
 
433
+ // ../../core/debug-log.js
434
+ var require_debug_log = __commonJS({
435
+ "../../core/debug-log.js"(exports2, module2) {
436
+ "use strict";
437
+ var NULL_LOGGER = Object.freeze({ logEvent(_event) {
438
+ } });
439
+ var ALLOWED_KEYS = Object.freeze([
440
+ "event",
441
+ "at",
442
+ "tool",
443
+ "provider",
444
+ "model",
445
+ "reasoningEffort",
446
+ "ms",
447
+ "isError",
448
+ "errorKind",
449
+ "usage",
450
+ "round",
451
+ "verdict",
452
+ "blindVerdict",
453
+ "converged",
454
+ "acceptedCritical",
455
+ "voices"
456
+ ]);
457
+ function sanitizeEvent(event) {
458
+ const out = {};
459
+ if (!event || typeof event !== "object") return out;
460
+ for (const k of ALLOWED_KEYS) {
461
+ const v = (
462
+ /** @type {Record<string, unknown>} */
463
+ event[k]
464
+ );
465
+ if (v !== void 0) out[k] = v;
466
+ }
467
+ return out;
468
+ }
469
+ function createFileLogger(path) {
470
+ const fs = require("node:fs");
471
+ return {
472
+ logEvent(event) {
473
+ try {
474
+ fs.appendFileSync(path, JSON.stringify(sanitizeEvent(event)) + "\n");
475
+ } catch {
476
+ }
477
+ }
478
+ };
479
+ }
480
+ function composeLoggers(sinks) {
481
+ const real = (Array.isArray(sinks) ? sinks : []).filter((s) => s && typeof s.logEvent === "function");
482
+ if (!real.length) return NULL_LOGGER;
483
+ if (real.length === 1) return real[0];
484
+ return {
485
+ logEvent(event) {
486
+ for (const s of real) {
487
+ try {
488
+ s.logEvent(event);
489
+ } catch {
490
+ }
491
+ }
492
+ }
493
+ };
494
+ }
495
+ module2.exports = { NULL_LOGGER, createFileLogger, composeLoggers, sanitizeEvent, ALLOWED_KEYS };
496
+ }
497
+ });
498
+
433
499
  // ../../core/orchestrate.js
434
500
  var require_orchestrate = __commonJS({
435
501
  "../../core/orchestrate.js"(exports2, module2) {
436
502
  "use strict";
437
503
  var { parseReview } = require_provider();
438
504
  var loop = require_consensus_loop();
439
- async function askAll2(providers, req) {
505
+ var { NULL_LOGGER } = require_debug_log();
506
+ function logProviderResult(logger, tool, r) {
507
+ try {
508
+ logger.logEvent({
509
+ event: "provider_result",
510
+ at: Date.now(),
511
+ tool,
512
+ provider: r.provider,
513
+ model: r.model,
514
+ reasoningEffort: r.reasoningEffort ?? null,
515
+ ms: r.ms,
516
+ isError: r.isError,
517
+ errorKind: r.isError ? r.errorKind : void 0,
518
+ usage: r.isError ? void 0 : r.usage
519
+ });
520
+ } catch {
521
+ }
522
+ }
523
+ async function callProvider(provider, req, logger, tool, cache) {
524
+ const useCache = cache && !(Array.isArray(req.files) && req.files.length);
525
+ if (useCache) {
526
+ const hit = cache.get(provider.name, req);
527
+ if (hit) {
528
+ logProviderResult(logger, tool, hit);
529
+ return hit;
530
+ }
531
+ }
532
+ const started = Date.now();
533
+ let r;
534
+ try {
535
+ r = await provider.ask({ ...req, files: req.files ? req.files.map((f) => ({ ...f })) : void 0 });
536
+ } catch (e) {
537
+ r = {
538
+ provider: provider.name,
539
+ model: "unknown",
540
+ isError: true,
541
+ errorKind: "unknown",
542
+ retryable: false,
543
+ message: String(e && /** @type {any} */
544
+ e.message || e),
545
+ ms: Date.now() - started
546
+ };
547
+ }
548
+ logProviderResult(logger, tool, r);
549
+ if (useCache) cache.set(provider.name, req, r);
550
+ return r;
551
+ }
552
+ async function askAll2(providers, req, opts = {}) {
553
+ const logger = opts.logger || NULL_LOGGER;
554
+ const tool = opts.tool || "ask-all";
440
555
  const settled = await Promise.allSettled(
441
- providers.map(
442
- (p) => p.ask({ ...req, files: req.files ? req.files.map((f) => ({ ...f })) : void 0 })
443
- )
556
+ providers.map((p) => callProvider(p, req, logger, tool, opts.cache))
444
557
  );
445
558
  return settled.map(
446
559
  (s, i) => s.status === "fulfilled" ? s.value : {
@@ -454,8 +567,8 @@ var require_orchestrate = __commonJS({
454
567
  }
455
568
  );
456
569
  }
457
- async function askOne2(provider, req) {
458
- return provider.ask({ ...req, files: req.files ? req.files.map((f) => ({ ...f })) : void 0 });
570
+ async function askOne2(provider, req, opts = {}) {
571
+ return callProvider(provider, req, opts.logger || NULL_LOGGER, opts.tool || "ask-one", opts.cache);
459
572
  }
460
573
  function buildArbiterPrompt(question, opinions) {
461
574
  const blocks = opinions.map((o, i) => `### Opinion ${i + 1}
@@ -490,7 +603,7 @@ ${blocks}`,
490
603
  /** @type {DelegationResult|null} */
491
604
  null
492
605
  );
493
- const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req), blindPromise]);
606
+ const [opinions, blindVerdict] = await Promise.all([askAll2(providers, req, { logger: opts.logger, tool: "consensus" }), blindPromise]);
494
607
  const ok = (
495
608
  /** @type {DelegationSuccess[]} */
496
609
  opinions.filter((o) => !o.isError)
@@ -539,6 +652,7 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
539
652
  }
540
653
  async function runToConvergence2(providers, req, opts = {}) {
541
654
  const arbiter = opts.arbiter;
655
+ const logger = opts.logger || NULL_LOGGER;
542
656
  if (!arbiter) return { converged: false, verdict: null, confidence: "none", rounds: [], opinions: [], error: "no-arbiter" };
543
657
  let state = loop.initConsensusLoop({
544
658
  plan: typeof req.prompt === "string" ? req.prompt : "",
@@ -550,9 +664,10 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
550
664
  try {
551
665
  while (state.status !== "converged" && state.status !== "unresolved") {
552
666
  const { peerPrompt, blindPrompt } = loop.prepareRound(state);
667
+ const roundNo = state.round;
553
668
  const [blindRes, peerResults] = await Promise.all([
554
669
  Promise.resolve().then(() => arbiter.ask({ ...req, prompt: blindPrompt })).then((r) => r, () => null),
555
- askAll2(providers, { ...req, prompt: peerPrompt })
670
+ askAll2(providers, { ...req, prompt: peerPrompt }, { logger, tool: "consensus" })
556
671
  ]);
557
672
  state = loop.recordBlindVerdict(state, okText(blindRes) || "(blind pass unavailable)");
558
673
  lastResults = peerResults.map(
@@ -570,6 +685,19 @@ ${feedback || "(reviewers gave no specific issues; tighten the weakest part)"}`
570
685
  } catch {
571
686
  }
572
687
  state = loop.submitAdjudication(state, { verdict, decisions: [] });
688
+ try {
689
+ logger.logEvent({
690
+ event: "round",
691
+ at: Date.now(),
692
+ tool: "consensus",
693
+ round: roundNo,
694
+ verdict,
695
+ converged: state.status === "converged",
696
+ blindVerdict: okText(blindRes) ? "(recorded)" : null,
697
+ voices: lastResults.length
698
+ });
699
+ } catch {
700
+ }
573
701
  if (state.status === "converged") break;
574
702
  let revised = state.currentPlan;
575
703
  try {
@@ -918,6 +1046,147 @@ var require_loop_store = __commonJS({
918
1046
  }
919
1047
  });
920
1048
 
1049
+ // ../../core/result-cache.js
1050
+ var require_result_cache = __commonJS({
1051
+ "../../core/result-cache.js"(exports2, module2) {
1052
+ "use strict";
1053
+ var DEFAULT_MAX = 100;
1054
+ var DEFAULT_TTL_MS = 6e5;
1055
+ function keyFor(providerName, req) {
1056
+ const files = Array.isArray(req.files) ? req.files.map((f) => `${f.path || ""}|${f.dir || ""}|${f.file_id || ""}|${f.file_url || ""}|${f.mode || ""}`) : [];
1057
+ return JSON.stringify([
1058
+ providerName,
1059
+ req.model || "",
1060
+ req.reasoningEffort || "",
1061
+ typeof req.temperature === "number" ? req.temperature : "",
1062
+ req.developerInstructions || "",
1063
+ req.prompt || "",
1064
+ files
1065
+ ]);
1066
+ }
1067
+ function makeResultCache(opts = {}) {
1068
+ const max = Number.isInteger(opts.max) && /** @type {number} */
1069
+ opts.max > 0 ? (
1070
+ /** @type {number} */
1071
+ opts.max
1072
+ ) : DEFAULT_MAX;
1073
+ const ttlMs = Number.isInteger(opts.ttlMs) && /** @type {number} */
1074
+ opts.ttlMs > 0 ? (
1075
+ /** @type {number} */
1076
+ opts.ttlMs
1077
+ ) : DEFAULT_TTL_MS;
1078
+ const map = /* @__PURE__ */ new Map();
1079
+ return {
1080
+ get(providerName, req) {
1081
+ const k = keyFor(providerName, req);
1082
+ const e = map.get(k);
1083
+ if (!e) return void 0;
1084
+ if (Date.now() - e.at > ttlMs) {
1085
+ map.delete(k);
1086
+ return void 0;
1087
+ }
1088
+ map.delete(k);
1089
+ map.set(k, e);
1090
+ return e.result;
1091
+ },
1092
+ set(providerName, req, result) {
1093
+ if (!result || result.isError) return;
1094
+ const k = keyFor(providerName, req);
1095
+ map.set(k, { result: { .../** @type {DelegationSuccess} */
1096
+ result, cached: true, ms: 0 }, at: Date.now() });
1097
+ if (map.size > max) {
1098
+ const oldest = map.keys().next().value;
1099
+ if (oldest !== void 0) map.delete(oldest);
1100
+ }
1101
+ },
1102
+ get size() {
1103
+ return map.size;
1104
+ }
1105
+ };
1106
+ }
1107
+ module2.exports = { makeResultCache, keyFor, DEFAULT_MAX, DEFAULT_TTL_MS };
1108
+ }
1109
+ });
1110
+
1111
+ // ../../core/paths.js
1112
+ var require_paths = __commonJS({
1113
+ "../../core/paths.js"(exports2, module2) {
1114
+ "use strict";
1115
+ var os = require("node:os");
1116
+ var path = require("node:path");
1117
+ function resolveInjection(opts) {
1118
+ return {
1119
+ home: opts && opts.home || os.homedir(),
1120
+ env: opts && opts.env || process.env,
1121
+ platform: opts && opts.platform || process.platform
1122
+ };
1123
+ }
1124
+ function isUsableBase(value, platform) {
1125
+ if (typeof value !== "string" || value.length === 0) return false;
1126
+ const impl = platform === "win32" ? path.win32 : path.posix;
1127
+ return impl.isAbsolute(value);
1128
+ }
1129
+ function canonicalConfigDir(home, env, platform) {
1130
+ if (platform === "win32") {
1131
+ const appData = env.APPDATA;
1132
+ const base2 = isUsableBase(appData, platform) ? appData : path.join(home, "AppData", "Roaming");
1133
+ return path.join(base2, "deliberation");
1134
+ }
1135
+ const xdg = env.XDG_CONFIG_HOME;
1136
+ const base = isUsableBase(xdg, platform) ? xdg : path.join(home, ".config");
1137
+ return path.join(base, "deliberation");
1138
+ }
1139
+ function resolveConfigPath(opts) {
1140
+ const { home, env, platform } = resolveInjection(opts);
1141
+ const override = env.DELIBERATION_CONFIG;
1142
+ if (typeof override === "string" && override.length > 0) {
1143
+ return override;
1144
+ }
1145
+ return path.join(canonicalConfigDir(home, env, platform), "config.json");
1146
+ }
1147
+ function canonicalCacheDir(home, env, platform) {
1148
+ if (platform === "win32") {
1149
+ const localAppData = env.LOCALAPPDATA;
1150
+ const base2 = isUsableBase(localAppData, platform) ? localAppData : path.join(home, "AppData", "Local");
1151
+ return path.join(base2, "deliberation");
1152
+ }
1153
+ const xdg = env.XDG_CACHE_HOME;
1154
+ const base = isUsableBase(xdg, platform) ? xdg : path.join(home, ".cache");
1155
+ return path.join(base, "deliberation");
1156
+ }
1157
+ function resolveGrokCachePath(opts) {
1158
+ const { home, env, platform } = resolveInjection(opts);
1159
+ const override = env.DELIBERATION_CACHE;
1160
+ if (typeof override === "string" && override.length > 0) {
1161
+ return override;
1162
+ }
1163
+ return path.join(canonicalCacheDir(home, env, platform), "grok-files.json");
1164
+ }
1165
+ function resolveSessionsDir(opts) {
1166
+ const { home, env, platform } = resolveInjection(opts);
1167
+ const override = env.DELIBERATION_SESSIONS;
1168
+ if (typeof override === "string" && override.length > 0) {
1169
+ return override;
1170
+ }
1171
+ return path.join(canonicalCacheDir(home, env, platform), "sessions");
1172
+ }
1173
+ function resolveDebugLogPath(opts) {
1174
+ const { home, env, platform } = resolveInjection(opts);
1175
+ const override = env.DELIBERATION_DEBUG_LOG;
1176
+ if (typeof override === "string" && override.length > 0) {
1177
+ return override;
1178
+ }
1179
+ return path.join(canonicalCacheDir(home, env, platform), "debug.jsonl");
1180
+ }
1181
+ module2.exports = {
1182
+ resolveConfigPath,
1183
+ resolveGrokCachePath,
1184
+ resolveSessionsDir,
1185
+ resolveDebugLogPath
1186
+ };
1187
+ }
1188
+ });
1189
+
921
1190
  // ../../core/providers/openai-compatible.js
922
1191
  var require_openai_compatible = __commonJS({
923
1192
  "../../core/providers/openai-compatible.js"(exports2, module2) {
@@ -960,13 +1229,14 @@ var require_openai_compatible = __commonJS({
960
1229
  started,
961
1230
  /** @type {any} */
962
1231
  e,
963
- () => ({ errorKind: "config", retryable: false })
1232
+ () => ({ errorKind: "config", retryable: false }),
1233
+ { reasoningEffort: req.reasoningEffort ?? null }
964
1234
  );
965
1235
  }
966
1236
  }
967
1237
  const turns = prior ? [...prior, { role: "user", text: req.prompt, inlineBlocks: blocks }] : bridge.buildInitialTurns(req.developerInstructions, req.prompt, blocks);
968
1238
  try {
969
- const { text } = await bridge.callOpenRouter({
1239
+ const { text, usage } = await bridge.callOpenRouter({
970
1240
  apiBase,
971
1241
  apiKey: req && req.apiKey || process.env[apiKeyEnv],
972
1242
  model,
@@ -981,7 +1251,7 @@ var require_openai_compatible = __commonJS({
981
1251
  const outText = notes.length ? `${text}
982
1252
 
983
1253
  [files] ${notes.join("; ")}` : text;
984
- return { provider: name, model, text: outText, threadId, isError: false, ms: Date.now() - started };
1254
+ return { provider: name, model, text: outText, threadId, isError: false, ms: Date.now() - started, reasoningEffort: req.reasoningEffort ?? null, usage };
985
1255
  } catch (e) {
986
1256
  return toErrorResult(
987
1257
  name,
@@ -989,7 +1259,8 @@ var require_openai_compatible = __commonJS({
989
1259
  started,
990
1260
  /** @type {any} */
991
1261
  e,
992
- bridge.classifyError
1262
+ bridge.classifyError,
1263
+ { reasoningEffort: req.reasoningEffort ?? null }
993
1264
  );
994
1265
  }
995
1266
  }
@@ -1044,7 +1315,7 @@ var require_grok = __commonJS({
1044
1315
  reasoningEffort,
1045
1316
  timeoutMs: req.timeoutMs
1046
1317
  });
1047
- return { provider: "grok", model, text: out.text || "", isError: false, ms: Date.now() - started };
1318
+ return { provider: "grok", model, text: out.text || "", isError: false, ms: Date.now() - started, reasoningEffort: reasoningEffort ?? null, usage: out.usage };
1048
1319
  } catch (e) {
1049
1320
  return toErrorResult(
1050
1321
  "grok",
@@ -1052,7 +1323,8 @@ var require_grok = __commonJS({
1052
1323
  started,
1053
1324
  /** @type {any} */
1054
1325
  e,
1055
- bridge.classifyGrokError
1326
+ bridge.classifyGrokError,
1327
+ { reasoningEffort: reasoningEffort ?? null }
1056
1328
  );
1057
1329
  }
1058
1330
  }
@@ -1091,7 +1363,7 @@ var require_antigravity = __commonJS({
1091
1363
  });
1092
1364
  try {
1093
1365
  const out = await bridge.runGemini(args, req.cwd, req.timeoutMs, void 0);
1094
- return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started };
1366
+ return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started, reasoningEffort: null };
1095
1367
  } catch (e) {
1096
1368
  const err = (
1097
1369
  /** @type {any} */
@@ -1102,7 +1374,8 @@ var require_antigravity = __commonJS({
1102
1374
  model,
1103
1375
  started,
1104
1376
  err,
1105
- (_status, code) => bridge.classifyGeminiError(err && err.message || "", code)
1377
+ (_status, code) => bridge.classifyGeminiError(err && err.message || "", code),
1378
+ { reasoningEffort: null }
1106
1379
  );
1107
1380
  }
1108
1381
  }
@@ -1166,7 +1439,7 @@ var require_codex = __commonJS({
1166
1439
  ${req.prompt}` : req.prompt;
1167
1440
  const { code, stdout, stderr } = await run({ prompt: full, cwd: req.cwd, timeoutMs: req.timeoutMs });
1168
1441
  if (code === 0) {
1169
- return { provider: "codex", model, text: stdout.trim(), isError: false, ms: Date.now() - started };
1442
+ return { provider: "codex", model, text: stdout.trim(), isError: false, ms: Date.now() - started, reasoningEffort: null };
1170
1443
  }
1171
1444
  const { errorKind, retryable } = classifyCodex(stderr);
1172
1445
  return {
@@ -1177,7 +1450,8 @@ ${req.prompt}` : req.prompt;
1177
1450
  retryable,
1178
1451
  // Error results carry no text; surface stdout/stderr diagnostics in message.
1179
1452
  message: stdout && stdout.trim() || stderr || void 0,
1180
- ms: Date.now() - started
1453
+ ms: Date.now() - started,
1454
+ reasoningEffort: null
1181
1455
  };
1182
1456
  }
1183
1457
  };
@@ -1243,6 +1517,7 @@ var require_config = __commonJS({
1243
1517
  const invalidModels = enabled ? parsed.invalidModels : [];
1244
1518
  const { consensus: consensus2, warnings } = resolveConsensus(raw.consensus, models);
1245
1519
  const { sessions, warnings: sessionsWarnings } = resolveSessions(raw.sessions);
1520
+ const { debug, warnings: debugWarnings } = resolveDebug(raw.debug);
1246
1521
  return {
1247
1522
  ok: true,
1248
1523
  error: null,
@@ -1252,13 +1527,32 @@ var require_config = __commonJS({
1252
1527
  openrouter: { enabled, apiKeyEnv, apiBase, allowRawModel, maxFanout, defaultModel, defaults, models, invalidModels },
1253
1528
  consensus: consensus2,
1254
1529
  sessions,
1255
- // Defaults- and sessions-validation warnings ride the same consensusWarnings
1256
- // channel the bridge already surfaces, so a dropped/degraded value is
1257
- // visible, not silent.
1258
- consensusWarnings: [...defaultsWarnings, ...warnings, ...sessionsWarnings]
1530
+ debug,
1531
+ // Defaults-, sessions-, and debug-validation warnings ride the same
1532
+ // consensusWarnings channel the bridge already surfaces, so a dropped/degraded
1533
+ // value is visible, not silent.
1534
+ consensusWarnings: [...defaultsWarnings, ...warnings, ...sessionsWarnings, ...debugWarnings]
1259
1535
  }
1260
1536
  };
1261
1537
  }
1538
+ function resolveDebug(raw) {
1539
+ const warnings = [];
1540
+ const out = { enabled: false, path: null };
1541
+ if (raw === void 0) return { debug: out, warnings };
1542
+ if (!isObject(raw)) {
1543
+ warnings.push(`debug must be an object (got ${JSON.stringify(raw)}); debug logging disabled`);
1544
+ return { debug: out, warnings };
1545
+ }
1546
+ if (raw.enabled !== void 0) {
1547
+ if (typeof raw.enabled === "boolean") out.enabled = raw.enabled;
1548
+ else warnings.push(`debug.enabled must be a boolean (got ${JSON.stringify(raw.enabled)}); using false`);
1549
+ }
1550
+ if (raw.path !== void 0) {
1551
+ if (typeof raw.path === "string" && raw.path.trim()) out.path = raw.path.trim();
1552
+ else warnings.push(`debug.path must be a non-empty string (got ${JSON.stringify(raw.path)}); using the default cache-dir path`);
1553
+ }
1554
+ return { debug: out, warnings };
1555
+ }
1262
1556
  function resolveSessions(raw) {
1263
1557
  const warnings = [];
1264
1558
  const out = { persist: false, maxRecords: DEFAULT_SESSIONS_MAX_RECORDS, maxAgeDays: DEFAULT_SESSIONS_MAX_AGE_DAYS };
@@ -1537,76 +1831,6 @@ var require_config = __commonJS({
1537
1831
  }
1538
1832
  });
1539
1833
 
1540
- // ../../core/paths.js
1541
- var require_paths = __commonJS({
1542
- "../../core/paths.js"(exports2, module2) {
1543
- "use strict";
1544
- var os = require("node:os");
1545
- var path = require("node:path");
1546
- function resolveInjection(opts) {
1547
- return {
1548
- home: opts && opts.home || os.homedir(),
1549
- env: opts && opts.env || process.env,
1550
- platform: opts && opts.platform || process.platform
1551
- };
1552
- }
1553
- function isUsableBase(value, platform) {
1554
- if (typeof value !== "string" || value.length === 0) return false;
1555
- const impl = platform === "win32" ? path.win32 : path.posix;
1556
- return impl.isAbsolute(value);
1557
- }
1558
- function canonicalConfigDir(home, env, platform) {
1559
- if (platform === "win32") {
1560
- const appData = env.APPDATA;
1561
- const base2 = isUsableBase(appData, platform) ? appData : path.join(home, "AppData", "Roaming");
1562
- return path.join(base2, "deliberation");
1563
- }
1564
- const xdg = env.XDG_CONFIG_HOME;
1565
- const base = isUsableBase(xdg, platform) ? xdg : path.join(home, ".config");
1566
- return path.join(base, "deliberation");
1567
- }
1568
- function resolveConfigPath(opts) {
1569
- const { home, env, platform } = resolveInjection(opts);
1570
- const override = env.DELIBERATION_CONFIG;
1571
- if (typeof override === "string" && override.length > 0) {
1572
- return override;
1573
- }
1574
- return path.join(canonicalConfigDir(home, env, platform), "config.json");
1575
- }
1576
- function canonicalCacheDir(home, env, platform) {
1577
- if (platform === "win32") {
1578
- const localAppData = env.LOCALAPPDATA;
1579
- const base2 = isUsableBase(localAppData, platform) ? localAppData : path.join(home, "AppData", "Local");
1580
- return path.join(base2, "deliberation");
1581
- }
1582
- const xdg = env.XDG_CACHE_HOME;
1583
- const base = isUsableBase(xdg, platform) ? xdg : path.join(home, ".cache");
1584
- return path.join(base, "deliberation");
1585
- }
1586
- function resolveGrokCachePath(opts) {
1587
- const { home, env, platform } = resolveInjection(opts);
1588
- const override = env.DELIBERATION_CACHE;
1589
- if (typeof override === "string" && override.length > 0) {
1590
- return override;
1591
- }
1592
- return path.join(canonicalCacheDir(home, env, platform), "grok-files.json");
1593
- }
1594
- function resolveSessionsDir(opts) {
1595
- const { home, env, platform } = resolveInjection(opts);
1596
- const override = env.DELIBERATION_SESSIONS;
1597
- if (typeof override === "string" && override.length > 0) {
1598
- return override;
1599
- }
1600
- return path.join(canonicalCacheDir(home, env, platform), "sessions");
1601
- }
1602
- module2.exports = {
1603
- resolveConfigPath,
1604
- resolveGrokCachePath,
1605
- resolveSessionsDir
1606
- };
1607
- }
1608
- });
1609
-
1610
1834
  // ../gemini/index.js
1611
1835
  var require_gemini = __commonJS({
1612
1836
  "../gemini/index.js"(exports2, module2) {
@@ -2931,7 +3155,19 @@ ${ref.inline_text}` });
2931
3155
  throw e;
2932
3156
  }
2933
3157
  const text = parseResponsesOutput(data);
2934
- return { text, output: Array.isArray(data.output) ? data.output : null };
3158
+ return { text, output: Array.isArray(data.output) ? data.output : null, usage: normalizeUsage(data.usage) };
3159
+ }
3160
+ function normalizeUsage(u) {
3161
+ if (!u || typeof u !== "object") return void 0;
3162
+ const prompt = typeof u.prompt_tokens === "number" ? u.prompt_tokens : typeof u.input_tokens === "number" ? u.input_tokens : void 0;
3163
+ const completion = typeof u.completion_tokens === "number" ? u.completion_tokens : typeof u.output_tokens === "number" ? u.output_tokens : void 0;
3164
+ const total = typeof u.total_tokens === "number" ? u.total_tokens : void 0;
3165
+ if (prompt === void 0 && completion === void 0 && total === void 0) return void 0;
3166
+ const out = {};
3167
+ if (prompt !== void 0) out.promptTokens = prompt;
3168
+ if (completion !== void 0) out.completionTokens = completion;
3169
+ if (total !== void 0) out.totalTokens = total;
3170
+ return out;
2935
3171
  }
2936
3172
  var STALE_FILE_ID_TEST = /file[-_][A-Za-z0-9_-]+/;
2937
3173
  var STALE_FILE_ID_EXTRACT = /file[-_][A-Za-z0-9_-]+/g;
@@ -2966,7 +3202,7 @@ ${ref.inline_text}` });
2966
3202
  }
2967
3203
  try {
2968
3204
  const out = await attempt(buildTurns(refs));
2969
- return { text: out.text, output: out.output, refs, ownedIds };
3205
+ return { text: out.text, output: out.output, refs, ownedIds, usage: out.usage };
2970
3206
  } catch (e) {
2971
3207
  if (!isStaleFileError(e)) throw e;
2972
3208
  const matches = (e.message || "").match(STALE_FILE_ID_EXTRACT) || [];
@@ -2998,7 +3234,7 @@ ${ref.inline_text}` });
2998
3234
  if (!reuploaded._fromCache) ownedIds.push(reuploaded.id);
2999
3235
  }
3000
3236
  const out = await attempt(buildTurns(refs));
3001
- return { text: out.text, output: out.output, refs, ownedIds };
3237
+ return { text: out.text, output: out.output, refs, ownedIds, usage: out.usage };
3002
3238
  }
3003
3239
  }
3004
3240
  function validateRoots(roots) {
@@ -3643,7 +3879,15 @@ var require_openrouter = __commonJS({
3643
3879
  e.code = "parse";
3644
3880
  throw e;
3645
3881
  }
3646
- return { text: parseCompletion(data) };
3882
+ return { text: parseCompletion(data), usage: normalizeUsage(data.usage) };
3883
+ }
3884
+ function normalizeUsage(u) {
3885
+ if (!u || typeof u !== "object") return void 0;
3886
+ const out = {};
3887
+ if (typeof u.prompt_tokens === "number") out.promptTokens = u.prompt_tokens;
3888
+ if (typeof u.completion_tokens === "number") out.completionTokens = u.completion_tokens;
3889
+ if (typeof u.total_tokens === "number") out.totalTokens = u.total_tokens;
3890
+ return Object.keys(out).length ? out : void 0;
3647
3891
  }
3648
3892
  var crypto = require("node:crypto");
3649
3893
  var { makeConfigReader } = require_config();
@@ -3930,6 +4174,42 @@ var EXPERT_DESCRIPTIONS = {
3930
4174
  "researcher": "Research specialist for external libraries, frameworks, APIs, and open-source code. Use for 'how do I use X', best-practice, or 'why does this dependency behave this way' questions, with evidence and honest unverified flags.",
3931
4175
  "debugger": "Debugging specialist that produces ranked root-cause hypotheses and the smallest safe fix from a bug report, logs, and code - or says honestly that the evidence shows no bug. Use for crashes, failing tests, or wrong output."
3932
4176
  };
4177
+ function panelInputSchema() {
4178
+ return {
4179
+ type: "object",
4180
+ properties: {
4181
+ expert: { type: "string" },
4182
+ cwd: { type: "string" }
4183
+ }
4184
+ };
4185
+ }
4186
+ function askOneInputSchema() {
4187
+ return {
4188
+ type: "object",
4189
+ required: ["provider", "prompt"],
4190
+ properties: {
4191
+ provider: { type: "string", description: 'A name from `panel` (e.g. "codex", "openrouter:<alias>")' },
4192
+ prompt: { type: "string" },
4193
+ expert: { type: "string" },
4194
+ developerInstructions: { type: "string" },
4195
+ cwd: { type: "string" },
4196
+ reasoningEffort: { type: "string", enum: ["low", "medium", "high", "none"] },
4197
+ files: {
4198
+ type: "array",
4199
+ items: {
4200
+ type: "object",
4201
+ properties: {
4202
+ path: { type: "string" },
4203
+ dir: { type: "string" },
4204
+ file_id: { type: "string" },
4205
+ file_url: { type: "string" },
4206
+ mode: { type: "string", enum: ["auto", "inline", "upload"] }
4207
+ }
4208
+ }
4209
+ }
4210
+ }
4211
+ };
4212
+ }
3933
4213
  function inputSchema() {
3934
4214
  return {
3935
4215
  type: "object",
@@ -4012,7 +4292,9 @@ function toolList() {
4012
4292
  const tools = [
4013
4293
  { name: "ask-all", description: "Fan out one question to GPT, Gemini, Grok, and any configured OpenRouter models in parallel for independent second opinions, then return all results (advisory, no cross-contamination). Pass `expert` to apply a persona to every delegate.", inputSchema: inputSchema(), annotations: ADVISORY },
4014
4294
  { name: "consensus", description: "Run the FULL multi-round consensus convergence loop server-side with a provider arbiter (blind pass + peer fan-out -> adjudicate -> revise) and return the converged verdict. Default depth is `consensus.maxRounds` (config, default 5); pass `maxRounds` to override. Pass `synthesizeAlways:true` for a SINGLE arbiter synthesis pass instead of the loop (best for open questions, not plan convergence): it returns a free-text `synthesis` and `maxRounds` is ignored. Configure the arbiter via `consensus.arbiter` - a concrete provider/openrouter alias runs server-side; `host` mode returns the opinions for YOU to synthesize. Advisory; pass `expert` to apply a persona. NOTE (Claude Code): use the `/consensus` slash command for the transcript-visible host-arbiter loop (it drives `consensus-step`); this tool is the provider-arbiter path for any host.", inputSchema: consensusInputSchema(), annotations: ADVISORY },
4015
- { name: "consensus-step", description: "Client-driven consensus loop where YOU (the host model) are the arbiter, one step per call: action=init (start, returns sessionId + blind prompt) -> record_blind (your pre-commit verdict) -> dispatch_peers (server fans out to the providers) -> submit_adjudication (your verdict + per-issue accept/dismiss/defer) -> submit_revision (your revised plan), looping until converged or consensus.maxRounds rounds (default 5). State is held server-side by sessionId. Advisory.", inputSchema: consensusStepInputSchema(), annotations: ADVISORY }
4295
+ { name: "consensus-step", description: "Client-driven consensus loop where YOU (the host model) are the arbiter, one step per call: action=init (start, returns sessionId + blind prompt) -> record_blind (your pre-commit verdict) -> dispatch_peers (server fans out to the providers) -> submit_adjudication (your verdict + per-issue accept/dismiss/defer) -> submit_revision (your revised plan), looping until converged or consensus.maxRounds rounds (default 5). State is held server-side by sessionId. Advisory.", inputSchema: consensusStepInputSchema(), annotations: ADVISORY },
4296
+ { name: "panel", description: "Return the names of the providers `ask-all` WOULD dispatch for the current config + expert (enabled built-ins + eligible OpenRouter aliases, fanout cap applied), WITHOUT calling them. Use this to discover the panel, then issue one `ask-one` call per provider in parallel for visible per-provider progress. Advisory, read-only.", inputSchema: panelInputSchema(), annotations: ADVISORY },
4297
+ { name: "ask-one", description: "Second opinion from ONE named provider in the active panel (e.g. `codex`, `gemini`, `grok`, `openrouter:<alias>` - get the names from `panel`). Returns the standard result envelope. Issue N of these in parallel (one per `panel` name) so each renders independently as it lands. Advisory, single-shot.", inputSchema: askOneInputSchema(), annotations: ADVISORY }
4016
4298
  ];
4017
4299
  for (const t of Object.keys(ASK_PROVIDER)) {
4018
4300
  tools.push({ name: t, description: `Single-provider second opinion via ${ASK_PROVIDER[t]} (advisory, single-shot). Pass \`expert\` to apply one of the expert personas.`, inputSchema: inputSchema(), annotations: ADVISORY });
@@ -4068,8 +4350,10 @@ async function isHealthy(p) {
4068
4350
  return false;
4069
4351
  }
4070
4352
  }
4071
- function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4353
+ function buildServer({ providers, getConfig, getConfigError, sessionsDir, notify }) {
4072
4354
  const registry = makeRegistry(providers);
4355
+ const sendNotify = typeof notify === "function" ? notify : (_m, _p) => {
4356
+ };
4073
4357
  const sessions = (
4074
4358
  /** @type {any} */
4075
4359
  require_sessions()
@@ -4081,6 +4365,52 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4081
4365
  const { parseReview } = require_provider();
4082
4366
  const { makeLoopStore } = require_loop_store();
4083
4367
  const loopStore = makeLoopStore();
4368
+ const { makeResultCache } = require_result_cache();
4369
+ const resultCache = makeResultCache();
4370
+ const debugLog = require_debug_log();
4371
+ const { resolveDebugLogPath } = require_paths();
4372
+ let _fileCache = { key: "", logger: debugLog.NULL_LOGGER };
4373
+ function fileSink() {
4374
+ const dbg = (getConfig() || {}).debug || { enabled: false, path: null };
4375
+ if (!dbg.enabled) {
4376
+ _fileCache = { key: "", logger: debugLog.NULL_LOGGER };
4377
+ return debugLog.NULL_LOGGER;
4378
+ }
4379
+ const path = typeof dbg.path === "string" && dbg.path || resolveDebugLogPath();
4380
+ const key = `file:${path}`;
4381
+ if (_fileCache.key !== key) _fileCache = { key, logger: debugLog.createFileLogger(path) };
4382
+ return _fileCache.logger;
4383
+ }
4384
+ const LEVEL_RANK = Object.freeze({ debug: 0, info: 1, notice: 2, warning: 3, error: 4, critical: 5, alert: 6, emergency: 7 });
4385
+ let notifyMinLevel = (
4386
+ /** @type {keyof typeof LEVEL_RANK} */
4387
+ "info"
4388
+ );
4389
+ const notifySink = {
4390
+ /** @param {import("../../core/debug-log.js").DebugEvent} e */
4391
+ logEvent(e) {
4392
+ if (LEVEL_RANK.info < LEVEL_RANK[notifyMinLevel]) return;
4393
+ try {
4394
+ sendNotify("notifications/message", {
4395
+ level: "info",
4396
+ logger: "deliberation",
4397
+ data: { event: e.event, tool: e.tool, provider: e.provider, ms: e.ms, round: e.round, verdict: e.verdict, isError: e.isError, errorKind: e.errorKind }
4398
+ });
4399
+ } catch {
4400
+ }
4401
+ }
4402
+ };
4403
+ function setLogLevel(level) {
4404
+ if (typeof level === "string" && Object.prototype.hasOwnProperty.call(LEVEL_RANK, level)) {
4405
+ notifyMinLevel = /** @type {keyof typeof LEVEL_RANK} */
4406
+ level;
4407
+ return true;
4408
+ }
4409
+ return false;
4410
+ }
4411
+ function currentLogger() {
4412
+ return debugLog.composeLoggers([fileSink(), notifySink]);
4413
+ }
4084
4414
  let clientName = (
4085
4415
  /** @type {string|null} */
4086
4416
  null
@@ -4183,9 +4513,17 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4183
4513
  return null;
4184
4514
  }
4185
4515
  }
4186
- async function runAskAll(req, expert) {
4516
+ async function runAskAll(req, expert, opts = (
4517
+ /** @type {{noCache?:boolean}} */
4518
+ {}
4519
+ )) {
4187
4520
  const { providers: selected, omitted } = registry.selectForAskAll({ config: getConfig(), expert: expert || "" });
4188
- const results = await askAll(selected, withPersona(req, expert));
4521
+ const lg = currentLogger();
4522
+ try {
4523
+ lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "ask-all", voices: selected.length });
4524
+ } catch {
4525
+ }
4526
+ const results = await askAll(selected, withPersona(req, expert), { logger: lg, tool: "ask-all", cache: opts.noCache ? void 0 : resultCache });
4189
4527
  return {
4190
4528
  payload: { results, omitted },
4191
4529
  parts: { opinions: results, blindVerdict: null, verdict: null, arbiter: null, warnings: [] }
@@ -4203,13 +4541,13 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4203
4541
  const resolved = await resolveArbiter(arbiterSpec, selected, registry, getConfig);
4204
4542
  if (resolved.warning) warnings.push(resolved.warning);
4205
4543
  if (resolved.mode === "host") {
4206
- const opinions = await askAll(selected, withPersona(req, expert));
4544
+ const opinions = await askAll(selected, withPersona(req, expert), { logger: currentLogger(), tool: "consensus" });
4207
4545
  const arbiter2 = { mode: "host" };
4208
4546
  const body = { opinions, blindVerdict: null, verdict: null, arbiter: arbiter2, warnings };
4209
4547
  return { payload: body, parts: body };
4210
4548
  }
4211
4549
  if (!resolved.provider) {
4212
- const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter });
4550
+ const out2 = await consensus(selected, withPersona(req, expert), { arbiterInstructions: PROMPTS.arbiter, logger: currentLogger() });
4213
4551
  const arbiter2 = { mode: "server", provider: null };
4214
4552
  return {
4215
4553
  payload: { opinions: out2.opinions, blindVerdict: out2.blindVerdict, verdict: out2.verdict, error: out2.error, arbiter: arbiter2, warnings },
@@ -4222,7 +4560,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4222
4560
  peers = selected;
4223
4561
  warnings.push(`panel too small to exclude arbiter '${arbiterP.name}'; kept it in the peer panel (floor of 2)`);
4224
4562
  }
4225
- const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote });
4563
+ const out = await consensus(peers, withPersona(req, expert), { arbiter: arbiterP, arbiterInstructions: PROMPTS.arbiter, blindVote, logger: currentLogger() });
4226
4564
  const arbiter = { mode: "server", provider: arbiterP.name };
4227
4565
  return {
4228
4566
  payload: { opinions: out.opinions, blindVerdict: out.blindVerdict, verdict: out.verdict, error: out.error, arbiter, warnings },
@@ -4252,7 +4590,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4252
4590
  }
4253
4591
  const maxRounds = Number.isInteger(maxRoundsOverride) && /** @type {number} */
4254
4592
  maxRoundsOverride > 0 ? maxRoundsOverride : Number.isInteger(cc.maxRounds) && cc.maxRounds > 0 ? cc.maxRounds : void 0;
4255
- const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds });
4593
+ const out = await runToConvergence(peers, withPersona(req, expert), { arbiter: arbiterP, maxRounds, logger: currentLogger() });
4256
4594
  const allWarnings = out.error ? warnings.concat([`loop: ${out.error}`]) : warnings;
4257
4595
  const rounds = Array.isArray(out.rounds) ? out.rounds.length : 0;
4258
4596
  const arbiter = { mode: "server", provider: arbiterP.name };
@@ -4388,9 +4726,14 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4388
4726
  const ex = cur.expert || expert || void 0;
4389
4727
  const { providers: selected } = registry.selectForConsensus({ config: getConfig() || {}, expert: ex || "" });
4390
4728
  const peerReq = { prompt: peerPrompt, expert: ex, cwd: typeof args.cwd === "string" ? args.cwd : void 0 };
4391
- const peerResults = await askAll(selected, withPersona(peerReq, ex));
4729
+ const lg = currentLogger();
4730
+ try {
4731
+ lg.logEvent({ event: "dispatch_start", at: Date.now(), tool: "consensus", round: cur.round, voices: selected.length });
4732
+ } catch {
4733
+ }
4734
+ const peerResults = await askAll(selected, withPersona(peerReq, ex), { logger: lg, tool: "consensus" });
4392
4735
  const results = peerResults.map(
4393
- (r) => r.isError ? { source: r.provider, isError: true, errorKind: r.errorKind, verdict: null, criticalIssues: [] } : { ...parseReview(typeof r.text === "string" ? r.text : ""), source: r.provider, isError: false }
4736
+ (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 }
4394
4737
  );
4395
4738
  const next = loop.addOpinions(cur, results);
4396
4739
  loopStore.put(sid, next);
@@ -4398,13 +4741,28 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4398
4741
  sessionId: sid,
4399
4742
  status: next.status,
4400
4743
  round: next.round,
4401
- opinions: results.map((r) => ({ source: r.source, isError: r.isError, verdict: r.verdict, criticalIssues: r.criticalIssues })),
4744
+ // model + reasoningEffort + ms ride along so the command can show real
4745
+ // reasoning effort per voice (no more hardcoded "n/a") and a time footer.
4746
+ opinions: results.map((r) => ({ source: r.source, isError: r.isError, errorKind: r.errorKind, verdict: r.verdict, criticalIssues: r.criticalIssues, model: r.model, reasoningEffort: r.reasoningEffort, ms: r.ms })),
4402
4747
  note: "adjudicate the opinions, then call submit_adjudication with your verdict + per-issue decisions"
4403
4748
  };
4404
4749
  }
4405
4750
  if (action === "submit_adjudication") {
4406
4751
  const decisions = Array.isArray(args.decisions) ? args.decisions : [];
4407
4752
  const next = loop.submitAdjudication(cur, { verdict: args.verdict, decisions });
4753
+ try {
4754
+ currentLogger().logEvent({
4755
+ event: "round",
4756
+ at: Date.now(),
4757
+ tool: "consensus",
4758
+ round: cur.round,
4759
+ verdict: typeof args.verdict === "string" ? args.verdict : null,
4760
+ converged: next.status === "converged",
4761
+ acceptedCritical: decisions.filter((d) => d && d.action === "accept").length,
4762
+ voices: Array.isArray(cur.results) ? cur.results.length : void 0
4763
+ });
4764
+ } catch {
4765
+ }
4408
4766
  if (next.status === "converged") {
4409
4767
  const { finalReport, confidence } = loop.finalize(next);
4410
4768
  loopStore.delete(sid);
@@ -4443,6 +4801,23 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4443
4801
  reasoningEffort: args.reasoningEffort,
4444
4802
  files: args.files
4445
4803
  };
4804
+ if (name === "panel") {
4805
+ const { providers: selected, omitted } = registry.selectForAskAll({ config: getConfig(), expert: expert || "" });
4806
+ return jsonResult({
4807
+ providers: selected.map((p) => p.name),
4808
+ omitted: (Array.isArray(omitted) ? omitted : []).map((o) => o && o.alias || String(o))
4809
+ });
4810
+ }
4811
+ if (name === "ask-one") {
4812
+ const want = typeof args.provider === "string" ? args.provider : "";
4813
+ const { providers: selected } = registry.selectForAskAll({ config: getConfig(), expert: expert || "" });
4814
+ const p = selected.find((x) => x.name === want);
4815
+ if (!p) {
4816
+ return jsonResult({ error: `provider "${want}" is not in the active panel`, panel: selected.map((x) => x.name) });
4817
+ }
4818
+ const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
4819
+ return jsonResult({ result });
4820
+ }
4446
4821
  if (name === "ask-all") {
4447
4822
  const { payload, parts } = await runAskAll(req, expert);
4448
4823
  const sid = persistRun("ask-all", req, expert, parts);
@@ -4495,7 +4870,7 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4495
4870
  cwd: typeof args.cwd === "string" ? args.cwd : void 0
4496
4871
  };
4497
4872
  const tool = rec.tool === "ask-all" ? "ask-all" : "consensus";
4498
- const { payload, parts } = tool === "ask-all" ? await runAskAll(childReq, childExpert) : await runConsensusTool(childReq, childExpert, { synthesizeAlways: rec.synthesizeAlways === true });
4873
+ const { payload, parts } = tool === "ask-all" ? await runAskAll(childReq, childExpert, { noCache: true }) : await runConsensusTool(childReq, childExpert, { synthesizeAlways: rec.synthesizeAlways === true });
4499
4874
  if (parts) {
4500
4875
  const sid = persistRun(tool, childReq, childExpert, { ...parts, parentId: rec.id });
4501
4876
  if (sid) payload.sessionId = sid;
@@ -4506,12 +4881,12 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4506
4881
  if (Object.prototype.hasOwnProperty.call(ASK_PROVIDER, name)) {
4507
4882
  const p = registry.get(ASK_PROVIDER[name]);
4508
4883
  if (!p) return { content: [{ type: "text", text: JSON.stringify({ error: `provider ${ASK_PROVIDER[name]} not registered` }) }] };
4509
- const result = await askOne(p, withPersona(req, expert));
4884
+ const result = await askOne(p, withPersona(req, expert), { logger: currentLogger(), tool: "ask-one", cache: resultCache });
4510
4885
  return { content: [{ type: "text", text: JSON.stringify({ result }) }] };
4511
4886
  }
4512
4887
  if (EXPERTS.includes(name)) {
4513
4888
  const { providers: selected } = registry.selectForAskAll({ config: getConfig(), expert: name });
4514
- const results = await askAll(selected, withPersona({ ...req, expert: name }, expert));
4889
+ const results = await askAll(selected, withPersona({ ...req, expert: name }, expert), { logger: currentLogger(), tool: name, cache: resultCache });
4515
4890
  return { content: [{ type: "text", text: JSON.stringify({ results }) }] };
4516
4891
  }
4517
4892
  throw new Error(`unknown tool: ${name}`);
@@ -4521,7 +4896,12 @@ function buildServer({ providers, getConfig, getConfigError, sessionsDir }) {
4521
4896
  if (msg.method === "initialize") {
4522
4897
  const ci = msg.params && msg.params.clientInfo;
4523
4898
  if (ci && typeof ci.name === "string") clientName = ci.name;
4524
- return { jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "deliberation-mcp", version: "0.1.0" } } };
4899
+ return { jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {}, logging: {} }, serverInfo: { name: "deliberation-mcp", version: "0.1.0" } } };
4900
+ }
4901
+ if (msg.method === "logging/setLevel") {
4902
+ const level = msg.params && msg.params.level;
4903
+ if (!setLogLevel(level)) return { jsonrpc: "2.0", id: msg.id, error: { code: -32602, message: `invalid log level: ${String(level)}` } };
4904
+ return { jsonrpc: "2.0", id: msg.id, result: {} };
4525
4905
  }
4526
4906
  if (msg.method === "tools/list") return { jsonrpc: "2.0", id: msg.id, result: { tools: toolList() } };
4527
4907
  if (msg.method === "tools/call") {
@@ -4569,7 +4949,8 @@ function startStdio() {
4569
4949
  })
4570
4950
  ];
4571
4951
  const sessionsDir = require_paths().resolveSessionsDir();
4572
- const srv = buildServer({ providers, getConfig, getConfigError, sessionsDir });
4952
+ const notify = (method, params) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
4953
+ const srv = buildServer({ providers, getConfig, getConfigError, sessionsDir, notify });
4573
4954
  if (typeof globalThis.fetch !== "function") {
4574
4955
  console.error("deliberation-mcp requires Node 18+ (global fetch unavailable).");
4575
4956
  process.exit(1);
package/dist/setup.js CHANGED
@@ -67,10 +67,19 @@ var require_paths = __commonJS({
67
67
  }
68
68
  return path2.join(canonicalCacheDir(home, env, platform), "sessions");
69
69
  }
70
+ function resolveDebugLogPath(opts) {
71
+ const { home, env, platform } = resolveInjection(opts);
72
+ const override = env.DELIBERATION_DEBUG_LOG;
73
+ if (typeof override === "string" && override.length > 0) {
74
+ return override;
75
+ }
76
+ return path2.join(canonicalCacheDir(home, env, platform), "debug.jsonl");
77
+ }
70
78
  module2.exports = {
71
79
  resolveConfigPath: resolveConfigPath2,
72
80
  resolveGrokCachePath,
73
- resolveSessionsDir
81
+ resolveSessionsDir,
82
+ resolveDebugLogPath
74
83
  };
75
84
  }
76
85
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antonbabenko/deliberation-mcp",
3
- "version": "3.1.1",
3
+ "version": "3.2.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" },