@mrrlin-dev/mcp 0.4.23 → 0.4.27

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 +227 -46
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -21478,14 +21478,23 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
21478
21478
  const checks = { cli: false, version: false, auth: false, mcpReachable: false };
21479
21479
  const details = [];
21480
21480
  let cliResult;
21481
+ let cliUnreachable = false;
21481
21482
  try {
21482
21483
  cliResult = run3(["--version"]);
21483
21484
  checks.cli = cliResult.status === 0;
21485
+ if (!checks.cli) {
21486
+ cliUnreachable = cliResult.status === null;
21487
+ details.push(
21488
+ cliUnreachable ? `cli: \`${provider}\` binary not found on PATH` : `cli: \`${provider} --version\` exited with code ${cliResult.status}`
21489
+ );
21490
+ }
21484
21491
  } catch {
21492
+ cliUnreachable = true;
21493
+ details.push(`cli: \`${provider}\` binary not found on PATH`);
21485
21494
  }
21486
- if (!checks.cli) details.push("cli: binary not available or exited non-zero");
21487
21495
  if (provider === "codex") {
21488
21496
  checks.version = true;
21497
+ } else if (cliUnreachable) {
21489
21498
  } else {
21490
21499
  const raw = (cliResult?.stdout || cliResult?.stderr) ?? "";
21491
21500
  const version2 = parseCodexVersion(raw);
@@ -21500,6 +21509,7 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
21500
21509
  } catch {
21501
21510
  details.push(`auth: ${authPath} not found`);
21502
21511
  }
21512
+ } else if (cliUnreachable) {
21503
21513
  } else {
21504
21514
  try {
21505
21515
  const result = run3(["auth", "status"]);
@@ -21516,6 +21526,8 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
21516
21526
  checks.mcpReachable = isExternalAgentsWrapper(parsed.mcp_servers?.["external-agents"]);
21517
21527
  } catch {
21518
21528
  }
21529
+ if (!checks.mcpReachable) details.push("mcpReachable: external-agents server entry missing or invalid");
21530
+ } else if (cliUnreachable) {
21519
21531
  } else {
21520
21532
  const configPath = resolveClaudeConfigPath2(opts);
21521
21533
  try {
@@ -21524,8 +21536,8 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
21524
21536
  checks.mcpReachable = isExternalAgentsWrapper(parsed.mcpServers?.["external-agents"]);
21525
21537
  } catch {
21526
21538
  }
21539
+ if (!checks.mcpReachable) details.push("mcpReachable: external-agents server entry missing or invalid");
21527
21540
  }
21528
- if (!checks.mcpReachable) details.push("mcpReachable: external-agents server entry missing or invalid");
21529
21541
  const ready = checks.cli && checks.version && checks.auth && checks.mcpReachable;
21530
21542
  return { ready, checks, details };
21531
21543
  }
@@ -37077,7 +37089,10 @@ var ASYNC_JOB_FAILURE_REASONS = [
37077
37089
  "provider_unavailable",
37078
37090
  "spec_changed",
37079
37091
  "stale_lease",
37080
- "consensus_blocked"
37092
+ "consensus_blocked",
37093
+ // async-flow-throughput Slice 1: the spec drafter rejected its own output for exceeding the
37094
+ // per-impact acceptance-criteria / character budget. Retryable via the normal async-job path.
37095
+ "spec_over_budget"
37081
37096
  ];
37082
37097
  var mrrlinAsyncJobStatusSchema = external_exports.enum(ASYNC_JOB_STATUSES);
37083
37098
  var mrrlinAsyncJobFailureReasonSchema = external_exports.enum(ASYNC_JOB_FAILURE_REASONS);
@@ -41278,6 +41293,82 @@ function describeError(error51) {
41278
41293
  const detail = detailFromCause(error51.cause);
41279
41294
  return detail ? `${error51.message} (${truncate(detail)})` : error51.message;
41280
41295
  }
41296
+ function errorKind(error51) {
41297
+ if (!(error51 instanceof Error)) return null;
41298
+ const cause = error51.cause;
41299
+ if (!cause || typeof cause !== "object") return null;
41300
+ const direct = cause.code;
41301
+ if (typeof direct === "string") return direct;
41302
+ const errors = cause.errors;
41303
+ if (Array.isArray(errors) && errors.length > 0) {
41304
+ const first = errors[0]?.code;
41305
+ if (typeof first === "string") return first;
41306
+ }
41307
+ return null;
41308
+ }
41309
+ var DEFAULT_FAILURE_LOG_WINDOW_SECONDS = 60;
41310
+ function failureLogWindowMs(env = process.env) {
41311
+ const raw = (env.MRRLIN_BRIDGE_FAILURE_LOG_WINDOW_SECONDS ?? "").trim();
41312
+ const parsed = Number.parseInt(raw, 10);
41313
+ const seconds = Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_FAILURE_LOG_WINDOW_SECONDS;
41314
+ return seconds * 1e3;
41315
+ }
41316
+ function createFailureLog(opts = {}) {
41317
+ const windowMs = opts.windowMs ?? failureLogWindowMs();
41318
+ const now = opts.now ?? Date.now;
41319
+ const lastEmit = /* @__PURE__ */ new Map();
41320
+ const counts = /* @__PURE__ */ new Map();
41321
+ let windowStartedAt = null;
41322
+ return {
41323
+ /** Decide whether this failure should be printed. Returns the suppressed count to include in
41324
+ * the line when it is. */
41325
+ shouldEmit(operation, kind) {
41326
+ const prev = lastEmit.get(operation);
41327
+ const sameKind = prev != null && prev.kind === kind;
41328
+ const withinWindow = prev != null && now() - prev.at < windowMs;
41329
+ if (sameKind && withinWindow) {
41330
+ prev.suppressed += 1;
41331
+ return { emit: false, suppressed: prev.suppressed };
41332
+ }
41333
+ const suppressed = sameKind ? prev?.suppressed ?? 0 : 0;
41334
+ lastEmit.set(operation, { kind, at: now(), suppressed: 0 });
41335
+ return { emit: true, suppressed };
41336
+ },
41337
+ /** A success resets the operation's suppression state, so the next failure always prints. */
41338
+ noteSuccess(operation) {
41339
+ lastEmit.delete(operation);
41340
+ },
41341
+ /** Count a failure for the degraded report. Only the breaker-tracked operations are counted. */
41342
+ recordFailure(operation, kind) {
41343
+ if (windowStartedAt == null) windowStartedAt = now();
41344
+ const entry = counts.get(operation) ?? { failures: 0, lastErrorKind: null };
41345
+ entry.failures += 1;
41346
+ entry.lastErrorKind = kind;
41347
+ counts.set(operation, entry);
41348
+ },
41349
+ /** Snapshot the window and start a fresh one. Always returns all three operations, reporting
41350
+ * zeros for a clean window — it never fabricates a degraded state and never omits the key. */
41351
+ drainWindow() {
41352
+ const startedAt = windowStartedAt;
41353
+ const report = {
41354
+ windowStartedAt: startedAt == null ? null : new Date(startedAt).toISOString(),
41355
+ operations: {
41356
+ schedulerTick: counts.get("schedulerTick") ?? { failures: 0, lastErrorKind: null },
41357
+ claimSweep: counts.get("claimSweep") ?? { failures: 0, lastErrorKind: null },
41358
+ asyncSweep: counts.get("asyncSweep") ?? { failures: 0, lastErrorKind: null }
41359
+ }
41360
+ };
41361
+ counts.clear();
41362
+ windowStartedAt = null;
41363
+ return report;
41364
+ }
41365
+ };
41366
+ }
41367
+ function failureLogLine(label, error51, suppressed) {
41368
+ const tail2 = suppressed > 0 ? ` (${suppressed} identical suppressed since the last line)` : "";
41369
+ return `[mrrlin-mcp director-bridge] ${label} failed: ${describeError(error51)}${tail2}
41370
+ `;
41371
+ }
41281
41372
 
41282
41373
  // src/token-refresher.ts
41283
41374
  function createTokenRefresher(opts) {
@@ -41292,12 +41383,19 @@ function createTokenRefresher(opts) {
41292
41383
  opts.persist(token);
41293
41384
  last = token;
41294
41385
  }
41386
+ opts.failureLog?.noteSuccess("tokenRefresh");
41295
41387
  opts.onOk?.();
41296
41388
  } catch (error51) {
41297
- process.stderr.write(
41298
- `[mrrlin-mcp director-bridge] token refresh failed (will retry next tick): ${describeError(error51)}
41389
+ const label = "token refresh (will retry next tick)";
41390
+ if (opts.failureLog) {
41391
+ const { emit, suppressed } = opts.failureLog.shouldEmit("tokenRefresh", errorKind(error51));
41392
+ if (emit) process.stderr.write(failureLogLine(label, error51, suppressed));
41393
+ } else {
41394
+ process.stderr.write(
41395
+ `[mrrlin-mcp director-bridge] token refresh failed (will retry next tick): ${describeError(error51)}
41299
41396
  `
41300
- );
41397
+ );
41398
+ }
41301
41399
  opts.onError?.(error51);
41302
41400
  }
41303
41401
  }
@@ -41724,7 +41822,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41724
41822
  }
41725
41823
 
41726
41824
  // src/_generated/version.ts
41727
- var PKG_VERSION = "0.4.23";
41825
+ var PKG_VERSION = "0.4.27";
41728
41826
 
41729
41827
  // src/api-base-url-migrate.ts
41730
41828
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -42180,6 +42278,32 @@ function computeNextTickDelayMs(nextWakeAt, nowMs, opts = {}) {
42180
42278
  function count(value) {
42181
42279
  return Array.isArray(value) ? value.length : 0;
42182
42280
  }
42281
+ var KNOWN_STOP_REASONS = new Set(mrrlinStopReasonSchema.options);
42282
+ function tallyReasons(value) {
42283
+ const out = {};
42284
+ if (!Array.isArray(value)) return out;
42285
+ for (const entry of value) {
42286
+ const raw = entry?.reason;
42287
+ const trimmed = typeof raw === "string" ? raw.trim() : "";
42288
+ const key = KNOWN_STOP_REASONS.has(trimmed) ? trimmed : "unknown";
42289
+ out[key] = (out[key] ?? 0) + 1;
42290
+ }
42291
+ return out;
42292
+ }
42293
+ function mergeReasons(parts) {
42294
+ const out = {};
42295
+ for (const part of parts) {
42296
+ for (const [key, n] of Object.entries(part)) out[key] = (out[key] ?? 0) + n;
42297
+ }
42298
+ return out;
42299
+ }
42300
+ function schedulerTickFailureLine(deps, error51) {
42301
+ const kind = errorKind(error51);
42302
+ deps.failureLog?.recordFailure("schedulerTick", kind);
42303
+ if (!deps.failureLog) return failureLogLine("scheduler tick", error51, 0);
42304
+ const { emit, suppressed } = deps.failureLog.shouldEmit("schedulerTick", kind);
42305
+ return emit ? failureLogLine("scheduler tick", error51, suppressed) : "";
42306
+ }
42183
42307
  function dispatchTickWork(deps, results) {
42184
42308
  if (!deps.dispatchEnqueued) return;
42185
42309
  for (const result of results) {
@@ -42210,8 +42334,7 @@ async function attemptSchedulerTick(deps) {
42210
42334
  const { response, parsedBody } = bridgeHealth ? await withTimeout(parseTick) : await parseTick(new AbortController().signal);
42211
42335
  if (!response.ok) {
42212
42336
  process.stderr.write(
42213
- `[mrrlin-mcp director-bridge] scheduler tick failed: HTTP ${response.status} from /internal/scheduler/run
42214
- `
42337
+ schedulerTickFailureLine(deps, new Error(`HTTP ${response.status} from /internal/scheduler/run`))
42215
42338
  );
42216
42339
  bridgeHealth?.recordFailure("schedulerTick", new Error(`HTTP ${response.status}`));
42217
42340
  return { nextWakeAt: null, failed: true };
@@ -42234,6 +42357,9 @@ async function attemptSchedulerTick(deps) {
42234
42357
  enqueuedSpecDrafts: results.reduce((sum, r) => sum + count(r.enqueuedSpecDrafts), 0),
42235
42358
  autoApprovedSpecs: results.reduce((sum, r) => sum + count(r.autoApprovedSpecs), 0),
42236
42359
  skippedTasks: results.reduce((sum, r) => sum + count(r.skippedTasks), 0),
42360
+ // Slice 4: always present. A tick with no skips emits `{}`, never an absent key and never
42361
+ // null, so a consumer never has to distinguish "no skips" from "this bridge is too old".
42362
+ skippedReasons: mergeReasons(results.map((r) => tallyReasons(r.skippedTasks))),
42237
42363
  failures: results.reduce((sum, r) => sum + count(r.failures), 0)
42238
42364
  },
42239
42365
  perProject: results.map((r) => ({
@@ -42244,18 +42370,31 @@ async function attemptSchedulerTick(deps) {
42244
42370
  enqueuedSpecDrafts: count(r.enqueuedSpecDrafts),
42245
42371
  autoApprovedSpecs: count(r.autoApprovedSpecs),
42246
42372
  skippedTasks: count(r.skippedTasks),
42373
+ skippedReasons: tallyReasons(r.skippedTasks),
42247
42374
  failures: count(r.failures)
42248
42375
  })),
42249
- nextWakeAt: typeof data.nextWakeAt === "string" ? data.nextWakeAt : null
42376
+ nextWakeAt: typeof data.nextWakeAt === "string" ? data.nextWakeAt : null,
42377
+ // Slice 3: partial degradation had no signal — the breaker only trips on TOTAL failure, so
42378
+ // ~2 800 connect timeouts a day were invisible outside raw stderr. This rides a payload the
42379
+ // operator already reads. Always present, always all three tracked operations, zeros for a
42380
+ // clean window: it never fabricates a degraded state and never omits the key.
42381
+ degraded: deps.failureLog?.drainWindow() ?? {
42382
+ windowStartedAt: null,
42383
+ operations: {
42384
+ schedulerTick: { failures: 0, lastErrorKind: null },
42385
+ claimSweep: { failures: 0, lastErrorKind: null },
42386
+ asyncSweep: { failures: 0, lastErrorKind: null }
42387
+ }
42388
+ }
42250
42389
  }
42251
42390
  });
42252
42391
  dispatchTickWork(deps, results);
42392
+ deps.failureLog?.noteSuccess("schedulerTick");
42253
42393
  await bridgeHealth?.recordSuccess();
42254
42394
  return { nextWakeAt: typeof data.nextWakeAt === "string" ? data.nextWakeAt : null, failed: false };
42255
42395
  } catch (error51) {
42256
42396
  process.stderr.write(
42257
- `[mrrlin-mcp director-bridge] scheduler tick failed: ${describeError(error51)}
42258
- `
42397
+ schedulerTickFailureLine(deps, error51)
42259
42398
  );
42260
42399
  bridgeHealth?.recordFailure("schedulerTick", error51);
42261
42400
  return { nextWakeAt: null, failed: true };
@@ -43093,6 +43232,16 @@ function parseAcceptanceCriteria(markdown) {
43093
43232
  }
43094
43233
  return items.length > 0 ? { items } : null;
43095
43234
  }
43235
+ function resolveSpecBudget(impact) {
43236
+ switch (impact?.toLowerCase()) {
43237
+ case "high":
43238
+ return { maxRequirements: 12, maxChars: 25e3 };
43239
+ case "medium":
43240
+ return { maxRequirements: 6, maxChars: 12e3 };
43241
+ default:
43242
+ return { maxRequirements: 3, maxChars: 6e3 };
43243
+ }
43244
+ }
43096
43245
 
43097
43246
  // src/consensus/spec-gate.ts
43098
43247
  var SEED_ABSENCE_QUESTION = "What are the requirements for this task? There is no operator request on record, so the spec cannot be validated against your intent \u2014 please state what this task must do.";
@@ -43971,7 +44120,7 @@ function renderCriteriaBlock(criteria) {
43971
44120
  return criteria.map((c, i) => `${i + 1}. [ ] ${c}`).join("\n");
43972
44121
  }
43973
44122
  async function buildDeliverableGateDeps(args) {
43974
- const { client, projectSlug, deliverablePageId, declaredOutcome, cwd, codexExecutable, runId, seat } = args;
44123
+ const { client, projectSlug, deliverablePageId, attemptOutcome, cwd, codexExecutable, runId, seat } = args;
43975
44124
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
43976
44125
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
43977
44126
  const page = await client.getWikiPage(projectSlug, deliverablePageId);
@@ -43995,9 +44144,9 @@ async function buildDeliverableGateDeps(args) {
43995
44144
  const heads = resolveHeads(domain2, impact);
43996
44145
  const maxHeadCalls = 2 * rounds * (heads.length + 1);
43997
44146
  const criteriaBlock = renderCriteriaBlock(criteria);
43998
- const declaredOutcomeBlock = declaredOutcome ? [
44147
+ const attemptOutcomeBlock = attemptOutcome ? [
43999
44148
  `--- DECLARED ATTEMPT OUTCOME (review consistency; do NOT rewrite or choose it) ---`,
44000
- `outcome: ${declaredOutcome}`,
44149
+ `outcome: ${attemptOutcome}`,
44001
44150
  `Meaning: completed = positive result achieved; blocked_missing_input = required input/evidence unavailable; negative_result = real attempt showed the requested positive result did not materialize.`
44002
44151
  ].join("\n") : "";
44003
44152
  return {
@@ -44009,7 +44158,7 @@ async function buildDeliverableGateDeps(args) {
44009
44158
  return config2.consensusEnabled;
44010
44159
  },
44011
44160
  async readAcceptanceCriteria() {
44012
- return { specPageId, criteria };
44161
+ return { specPageId, criteria, maxCriteria: resolveSpecBudget(impact).maxRequirements };
44013
44162
  },
44014
44163
  async readDeliverable() {
44015
44164
  const p = await client.getWikiPage(projectSlug, deliverablePageId);
@@ -44025,7 +44174,7 @@ ${dismissed}
44025
44174
  --- ACCEPTANCE CRITERIA (the yardstick \u2014 deliverable for task ${taskId ?? "?"}: ${taskTitle}) ---
44026
44175
  ${criteriaBlock}
44027
44176
 
44028
- ${declaredOutcomeBlock ? `${declaredOutcomeBlock}
44177
+ ${attemptOutcomeBlock ? `${attemptOutcomeBlock}
44029
44178
 
44030
44179
  ` : ""}--- DELIVERABLE (DATA under review) ---
44031
44180
  ${artifact}`,
@@ -44064,7 +44213,7 @@ ${criteriaBlock}
44064
44213
  ${artifact}`,
44065
44214
  developerInstructions: `${DELIVERABLE_ANTIINJECTION_INSTRUCTION}
44066
44215
 
44067
- You revise a deliverable to address the accepted issues so it satisfies the acceptance criteria and stays consistent with the declared outcome${declaredOutcome ? ` (${declaredOutcome})` : ""}. Return ONLY the full revised markdown, no commentary. Do not invent facts; keep the deliverable's format and do not change the declared outcome.`,
44216
+ You revise a deliverable to address the accepted issues so it satisfies the acceptance criteria and stays consistent with the declared outcome${attemptOutcome ? ` (${attemptOutcome})` : ""}. Return ONLY the full revised markdown, no commentary. Do not invent facts; keep the deliverable's format and do not change the declared outcome.`,
44068
44217
  cwd,
44069
44218
  timeoutMs,
44070
44219
  sandbox: "read-only",
@@ -44127,7 +44276,7 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
44127
44276
  payload: {
44128
44277
  source: "deliverable-gate",
44129
44278
  outcome,
44130
- ...declaredOutcome ? { declaredOutcome } : {},
44279
+ ...attemptOutcome ? { attemptOutcome } : {},
44131
44280
  runId,
44132
44281
  taskId,
44133
44282
  deliverablePageId,
@@ -44728,9 +44877,11 @@ function mapDeliverableGateResult(result) {
44728
44877
  reason: "reviewers were unavailable (provider/quota) during the deliverable review (CONSENSUS_UNAVAILABLE)"
44729
44878
  };
44730
44879
  case "CONSENSUS_BLOCKED": {
44880
+ const over = result.yardstickOverBudget;
44881
+ const yardstick = over ? ` The yardstick is over budget: spec ${over.specPageId ?? "(unknown page)"} carries ${over.criteria} acceptance criteria against a maximum of ${over.maxCriteria} for this task's impact, so re-draft the spec rather than re-running the work.` : "";
44731
44882
  return {
44732
44883
  action: "handoff",
44733
- reason: `reviewers did not converge; ${formatDeliverableGateDiagnostics(result.diagnostics ?? [])} (CONSENSUS_BLOCKED)`
44884
+ reason: `reviewers did not converge; ${formatDeliverableGateDiagnostics(result.diagnostics ?? [])} (CONSENSUS_BLOCKED)${yardstick}`
44734
44885
  };
44735
44886
  }
44736
44887
  case "MISSING_ACCEPTANCE_CRITERIA":
@@ -44756,17 +44907,6 @@ var SPECS_FOLDER = "Specs";
44756
44907
  var DRAFT_SPEC_TLDR_MAX_CHARS = 300;
44757
44908
  var DRAFT_SPEC_PERSONA = "spec-author";
44758
44909
  var PAGE_ID_MAX_ATTEMPTS = 5;
44759
- function resolveSpecBudget(impact) {
44760
- const tier = impact?.toLowerCase();
44761
- switch (tier) {
44762
- case "high":
44763
- return { maxRequirements: 12, maxChars: 25e3 };
44764
- case "medium":
44765
- return { maxRequirements: 6, maxChars: 12e3 };
44766
- default:
44767
- return { maxRequirements: 3, maxChars: 6e3 };
44768
- }
44769
- }
44770
44910
  function defaultSleep(ms) {
44771
44911
  return new Promise((resolve) => setTimeout(resolve, ms));
44772
44912
  }
@@ -44792,6 +44932,25 @@ ${body}`;
44792
44932
  }
44793
44933
  return { markdown: body, tldrChars };
44794
44934
  }
44935
+ function checkSpecBudget(markdown, impact) {
44936
+ const budget = resolveSpecBudget(impact);
44937
+ const parsed = parseAcceptanceCriteria(markdown);
44938
+ const criteria = parsed?.items.length ?? 0;
44939
+ const chars = markdown.length;
44940
+ const overCriteria = parsed != null && criteria > budget.maxRequirements;
44941
+ const overChars = chars > budget.maxChars;
44942
+ if (!overCriteria && !overChars) return null;
44943
+ const faults = [];
44944
+ if (overCriteria) faults.push(`${criteria} acceptance-criteria items (maximum ${budget.maxRequirements})`);
44945
+ if (overChars) faults.push(`${chars} characters (maximum ${budget.maxChars})`);
44946
+ return {
44947
+ criteria,
44948
+ chars,
44949
+ maxCriteria: budget.maxRequirements,
44950
+ maxChars: budget.maxChars,
44951
+ message: `spec draft is over budget: ${faults.join(" and ")}`
44952
+ };
44953
+ }
44795
44954
  function extractDeployTarget(markdown) {
44796
44955
  const section = /(## Deploy Target\s*\n)([\s\S]*?)(?=\n## |$)/i.exec(markdown);
44797
44956
  if (!section || section.index === void 0) {
@@ -45059,6 +45218,10 @@ async function runDraftSpec(ctx, input, deps) {
45059
45218
  }
45060
45219
  const { markdown: specMarkdown, deployTarget: parsedDeployTarget } = extractDeployTarget(exec.text);
45061
45220
  const { markdown, tldrChars } = enforceSpecOutputContract(specMarkdown, taskId, task.title);
45221
+ const overBudget = checkSpecBudget(markdown, task.impact);
45222
+ if (overBudget) {
45223
+ return { kind: "retry", failureReason: "spec_over_budget", error: overBudget.message };
45224
+ }
45062
45225
  let pageId;
45063
45226
  if (adoptedId) {
45064
45227
  try {
@@ -47735,7 +47898,7 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47735
47898
  taskId: task.id,
47736
47899
  runId,
47737
47900
  deliverablePageId: pageId,
47738
- declaredOutcome: parsedResult.outcome
47901
+ attemptOutcome: parsedResult.outcome
47739
47902
  });
47740
47903
  } catch (error51) {
47741
47904
  await failRun(
@@ -48595,6 +48758,7 @@ async function runDeliverableGate(target, deps) {
48595
48758
  const deliverable = await deps.readDeliverable();
48596
48759
  let currentHash = deliverable.contentHash;
48597
48760
  let conflicted = false;
48761
+ const yardstickOverBudget = ac.maxCriteria != null && ac.criteria.length > ac.maxCriteria ? { specPageId: ac.specPageId, criteria: ac.criteria.length, maxCriteria: ac.maxCriteria } : void 0;
48598
48762
  const result = await runConsensus({
48599
48763
  artifact: deliverable.markdownBody,
48600
48764
  heads: deps.heads,
@@ -48636,7 +48800,7 @@ async function runDeliverableGate(target, deps) {
48636
48800
  }
48637
48801
  await deps.writeEvidence({
48638
48802
  deliverablePageId: deliverable.pageId,
48639
- ...target.declaredOutcome ? { declaredOutcome: target.declaredOutcome } : {},
48803
+ ...target.attemptOutcome ? { attemptOutcome: target.attemptOutcome } : {},
48640
48804
  outcome: result.outcome,
48641
48805
  rounds: result.rounds,
48642
48806
  history: result.history,
@@ -48648,7 +48812,10 @@ async function runDeliverableGate(target, deps) {
48648
48812
  outcome: "CONSENSUS_BLOCKED",
48649
48813
  rounds: result.rounds,
48650
48814
  diagnostics: diagnostics ?? [],
48651
- ...result.escalation ? { escalation: result.escalation } : {}
48815
+ ...result.escalation ? { escalation: result.escalation } : {},
48816
+ // Slice 2: attached ONLY on the blocked path. A converged review needs no explanation, and a
48817
+ // transient outcome (changed-during-review, reviewers unavailable) is not about the yardstick.
48818
+ ...yardstickOverBudget ? { yardstickOverBudget } : {}
48652
48819
  };
48653
48820
  }
48654
48821
  if (result.outcome === "CONVERGED") {
@@ -55865,6 +56032,11 @@ function checkTelemetryDefaultProject(env, warn) {
55865
56032
  }
55866
56033
  async function startDirectorBridge() {
55867
56034
  const config2 = parseConfig();
56035
+ const failureLog = createFailureLog();
56036
+ const suppressedPushLine = (label, error51) => {
56037
+ const { emit, suppressed } = failureLog.shouldEmit(label, errorKind(error51));
56038
+ return emit ? failureLogLine(label, error51, suppressed) : "";
56039
+ };
55868
56040
  let telemetryPipelineRunning = false;
55869
56041
  checkTelemetryDefaultProject(process.env, (m) => console.error(m));
55870
56042
  const turnLock = createTurnLock();
@@ -55949,6 +56121,7 @@ async function startDirectorBridge() {
55949
56121
  provider: tokenProvider,
55950
56122
  persist: persistOperatorToken,
55951
56123
  intervalMs: tokenRefreshIntervalMs(),
56124
+ failureLog,
55952
56125
  onOk: () => health.setApiAuth("ok"),
55953
56126
  onError: (e) => {
55954
56127
  if (e instanceof AgentTokenProviderError && e.code === "REVOKED_OR_EXPIRED") {
@@ -56079,17 +56252,17 @@ async function startDirectorBridge() {
56079
56252
  // Builds the gate deps (resolving domain/heads/criteria from the page's task + spec) and runs the
56080
56253
  // same convergence loop the spec-gate uses. runId is threaded for verdict provenance (decision 10).
56081
56254
  // Authoring uses the defaults (runAgentExec + loadPersona) — no extra wiring needed for those.
56082
- runDeliverableGate: async ({ projectSlug, runId, deliverablePageId, declaredOutcome }) => {
56255
+ runDeliverableGate: async ({ projectSlug, runId, deliverablePageId, attemptOutcome }) => {
56083
56256
  const gateDeps = await buildDeliverableGateDeps({
56084
56257
  client,
56085
56258
  projectSlug,
56086
56259
  deliverablePageId,
56087
- declaredOutcome,
56260
+ attemptOutcome,
56088
56261
  cwd: config2.codexCwd ?? process.cwd(),
56089
56262
  codexExecutable: config2.codexExecutable,
56090
56263
  runId
56091
56264
  });
56092
- return runDeliverableGate({ projectSlug, deliverablePageId, declaredOutcome }, gateDeps);
56265
+ return runDeliverableGate({ projectSlug, deliverablePageId, attemptOutcome }, gateDeps);
56093
56266
  }
56094
56267
  };
56095
56268
  const sessionSinks = createSessionSinkRegistry();
@@ -56471,12 +56644,13 @@ async function startDirectorBridge() {
56471
56644
  const runTrackedSweep = async (id, label, fn) => {
56472
56645
  try {
56473
56646
  await fn();
56647
+ failureLog.noteSuccess(id);
56474
56648
  await bridgeHealth.recordSuccess();
56475
56649
  } catch (error51) {
56476
- process.stderr.write(
56477
- `[mrrlin-mcp director-bridge] ${label} failed: ${describeError(error51)}
56478
- `
56479
- );
56650
+ const kind = errorKind(error51);
56651
+ failureLog.recordFailure(id, kind);
56652
+ const { emit, suppressed } = failureLog.shouldEmit(id, kind);
56653
+ if (emit) process.stderr.write(failureLogLine(label, error51, suppressed));
56480
56654
  bridgeHealth.recordFailure(id, error51);
56481
56655
  await bridgeHealth.maybeCrash();
56482
56656
  }
@@ -56612,6 +56786,7 @@ async function startDirectorBridge() {
56612
56786
  fetchImpl: authedFetch,
56613
56787
  logger: bridgeLogger,
56614
56788
  bridgeHealth,
56789
+ failureLog,
56615
56790
  dispatchEnqueued: (projectSlug, work) => {
56616
56791
  if (work.kind === "execution_run") driveRunNow(projectSlug, work.runId);
56617
56792
  else driveAsyncNow(projectSlug, work.jobId);
@@ -56641,8 +56816,7 @@ async function startDirectorBridge() {
56641
56816
  agentPoolLoopHandle = startAgentPoolLoop({
56642
56817
  push: (snapshot) => client.putOperatorAgentPool(snapshot),
56643
56818
  onError: (err) => process.stderr.write(
56644
- `[mrrlin-mcp director-bridge] agent-pool snapshot push failed: ${describeError(err)}
56645
- `
56819
+ suppressedPushLine("agent-pool snapshot push", err)
56646
56820
  )
56647
56821
  });
56648
56822
  }
@@ -56651,8 +56825,7 @@ async function startDirectorBridge() {
56651
56825
  providerReadinessLoopHandle = startProviderReadinessLoop({
56652
56826
  push: (readiness) => client.putOperatorProviderReadiness(readiness),
56653
56827
  onError: (err) => process.stderr.write(
56654
- `[mrrlin-mcp director-bridge] provider-readiness push failed: ${describeError(err)}
56655
- `
56828
+ suppressedPushLine("provider-readiness push", err)
56656
56829
  )
56657
56830
  });
56658
56831
  }
@@ -64216,7 +64389,8 @@ function installService(deps) {
64216
64389
  deps.log("pm2 not found on PATH. Install it first: npm i -g pm2");
64217
64390
  return { ok: false };
64218
64391
  }
64219
- const resolvedBins = bins.filter((b) => b !== null);
64392
+ const claudeBin = deps.which("claude");
64393
+ const resolvedBins = [...bins, claudeBin].filter((b) => b !== null);
64220
64394
  const pathEnv = Array.from(new Set(resolvedBins.map((b) => import_node_path28.default.dirname(b)))).join(":");
64221
64395
  const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim() || void 0;
64222
64396
  const staging = deps.env.MRRLIN_STAGING === "1" || !apiBaseUrl && bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL);
@@ -64613,6 +64787,13 @@ Usage:
64613
64787
  - MRRLIN_DIRECTOR_BRIDGE_EXEC_TURN_TIMEOUT_SECONDS (DEPRECATED;
64614
64788
  unset by default. When set, an extra per-turn hard cap: the
64615
64789
  effective turn deadline becomes min(this, remaining run budget))
64790
+ - MRRLIN_BRIDGE_FAILURE_LOG_WINDOW_SECONDS (default 60;
64791
+ how long an IDENTICAL failure of the same periodic operation
64792
+ is suppressed on stderr before it prints again, carrying the
64793
+ count it swallowed. A changed error kind, a different
64794
+ operation, or the first failure after a success always
64795
+ prints immediately. Failures are counted for the tick's
64796
+ scheduler-tick degraded block whether or not the line prints)
64616
64797
  - MRRLIN_DIRECTOR_BRIDGE_CONTEXT_MAX_ARTIFACTS (default 50;
64617
64798
  most-recent artifacts kept in the execution context bundle)
64618
64799
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.23",
3
+ "version": "0.4.27",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -24,9 +24,9 @@
24
24
  "@mrrlin/client": "0.0.0",
25
25
  "@mrrlin/agent-client": "0.0.0",
26
26
  "@mrrlin/director-e2e": "0.0.0",
27
+ "@mrrlin/wiki": "0.0.0",
27
28
  "@mrrlin/schemas": "0.0.0",
28
- "@mrrlin/tsconfig": "0.0.0",
29
- "@mrrlin/wiki": "0.0.0"
29
+ "@mrrlin/tsconfig": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",