@mrrlin-dev/mcp 0.3.55 → 0.3.57

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 +521 -162
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -20658,14 +20658,17 @@ 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
  }
20665
20665
  var RETIRED_API_BASE_URLS = {
20666
20666
  // Retired 2026-07-28 when staging moved to the mrrlin2 Vercel team; serves a decommissioned
20667
20667
  // Supabase project and now answers `GET /health` with 402 DEPLOYMENT_DISABLED. Added in 0.3.50.
20668
- "mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL
20668
+ "mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL,
20669
+ // Retired 2026-08-11 when staging moved to the mrrlin3 Vercel team after mrrlin2 hit usage
20670
+ // limits. Added in 0.3.57.
20671
+ "mrrlin-api-mrrlin2.vercel.app": STAGING_API_BASE_URL
20669
20672
  };
20670
20673
  function isSameApiHost(a, b) {
20671
20674
  const origin = (value) => {
@@ -21153,11 +21156,11 @@ ${block}`;
21153
21156
  }
21154
21157
 
21155
21158
  // src/director-bridge.ts
21156
- var import_node_fs14 = __toESM(require("node:fs"), 1);
21159
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
21157
21160
  var import_node_http = __toESM(require("node:http"), 1);
21158
21161
  var import_node_os9 = __toESM(require("node:os"), 1);
21159
21162
  var import_node_path18 = __toESM(require("node:path"), 1);
21160
- var import_node_child_process10 = require("node:child_process");
21163
+ var import_node_child_process11 = require("node:child_process");
21161
21164
  var import_node_crypto8 = __toESM(require("node:crypto"), 1);
21162
21165
 
21163
21166
  // src/turn-replay-buffer.ts
@@ -21289,11 +21292,25 @@ function createSuspensionDetector(opts) {
21289
21292
  const t = now();
21290
21293
  const gap = t - last;
21291
21294
  last = t;
21292
- if (gap >= gapMs) suspended += gap - tickMs;
21295
+ if (gap >= gapMs) {
21296
+ suspended += gap - tickMs;
21297
+ }
21293
21298
  }, tickMs);
21294
21299
  timer.unref?.();
21300
+ const settle = () => {
21301
+ const t = now();
21302
+ const gap = t - last;
21303
+ if (gap >= gapMs) {
21304
+ suspended += gap - tickMs;
21305
+ last = t;
21306
+ }
21307
+ };
21295
21308
  return {
21296
- suspendedMs: () => suspended,
21309
+ suspendedMs: () => {
21310
+ settle();
21311
+ return suspended;
21312
+ },
21313
+ settle,
21297
21314
  dispose: () => clearI(timer)
21298
21315
  };
21299
21316
  }
@@ -21368,20 +21385,22 @@ function transition(state, event) {
21368
21385
 
21369
21386
  // src/bridge-tombstone.ts
21370
21387
  var DEFAULT_TOMBSTONE_TTL_MS = 36e5;
21371
- function writeTombstone(tombstones, directorSessionId, buf, now) {
21388
+ function writeTombstone(tombstones, directorSessionId, buf, now, suspendedMsNow) {
21372
21389
  tombstones.set(directorSessionId, {
21373
21390
  turnId: buf.turnId,
21374
21391
  lastSeq: buf.lastSeq,
21375
- expiredAt: now
21392
+ expiredAt: now,
21393
+ expiredSuspendedMs: suspendedMsNow
21376
21394
  });
21377
21395
  }
21378
21396
  function lookupTombstone(tombstones, directorSessionId) {
21379
21397
  return tombstones.get(directorSessionId) ?? null;
21380
21398
  }
21381
- function sweepExpiredTombstones(tombstones, now, ttlMs = DEFAULT_TOMBSTONE_TTL_MS) {
21399
+ function sweepExpiredTombstones(tombstones, now, ttlMs = DEFAULT_TOMBSTONE_TTL_MS, suspendedMsNow) {
21382
21400
  let removed = 0;
21383
21401
  for (const [sid, t] of tombstones) {
21384
- if (now - t.expiredAt > ttlMs) {
21402
+ const suspendedSinceWrite = Math.max(0, suspendedMsNow - t.expiredSuspendedMs);
21403
+ if (now - t.expiredAt - suspendedSinceWrite > ttlMs) {
21385
21404
  tombstones.delete(sid);
21386
21405
  removed += 1;
21387
21406
  }
@@ -36479,6 +36498,7 @@ var openApiOperationIds = {
36479
36498
  requeueAsyncJob: "requeueAsyncJob",
36480
36499
  createGithubBranch: "createGithubBranch",
36481
36500
  createGithubPullRequest: "createGithubPullRequest",
36501
+ getGithubPullRequest: "getGithubPullRequest",
36482
36502
  getGithubWorkflowRunLatest: "getGithubWorkflowRunLatest",
36483
36503
  getGithubLatestDeployment: "getGithubLatestDeployment",
36484
36504
  mintGithubPushToken: "mintGithubPushToken",
@@ -36540,7 +36560,9 @@ var openApiOperationIds = {
36540
36560
  markSpecReady: "markSpecReady",
36541
36561
  resetDirectorThread: "resetDirectorThread",
36542
36562
  claimExecutionRun: "claimExecutionRun",
36563
+ reclaimExecutionRunLease: "reclaimExecutionRunLease",
36543
36564
  requeueExecutionRun: "requeueExecutionRun",
36565
+ restartExecutionRun: "restartExecutionRun",
36544
36566
  resolveHandoff: "resolveHandoff",
36545
36567
  touchExecutionRun: "touchExecutionRun",
36546
36568
  issueAgentCredential: "issueAgentCredential",
@@ -36617,6 +36639,7 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
36617
36639
  });
36618
36640
  var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
36619
36641
  var operatorPresenceResponseSchema = external_exports.object({ data: external_exports.object({ lastPresenceAt: external_exports.string().datetime() }) });
36642
+ var operatorPresenceRequestSchema = external_exports.object({ projectSlug: external_exports.string().min(1).optional() }).strict().optional();
36620
36643
  var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
36621
36644
  var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
36622
36645
  var agentPoolSnapshotSchema = external_exports.object({
@@ -36824,6 +36847,14 @@ var mrrlinExecutionRunStatusSchema = external_exports.enum([
36824
36847
  "handoff_queued",
36825
36848
  "cancelled"
36826
36849
  ]);
36850
+ var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
36851
+ "local_checkout_missing",
36852
+ "task_blocked",
36853
+ "default_branch_unresolved",
36854
+ "execution_turn_timeout",
36855
+ "deliverable_gate_exhausted",
36856
+ "unknown"
36857
+ ]);
36827
36858
  var mrrlinExecutionArtifactKindSchema = external_exports.enum([
36828
36859
  "plan",
36829
36860
  "checkpoint",
@@ -36838,7 +36869,8 @@ var mrrlinExecutionArtifactKindSchema = external_exports.enum([
36838
36869
  "verification",
36839
36870
  "produced_url",
36840
36871
  "resume_spec",
36841
- "deliverable"
36872
+ "deliverable",
36873
+ "pull_request_state"
36842
36874
  ]);
36843
36875
  var mrrlinDeployAttemptPayloadSchema = external_exports.object({
36844
36876
  env: external_exports.enum(["dev", "prod"]),
@@ -36991,6 +37023,27 @@ var mrrlinGithubPullRequestSchema = external_exports.object({
36991
37023
  title: external_exports.string().min(1),
36992
37024
  url: external_exports.string().url()
36993
37025
  });
37026
+ var mrrlinGithubPullRequestCheckStateSchema = external_exports.enum([
37027
+ "passing",
37028
+ "failing",
37029
+ "running",
37030
+ "none",
37031
+ "unknown"
37032
+ ]);
37033
+ var mrrlinGithubPullRequestCheckSourceSchema = external_exports.enum(["check_run", "commit_status"]);
37034
+ var mrrlinGithubPullRequestObservedSchema = external_exports.object({
37035
+ checks: mrrlinGithubPullRequestCheckStateSchema,
37036
+ failingChecks: external_exports.array(external_exports.object({
37037
+ name: external_exports.string().min(1),
37038
+ source: mrrlinGithubPullRequestCheckSourceSchema
37039
+ })),
37040
+ headSha: external_exports.string().min(1),
37041
+ merged: external_exports.boolean(),
37042
+ mergedAt: external_exports.string().datetime().nullable(),
37043
+ number: external_exports.number().int().positive(),
37044
+ state: external_exports.string().min(1),
37045
+ url: external_exports.string().url()
37046
+ });
36994
37047
  var mrrlinGithubWorkflowSchema = external_exports.object({
36995
37048
  id: external_exports.union([external_exports.number().int().positive(), external_exports.string().min(1)]),
36996
37049
  name: external_exports.string().min(1),
@@ -37287,6 +37340,9 @@ var githubCreatePullRequestRequestSchema = external_exports.object({
37287
37340
  var githubCreatePullRequestResponseSchema = external_exports.object({
37288
37341
  data: mrrlinGithubPullRequestSchema
37289
37342
  });
37343
+ var githubPullRequestNumberParamSchema = external_exports.object({
37344
+ pullNumber: external_exports.coerce.number().int().positive()
37345
+ });
37290
37346
  var githubWorkflowIdParamSchema = external_exports.object({
37291
37347
  workflowId: external_exports.string().min(1)
37292
37348
  });
@@ -37300,6 +37356,9 @@ var githubWorkflowDispatchResponseSchema = external_exports.object({
37300
37356
  var githubWorkflowListResponseSchema = external_exports.object({
37301
37357
  data: external_exports.array(mrrlinGithubWorkflowSchema)
37302
37358
  });
37359
+ var githubPullRequestResponseSchema = external_exports.object({
37360
+ data: mrrlinGithubPullRequestObservedSchema
37361
+ });
37303
37362
  var githubWorkflowRunQuerySchema = external_exports.object({
37304
37363
  workflowId: external_exports.string().min(1),
37305
37364
  branch: external_exports.string().min(1),
@@ -37400,6 +37459,7 @@ var mrrlinExecutionRunSchema = external_exports.object({
37400
37459
  actor: external_exports.string().min(1),
37401
37460
  baseSha: external_exports.string().min(1).nullable(),
37402
37461
  branch: external_exports.string().min(1).nullable(),
37462
+ blockingReason: mrrlinRunBlockingReasonCodeSchema.nullable(),
37403
37463
  checkpointCursor: external_exports.string().min(1).nullable(),
37404
37464
  claimedAt: external_exports.string().datetime().nullable(),
37405
37465
  codeExecution: external_exports.boolean(),
@@ -37414,6 +37474,7 @@ var mrrlinExecutionRunSchema = external_exports.object({
37414
37474
  lastActor: external_exports.string().min(1).nullable(),
37415
37475
  leaseId: external_exports.string().min(1).nullable(),
37416
37476
  prerequisites: external_exports.array(mrrlinRunPrerequisiteSchema),
37477
+ prState: external_exports.enum(["open", "merged", "closed"]).optional(),
37417
37478
  prodEnvUrl: external_exports.string().min(1).nullable(),
37418
37479
  prodRunUrl: external_exports.string().min(1).nullable(),
37419
37480
  prodWorkflowId: external_exports.number().int().positive().nullable(),
@@ -37443,6 +37504,7 @@ var mrrlinExecutionRunCreateSchema = external_exports.object({
37443
37504
  var mrrlinExecutionRunUpdateSchema = external_exports.object({
37444
37505
  baseSha: external_exports.string().min(1).nullable().optional(),
37445
37506
  branch: external_exports.string().min(1).nullable().optional(),
37507
+ blockingReason: mrrlinRunBlockingReasonCodeSchema.nullable().optional(),
37446
37508
  checkpointCursor: external_exports.string().min(1).nullable().optional(),
37447
37509
  devEnvUrl: external_exports.string().min(1).nullable().optional(),
37448
37510
  devRunUrl: external_exports.string().min(1).nullable().optional(),
@@ -37469,6 +37531,7 @@ var mrrlinResumeSpecPayloadSchema = external_exports.object({
37469
37531
  class: external_exports.literal("tool_assisted"),
37470
37532
  question: external_exports.string().min(1),
37471
37533
  prerequisites: external_exports.array(external_exports.object({ mcp: external_exports.string().min(1), reason: external_exports.string().min(1) })).default([]),
37534
+ reasonCode: mrrlinRunBlockingReasonCodeSchema.optional(),
37472
37535
  steps: external_exports.array(external_exports.string().min(1)).min(1),
37473
37536
  runId: mrrlinExecutionRunIdSchema,
37474
37537
  artifactId: mrrlinExecutionArtifactIdSchema,
@@ -37498,7 +37561,13 @@ var mrrlinResolveHandoffSchema = external_exports.object({
37498
37561
  }
37499
37562
  });
37500
37563
  var resolveHandoffResponseSchema = external_exports.object({ data: mrrlinExecutionRunSchema });
37501
- var createExecutionRunHandoffSchema = external_exports.object({ reason: external_exports.string().min(1) });
37564
+ var createExecutionRunHandoffSchema = external_exports.object({
37565
+ reason: external_exports.string().min(1),
37566
+ reasonCode: mrrlinRunBlockingReasonCodeSchema.optional()
37567
+ });
37568
+ var mrrlinRestartExecutionRunSchema = external_exports.object({
37569
+ restartedBy: external_exports.string().min(1)
37570
+ });
37502
37571
  var mrrlinExecutionRunTouchSchema = external_exports.object({
37503
37572
  leaseId: external_exports.string().min(1)
37504
37573
  });
@@ -38363,6 +38432,10 @@ function createMrrlinClient(config2) {
38363
38432
  const body = await request(`${projectPath(projectSlug)}/github/workflow-runs/latest?${params.toString()}`);
38364
38433
  return githubWorkflowRunResponseSchema.parse(body).data;
38365
38434
  },
38435
+ async getGithubPullRequest(projectSlug, pullNumber) {
38436
+ const body = await request(`${projectPath(projectSlug)}/github/pull-requests/${encodeURIComponent(String(pullNumber))}`);
38437
+ return githubPullRequestResponseSchema.parse(body).data;
38438
+ },
38366
38439
  async getGithubLatestDeployment(projectSlug, query) {
38367
38440
  const params = new URLSearchParams({ environment: query.environment });
38368
38441
  const body = await request(`${projectPath(projectSlug)}/github/deployments/latest?${params.toString()}`);
@@ -38696,7 +38769,7 @@ function createMrrlinClient(config2) {
38696
38769
  async requeueExecutionRun(projectSlug, runId, staleSeconds) {
38697
38770
  try {
38698
38771
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/requeue`, {
38699
- body: JSON.stringify({ staleSeconds }),
38772
+ body: JSON.stringify(mrrlinExecutionRunRequeueSchema.parse({ staleSeconds })),
38700
38773
  headers: { "content-type": "application/json" },
38701
38774
  method: "POST"
38702
38775
  });
@@ -38707,6 +38780,28 @@ function createMrrlinClient(config2) {
38707
38780
  throw error51;
38708
38781
  }
38709
38782
  },
38783
+ async reclaimExecutionRunLease(projectSlug, runId, staleSeconds) {
38784
+ try {
38785
+ const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/reclaim-lease`, {
38786
+ body: JSON.stringify(mrrlinExecutionRunRequeueSchema.parse({ staleSeconds })),
38787
+ headers: { "content-type": "application/json" },
38788
+ method: "POST"
38789
+ });
38790
+ return executionRunResponseSchema.parse(body).data;
38791
+ } catch (error51) {
38792
+ if (error51 instanceof MrrlinApiError && error51.status === 409 && error51.code === "RUN_LEASE_NOT_STALE")
38793
+ return null;
38794
+ throw error51;
38795
+ }
38796
+ },
38797
+ async restartExecutionRun(projectSlug, runId, input) {
38798
+ const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/restart`, {
38799
+ body: JSON.stringify(mrrlinRestartExecutionRunSchema.parse(input)),
38800
+ headers: { "content-type": "application/json" },
38801
+ method: "POST"
38802
+ });
38803
+ return executionRunResponseSchema.parse(body).data;
38804
+ },
38710
38805
  async touchExecutionRun(projectSlug, runId, leaseId) {
38711
38806
  try {
38712
38807
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/touch`, {
@@ -38841,8 +38936,14 @@ function createMrrlinClient(config2) {
38841
38936
  });
38842
38937
  return operatorSettingsResponseSchema.parse(body).data;
38843
38938
  },
38844
- async recordOperatorPresence() {
38845
- const body = await request(`/operator/presence`, { method: "POST" });
38939
+ async recordOperatorPresence(input) {
38940
+ const projectSlug = input?.projectSlug;
38941
+ const body = await request(`/operator/presence`, {
38942
+ method: "POST",
38943
+ // Omit the body entirely when no slug is given — background tabs beat bodyless today and
38944
+ // must continue to (see design.md deploy-skew probe).
38945
+ ...projectSlug !== void 0 ? { body: JSON.stringify({ projectSlug }), headers: { "content-type": "application/json" } } : {}
38946
+ });
38846
38947
  return operatorPresenceResponseSchema.parse(body).data;
38847
38948
  },
38848
38949
  async getOperatorAgentPool() {
@@ -39359,7 +39460,7 @@ function startCodexProbe(deps = {}) {
39359
39460
  }
39360
39461
 
39361
39462
  // src/_generated/version.ts
39362
- var PKG_VERSION = "0.3.55";
39463
+ var PKG_VERSION = "0.3.57";
39363
39464
 
39364
39465
  // src/api-base-url-migrate.ts
39365
39466
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -42186,6 +42287,7 @@ ${lines2.join("\n")}`;
42186
42287
 
42187
42288
  // src/execution-consumer.ts
42188
42289
  var DEFAULT_STALE_SECONDS = 300;
42290
+ var DEFAULT_MAX_LEASE_RECLAIMS = 3;
42189
42291
  var DEFAULT_TOUCH_INTERVAL_MS = 6e4;
42190
42292
  var DEFAULT_CLAIM_INTERVAL_SECONDS = 30;
42191
42293
  var DEFAULT_EXEC_TURN_IDLE_TIMEOUT_SECONDS = 600;
@@ -42276,8 +42378,8 @@ function buildCompletionTurnPrompt(taskId, currentStatus) {
42276
42378
  "If the work is NOT complete \u2014 be honest, do not close incomplete work:",
42277
42379
  "1. Call update_task with the status that matches why it is not complete, and explain what remains",
42278
42380
  " in the notes:",
42279
- " - if the code work itself is done and only a PR or merge remains, use status=awaiting_verification;",
42280
- " - if you are blocked and need an operator decision before the work can continue, use status=needs_human.",
42381
+ " - if the code work itself is done and only an open PR still awaits operator merge or follow-up, use status=awaiting_verification;",
42382
+ " - if you are blocked and need an operator decision before the work can continue, use status=handoff_needed.",
42281
42383
  "2. Append a handoff_judgement artifact explaining what the operator needs to do.",
42282
42384
  "",
42283
42385
  "You must pick one of these paths \u2014 do not leave the task in_progress."
@@ -42301,9 +42403,9 @@ var BASE_INSTRUCTIONS_PREFIX = [
42301
42403
  "- For a CODE task the run opens a draft PR for you after your turn; opening that PR is NOT",
42302
42404
  " completion. Do NOT set closed_done for code work \u2014 leave the task open; the run parks at",
42303
42405
  " `pull_request` (waiting for merge) and the operator merges. Closure happens after merge.",
42304
- "- Pick the right open status when you stop a CODE task: if the code work itself is done and only a",
42305
- " PR or merge remains, leave the task at `awaiting_verification`; if you are blocked and a human",
42306
- " must decide before the work can continue, leave it at `needs_human`.",
42406
+ "- Pick the right open status when you stop a CODE task: if the code work itself is done and only an",
42407
+ " already-open PR still awaits operator merge or follow-up, leave the task at `awaiting_verification`;",
42408
+ " if you are blocked and a human must decide before the work can continue, leave it at `handoff_needed`.",
42307
42409
  "",
42308
42410
  "File artifacts:",
42309
42411
  "- When your output is a file (markdown report, HTML, screenshot), upload it with",
@@ -42549,14 +42651,19 @@ async function assertRunCompletionReady(deps, projectSlug, taskId, runId, allowe
42549
42651
  task
42550
42652
  };
42551
42653
  }
42552
- const artifacts = await deps.client.listExecutionArtifacts(projectSlug, runId);
42553
- const hasEvidence = artifacts.some(
42554
- (artifact) => COMPLETION_EVIDENCE_KINDS.has(artifact.kind) && !isBridgeGeneratedArtifact(artifact)
42654
+ const runs = await deps.client.listExecutionRuns(projectSlug, { taskId });
42655
+ const runIds = new Set(runs.map((run3) => run3.id));
42656
+ runIds.add(runId);
42657
+ const artifactsNested = await Promise.all(
42658
+ [...runIds].map(async (candidateRunId) => deps.client.listExecutionArtifacts(projectSlug, candidateRunId))
42659
+ );
42660
+ const hasEvidence = artifactsNested.flat().some(
42661
+ (artifact) => COMPLETION_EVIDENCE_KINDS.has(artifact.kind) && !isBridgeGeneratedArtifact(artifact) && runIds.has(artifact.runId)
42555
42662
  );
42556
42663
  if (!hasEvidence) {
42557
42664
  return {
42558
42665
  ok: false,
42559
- reason: `Codex turn completed and task ${task.id} closed, but run ${runId} has no durable completion evidence artifact.`,
42666
+ reason: `Codex turn completed and task ${task.id} closed, but task lineage for run ${runId} has no durable completion evidence artifact.`,
42560
42667
  task
42561
42668
  };
42562
42669
  }
@@ -42699,7 +42806,8 @@ async function driveRun(deps, projectSlug, runId) {
42699
42806
  task,
42700
42807
  runId,
42701
42808
  leaseId,
42702
- `task_blocked: run ${runId} targets ${task.id} but blocker tasks could not be loaded: ${missingBlockers.join(", ")}`
42809
+ `task_blocked: run ${runId} targets ${task.id} but blocker tasks could not be loaded: ${missingBlockers.join(", ")}`,
42810
+ "task_blocked"
42703
42811
  );
42704
42812
  return;
42705
42813
  }
@@ -42711,7 +42819,8 @@ async function driveRun(deps, projectSlug, runId) {
42711
42819
  task,
42712
42820
  runId,
42713
42821
  leaseId,
42714
- `task_blocked: run ${runId} targets ${task.id} but active blockers remain open: ${activeBlockers.join(", ")}`
42822
+ `task_blocked: run ${runId} targets ${task.id} but active blockers remain open: ${activeBlockers.join(", ")}`,
42823
+ "task_blocked"
42715
42824
  );
42716
42825
  return;
42717
42826
  }
@@ -42748,7 +42857,7 @@ async function driveRun(deps, projectSlug, runId) {
42748
42857
  2. If you find a likely match, tell the operator where you think it is and ask them to confirm \u2014 e.g. "For code changes I need this project's repo checked out here. I think it's at <path> \u2014 should I register that one, or do you want to point me at a different path?"
42749
42858
  3. When the operator confirms a path, call register_local_checkout({ projectSlug: "${projectSlug}", path: "<absolute path>", repo: "<this project's GitHub repo>" }).
42750
42859
  4. If no candidate is found, ask the operator for the absolute path to the checkout (or to clone the repo first), then register it. Once a checkout is registered, the run can be re-queued.`;
42751
- await failRun(deps, projectSlug, task, runId, leaseId, reason);
42860
+ await failRun(deps, projectSlug, task, runId, leaseId, reason, "local_checkout_missing");
42752
42861
  return;
42753
42862
  }
42754
42863
  try {
@@ -42782,7 +42891,15 @@ async function driveRun(deps, projectSlug, runId) {
42782
42891
  } catch (error51) {
42783
42892
  const detail = error51 instanceof Error ? error51.message : String(error51);
42784
42893
  const reason = `worktree_setup_failed: run ${runId} could not provision a code-execution worktree for project ${projectSlug}: ${detail}. (A missing default branch surfaces here as default_branch_unresolved; the operator must resolve the repo state before re-queuing.)`;
42785
- await failRun(deps, projectSlug, task, runId, leaseId, reason);
42894
+ await failRun(
42895
+ deps,
42896
+ projectSlug,
42897
+ task,
42898
+ runId,
42899
+ leaseId,
42900
+ reason,
42901
+ detail.includes("default_branch_unresolved") ? "default_branch_unresolved" : void 0
42902
+ );
42786
42903
  return;
42787
42904
  }
42788
42905
  } else {
@@ -42821,7 +42938,15 @@ async function driveRun(deps, projectSlug, runId) {
42821
42938
  if (!resumed) {
42822
42939
  const primingBudgetMs = remainingTurnBudgetMs();
42823
42940
  if (primingBudgetMs <= 0) {
42824
- await failRun(deps, projectSlug, task, runId, leaseId, executionTurnBudgetMessage(execRunMaxMs()));
42941
+ await failRun(
42942
+ deps,
42943
+ projectSlug,
42944
+ task,
42945
+ runId,
42946
+ leaseId,
42947
+ executionTurnBudgetMessage(execRunMaxMs()),
42948
+ "execution_turn_timeout"
42949
+ );
42825
42950
  return;
42826
42951
  }
42827
42952
  const primed = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, bundle, primingBudgetMs, "priming");
@@ -42836,7 +42961,15 @@ async function driveRun(deps, projectSlug, runId) {
42836
42961
  Continue executing task ${task.id}. Make the next concrete increment of progress and record artifacts/judgements via MCP tools.` : `Continue executing task ${task.id}. Make the next concrete increment of progress and record artifacts/judgements via MCP tools.`;
42837
42962
  const workBudgetMs = remainingTurnBudgetMs();
42838
42963
  if (workBudgetMs <= 0) {
42839
- await failRun(deps, projectSlug, task, runId, leaseId, executionTurnBudgetMessage(execRunMaxMs()));
42964
+ await failRun(
42965
+ deps,
42966
+ projectSlug,
42967
+ task,
42968
+ runId,
42969
+ leaseId,
42970
+ executionTurnBudgetMessage(execRunMaxMs()),
42971
+ "execution_turn_timeout"
42972
+ );
42840
42973
  return;
42841
42974
  }
42842
42975
  const outcome = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, executionPrompt, workBudgetMs, "work");
@@ -43003,6 +43136,13 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
43003
43136
  pullRequestUrl: pr.url,
43004
43137
  status: "dev_deploy"
43005
43138
  });
43139
+ await reconcileTaskAwaitingMerge(
43140
+ deps,
43141
+ projectSlug,
43142
+ readiness.task,
43143
+ runId,
43144
+ `PR opened (${pr.url}); deploy gate is running, but the operator still owns the merge decision.`
43145
+ );
43006
43146
  } else {
43007
43147
  await deps.client.updateExecutionRun(projectSlug, runId, {
43008
43148
  leaseId,
@@ -43258,7 +43398,7 @@ async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
43258
43398
  await deps.client.resetDirectorThread(projectSlug, taskId).catch(() => {
43259
43399
  });
43260
43400
  }
43261
- async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
43401
+ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCode) {
43262
43402
  let currentRun;
43263
43403
  try {
43264
43404
  currentRun = await deps.client.getExecutionRun(projectSlug, runId);
@@ -43317,13 +43457,21 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
43317
43457
  }
43318
43458
  }
43319
43459
  try {
43320
- await deps.client.createExecutionRunHandoff(projectSlug, runId, { reason });
43460
+ await deps.client.createExecutionRunHandoff(projectSlug, runId, {
43461
+ reason,
43462
+ ...reasonCode ? { reasonCode } : {}
43463
+ });
43321
43464
  } catch {
43322
43465
  await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
43323
43466
  }
43324
43467
  }
43325
43468
  async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
43326
43469
  if (task.status === "handoff_needed") return;
43470
+ const run3 = await deps.client.getExecutionRun(projectSlug, runId).catch(() => null);
43471
+ if (run3?.prState === "merged") {
43472
+ console.log(JSON.stringify({ event: "pr_merge_already_observed", projectSlug, runId, taskId: task.id }));
43473
+ return;
43474
+ }
43327
43475
  await deps.client.appendExecutionArtifact(projectSlug, runId, {
43328
43476
  id: newArtifactId(),
43329
43477
  kind: "handoff_judgement",
@@ -43425,6 +43573,27 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
43425
43573
  pending = [];
43426
43574
  }
43427
43575
  for (const run3 of pending) {
43576
+ const updatedAtMs = run3.updatedAt ? Date.parse(run3.updatedAt) : Number.NaN;
43577
+ const staleBeforeMs = Date.now() - deps.staleSeconds * 1e3;
43578
+ if (run3.leaseId) {
43579
+ if (!Number.isFinite(updatedAtMs) || updatedAtMs >= staleBeforeMs) continue;
43580
+ try {
43581
+ const reclaimed = await deps.client.reclaimExecutionRunLease(slug, run3.id, deps.staleSeconds);
43582
+ if (!reclaimed) continue;
43583
+ } catch (error51) {
43584
+ if (error51 instanceof MrrlinApiError && error51.status === 404) continue;
43585
+ if (error51 instanceof MrrlinApiError && error51.status === 409 && error51.code === "RUN_LEASE_RECLAIM_EXHAUSTED") {
43586
+ await deps.client.createInboxItem(slug, {
43587
+ id: `II-${run3.id}-lease-stuck`,
43588
+ kind: "handoff",
43589
+ taskId: run3.taskId,
43590
+ body: `Execution run ${run3.id} exceeded its stale-lease reclaim budget (${maxLeaseReclaims()} attempts) and still is not making progress.`
43591
+ }).catch(() => {
43592
+ });
43593
+ }
43594
+ continue;
43595
+ }
43596
+ }
43428
43597
  await drive(slug, run3.id);
43429
43598
  }
43430
43599
  if (deps.deployGate) {
@@ -43493,6 +43662,9 @@ function claimIntervalMs() {
43493
43662
  const jitter = base * 0.25 * (Math.random() * 2 - 1);
43494
43663
  return Math.round((base + jitter) * 1e3);
43495
43664
  }
43665
+ function maxLeaseReclaims() {
43666
+ return readPositiveIntEnv("MRRLIN_MAX_LEASE_RECLAIMS", DEFAULT_MAX_LEASE_RECLAIMS);
43667
+ }
43496
43668
 
43497
43669
  // src/async-job-consumer.ts
43498
43670
  var import_node_crypto6 = __toESM(require("node:crypto"), 1);
@@ -47950,6 +48122,103 @@ var CheckoutRegistry = class {
47950
48122
  }
47951
48123
  };
47952
48124
 
48125
+ // src/checkout-handlers.ts
48126
+ var import_node_child_process10 = require("node:child_process");
48127
+ var import_node_fs12 = require("node:fs");
48128
+
48129
+ // src/git-remote-match.ts
48130
+ function normalizeRemote(url2) {
48131
+ const trimmed = url2.trim().replace(/\.git$/i, "");
48132
+ const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
48133
+ const pathPart = scp ? scp[1] : (() => {
48134
+ try {
48135
+ return new URL(trimmed).pathname.replace(/^\/+/, "");
48136
+ } catch {
48137
+ return null;
48138
+ }
48139
+ })();
48140
+ if (!pathPart) return null;
48141
+ const segs = pathPart.split("/").filter(Boolean);
48142
+ if (segs.length < 2) return null;
48143
+ return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
48144
+ }
48145
+ function remoteMatchesRepo(remoteUrl, repoFullName) {
48146
+ const a = normalizeRemote(remoteUrl);
48147
+ return a !== null && a === repoFullName.trim().toLowerCase();
48148
+ }
48149
+
48150
+ // src/checkout-handlers.ts
48151
+ function validateCheckoutPath(checkoutPath, repo) {
48152
+ const trimmedPath = checkoutPath.trim();
48153
+ if (!trimmedPath.startsWith("/")) {
48154
+ return {
48155
+ ok: false,
48156
+ code: "not_absolute",
48157
+ detail: `Path must be absolute, got: ${trimmedPath}`
48158
+ };
48159
+ }
48160
+ let resolvedPath;
48161
+ try {
48162
+ resolvedPath = (0, import_node_fs12.realpathSync)(trimmedPath);
48163
+ } catch {
48164
+ return {
48165
+ ok: false,
48166
+ code: "not_found",
48167
+ detail: `Path does not exist or is not accessible: ${trimmedPath}`
48168
+ };
48169
+ }
48170
+ let remoteUrl;
48171
+ try {
48172
+ remoteUrl = (0, import_node_child_process10.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
48173
+ encoding: "utf8",
48174
+ stdio: ["ignore", "pipe", "ignore"],
48175
+ timeout: 15e3,
48176
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
48177
+ }).trim();
48178
+ } catch {
48179
+ return {
48180
+ ok: false,
48181
+ code: "not_a_git_repo",
48182
+ detail: `Path is not a git repository or has no origin remote: ${resolvedPath}`
48183
+ };
48184
+ }
48185
+ if (!remoteMatchesRepo(remoteUrl, repo)) {
48186
+ return {
48187
+ ok: false,
48188
+ code: "remote_mismatch",
48189
+ detail: `Origin remote ${remoteUrl} does not match repo ${repo}.`
48190
+ };
48191
+ }
48192
+ return { ok: true, resolvedPath };
48193
+ }
48194
+ function readCheckoutState(registry3, projectSlug, machineLabel) {
48195
+ const entry = registry3.get(projectSlug);
48196
+ if (!entry) return null;
48197
+ return checkoutStateData(projectSlug, entry, machineLabel);
48198
+ }
48199
+ function registerCheckout(registry3, input, machineLabel, now = (/* @__PURE__ */ new Date()).toISOString()) {
48200
+ const validated = validateCheckoutPath(input.path, input.repo);
48201
+ if (!validated.ok) return validated;
48202
+ registry3.confirm(input.projectSlug, validated.resolvedPath, now);
48203
+ return {
48204
+ ok: true,
48205
+ data: {
48206
+ projectSlug: input.projectSlug,
48207
+ path: validated.resolvedPath,
48208
+ confirmedAt: now,
48209
+ machineLabel
48210
+ }
48211
+ };
48212
+ }
48213
+ function checkoutStateData(projectSlug, entry, machineLabel) {
48214
+ return {
48215
+ projectSlug,
48216
+ path: entry.path,
48217
+ confirmedAt: entry.confirmedAt,
48218
+ machineLabel
48219
+ };
48220
+ }
48221
+
47953
48222
  // src/executor-server.ts
47954
48223
  async function startExecutorServer(opts) {
47955
48224
  const manager = new CodexServerManager();
@@ -48157,7 +48426,7 @@ function extractVerificationSection(markdown) {
48157
48426
  }
48158
48427
 
48159
48428
  // src/bridge-log.ts
48160
- var import_node_fs12 = require("node:fs");
48429
+ var import_node_fs13 = require("node:fs");
48161
48430
  var import_node_path16 = require("node:path");
48162
48431
 
48163
48432
  // src/redact.ts
@@ -48245,7 +48514,7 @@ function createBridgeLogger(stateDir, env = process.env) {
48245
48514
  if (!resolveBridgeLogEnabled(env)) return NOOP_LOGGER;
48246
48515
  const logsDir = (0, import_node_path16.join)(stateDir, "logs");
48247
48516
  try {
48248
- (0, import_node_fs12.mkdirSync)(logsDir, { recursive: true });
48517
+ (0, import_node_fs13.mkdirSync)(logsDir, { recursive: true });
48249
48518
  } catch {
48250
48519
  return NOOP_LOGGER;
48251
48520
  }
@@ -48262,7 +48531,7 @@ function createBridgeLogger(stateDir, env = process.env) {
48262
48531
  ...rec.ms !== void 0 ? { ms: rec.ms } : {},
48263
48532
  payload: redactMessage(rec.payload, { includePrompts })
48264
48533
  }) + "\n";
48265
- (0, import_node_fs12.appendFileSync)(fileFor(), line);
48534
+ (0, import_node_fs13.appendFileSync)(fileFor(), line);
48266
48535
  } catch {
48267
48536
  }
48268
48537
  };
@@ -48275,7 +48544,7 @@ function createBridgeLogger(stateDir, env = process.env) {
48275
48544
  }
48276
48545
 
48277
48546
  // src/external-agents-scrub.ts
48278
- var import_node_fs13 = __toESM(require("node:fs"), 1);
48547
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
48279
48548
  var import_node_os8 = __toESM(require("node:os"), 1);
48280
48549
  var import_node_path17 = __toESM(require("node:path"), 1);
48281
48550
  var REDACTION = "[REDACTED]";
@@ -48286,7 +48555,7 @@ function readKeysEnv() {
48286
48555
  const filePath = KEYS_ENV_PATH();
48287
48556
  let mtimeMs = 0;
48288
48557
  try {
48289
- mtimeMs = import_node_fs13.default.statSync(filePath).mtimeMs;
48558
+ mtimeMs = import_node_fs14.default.statSync(filePath).mtimeMs;
48290
48559
  } catch {
48291
48560
  mtimeMs = 0;
48292
48561
  }
@@ -48295,7 +48564,7 @@ function readKeysEnv() {
48295
48564
  }
48296
48565
  const map2 = /* @__PURE__ */ new Map();
48297
48566
  try {
48298
- const content = import_node_fs13.default.readFileSync(filePath, "utf8");
48567
+ const content = import_node_fs14.default.readFileSync(filePath, "utf8");
48299
48568
  for (const rawLine of content.split(/\r?\n/)) {
48300
48569
  const line = rawLine.trim();
48301
48570
  if (line === "" || line.startsWith("#")) continue;
@@ -48638,7 +48907,9 @@ var DEFAULT_DIRECTOR_BRIDGE_ALLOWED_ORIGINS = [
48638
48907
  "http://[::1]:*",
48639
48908
  // Staging Vercel team migrated 2026-07-28 (see docs/reference/deployments.md). The prior
48640
48909
  // fnnzzz-fnnzzzs-projects web alias was dropped once no operator was left on an old build.
48910
+ // mrrlin2 hit usage limits and was replaced by mrrlin3 on 2026-08-11.
48641
48911
  "https://mrrlin-web-mrrlin2.vercel.app",
48912
+ "https://mrrlin-web-mrrlin3.vercel.app",
48642
48913
  "https://app.mrrlin.dev",
48643
48914
  "https://mrrlin-web.vercel.app"
48644
48915
  ];
@@ -48687,7 +48958,7 @@ function addBridgeModel(target, model) {
48687
48958
  }
48688
48959
  function readTextIfExists(filePath) {
48689
48960
  try {
48690
- return import_node_fs14.default.readFileSync(filePath, "utf8");
48961
+ return import_node_fs15.default.readFileSync(filePath, "utf8");
48691
48962
  } catch {
48692
48963
  return null;
48693
48964
  }
@@ -48766,7 +49037,7 @@ function readCodexFallbackModels() {
48766
49037
  }
48767
49038
  const automationsDir = import_node_path18.default.join(codexHome, "automations");
48768
49039
  try {
48769
- for (const entry of import_node_fs14.default.readdirSync(automationsDir, { withFileTypes: true })) {
49040
+ for (const entry of import_node_fs15.default.readdirSync(automationsDir, { withFileTypes: true })) {
48770
49041
  if (!entry.isDirectory()) continue;
48771
49042
  for (const slug of readKnownGptSlugsFromTomlFile(import_node_path18.default.join(automationsDir, entry.name, "automation.toml"))) {
48772
49043
  addBridgeModel(
@@ -48790,7 +49061,7 @@ function readCodexFallbackModels() {
48790
49061
  function neutralCheckoutCwd(stateDir) {
48791
49062
  const dir = import_node_path18.default.join(stateDir, "no-checkout");
48792
49063
  try {
48793
- import_node_fs14.default.mkdirSync(dir, { recursive: true });
49064
+ import_node_fs15.default.mkdirSync(dir, { recursive: true });
48794
49065
  } catch {
48795
49066
  }
48796
49067
  return dir;
@@ -49111,7 +49382,7 @@ function resolveDefaultBranch(checkoutPath) {
49111
49382
  if (!checkoutPath) return "main";
49112
49383
  const runGit = (args) => {
49113
49384
  try {
49114
- return (0, import_node_child_process10.execFileSync)("git", ["-C", checkoutPath, ...args], {
49385
+ return (0, import_node_child_process11.execFileSync)("git", ["-C", checkoutPath, ...args], {
49115
49386
  encoding: "utf8",
49116
49387
  stdio: ["ignore", "pipe", "ignore"],
49117
49388
  timeout: 15e3,
@@ -49143,7 +49414,7 @@ async function runOrphanWorktreeSweep(client, opts) {
49143
49414
  const cutoff = Date.now() - worktreeRetentionMs();
49144
49415
  const isReapable = (worktreePath) => {
49145
49416
  try {
49146
- return import_node_fs14.default.statSync(worktreePath).mtimeMs < cutoff;
49417
+ return import_node_fs15.default.statSync(worktreePath).mtimeMs < cutoff;
49147
49418
  } catch {
49148
49419
  return false;
49149
49420
  }
@@ -49447,7 +49718,9 @@ function createBridgeMessageHandler(deps) {
49447
49718
  logResumeRejected(req.directorSessionId, reason);
49448
49719
  return;
49449
49720
  }
49450
- const buf = t?.replay ?? null;
49721
+ const parked = t?.supersededReplay ?? null;
49722
+ const buf = req.acceptSuperseded === true && parked && parked.turnId === req.turnId ? parked : t?.replay ?? null;
49723
+ const servingSuperseded = buf !== null && buf === parked;
49451
49724
  if (!buf) {
49452
49725
  sendMsg({ type: "resume", status: "no-buffer", reason: "wrong-session" });
49453
49726
  logResumeRejected(req.directorSessionId, "wrong-session");
@@ -49478,15 +49751,17 @@ function createBridgeMessageHandler(deps) {
49478
49751
  buf.state = result.next;
49479
49752
  incrementBindEpoch(buf);
49480
49753
  buf.replaying = true;
49481
- deps.bindSessionSink?.(req.directorSessionId, send);
49482
- boundSessions.add(req.directorSessionId);
49483
- if (buf.triggerDetach) {
49484
- const detach = buf.triggerDetach;
49485
- const epochAtBind = buf.bindEpoch;
49486
- socket.once("close", () => {
49487
- if (buf.bindEpoch !== epochAtBind) return;
49488
- detach();
49489
- });
49754
+ if (!servingSuperseded) {
49755
+ deps.bindSessionSink?.(req.directorSessionId, send);
49756
+ boundSessions.add(req.directorSessionId);
49757
+ if (buf.triggerDetach) {
49758
+ const detach = buf.triggerDetach;
49759
+ const epochAtBind = buf.bindEpoch;
49760
+ socket.once("close", () => {
49761
+ if (buf.bindEpoch !== epochAtBind) return;
49762
+ detach();
49763
+ });
49764
+ }
49490
49765
  }
49491
49766
  const willStreamLive = result.next === "live";
49492
49767
  sendMsg({
@@ -49563,7 +49838,8 @@ function createBridgeMessageHandler(deps) {
49563
49838
  }
49564
49839
  const msg = parsed;
49565
49840
  const sid = sessionIdOf(msg);
49566
- if (sid && deps.bindSessionSink && !boundSessions.has(sid)) {
49841
+ const skipEagerBindForSupersededResume = msg.type === "resume" && msg.acceptSuperseded === true;
49842
+ if (sid && deps.bindSessionSink && !boundSessions.has(sid) && !skipEagerBindForSupersededResume) {
49567
49843
  deps.bindSessionSink(sid, send);
49568
49844
  boundSessions.add(sid);
49569
49845
  }
@@ -49585,6 +49861,57 @@ function createBridgeMessageHandler(deps) {
49585
49861
  sendForSpan({ type: "pairing-qr", ...result });
49586
49862
  return;
49587
49863
  }
49864
+ if (msg.type === "checkout-state") {
49865
+ if (!deps.checkoutRegistry) {
49866
+ sendForSpan({ type: "checkout-state", ok: false, error: { code: "bridge_offline", detail: "checkout registry is not configured on this bridge" } });
49867
+ return;
49868
+ }
49869
+ const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
49870
+ if (!projectSlug) {
49871
+ sendForSpan({ type: "checkout-state", ok: false, error: { code: "invalid_input", fields: { projectSlug: "required" } } });
49872
+ return;
49873
+ }
49874
+ const state = readCheckoutState(deps.checkoutRegistry, projectSlug, import_node_os9.default.hostname());
49875
+ if (!state) {
49876
+ sendForSpan({ type: "checkout-state", ok: false, error: { code: "checkout_missing" } });
49877
+ return;
49878
+ }
49879
+ sendForSpan({ type: "checkout-state", ok: true, data: state });
49880
+ return;
49881
+ }
49882
+ if (msg.type === "checkout-register") {
49883
+ if (!deps.checkoutRegistry) {
49884
+ sendForSpan({ type: "checkout-register", ok: false, error: { code: "bridge_offline", detail: "checkout registry is not configured on this bridge" } });
49885
+ return;
49886
+ }
49887
+ const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
49888
+ const repo = typeof msg.repo === "string" ? msg.repo.trim() : "";
49889
+ if (!projectSlug || !repo) {
49890
+ sendForSpan({
49891
+ type: "checkout-register",
49892
+ ok: false,
49893
+ error: {
49894
+ code: "invalid_input",
49895
+ fields: {
49896
+ ...!projectSlug ? { projectSlug: "required" } : {},
49897
+ ...!repo ? { repo: "required" } : {}
49898
+ }
49899
+ }
49900
+ });
49901
+ return;
49902
+ }
49903
+ const result = registerCheckout(
49904
+ deps.checkoutRegistry,
49905
+ { projectSlug, path: typeof msg.path === "string" ? msg.path : "", repo },
49906
+ import_node_os9.default.hostname()
49907
+ );
49908
+ if (!result.ok) {
49909
+ sendForSpan({ type: "checkout-register", ok: false, error: { code: result.code, detail: result.detail } });
49910
+ return;
49911
+ }
49912
+ sendForSpan({ type: "checkout-register", ok: true, data: result.data });
49913
+ return;
49914
+ }
49588
49915
  if (msg.type === "agents-state") {
49589
49916
  if (!deps.runExternalAgentsCli) {
49590
49917
  sendForSpan({ type: "agents-state", ok: false, error: { code: "bridge_offline", detail: "external-agents is not configured on this bridge" } });
@@ -49894,6 +50221,10 @@ function createBridgeMessageHandler(deps) {
49894
50221
  recordInterrupted(oldBuf, Date.now(), deps.suspendedMs?.() ?? 0);
49895
50222
  }
49896
50223
  }
50224
+ const droppedParked = takenOver.supersededReplay;
50225
+ if (oldBuf) {
50226
+ takenOver.supersededReplay = oldBuf;
50227
+ }
49897
50228
  takenOver.cancel?.();
49898
50229
  void withTimeout2(
49899
50230
  codexClient.turn.interrupt({ threadId: takenOver.threadId }),
@@ -49911,7 +50242,13 @@ function createBridgeMessageHandler(deps) {
49911
50242
  spanId,
49912
50243
  sessionId: directorSessionId,
49913
50244
  type: "<turn-takeover>",
49914
- payload: { sessionId: directorSessionId, clean: cleared, oldTurnId: oldBuf?.turnId ?? null }
50245
+ payload: {
50246
+ sessionId: directorSessionId,
50247
+ clean: cleared,
50248
+ oldTurnId: oldBuf?.turnId ?? null,
50249
+ parkedTurnId: oldBuf?.turnId ?? null,
50250
+ droppedParkedTurnId: droppedParked?.turnId ?? null
50251
+ }
49915
50252
  });
49916
50253
  }
49917
50254
  const desiredModel = normalizeModel(msg.model);
@@ -49923,10 +50260,12 @@ function createBridgeMessageHandler(deps) {
49923
50260
  threadId: "",
49924
50261
  mapper: new CodexBridgeEventMapper(),
49925
50262
  lastUsedAt: Date.now(),
50263
+ lastUsedAtSuspendedMs: deps.suspendedMs?.() ?? 0,
49926
50264
  inFlight: false,
49927
50265
  model: desiredModel,
49928
50266
  reasoningEffort: desiredEffort,
49929
- replay: null
50267
+ replay: null,
50268
+ supersededReplay: null
49930
50269
  };
49931
50270
  await acquireThread(codexClient, {
49932
50271
  threadId: null,
@@ -49992,6 +50331,7 @@ function createBridgeMessageHandler(deps) {
49992
50331
  ...deps.ackLingerMs !== void 0 ? { ackLingerMs: deps.ackLingerMs } : {}
49993
50332
  });
49994
50333
  thread.lastUsedAt = Date.now();
50334
+ thread.lastUsedAtSuspendedMs = deps.suspendedMs?.() ?? 0;
49995
50335
  const threadId = thread.threadId;
49996
50336
  const currentReplayBuf = thread.replay;
49997
50337
  sendForSpan({
@@ -50210,6 +50550,7 @@ ${effectiveText}` : effectiveText;
50210
50550
  thread.inFlight = false;
50211
50551
  }
50212
50552
  thread.lastUsedAt = Date.now();
50553
+ thread.lastUsedAtSuspendedMs = deps.suspendedMs?.() ?? 0;
50213
50554
  deps.persistThreads();
50214
50555
  deps.releaseCodex();
50215
50556
  }
@@ -50235,6 +50576,56 @@ function createBridgeShutdown(deps) {
50235
50576
  deps.exit(0);
50236
50577
  };
50237
50578
  }
50579
+ function shouldReapIdleThread(thread, now, suspendedMsNow, idleThreadTtlMs) {
50580
+ const suspendedSinceUse = Math.max(0, suspendedMsNow - thread.lastUsedAtSuspendedMs);
50581
+ return now - thread.lastUsedAt - suspendedSinceUse > idleThreadTtlMs;
50582
+ }
50583
+ function sweepIdleThreads(threads, opts) {
50584
+ const suspendedNow = opts.suspendedMs();
50585
+ const reaped = [];
50586
+ for (const [directorSessionId, thread] of threads.entries()) {
50587
+ if (thread.inFlight) continue;
50588
+ if (thread.replay && !shouldGc(thread.replay, opts.now, suspendedNow)) continue;
50589
+ if (thread.supersededReplay && !shouldGc(thread.supersededReplay, opts.now, suspendedNow)) continue;
50590
+ if (!shouldReapIdleThread(thread, opts.now, suspendedNow, opts.idleThreadTtlMs)) continue;
50591
+ threads.delete(directorSessionId);
50592
+ reaped.push(directorSessionId);
50593
+ }
50594
+ return reaped;
50595
+ }
50596
+ function sweepReplayBuffers(threads, tombstones, opts) {
50597
+ const suspendedNow = opts.suspendedMs();
50598
+ let retainedBuffers = 0;
50599
+ let retainedBytes = 0;
50600
+ for (const [directorSessionId, thread] of threads) {
50601
+ if (thread.replay && shouldGc(thread.replay, opts.now, suspendedNow)) {
50602
+ const turnId = thread.replay.turnId;
50603
+ const lastSeq = thread.replay.lastSeq;
50604
+ const expiredUnacked = thread.replay.ackedAt === null;
50605
+ writeTombstone(tombstones, directorSessionId, thread.replay, opts.now, suspendedNow);
50606
+ opts.onTombstone?.({ directorSessionId, turnId, lastSeq, expiredUnacked });
50607
+ thread.replay = null;
50608
+ } else if (thread.replay) {
50609
+ retainedBuffers += 1;
50610
+ retainedBytes += thread.replay.bytesUsed;
50611
+ }
50612
+ if (thread.supersededReplay && shouldGc(thread.supersededReplay, opts.now, suspendedNow)) {
50613
+ const turnId = thread.supersededReplay.turnId;
50614
+ thread.supersededReplay = null;
50615
+ opts.onSupersededDropped?.({ directorSessionId, turnId });
50616
+ } else if (thread.supersededReplay) {
50617
+ retainedBuffers += 1;
50618
+ retainedBytes += thread.supersededReplay.bytesUsed;
50619
+ }
50620
+ }
50621
+ const tombstonesRemoved = sweepExpiredTombstones(
50622
+ tombstones,
50623
+ opts.now,
50624
+ opts.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS,
50625
+ suspendedNow
50626
+ );
50627
+ return { retainedBuffers, retainedBytes, tombstonesRemoved };
50628
+ }
50238
50629
  function parseConfig() {
50239
50630
  const host = (process.env.MRRLIN_DIRECTOR_BRIDGE_HOST ?? "127.0.0.1").trim() || "127.0.0.1";
50240
50631
  const port = readEnvInt("MRRLIN_DIRECTOR_BRIDGE_PORT", 8791);
@@ -50248,7 +50639,7 @@ function parseConfig() {
50248
50639
  }
50249
50640
  function scanForOtherBridges() {
50250
50641
  try {
50251
- const result = (0, import_node_child_process10.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
50642
+ const result = (0, import_node_child_process11.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
50252
50643
  encoding: "utf8"
50253
50644
  });
50254
50645
  if (result.status !== 0) return [];
@@ -50428,13 +50819,13 @@ async function startDirectorBridge() {
50428
50819
  // egressDomains is retained in the seam signature (verification-turn.ts still computes the
50429
50820
  // env host) but no longer constrains anything — the executor is unrestricted.
50430
50821
  startSandbox: async (_egressDomains) => {
50431
- const cwd = import_node_fs14.default.mkdtempSync(import_node_path18.default.join(import_node_os9.default.tmpdir(), "mrrlin-verify-"));
50822
+ const cwd = import_node_fs15.default.mkdtempSync(import_node_path18.default.join(import_node_os9.default.tmpdir(), "mrrlin-verify-"));
50432
50823
  let handle;
50433
50824
  try {
50434
50825
  handle = await startExecutorServer({ worktree: cwd });
50435
50826
  } catch (error51) {
50436
50827
  try {
50437
- import_node_fs14.default.rmSync(cwd, { recursive: true, force: true });
50828
+ import_node_fs15.default.rmSync(cwd, { recursive: true, force: true });
50438
50829
  } catch {
50439
50830
  }
50440
50831
  throw error51;
@@ -50445,7 +50836,7 @@ async function startDirectorBridge() {
50445
50836
  dispose: () => {
50446
50837
  handle.dispose();
50447
50838
  try {
50448
- import_node_fs14.default.rmSync(cwd, { recursive: true, force: true });
50839
+ import_node_fs15.default.rmSync(cwd, { recursive: true, force: true });
50449
50840
  } catch {
50450
50841
  }
50451
50842
  }
@@ -50557,10 +50948,12 @@ async function startDirectorBridge() {
50557
50948
  threadId: entry.threadId,
50558
50949
  mapper: new CodexBridgeEventMapper(),
50559
50950
  lastUsedAt: entry.lastUsedAt,
50951
+ lastUsedAtSuspendedMs: suspensionDetector.suspendedMs(),
50560
50952
  inFlight: false,
50561
50953
  model: entry.model,
50562
50954
  reasoningEffort: normalizeReasoningEffort(entry.reasoningEffort),
50563
- replay: null
50955
+ replay: null,
50956
+ supersededReplay: null
50564
50957
  });
50565
50958
  }
50566
50959
  const persistThreads = () => {
@@ -50585,17 +50978,12 @@ async function startDirectorBridge() {
50585
50978
  }
50586
50979
  };
50587
50980
  const cleanupTimer = setInterval(() => {
50588
- const now = Date.now();
50589
- let changed = false;
50590
- for (const [directorSessionId, thread] of threads.entries()) {
50591
- if (thread.inFlight) continue;
50592
- if (thread.replay && !shouldGc(thread.replay, now, suspensionDetector.suspendedMs())) continue;
50593
- if (now - thread.lastUsedAt > config2.idleThreadTtlMs) {
50594
- threads.delete(directorSessionId);
50595
- changed = true;
50596
- }
50597
- }
50598
- if (changed) persistThreads();
50981
+ const reaped = sweepIdleThreads(threads, {
50982
+ now: Date.now(),
50983
+ suspendedMs: () => suspensionDetector.suspendedMs(),
50984
+ idleThreadTtlMs: config2.idleThreadTtlMs
50985
+ });
50986
+ if (reaped.length > 0) persistThreads();
50599
50987
  }, 6e4);
50600
50988
  cleanupTimer.unref?.();
50601
50989
  let remoteRelay = null;
@@ -50656,7 +51044,7 @@ async function startDirectorBridge() {
50656
51044
  const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
50657
51045
  let bridgeBinShaShort = "unknown00000";
50658
51046
  try {
50659
- bridgeBinShaShort = import_node_crypto8.default.createHash("sha256").update(import_node_fs14.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
51047
+ bridgeBinShaShort = import_node_crypto8.default.createHash("sha256").update(import_node_fs15.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
50660
51048
  } catch {
50661
51049
  }
50662
51050
  attachBridgeWss(server, {
@@ -50790,28 +51178,27 @@ async function startDirectorBridge() {
50790
51178
  })
50791
51179
  );
50792
51180
  enqueueRun(async () => {
50793
- const now = Date.now();
50794
- const suspendedNow = suspensionDetector.suspendedMs();
50795
- let retainedBuffers = 0;
50796
- let retainedBytes = 0;
50797
- for (const [sid, t] of threads) {
50798
- if (t.replay && shouldGc(t.replay, now, suspendedNow)) {
50799
- const turnId = t.replay.turnId;
50800
- const lastSeq = t.replay.lastSeq;
50801
- const expiredUnacked = t.replay.ackedAt === null;
50802
- writeTombstone(tombstones, sid, t.replay, now);
51181
+ const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
51182
+ now: Date.now(),
51183
+ suspendedMs: () => suspensionDetector.suspendedMs(),
51184
+ tombstoneTtlMs: DEFAULT_TOMBSTONE_TTL_MS,
51185
+ onTombstone: ({ directorSessionId, turnId, lastSeq, expiredUnacked }) => {
50803
51186
  bridgeLogger.logOut({
50804
51187
  spanId: "gc",
50805
- sessionId: sid,
51188
+ sessionId: directorSessionId,
50806
51189
  type: expiredUnacked ? "<result-expired>" : "<tombstone-written>",
50807
- payload: { sessionId: sid, turnId, lastSeq }
51190
+ payload: { sessionId: directorSessionId, turnId, lastSeq }
51191
+ });
51192
+ },
51193
+ onSupersededDropped: ({ directorSessionId, turnId }) => {
51194
+ bridgeLogger.logOut({
51195
+ spanId: "gc",
51196
+ sessionId: directorSessionId,
51197
+ type: "<superseded-dropped>",
51198
+ payload: { sessionId: directorSessionId, turnId }
50808
51199
  });
50809
- t.replay = null;
50810
- } else if (t.replay) {
50811
- retainedBuffers += 1;
50812
- retainedBytes += t.replay.bytesUsed;
50813
51200
  }
50814
- }
51201
+ });
50815
51202
  if (retainedBuffers > 0) {
50816
51203
  bridgeLogger.logOut({
50817
51204
  spanId: "gc",
@@ -50820,13 +51207,12 @@ async function startDirectorBridge() {
50820
51207
  payload: { retainedBuffers, retainedBytes }
50821
51208
  });
50822
51209
  }
50823
- const removed = sweepExpiredTombstones(tombstones, now);
50824
- if (removed > 0) {
51210
+ if (tombstonesRemoved > 0) {
50825
51211
  bridgeLogger.logOut({
50826
51212
  spanId: "gc",
50827
51213
  sessionId: null,
50828
51214
  type: "<tombstone-expired>",
50829
- payload: { removed }
51215
+ payload: { removed: tombstonesRemoved }
50830
51216
  });
50831
51217
  }
50832
51218
  });
@@ -51050,13 +51436,13 @@ function readOrCreateBridgeToken(stateDir) {
51050
51436
  if (explicit) return explicit;
51051
51437
  const tokenPath = import_node_path18.default.join(stateDir, "token.txt");
51052
51438
  try {
51053
- const existing = import_node_fs14.default.readFileSync(tokenPath, "utf8").trim();
51439
+ const existing = import_node_fs15.default.readFileSync(tokenPath, "utf8").trim();
51054
51440
  if (existing) return existing;
51055
51441
  } catch {
51056
51442
  }
51057
- import_node_fs14.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
51443
+ import_node_fs15.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
51058
51444
  const token = import_node_crypto8.default.randomBytes(32).toString("base64url");
51059
- import_node_fs14.default.writeFileSync(tokenPath, `${token}
51445
+ import_node_fs15.default.writeFileSync(tokenPath, `${token}
51060
51446
  `, { mode: 384 });
51061
51447
  return token;
51062
51448
  }
@@ -54440,10 +54826,8 @@ var StdioServerTransport = class {
54440
54826
  // src/tools.ts
54441
54827
  var import_node_crypto11 = require("node:crypto");
54442
54828
  var import_promises3 = require("node:fs/promises");
54443
- var import_node_fs15 = require("node:fs");
54444
54829
  var import_node_path20 = __toESM(require("node:path"), 1);
54445
54830
  var import_node_os10 = __toESM(require("node:os"), 1);
54446
- var import_node_child_process12 = require("node:child_process");
54447
54831
 
54448
54832
  // ../../packages/wiki/dist/index.js
54449
54833
  var import_promises2 = __toESM(require("node:fs/promises"), 1);
@@ -54901,7 +55285,7 @@ async function runCodeGate(_target, deps) {
54901
55285
  }
54902
55286
 
54903
55287
  // src/consensus/multi-llm-head.ts
54904
- var import_node_child_process11 = require("node:child_process");
55288
+ var import_node_child_process12 = require("node:child_process");
54905
55289
  function errMessage3(err) {
54906
55290
  return err instanceof Error ? err.message : String(err);
54907
55291
  }
@@ -54943,7 +55327,7 @@ function runExternalAgents(bin, args, label, timeoutMs, spawn3) {
54943
55327
  });
54944
55328
  }
54945
55329
  async function pickPanel(input, deps) {
54946
- const spawn3 = deps?.spawn ?? import_node_child_process11.spawn;
55330
+ const spawn3 = deps?.spawn ?? import_node_child_process12.spawn;
54947
55331
  const { command, prefixArgs } = resolveExternalAgentsCliInvocation(deps?.externalAgentsBin);
54948
55332
  const args = [
54949
55333
  "pick",
@@ -54964,7 +55348,7 @@ async function pickPanel(input, deps) {
54964
55348
  return { ok: true, agents };
54965
55349
  }
54966
55350
  async function dispatchHead(agentId, prompt, timeoutMs, deps) {
54967
- const spawn3 = deps?.spawn ?? import_node_child_process11.spawn;
55351
+ const spawn3 = deps?.spawn ?? import_node_child_process12.spawn;
54968
55352
  const { command, prefixArgs } = resolveExternalAgentsCliInvocation(deps?.externalAgentsBin);
54969
55353
  const args = ["dispatch", agentId, "--json", prompt];
54970
55354
  const r = await runExternalAgents(command, [...prefixArgs, ...args], "dispatch", timeoutMs, spawn3);
@@ -55622,27 +56006,6 @@ async function enqueueAsyncTool(client, descriptor2, input) {
55622
56006
  }
55623
56007
  }
55624
56008
 
55625
- // src/git-remote-match.ts
55626
- function normalizeRemote(url2) {
55627
- const trimmed = url2.trim().replace(/\.git$/i, "");
55628
- const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
55629
- const pathPart = scp ? scp[1] : (() => {
55630
- try {
55631
- return new URL(trimmed).pathname.replace(/^\/+/, "");
55632
- } catch {
55633
- return null;
55634
- }
55635
- })();
55636
- if (!pathPart) return null;
55637
- const segs = pathPart.split("/").filter(Boolean);
55638
- if (segs.length < 2) return null;
55639
- return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
55640
- }
55641
- function remoteMatchesRepo(remoteUrl, repoFullName) {
55642
- const a = normalizeRemote(remoteUrl);
55643
- return a !== null && a === repoFullName.trim().toLowerCase();
55644
- }
55645
-
55646
56009
  // src/tool-names.ts
55647
56010
  var mcpToolNames = {
55648
56011
  appendExecutionArtifact: "append_execution_artifact",
@@ -55704,6 +56067,8 @@ var mcpToolNames = {
55704
56067
  runCodeReviewerGate: "run_code_reviewer_gate",
55705
56068
  uploadArtifact: "upload_artifact",
55706
56069
  listArtifactFiles: "list_artifact_files",
56070
+ reclaimExecutionRunLease: "reclaim_execution_run_lease",
56071
+ restartExecutionRun: "restart_execution_run",
55707
56072
  resolveHandoff: "resolve_handoff",
55708
56073
  recordProducedUrl: "record_produced_url",
55709
56074
  registerLocalCheckout: "register_local_checkout",
@@ -56302,6 +56667,8 @@ var listArtifactFilesInputSchema = projectScopedSchema.merge(
56302
56667
  runId: mrrlinExecutionRunIdSchema.optional()
56303
56668
  })
56304
56669
  );
56670
+ var reclaimExecutionRunLeaseInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinExecutionRunRequeueSchema);
56671
+ var restartExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinRestartExecutionRunSchema);
56305
56672
  var listInboxItemsInputSchema = projectScopedSchema.merge(mrrlinInboxItemListFiltersSchema);
56306
56673
  var createInboxItemInputSchema = projectScopedSchema.merge(mrrlinInboxItemCreateSchema);
56307
56674
  var decideInboxItemInputSchema = projectScopedSchema.extend({ itemId: mrrlinInboxItemIdSchema }).merge(mrrlinInboxItemDecideSchema);
@@ -56378,6 +56745,8 @@ var mcpToolInputSchemas = {
56378
56745
  [mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
56379
56746
  [mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
56380
56747
  [mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
56748
+ [mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
56749
+ [mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
56381
56750
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
56382
56751
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
56383
56752
  [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
@@ -56441,6 +56810,8 @@ var mcpToolOperationIds = {
56441
56810
  [mcpToolNames.decideInboxItem]: openApiOperationIds.decideInboxItem,
56442
56811
  [mcpToolNames.uploadArtifact]: openApiOperationIds.createArtifactFile,
56443
56812
  [mcpToolNames.listArtifactFiles]: openApiOperationIds.listArtifactFiles,
56813
+ [mcpToolNames.reclaimExecutionRunLease]: openApiOperationIds.reclaimExecutionRunLease,
56814
+ [mcpToolNames.restartExecutionRun]: openApiOperationIds.restartExecutionRun,
56444
56815
  [mcpToolNames.resolveHandoff]: openApiOperationIds.resolveHandoff
56445
56816
  };
56446
56817
  var mcpToolDescriptions = {
@@ -56504,6 +56875,8 @@ var mcpToolDescriptions = {
56504
56875
  [mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
56505
56876
  [mcpToolNames.uploadArtifact]: "Upload a local file (md/txt/html/csv/json/pdf/png/jpg/webp/gif, <=5MB) as a project artifact. Returns a stable markdown link to embed in chat/spec/handoff text. Use class=temp for one-off hand-offs, class=durable for anything referenced by specs or evidence. Pass runId to record the file in that run's artifact history; pass taskId to surface it on the task's Artifacts list.",
56506
56877
  [mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
56878
+ [mcpToolNames.reclaimExecutionRunLease]: "Clear a stale pending execution-run lease when the lease has aged past staleSeconds. Returns the reclaimed run on success, null when the lease is not stale yet, and surfaces a structured conflict when reclaim retries are exhausted.",
56879
+ [mcpToolNames.restartExecutionRun]: "Restart a parked execution run (handoff_requested or handoff_queued) back to pending so the queue can resume it.",
56507
56880
  [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent.",
56508
56881
  [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry.",
56509
56882
  [mcpToolNames.retrieveContext]: "Rehydrate a mrrlin:// handle emitted by the compact-context layer into the full canonical Mrrlin record (task/wiki page/project snapshot/execution run/artifacts). Use whenever you need the full source \u2014 irreversible decisions (closing tasks, dispatching deploys, writing specs) MUST call this first; never act on a compact envelope alone. Returns `{ kind, data }`. Invalid or unsupported handle \u2192 structured `HANDLE_INVALID` error."
@@ -57261,6 +57634,20 @@ function createMrrlinTools(options) {
57261
57634
  "Unable to list artifact files.",
57262
57635
  async (c, { projectSlug, taskId, runId }) => await c.listArtifactFiles(projectSlug, { runId, taskId })
57263
57636
  ),
57637
+ [mcpToolNames.reclaimExecutionRunLease]: makeTool(
57638
+ client,
57639
+ mcpToolNames.reclaimExecutionRunLease,
57640
+ "EXECUTION_RUN_RECLAIM_FAILED",
57641
+ "Unable to reclaim the execution run lease.",
57642
+ async (c, { projectSlug, runId, staleSeconds }) => await c.reclaimExecutionRunLease(projectSlug, runId, staleSeconds)
57643
+ ),
57644
+ [mcpToolNames.restartExecutionRun]: makeTool(
57645
+ client,
57646
+ mcpToolNames.restartExecutionRun,
57647
+ "EXECUTION_RUN_RESTART_FAILED",
57648
+ "Unable to restart the execution run.",
57649
+ async (c, { projectSlug, runId, ...input }) => await c.restartExecutionRun(projectSlug, runId, input)
57650
+ ),
57264
57651
  [mcpToolNames.resolveHandoff]: makeTool(
57265
57652
  client,
57266
57653
  mcpToolNames.resolveHandoff,
@@ -57274,47 +57661,19 @@ function createMrrlinTools(options) {
57274
57661
  "LOCAL_CHECKOUT_REGISTER_FAILED",
57275
57662
  "Unable to register local checkout.",
57276
57663
  async (_c, { projectSlug, path: checkoutPath, repo }) => {
57277
- const trimmedPath = checkoutPath.trim();
57278
- if (!import_node_path20.default.isAbsolute(trimmedPath)) {
57279
- throw new McpToolCodedError(
57280
- "LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
57281
- `Path must be absolute, got: ${trimmedPath}`
57282
- );
57283
- }
57284
- let resolvedPath;
57285
- try {
57286
- resolvedPath = (0, import_node_fs15.realpathSync)(trimmedPath);
57287
- } catch {
57288
- throw new McpToolCodedError(
57289
- "LOCAL_CHECKOUT_PATH_NOT_FOUND",
57290
- `Path does not exist or is not accessible: ${trimmedPath}`
57291
- );
57292
- }
57293
- let remoteUrl;
57294
- try {
57295
- remoteUrl = (0, import_node_child_process12.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
57296
- encoding: "utf8",
57297
- stdio: ["ignore", "pipe", "ignore"],
57298
- timeout: 15e3,
57299
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
57300
- }).trim();
57301
- } catch {
57302
- throw new McpToolCodedError(
57303
- "LOCAL_CHECKOUT_NOT_A_GIT_REPO",
57304
- `Path is not a git repository or has no origin remote: ${resolvedPath}`
57305
- );
57306
- }
57307
- if (!remoteMatchesRepo(remoteUrl, repo)) {
57308
- throw new McpToolCodedError(
57309
- "LOCAL_CHECKOUT_REMOTE_MISMATCH",
57310
- `Origin remote ${remoteUrl} does not match repo ${repo}.`
57311
- );
57312
- }
57313
57664
  const stateDir = process.env.CODEX_HOME ? import_node_path20.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge") : import_node_path20.default.join(import_node_os10.default.homedir(), ".mrrlin", "director-bridge");
57314
57665
  const registry3 = new CheckoutRegistry(stateDir);
57315
- const confirmedAt = (/* @__PURE__ */ new Date()).toISOString();
57316
- registry3.confirm(projectSlug, resolvedPath, confirmedAt);
57317
- return { projectSlug, path: resolvedPath, confirmedAt };
57666
+ const result = registerCheckout(registry3, { projectSlug, path: checkoutPath, repo }, import_node_os10.default.hostname());
57667
+ if (!result.ok) {
57668
+ const codeMap = {
57669
+ not_absolute: "LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
57670
+ not_found: "LOCAL_CHECKOUT_PATH_NOT_FOUND",
57671
+ not_a_git_repo: "LOCAL_CHECKOUT_NOT_A_GIT_REPO",
57672
+ remote_mismatch: "LOCAL_CHECKOUT_REMOTE_MISMATCH"
57673
+ };
57674
+ throw new McpToolCodedError(codeMap[result.code], result.detail);
57675
+ }
57676
+ return result.data;
57318
57677
  }
57319
57678
  ),
57320
57679
  [mcpToolNames.retrieveContext]: makeTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.3.55",
3
+ "version": "0.3.57",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -21,11 +21,11 @@
21
21
  "@types/ws": "^8.18.1",
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
- "@mrrlin/client": "0.0.0",
24
+ "@mrrlin/schemas": "0.0.0",
25
25
  "@mrrlin/codex-client": "0.0.0",
26
- "@mrrlin/tsconfig": "0.0.0",
26
+ "@mrrlin/client": "0.0.0",
27
27
  "@mrrlin/director-e2e": "0.0.0",
28
- "@mrrlin/schemas": "0.0.0",
28
+ "@mrrlin/tsconfig": "0.0.0",
29
29
  "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {