@mrrlin-dev/mcp 0.4.32 → 0.4.33

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 +117 -4
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -37520,6 +37520,12 @@ var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
37520
37520
  // start (verifyProviderReadiness returned ready:false) — an environment/infrastructure problem,
37521
37521
  // not a task-content one, so it gets its own code rather than reusing task_blocked.
37522
37522
  "provider_not_ready",
37523
+ // The agent provider refused work MID-RUN because its usage quota is exhausted — distinct from
37524
+ // `provider_not_ready`, which is the pre-flight readiness gate refusing to start. A quota is
37525
+ // transient and the run is retriable once the limit resets, so this code must never be conflated
37526
+ // with a task-content block; before it existed such a failure fell through to `unknown` and was
37527
+ // handed off to the operator as though the Director had judged the task.
37528
+ "provider_limit_exhausted",
37523
37529
  "unknown"
37524
37530
  ]);
37525
37531
  var mrrlinExecutionArtifactKindSchema = external_exports.enum([
@@ -39339,6 +39345,36 @@ function detectExhaustedProviderLimit(snapshot, nowMs) {
39339
39345
  const resetAt = candidates.length > 0 ? candidates.reduce((a, b) => a > b ? a : b) : null;
39340
39346
  return { exhausted: true, resetAt };
39341
39347
  }
39348
+ var PROVIDER_LIMIT_ERROR_PATTERNS = [
39349
+ // OpenAI / Codex, verbatim from the observed event.
39350
+ /you'?ve hit your usage limit/i,
39351
+ // Anthropic phrasing for the same condition.
39352
+ /you have exceeded your (?:rate limit|usage limit)/i,
39353
+ /credit balance is too low/i,
39354
+ // START-ANCHORED quota wording. Round 2 split on this: the unanchored version was removed in
39355
+ // round 1 for matching task prose ("the quota is being exceeded by another worker"), and a round-2
39356
+ // reviewer then objected that removing it also drops real provider messages that LEAD with the
39357
+ // condition ("Monthly quota exceeded."). Anchoring resolves both — a provider error states the
39358
+ // condition first, prose embeds it — so the pattern is kept, but only at the head of the message.
39359
+ // Note this is a second line of defence either way: a provider that emits a structured
39360
+ // `rate_limits` snapshot is caught by detectExhaustedProviderLimit regardless of wording.
39361
+ // NOTE an unanchored `/usage limit reached/i` used to sit above, and it was the one real leak a
39362
+ // round-4 reviewer's finding pointed at (though not the example they gave): it matched
39363
+ // "[ERROR] memory usage limit reached", where the limit belongs to memory, not the provider. The
39364
+ // anchored pattern below already covers "Usage limit reached …", so the unanchored one was pure
39365
+ // false-positive surface and is gone.
39366
+ //
39367
+ // The optional prefix covers the log decorations providers actually ship with — "Error: ",
39368
+ // "[ERROR] ", "(fatal) " — without letting the condition sit anywhere in a sentence. The noun set
39369
+ // covers "rate limit"/"api limit" forms that carry no "quota" or "credits" at all.
39370
+ /^\s*(?:[[(]?\s*(?:error|fatal|warn(?:ing)?)\s*[\])]?\s*[:-]?\s*)?(?:monthly |daily |monthly\/daily )?(?:quota|usage limit|rate limit|api limit|api credits?|credits?) (?:has been |have been |is |are )?(?:exceeded|exhausted|used up|reached)\b/i
39371
+ ];
39372
+ function detectProviderLimitFromErrorText(text) {
39373
+ if (typeof text !== "string" || text.trim().length === 0)
39374
+ return null;
39375
+ const matched = PROVIDER_LIMIT_ERROR_PATTERNS.some((pattern) => pattern.test(text));
39376
+ return matched ? { exhausted: true, resetAt: null } : null;
39377
+ }
39342
39378
 
39343
39379
  // ../../packages/agent-client/dist/providers/codex/adapter.js
39344
39380
  var CodexProviderAdapter = class {
@@ -41853,7 +41889,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41853
41889
  }
41854
41890
 
41855
41891
  // src/_generated/version.ts
41856
- var PKG_VERSION = "0.4.32";
41892
+ var PKG_VERSION = "0.4.33";
41857
41893
 
41858
41894
  // src/api-base-url-migrate.ts
41859
41895
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -46515,10 +46551,31 @@ function executionTurnBudgetMessage(budgetMs) {
46515
46551
  function isExecutionTurnTimeoutReason(reason) {
46516
46552
  return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX) || reason.startsWith(EXECUTION_TURN_STALL_PREFIX);
46517
46553
  }
46554
+ var PROVIDER_LIMIT_RETRY_CURSOR_PREFIX = "provider_limit_retry:";
46555
+ var PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS = 1;
46556
+ var PROVIDER_LIMIT_RETRY_CODE = "provider_limit_retry";
46557
+ var PROVIDER_LIMIT_PAUSE_REASON = "Agent provider usage limit exhausted during an autonomous execution run.";
46558
+ var PROVIDER_LIMIT_HANDOFF_REASON = "The agent provider's usage limit stayed exhausted across every automatic retry, so this run could not proceed. This is a provider quota problem, not a problem with the task.";
46559
+ function providerLimitRetryCursor(attempt) {
46560
+ return `${PROVIDER_LIMIT_RETRY_CURSOR_PREFIX}${attempt}`;
46561
+ }
46562
+ function providerLimitRetryAttempt(checkpointCursor) {
46563
+ if (!checkpointCursor || !checkpointCursor.startsWith(PROVIDER_LIMIT_RETRY_CURSOR_PREFIX)) return 0;
46564
+ const parsed = Number.parseInt(checkpointCursor.slice(PROVIDER_LIMIT_RETRY_CURSOR_PREFIX.length), 10);
46565
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
46566
+ }
46518
46567
  function reportAutopilotLimitPause(client, reason, resetAt) {
46519
46568
  void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
46520
46569
  });
46521
46570
  }
46571
+ async function applyAutopilotLimitPause(client, reason, resetAt) {
46572
+ try {
46573
+ await client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt });
46574
+ return true;
46575
+ } catch {
46576
+ return false;
46577
+ }
46578
+ }
46522
46579
  function isDeliverableGateRetryableReason(reason) {
46523
46580
  return DELIVERABLE_GATE_RETRYABLE_REASON_PREFIXES.some((prefix) => reason.startsWith(prefix));
46524
46581
  }
@@ -48027,6 +48084,62 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
48027
48084
  return;
48028
48085
  }
48029
48086
  if (currentRun.status !== "running" || currentRun.leaseId !== leaseId) return;
48087
+ let handoffReason = reason;
48088
+ let handoffReasonCode = reasonCode;
48089
+ const providerLimit = detectProviderLimitFromErrorText(reason);
48090
+ if (providerLimit) {
48091
+ const autopilotPaused = await applyAutopilotLimitPause(
48092
+ deps.client,
48093
+ PROVIDER_LIMIT_PAUSE_REASON,
48094
+ providerLimit.resetAt
48095
+ );
48096
+ const attempt = providerLimitRetryAttempt(currentRun.checkpointCursor) + 1;
48097
+ if (attempt <= PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS) {
48098
+ try {
48099
+ await deps.client.updateExecutionRun(projectSlug, runId, {
48100
+ status: "pending",
48101
+ leaseId: null,
48102
+ checkpointCursor: providerLimitRetryCursor(attempt)
48103
+ });
48104
+ await deps.client.appendExecutionArtifact(projectSlug, runId, {
48105
+ id: newArtifactId(),
48106
+ kind: "error",
48107
+ payload: {
48108
+ code: PROVIDER_LIMIT_RETRY_CODE,
48109
+ source: "execution-consumer",
48110
+ attempt,
48111
+ maxAttempts: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48112
+ // The provider's raw sentence belongs HERE — diagnostic detail on an error artifact —
48113
+ // and never in a handoff `reason`, which the DB trigger caches as the task's judgement.
48114
+ providerMessage: reason,
48115
+ // Whether the brake actually engaged. If it did not, the requeued run is only held
48116
+ // back by its own attempt budget, so this is the difference between "waiting for the
48117
+ // quota" and "about to fail once more"; it must be visible, not swallowed.
48118
+ autopilotPaused,
48119
+ summary: autopilotPaused ? `Agent provider usage limit exhausted; autopilot paused and the run requeued for retry ${attempt}/${PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS}.` : `Agent provider usage limit exhausted; the run was requeued for retry ${attempt}/${PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS}, but pausing autopilot FAILED, so nothing is holding it back.`
48120
+ }
48121
+ }).catch(() => {
48122
+ });
48123
+ return;
48124
+ } catch {
48125
+ }
48126
+ }
48127
+ handoffReason = PROVIDER_LIMIT_HANDOFF_REASON;
48128
+ handoffReasonCode = "provider_limit_exhausted";
48129
+ await deps.client.appendExecutionArtifact(projectSlug, runId, {
48130
+ id: newArtifactId(),
48131
+ kind: "error",
48132
+ payload: {
48133
+ code: PROVIDER_LIMIT_RETRY_CODE,
48134
+ source: "execution-consumer",
48135
+ attempt: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48136
+ maxAttempts: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48137
+ providerMessage: reason,
48138
+ summary: "Agent provider usage limit still exhausted after every retry; handing off to the operator."
48139
+ }
48140
+ }).catch(() => {
48141
+ });
48142
+ }
48030
48143
  if (isExecutionTurnTimeoutReason(reason) && currentRun.checkpointCursor !== EXECUTION_TURN_TIMEOUT_RETRY_CURSOR) {
48031
48144
  try {
48032
48145
  await deps.client.updateExecutionRun(projectSlug, runId, {
@@ -48079,11 +48192,11 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
48079
48192
  }
48080
48193
  try {
48081
48194
  await deps.client.createExecutionRunHandoff(projectSlug, runId, {
48082
- reason,
48083
- ...reasonCode ? { reasonCode } : {}
48195
+ reason: handoffReason,
48196
+ ...handoffReasonCode ? { reasonCode: handoffReasonCode } : {}
48084
48197
  });
48085
48198
  } catch {
48086
- await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
48199
+ await notifyOperatorHandoff(deps, projectSlug, task, runId, handoffReason);
48087
48200
  }
48088
48201
  }
48089
48202
  async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.32",
3
+ "version": "0.4.33",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -25,8 +25,8 @@
25
25
  "@mrrlin/director-e2e": "0.0.0",
26
26
  "@mrrlin/schemas": "0.0.0",
27
27
  "@mrrlin/tsconfig": "0.0.0",
28
- "@mrrlin/agent-client": "0.0.0",
29
- "@mrrlin/wiki": "0.0.0"
28
+ "@mrrlin/wiki": "0.0.0",
29
+ "@mrrlin/agent-client": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",