@mrrlin-dev/mcp 0.4.31 → 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 +159 -28
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -37388,6 +37388,22 @@ function isRunnableProposal(item) {
37388
37388
  return true;
37389
37389
  }
37390
37390
 
37391
+ // ../../packages/schemas/dist/task-notes.js
37392
+ var TASK_NOTES_BRIEF_SECTIONS = [
37393
+ { heading: "Context", description: "why this task exists; link to parent/trigger task if follow-up." },
37394
+ { heading: "Scope", description: "concrete actions and acceptance criteria." },
37395
+ { heading: "References", description: "links, data, relevant findings (omit if nothing to reference)." }
37396
+ ];
37397
+ var TASK_NOTES_JOURNAL_SECTIONS = [
37398
+ { heading: "Result", description: "what was accomplished, key findings or metrics, follow-up tasks created." },
37399
+ {
37400
+ heading: "Recheck",
37401
+ dated: true,
37402
+ description: "what you checked, what you found, and what happens next."
37403
+ }
37404
+ ];
37405
+ var JOURNAL_HEADING_RE = new RegExp(`^##[ \\t]+(${TASK_NOTES_JOURNAL_SECTIONS.map((s) => s.heading).join("|")})\\b[ \\t]*(?:[\u2014\u2013-][ \\t]*(\\S.*?))?[ \\t]*$`);
37406
+
37391
37407
  // ../../packages/schemas/dist/index.js
37392
37408
  var mrrlinTaskStatusSchema = external_exports.enum([
37393
37409
  "backlog",
@@ -37504,6 +37520,12 @@ var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
37504
37520
  // start (verifyProviderReadiness returned ready:false) — an environment/infrastructure problem,
37505
37521
  // not a task-content one, so it gets its own code rather than reusing task_blocked.
37506
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",
37507
37529
  "unknown"
37508
37530
  ]);
37509
37531
  var mrrlinExecutionArtifactKindSchema = external_exports.enum([
@@ -39323,6 +39345,36 @@ function detectExhaustedProviderLimit(snapshot, nowMs) {
39323
39345
  const resetAt = candidates.length > 0 ? candidates.reduce((a, b) => a > b ? a : b) : null;
39324
39346
  return { exhausted: true, resetAt };
39325
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
+ }
39326
39378
 
39327
39379
  // ../../packages/agent-client/dist/providers/codex/adapter.js
39328
39380
  var CodexProviderAdapter = class {
@@ -41837,7 +41889,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41837
41889
  }
41838
41890
 
41839
41891
  // src/_generated/version.ts
41840
- var PKG_VERSION = "0.4.31";
41892
+ var PKG_VERSION = "0.4.33";
41841
41893
 
41842
41894
  // src/api-base-url-migrate.ts
41843
41895
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -46039,6 +46091,30 @@ function isDeployGateRunStatus(status) {
46039
46091
  return status === "dev_deploy" || status === "prod_deploy";
46040
46092
  }
46041
46093
 
46094
+ // src/prompts/task-notes-contract.ts
46095
+ function renderTaskNotesContractLines() {
46096
+ const brief = TASK_NOTES_BRIEF_SECTIONS.map((s) => ` ## ${s.heading} \u2014 ${s.description}`);
46097
+ const [result, recheck] = TASK_NOTES_JOURNAL_SECTIONS;
46098
+ return [
46099
+ "- Notes (markdown): write for a human operator scanning a task list.",
46100
+ " First line: one plain-text sentence summarizing the task (no heading).",
46101
+ " Then scale the structure to complexity:",
46102
+ " For simple tasks \u2014 the one-line summary may be enough. No headings required.",
46103
+ " For non-trivial tasks, use these sections as needed:",
46104
+ ...brief,
46105
+ " When closing a task (update_task status=closed_done), APPEND a Result section:",
46106
+ ` ## ${result?.heading} \u2014 ${result?.description}`,
46107
+ " When you revisit a task that already has a Result \u2014 a later verification, a retry, a status",
46108
+ " re-check \u2014 APPEND a dated entry instead of rewriting Result:",
46109
+ ` ## ${recheck?.heading} \u2014 YYYY-MM-DD \u2014 ${recheck?.description}`,
46110
+ " Do NOT remove earlier sections \u2014 they remain useful for context.",
46111
+ " The sections above the first Result/Recheck are the brief (what the task IS); Result and",
46112
+ " Recheck are the execution journal (what HAPPENED). The task page renders them separately and",
46113
+ " collapses the journal, so keep each kind of content under the right heading.",
46114
+ " Adapt depth to the task: a one-line fix needs one sentence, not four headings."
46115
+ ];
46116
+ }
46117
+
46042
46118
  // src/turn-deadline.ts
46043
46119
  var DEFAULT_TURN_INTERRUPT_TIMEOUT_MS = 5e3;
46044
46120
  function withTimeout2(promise2, ms, message) {
@@ -46475,10 +46551,31 @@ function executionTurnBudgetMessage(budgetMs) {
46475
46551
  function isExecutionTurnTimeoutReason(reason) {
46476
46552
  return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX) || reason.startsWith(EXECUTION_TURN_STALL_PREFIX);
46477
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
+ }
46478
46567
  function reportAutopilotLimitPause(client, reason, resetAt) {
46479
46568
  void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
46480
46569
  });
46481
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
+ }
46482
46579
  function isDeliverableGateRetryableReason(reason) {
46483
46580
  return DELIVERABLE_GATE_RETRYABLE_REASON_PREFIXES.some((prefix) => reason.startsWith(prefix));
46484
46581
  }
@@ -46612,18 +46709,7 @@ var BASE_INSTRUCTIONS_PREFIX = [
46612
46709
  "- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
46613
46710
  ' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
46614
46711
  ' Bad: "GSC coverage triage crawled-not-indexed + 404"',
46615
- "- Notes (markdown): write for a human operator scanning a task list.",
46616
- " First line: one plain-text sentence summarizing the task (no heading).",
46617
- " Then scale the structure to complexity:",
46618
- " For simple tasks \u2014 the one-line summary may be enough. No headings required.",
46619
- " For non-trivial tasks, use these sections as needed:",
46620
- " ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
46621
- " ## Scope \u2014 concrete actions and acceptance criteria.",
46622
- " ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
46623
- " When closing a task (update_task status=closed_done), APPEND a Result section:",
46624
- " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
46625
- " Do NOT remove earlier sections \u2014 they remain useful for context.",
46626
- " Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
46712
+ ...renderTaskNotesContractLines(),
46627
46713
  "",
46628
46714
  // Slice 4 of token-efficient-execution-runs (D8). This is the ONLY way the reasoning behind a
46629
46715
  // decision survives past this conversation: a thread is retired once it has accumulated too many
@@ -47998,6 +48084,62 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
47998
48084
  return;
47999
48085
  }
48000
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
+ }
48001
48143
  if (isExecutionTurnTimeoutReason(reason) && currentRun.checkpointCursor !== EXECUTION_TURN_TIMEOUT_RETRY_CURSOR) {
48002
48144
  try {
48003
48145
  await deps.client.updateExecutionRun(projectSlug, runId, {
@@ -48050,11 +48192,11 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
48050
48192
  }
48051
48193
  try {
48052
48194
  await deps.client.createExecutionRunHandoff(projectSlug, runId, {
48053
- reason,
48054
- ...reasonCode ? { reasonCode } : {}
48195
+ reason: handoffReason,
48196
+ ...handoffReasonCode ? { reasonCode: handoffReasonCode } : {}
48055
48197
  });
48056
48198
  } catch {
48057
- await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
48199
+ await notifyOperatorHandoff(deps, projectSlug, task, runId, handoffReason);
48058
48200
  }
48059
48201
  }
48060
48202
  async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
@@ -54218,18 +54360,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
54218
54360
  "- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
54219
54361
  ' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
54220
54362
  ' Bad: "GSC coverage triage crawled-not-indexed + 404"',
54221
- "- Notes (markdown): write for a human operator scanning a task list.",
54222
- " First line: one plain-text sentence summarizing the task (no heading).",
54223
- " Then scale the structure to complexity:",
54224
- " For simple tasks \u2014 the one-line summary may be enough. No headings required.",
54225
- " For non-trivial tasks, use these sections as needed:",
54226
- " ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
54227
- " ## Scope \u2014 concrete actions and acceptance criteria.",
54228
- " ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
54229
- " When closing a task (update_task status=closed_done), APPEND a Result section:",
54230
- " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
54231
- " Do NOT remove earlier sections \u2014 they remain useful for context.",
54232
- " Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
54363
+ ...renderTaskNotesContractLines(),
54233
54364
  // early-intent-confirmation (design.md, "The chat contract"): the core block below is copied
54234
54365
  // VERBATIM from design.md, per tasks.md 10.1. It replaces an older sentence that also carried
54235
54366
  // "omit operatorSeedRequest only when there is genuinely no operator request behind the task
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.31",
3
+ "version": "0.4.33",
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/client": "0.0.0",
25
- "@mrrlin/agent-client": "0.0.0",
26
25
  "@mrrlin/director-e2e": "0.0.0",
27
26
  "@mrrlin/schemas": "0.0.0",
28
27
  "@mrrlin/tsconfig": "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",