metheus-governance-mcp-cli 0.2.85 → 0.2.87

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/README.md CHANGED
@@ -410,7 +410,8 @@ Recommended role mapping:
410
410
  Role profile note:
411
411
  - Claude maps `reasoning_effort` to `--effort`.
412
412
  - Codex maps `reasoning_effort` to `-c model_reasoning_effort="..."`.
413
- - Gemini CLI still has no dedicated effort flag, so the runner keeps the value in env/prompt context for policy parity.
413
+ - Gemini applies `reasoning_effort` by injecting a temporary `~/.gemini/settings.json` override for the invocation.
414
+ - For Gemini 3 models, `low -> thinkingLevel LOW`, `medium -> THINKING_LEVEL_UNSPECIFIED` (Gemini default), and `high -> thinkingLevel HIGH`.
414
415
 
415
416
  Trigger policy fields:
416
417
  - `mentions_only`: in groups, react only when the bot is mentioned or when a message replies to the bot
package/cli.mjs CHANGED
@@ -11,6 +11,7 @@ import https from "node:https";
11
11
  import {
12
12
  DEFAULT_LOCAL_AI_CLIENT,
13
13
  resolveLocalAIExecutionModel,
14
+ resolveGeminiReasoningConfig,
14
15
  SUPPORTED_LOCAL_AI_CLIENTS,
15
16
  normalizeLocalAIClientName,
16
17
  normalizeLocalAIPermissionMode,
@@ -3358,6 +3359,7 @@ function buildBotCommandDeps() {
3358
3359
  normalizeLocalAIClientName,
3359
3360
  normalizeLocalAIPermissionMode,
3360
3361
  normalizeLocalAIReasoningEffort,
3362
+ resolveGeminiReasoningConfig,
3361
3363
  supportedLocalAIClients: SUPPORTED_LOCAL_AI_CLIENTS,
3362
3364
  summarizeProviderSupport,
3363
3365
  loadProviderEnvConfig,
@@ -5209,6 +5211,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
5209
5211
  cliPath: fileURLToPath(import.meta.url),
5210
5212
  parseSimpleEnvText,
5211
5213
  resolveLocalAIExecutionModel,
5214
+ resolveGeminiReasoningConfig,
5212
5215
  });
5213
5216
 
5214
5217
  const payload = buildSelftestPayload(checks);
@@ -633,6 +633,23 @@ function displayLocalAIClientName(clientName) {
633
633
  return String(clientName || "").trim();
634
634
  }
635
635
 
636
+ function formatRuntimeReasoningField(clientName, reasoningEffort) {
637
+ const normalizedClient = String(clientName || "").trim().toLowerCase();
638
+ const normalizedEffort = String(reasoningEffort || "").trim();
639
+ if (!normalizedEffort) {
640
+ if (normalizedClient === "claude") return "thinking=(blank)";
641
+ if (normalizedClient === "gemini") return "thinking=(blank)";
642
+ return "reasoning=(blank)";
643
+ }
644
+ if (normalizedClient === "claude") {
645
+ return `thinking=${normalizedEffort} (Claude effort)`;
646
+ }
647
+ if (normalizedClient === "gemini") {
648
+ return `thinking=${normalizedEffort} (Gemini level)`;
649
+ }
650
+ return `reasoning=${normalizedEffort}`;
651
+ }
652
+
636
653
  function telegramEntryDisplayDescription(entry) {
637
654
  const current = safeObject(entry);
638
655
  const detail = [
@@ -948,7 +965,7 @@ function formatRoleProfileOutputLine(profile) {
948
965
  current.client ? `client=${displayLocalAIClientName(current.client)}` : "client=(blank)",
949
966
  current.model ? `model=${current.model}` : "model=(blank)",
950
967
  current.permissionMode ? `permission=${current.permissionMode}` : "permission=(blank)",
951
- current.reasoningEffort ? `reasoning=${current.reasoningEffort}` : "reasoning=(blank)",
968
+ formatRuntimeReasoningField(current.client, current.reasoningEffort),
952
969
  ].join(" | ");
953
970
  }
954
971
 
@@ -8,6 +8,15 @@ export const DEFAULT_LOCAL_AI_CLIENT = "codex";
8
8
 
9
9
  const SUPPORTED_PERMISSION_MODES = ["read_only", "workspace_write", "danger_full_access"];
10
10
  const SUPPORTED_REASONING_EFFORTS = ["low", "medium", "high"];
11
+ const GEMINI_HOME_SYNC_FILES = [
12
+ "google_accounts.json",
13
+ "oauth_creds.json",
14
+ "installation_id",
15
+ "state.json",
16
+ "trustedFolders.json",
17
+ "projects.json",
18
+ "settings.json",
19
+ ];
11
20
  const LOCAL_AI_MODEL_MAPPINGS = {
12
21
  codex: [
13
22
  {
@@ -80,6 +89,10 @@ function ensureArray(value) {
80
89
  return Array.isArray(value) ? value : [];
81
90
  }
82
91
 
92
+ function safeObject(value) {
93
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
94
+ }
95
+
83
96
  function intFromRawAllowZero(rawValue, fallback = 0) {
84
97
  const parsed = Number.parseInt(String(rawValue ?? "").trim(), 10);
85
98
  if (!Number.isFinite(parsed) || parsed < 0) {
@@ -330,6 +343,122 @@ function buildGeminiArgs({ promptText, model, permissionMode }) {
330
343
  return args;
331
344
  }
332
345
 
346
+ function isGemini3ExecutionModel(model) {
347
+ const normalizedModel = normalizeModelAliasText(model);
348
+ return normalizedModel === "auto-gemini-3" || normalizedModel.startsWith("gemini-3");
349
+ }
350
+
351
+ function readJsonFileIfExists(filePath) {
352
+ if (!filePath || !fs.existsSync(filePath)) {
353
+ return null;
354
+ }
355
+ try {
356
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
357
+ } catch {
358
+ return null;
359
+ }
360
+ }
361
+
362
+ function copyFileIfExists(sourcePath, targetPath) {
363
+ if (!sourcePath || !targetPath || !fs.existsSync(sourcePath)) {
364
+ return;
365
+ }
366
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
367
+ fs.copyFileSync(sourcePath, targetPath);
368
+ }
369
+
370
+ function buildGeminiThinkingConfig(model, reasoningEffort) {
371
+ const normalizedReasoningEffort = normalizeLocalAIReasoningEffort(reasoningEffort, "");
372
+ if (!normalizedReasoningEffort) {
373
+ return null;
374
+ }
375
+ if (isGemini3ExecutionModel(model)) {
376
+ if (normalizedReasoningEffort === "low") {
377
+ return { thinkingLevel: "LOW" };
378
+ }
379
+ if (normalizedReasoningEffort === "high") {
380
+ return { thinkingLevel: "HIGH" };
381
+ }
382
+ return { thinkingLevel: "THINKING_LEVEL_UNSPECIFIED" };
383
+ }
384
+ const thinkingBudgetMap = {
385
+ low: 0,
386
+ medium: 4096,
387
+ high: 8192,
388
+ };
389
+ return {
390
+ thinkingBudget: thinkingBudgetMap[normalizedReasoningEffort] ?? thinkingBudgetMap.medium,
391
+ };
392
+ }
393
+
394
+ export function resolveGeminiReasoningConfig(rawModelValue = "", reasoningEffort = "medium") {
395
+ const executionModel = resolveLocalAIExecutionModel("gemini", rawModelValue);
396
+ const thinkingConfig = buildGeminiThinkingConfig(executionModel, reasoningEffort);
397
+ if (!thinkingConfig) {
398
+ return null;
399
+ }
400
+ return {
401
+ model: executionModel,
402
+ thinkingConfig,
403
+ };
404
+ }
405
+
406
+ function buildGeminiSettingsOverride(existingSettings, model, reasoningEffort) {
407
+ const resolved = resolveGeminiReasoningConfig(model, reasoningEffort);
408
+ if (!resolved) {
409
+ return safeObject(existingSettings);
410
+ }
411
+ const baseSettings = safeObject(existingSettings);
412
+ const baseModelConfigs = safeObject(baseSettings.modelConfigs);
413
+ const nextOverrides = ensureArray(baseModelConfigs.overrides).slice();
414
+ nextOverrides.push({
415
+ match: { model: resolved.model },
416
+ modelConfig: {
417
+ generateContentConfig: {
418
+ thinkingConfig: resolved.thinkingConfig,
419
+ },
420
+ },
421
+ });
422
+ return {
423
+ ...baseSettings,
424
+ modelConfigs: {
425
+ ...baseModelConfigs,
426
+ overrides: nextOverrides,
427
+ },
428
+ };
429
+ }
430
+
431
+ function prepareGeminiRuntimeEnv({ model, reasoningEffort, env }) {
432
+ const sourceHome = String(env?.GEMINI_CLI_HOME || os.homedir() || "").trim();
433
+ const sourceGeminiDir = sourceHome ? path.join(sourceHome, ".gemini") : "";
434
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-gemini-home-"));
435
+ const tempGeminiDir = path.join(tempHome, ".gemini");
436
+ fs.mkdirSync(tempGeminiDir, { recursive: true });
437
+ for (const fileName of GEMINI_HOME_SYNC_FILES) {
438
+ copyFileIfExists(
439
+ sourceGeminiDir ? path.join(sourceGeminiDir, fileName) : "",
440
+ path.join(tempGeminiDir, fileName),
441
+ );
442
+ }
443
+ const mergedSettings = buildGeminiSettingsOverride(
444
+ readJsonFileIfExists(path.join(tempGeminiDir, "settings.json")),
445
+ model,
446
+ reasoningEffort,
447
+ );
448
+ fs.writeFileSync(path.join(tempGeminiDir, "settings.json"), `${JSON.stringify(mergedSettings, null, 2)}\n`, "utf8");
449
+ return {
450
+ env: {
451
+ ...env,
452
+ GEMINI_CLI_HOME: tempHome,
453
+ },
454
+ cleanup() {
455
+ if (fs.existsSync(tempHome)) {
456
+ fs.rmSync(tempHome, { recursive: true, force: true });
457
+ }
458
+ },
459
+ };
460
+ }
461
+
333
462
  function runCodexAdapter({ promptText, workspaceDir, model, permissionMode, reasoningEffort, env }) {
334
463
  const outputPath = path.join(os.tmpdir(), `metheus-runner-codex-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
335
464
  const codexCommand = resolveLocalCliCommand("codex");
@@ -392,29 +521,34 @@ function runClaudeAdapter({ promptText, workspaceDir, model, permissionMode, rea
392
521
  return normalizeCliOutput(result.stdout);
393
522
  }
394
523
 
395
- function runGeminiAdapter({ promptText, workspaceDir, model, permissionMode, env }) {
524
+ function runGeminiAdapter({ promptText, workspaceDir, model, permissionMode, reasoningEffort, env }) {
396
525
  const geminiCommand = resolveLocalCliCommand("gemini");
397
- const result = spawnCli(
398
- geminiCommand,
399
- buildGeminiArgs({
400
- promptText,
401
- model,
402
- permissionMode,
403
- }),
404
- {
405
- cwd: workspaceDir,
406
- encoding: "utf8",
407
- env,
408
- maxBuffer: 8 * 1024 * 1024,
409
- },
410
- );
411
- if (result.error) {
412
- throw new Error(String(result.error?.message || result.error));
413
- }
414
- if (result.status !== 0) {
415
- throw new Error(String(result.stderr || result.stdout || `gemini exited with status ${result.status}`));
526
+ const runtime = prepareGeminiRuntimeEnv({ model, reasoningEffort, env });
527
+ try {
528
+ const result = spawnCli(
529
+ geminiCommand,
530
+ buildGeminiArgs({
531
+ promptText,
532
+ model,
533
+ permissionMode,
534
+ }),
535
+ {
536
+ cwd: workspaceDir,
537
+ encoding: "utf8",
538
+ env: runtime.env,
539
+ maxBuffer: 8 * 1024 * 1024,
540
+ },
541
+ );
542
+ if (result.error) {
543
+ throw new Error(String(result.error?.message || result.error));
544
+ }
545
+ if (result.status !== 0) {
546
+ throw new Error(String(result.stderr || result.stdout || `gemini exited with status ${result.status}`));
547
+ }
548
+ return normalizeCliOutput(result.stdout);
549
+ } finally {
550
+ runtime.cleanup();
416
551
  }
417
- return normalizeCliOutput(result.stdout);
418
552
  }
419
553
 
420
554
  function runSampleAdapter(payload) {
@@ -608,6 +742,7 @@ export function runLocalAIClient({
608
742
  workspaceDir: resolvedWorkspaceDir,
609
743
  model: resolvedExecutionModel,
610
744
  permissionMode: normalizedPermissionMode,
745
+ reasoningEffort: normalizedReasoningEffort,
611
746
  env: nextEnv,
612
747
  });
613
748
  }
@@ -229,6 +229,7 @@ export async function runSelftestBotCommands(push, deps) {
229
229
  const cliPath = String(requireDependency(deps, "cliPath") || "").trim();
230
230
  const parseSimpleEnvText = requireDependency(deps, "parseSimpleEnvText");
231
231
  const resolveLocalAIExecutionModel = requireDependency(deps, "resolveLocalAIExecutionModel");
232
+ const resolveGeminiReasoningConfig = requireDependency(deps, "resolveGeminiReasoningConfig");
232
233
  let tempHome = "";
233
234
  let mock = null;
234
235
  try {
@@ -253,6 +254,22 @@ export async function runSelftestBotCommands(push, deps) {
253
254
  ].join(" "),
254
255
  );
255
256
 
257
+ const geminiLowReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "low");
258
+ const geminiMediumReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "medium");
259
+ const geminiHighReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "high");
260
+ push(
261
+ "gemini_reasoning_effort_maps_to_runtime_settings_override",
262
+ String(geminiLowReasoning?.model || "") === "auto-gemini-3"
263
+ && String(safeObject(geminiLowReasoning?.thinkingConfig).thinkingLevel || "") === "LOW"
264
+ && String(safeObject(geminiMediumReasoning?.thinkingConfig).thinkingLevel || "") === "THINKING_LEVEL_UNSPECIFIED"
265
+ && String(safeObject(geminiHighReasoning?.thinkingConfig).thinkingLevel || "") === "HIGH",
266
+ [
267
+ `low=${JSON.stringify(geminiLowReasoning?.thinkingConfig || {})}`,
268
+ `medium=${JSON.stringify(geminiMediumReasoning?.thinkingConfig || {})}`,
269
+ `high=${JSON.stringify(geminiHighReasoning?.thinkingConfig || {})}`,
270
+ ].join(" "),
271
+ );
272
+
256
273
  tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-bot-selftest-"));
257
274
  mock = await createMockServer().listen();
258
275
  const env = buildSpawnEnv(tempHome);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.85",
3
+ "version": "0.2.87",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [