@mrrlin-dev/mcp 0.3.56 → 0.3.59

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/bin.cjs +99 -20
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -20658,7 +20658,7 @@ var toml = __toESM(require_toml(), 1);
20658
20658
 
20659
20659
  // src/api-base-url.ts
20660
20660
  var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
20661
- var STAGING_API_BASE_URL = "https://mrrlin-api-mrrlin2.vercel.app";
20661
+ var STAGING_API_BASE_URL = "https://mrrlin-api-mrrlin3.vercel.app";
20662
20662
  function normalizeUrl(value) {
20663
20663
  return value.replace(/\/$/, "");
20664
20664
  }
@@ -36636,6 +36636,7 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
36636
36636
  });
36637
36637
  var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
36638
36638
  var operatorPresenceResponseSchema = external_exports.object({ data: external_exports.object({ lastPresenceAt: external_exports.string().datetime() }) });
36639
+ var operatorPresenceRequestSchema = external_exports.object({ projectSlug: external_exports.string().min(1).optional() }).strict().optional();
36639
36640
  var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
36640
36641
  var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
36641
36642
  var agentPoolSnapshotSchema = external_exports.object({
@@ -38932,8 +38933,14 @@ function createMrrlinClient(config2) {
38932
38933
  });
38933
38934
  return operatorSettingsResponseSchema.parse(body).data;
38934
38935
  },
38935
- async recordOperatorPresence() {
38936
- const body = await request(`/operator/presence`, { method: "POST" });
38936
+ async recordOperatorPresence(input) {
38937
+ const projectSlug = input?.projectSlug;
38938
+ const body = await request(`/operator/presence`, {
38939
+ method: "POST",
38940
+ // Omit the body entirely when no slug is given — background tabs beat bodyless today and
38941
+ // must continue to (see design.md deploy-skew probe).
38942
+ ...projectSlug !== void 0 ? { body: JSON.stringify({ projectSlug }), headers: { "content-type": "application/json" } } : {}
38943
+ });
38937
38944
  return operatorPresenceResponseSchema.parse(body).data;
38938
38945
  },
38939
38946
  async getOperatorAgentPool() {
@@ -39450,7 +39457,7 @@ function startCodexProbe(deps = {}) {
39450
39457
  }
39451
39458
 
39452
39459
  // src/_generated/version.ts
39453
- var PKG_VERSION = "0.3.56";
39460
+ var PKG_VERSION = "0.3.59";
39454
39461
 
39455
39462
  // src/api-base-url-migrate.ts
39456
39463
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -40739,7 +40746,7 @@ function reviserIdentityInstructions(taskId, title) {
40739
40746
  function buildSpecGateDeps(args) {
40740
40747
  const { client, projectSlug, specPageId, cwd, codexExecutable, extraHeadCount = 0 } = args;
40741
40748
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
40742
- const maxHeadCalls = rounds * (4 + extraHeadCount + 1);
40749
+ const maxHeadCalls = 2 * rounds * (4 + extraHeadCount + 1);
40743
40750
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
40744
40751
  let intentPromise = null;
40745
40752
  function loadIntent() {
@@ -40903,7 +40910,7 @@ function buildCodeGateDeps(args) {
40903
40910
  const { client, projectSlug, baseRef, cwd, codexExecutable, extraHeads } = args;
40904
40911
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
40905
40912
  const heads = ["code-reviewer", ...extraHeads ?? []];
40906
- const maxHeadCalls = rounds * (heads.length + 1);
40913
+ const maxHeadCalls = 2 * rounds * (heads.length + 1);
40907
40914
  const headTimeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
40908
40915
  const reviserTimeoutMs = Number(process.env["MRRLIN_CODEGATE_REVISER_TIMEOUT_MS"] ?? 6e5);
40909
40916
  const gitArgs = { cwd, baseRef, run: defaultRunCmd };
@@ -41001,7 +41008,7 @@ async function buildDeliverableGateDeps(args) {
41001
41008
  }
41002
41009
  }
41003
41010
  const heads = resolveHeads(domain2);
41004
- const maxHeadCalls = rounds * (heads.length + 1);
41011
+ const maxHeadCalls = 2 * rounds * (heads.length + 1);
41005
41012
  const criteriaBlock = renderCriteriaBlock(criteria);
41006
41013
  return {
41007
41014
  heads,
@@ -43887,6 +43894,46 @@ function createBoundedSemaphore(slots) {
43887
43894
  };
43888
43895
  }
43889
43896
 
43897
+ // src/consensus/escalation.ts
43898
+ function diagnoseStall(history, configuredRounds) {
43899
+ const n = history.length;
43900
+ const nonRespPerRound = history.map((h) => h.headOutcomes.filter((o) => !o.responded).length);
43901
+ const infraRoundCount = nonRespPerRound.filter((c) => c >= 2).length;
43902
+ const infraQualifies = infraRoundCount > n / 2;
43903
+ if (infraQualifies) {
43904
+ const roundIndices = [];
43905
+ nonRespPerRound.forEach((c, i) => {
43906
+ if (c >= 2) roundIndices.push(i + 1);
43907
+ });
43908
+ const evidence2 = `INFRA basis: \u22652 non\u2011responding heads in ${infraRoundCount} of ${n} rounds (rounds ${roundIndices.join(",")})`;
43909
+ let grant = 3;
43910
+ const headroom = 2 * configuredRounds - n;
43911
+ grant = Math.max(1, Math.min(grant, headroom));
43912
+ return { diagnosis: "INFRA", grantedRounds: grant, evidence: evidence2 };
43913
+ }
43914
+ if (n >= 2) {
43915
+ const counts = history.map((h) => h.accepted.length);
43916
+ const last = counts[n - 1];
43917
+ const secondLast = counts[n - 2];
43918
+ const lastFall = last < secondLast;
43919
+ if (lastFall) {
43920
+ let falls = 1;
43921
+ if (n >= 3 && secondLast < counts[n - 3]) {
43922
+ falls++;
43923
+ }
43924
+ let grant = 2 + falls;
43925
+ if (grant > 5) grant = 5;
43926
+ const headroom = 2 * configuredRounds - n;
43927
+ grant = Math.max(1, Math.min(grant, headroom));
43928
+ const seqDesc = counts.map((c, i) => `round${i + 1}:${c}`).join(" \u2192 ");
43929
+ const evidence2 = `SUBSTANCE basis: accepted count fell (${seqDesc})`;
43930
+ return { diagnosis: "SUBSTANCE", grantedRounds: grant, evidence: evidence2 };
43931
+ }
43932
+ }
43933
+ const evidence = n === 0 ? "empty history" : `no infra or substance basis found in ${n} rounds`;
43934
+ return { diagnosis: "AMBIGUITY", grantedRounds: 0, evidence };
43935
+ }
43936
+
43890
43937
  // src/consensus/loop.ts
43891
43938
  async function runConsensus(input) {
43892
43939
  const sem = createBoundedSemaphore(input.concurrency ?? input.heads.length);
@@ -43894,9 +43941,12 @@ async function runConsensus(input) {
43894
43941
  let artifact = input.artifact;
43895
43942
  let calls = 0;
43896
43943
  const dismissedFwd = [];
43897
- for (let round = 1; round <= input.rounds; round++) {
43944
+ let effectiveRounds = input.rounds;
43945
+ let escalated = false;
43946
+ let decision;
43947
+ for (let round = 1; round <= effectiveRounds; round++) {
43898
43948
  if (calls + input.heads.length > input.maxHeadCalls) {
43899
- return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history };
43949
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43900
43950
  }
43901
43951
  const dismissedBlock = dismissedFwd.length ? `PREVIOUSLY DISMISSED (do not re-raise unless new information):
43902
43952
  ${dismissedFwd.slice(-20).join("\n")}` : "";
@@ -43920,12 +43970,19 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43920
43970
  if (responding.length === 0) {
43921
43971
  return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history };
43922
43972
  }
43973
+ const headOutcomes = input.heads.map((persona) => ({
43974
+ persona,
43975
+ responded: false
43976
+ }));
43923
43977
  const issues = [];
43924
- const verdicts = [];
43925
43978
  for (const { idx, res } of responding) {
43926
43979
  const parsed = parseVerdict(res.text);
43927
- verdicts.push(parsed.verdict);
43928
43980
  parsed.issues.forEach((iss, n) => issues.push({ ...iss, id: `r${round}-h${idx}-${n}` }));
43981
+ const outcome = headOutcomes[idx];
43982
+ if (outcome) {
43983
+ outcome.responded = true;
43984
+ outcome.verdict = parsed.verdict;
43985
+ }
43929
43986
  if (parsed.verdict !== "APPROVE" && parsed.issues.length === 0 && parsed.parseFallbacks.length === 0) {
43930
43987
  issues.push({
43931
43988
  category: "ambiguity",
@@ -43937,7 +43994,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43937
43994
  let adj;
43938
43995
  if (issues.length > 0) {
43939
43996
  if (calls + 1 > input.maxHeadCalls) {
43940
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history };
43997
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43941
43998
  }
43942
43999
  calls++;
43943
44000
  try {
@@ -43948,17 +44005,25 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
43948
44005
  } else {
43949
44006
  adj = { adjudicatedVerdict: "APPROVE", accepted: [], dismissed: [] };
43950
44007
  }
43951
- history.push({ round, verdicts, accepted: adj.accepted, dismissed: adj.dismissed });
44008
+ history.push({ round, accepted: adj.accepted, dismissed: adj.dismissed, headOutcomes });
43952
44009
  adj.dismissed.forEach((d) => dismissedFwd.push(`- ${d.id}: ${d.reason}`));
43953
44010
  if (adj.accepted.length === 0) {
43954
- return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history };
44011
+ return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
43955
44012
  }
43956
- if (round === input.rounds) {
43957
- return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history };
44013
+ if (round === effectiveRounds) {
44014
+ if (!decision) {
44015
+ decision = diagnoseStall(history, input.rounds);
44016
+ }
44017
+ if (decision.grantedRounds > 0 && !escalated) {
44018
+ escalated = true;
44019
+ effectiveRounds = Math.min(effectiveRounds + decision.grantedRounds, input.rounds * 2);
44020
+ } else {
44021
+ return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
44022
+ }
43958
44023
  }
43959
44024
  artifact = await input.revise(artifact, adj.accepted);
43960
44025
  }
43961
- return { outcome: "CONSENSUS_BLOCKED", rounds: input.rounds, finalArtifact: artifact, history };
44026
+ return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history };
43962
44027
  }
43963
44028
 
43964
44029
  // src/consensus/deliverable-gate.ts
@@ -44008,7 +44073,12 @@ async function runDeliverableGate(_target, deps) {
44008
44073
  whyBlocking: `reviewer raised a ${issue2.category} issue`,
44009
44074
  category: issue2.category
44010
44075
  }));
44011
- return clarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", rounds: result.rounds, clarifications } : { outcome: "CONSENSUS_BLOCKED", rounds: result.rounds };
44076
+ return {
44077
+ outcome: "CONSENSUS_BLOCKED",
44078
+ rounds: result.rounds,
44079
+ ...clarifications.length > 0 ? { clarifications } : {},
44080
+ ...result.escalation ? { escalation: result.escalation } : {}
44081
+ };
44012
44082
  }
44013
44083
  return { outcome: result.outcome, rounds: result.rounds };
44014
44084
  }
@@ -48897,7 +48967,9 @@ var DEFAULT_DIRECTOR_BRIDGE_ALLOWED_ORIGINS = [
48897
48967
  "http://[::1]:*",
48898
48968
  // Staging Vercel team migrated 2026-07-28 (see docs/reference/deployments.md). The prior
48899
48969
  // fnnzzz-fnnzzzs-projects web alias was dropped once no operator was left on an old build.
48900
- "https://mrrlin-web-mrrlin2.vercel.app",
48970
+ // mrrlin2 hit usage limits and was replaced by mrrlin3 on 2026-08-11; the mrrlin2 web alias
48971
+ // was dropped the same day since no operator was left on that build either.
48972
+ "https://mrrlin-web-mrrlin3.vercel.app",
48901
48973
  "https://app.mrrlin.dev",
48902
48974
  "https://mrrlin-web.vercel.app"
48903
48975
  ];
@@ -55195,7 +55267,11 @@ async function runSpecGate(_target, deps, opts) {
55195
55267
  if (result.outcome === "CONSENSUS_BLOCKED") {
55196
55268
  const accepted = result.history.at(-1)?.accepted ?? [];
55197
55269
  const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
55198
- return operatorClarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", operatorClarifications } : { outcome: "CONSENSUS_BLOCKED" };
55270
+ return {
55271
+ outcome: "CONSENSUS_BLOCKED",
55272
+ ...operatorClarifications.length > 0 ? { operatorClarifications } : {},
55273
+ ...result.escalation ? { escalation: result.escalation } : {}
55274
+ };
55199
55275
  }
55200
55276
  return { outcome: result.outcome };
55201
55277
  }
@@ -55762,6 +55838,9 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55762
55838
  } else {
55763
55839
  error51 = "Consensus blocked: reviewers did not converge within budget.";
55764
55840
  }
55841
+ if (result.escalation) {
55842
+ error51 += ` (diagnosis: ${result.escalation.diagnosis})`;
55843
+ }
55765
55844
  return {
55766
55845
  kind: "failed",
55767
55846
  failureReason: "consensus_blocked",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.3.56",
3
+ "version": "0.3.59",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -22,11 +22,11 @@
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
24
  "@mrrlin/director-e2e": "0.0.0",
25
- "@mrrlin/schemas": "0.0.0",
26
25
  "@mrrlin/wiki": "0.0.0",
26
+ "@mrrlin/schemas": "0.0.0",
27
27
  "@mrrlin/client": "0.0.0",
28
- "@mrrlin/tsconfig": "0.0.0",
29
- "@mrrlin/codex-client": "0.0.0"
28
+ "@mrrlin/codex-client": "0.0.0",
29
+ "@mrrlin/tsconfig": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",