@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.
- package/dist/bin.cjs +227 -46
- 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
|
-
|
|
41298
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
44147
|
+
const attemptOutcomeBlock = attemptOutcome ? [
|
|
43999
44148
|
`--- DECLARED ATTEMPT OUTCOME (review consistency; do NOT rewrite or choose it) ---`,
|
|
44000
|
-
`outcome: ${
|
|
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
|
-
${
|
|
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${
|
|
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
|
-
...
|
|
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
|
-
|
|
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.
|
|
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,
|
|
56255
|
+
runDeliverableGate: async ({ projectSlug, runId, deliverablePageId, attemptOutcome }) => {
|
|
56083
56256
|
const gateDeps = await buildDeliverableGateDeps({
|
|
56084
56257
|
client,
|
|
56085
56258
|
projectSlug,
|
|
56086
56259
|
deliverablePageId,
|
|
56087
|
-
|
|
56260
|
+
attemptOutcome,
|
|
56088
56261
|
cwd: config2.codexCwd ?? process.cwd(),
|
|
56089
56262
|
codexExecutable: config2.codexExecutable,
|
|
56090
56263
|
runId
|
|
56091
56264
|
});
|
|
56092
|
-
return runDeliverableGate({ projectSlug, deliverablePageId,
|
|
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
|
-
|
|
56477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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",
|