@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.
- package/dist/bin.cjs +521 -162
- 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-
|
|
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
|
|
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
|
|
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)
|
|
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: () =>
|
|
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
|
-
|
|
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({
|
|
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
|
|
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.
|
|
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
|
|
42280
|
-
" - if you are blocked and need an operator decision before the work can continue, use status=
|
|
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
|
|
42305
|
-
" PR
|
|
42306
|
-
" must decide before the work can continue, leave it at `
|
|
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
|
|
42553
|
-
const
|
|
42554
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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, {
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
49482
|
-
|
|
49483
|
-
|
|
49484
|
-
|
|
49485
|
-
|
|
49486
|
-
|
|
49487
|
-
|
|
49488
|
-
|
|
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
|
-
|
|
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: {
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
50589
|
-
|
|
50590
|
-
|
|
50591
|
-
|
|
50592
|
-
|
|
50593
|
-
|
|
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(
|
|
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
|
|
50794
|
-
|
|
50795
|
-
|
|
50796
|
-
|
|
50797
|
-
|
|
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:
|
|
51188
|
+
sessionId: directorSessionId,
|
|
50806
51189
|
type: expiredUnacked ? "<result-expired>" : "<tombstone-written>",
|
|
50807
|
-
payload: { sessionId:
|
|
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
|
-
|
|
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 =
|
|
51439
|
+
const existing = import_node_fs15.default.readFileSync(tokenPath, "utf8").trim();
|
|
51054
51440
|
if (existing) return existing;
|
|
51055
51441
|
} catch {
|
|
51056
51442
|
}
|
|
51057
|
-
|
|
51443
|
+
import_node_fs15.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
|
|
51058
51444
|
const token = import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
51059
|
-
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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
|
|
57316
|
-
|
|
57317
|
-
|
|
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.
|
|
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/
|
|
24
|
+
"@mrrlin/schemas": "0.0.0",
|
|
25
25
|
"@mrrlin/codex-client": "0.0.0",
|
|
26
|
-
"@mrrlin/
|
|
26
|
+
"@mrrlin/client": "0.0.0",
|
|
27
27
|
"@mrrlin/director-e2e": "0.0.0",
|
|
28
|
-
"@mrrlin/
|
|
28
|
+
"@mrrlin/tsconfig": "0.0.0",
|
|
29
29
|
"@mrrlin/wiki": "0.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|