@mrrlin-dev/mcp 0.3.54 → 0.3.56
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 +730 -264
- package/package.json +2 -2
package/dist/bin.cjs
CHANGED
|
@@ -21153,11 +21153,11 @@ ${block}`;
|
|
|
21153
21153
|
}
|
|
21154
21154
|
|
|
21155
21155
|
// src/director-bridge.ts
|
|
21156
|
-
var
|
|
21156
|
+
var import_node_fs15 = __toESM(require("node:fs"), 1);
|
|
21157
21157
|
var import_node_http = __toESM(require("node:http"), 1);
|
|
21158
21158
|
var import_node_os9 = __toESM(require("node:os"), 1);
|
|
21159
21159
|
var import_node_path18 = __toESM(require("node:path"), 1);
|
|
21160
|
-
var
|
|
21160
|
+
var import_node_child_process11 = require("node:child_process");
|
|
21161
21161
|
var import_node_crypto8 = __toESM(require("node:crypto"), 1);
|
|
21162
21162
|
|
|
21163
21163
|
// src/turn-replay-buffer.ts
|
|
@@ -21289,11 +21289,25 @@ function createSuspensionDetector(opts) {
|
|
|
21289
21289
|
const t = now();
|
|
21290
21290
|
const gap = t - last;
|
|
21291
21291
|
last = t;
|
|
21292
|
-
if (gap >= gapMs)
|
|
21292
|
+
if (gap >= gapMs) {
|
|
21293
|
+
suspended += gap - tickMs;
|
|
21294
|
+
}
|
|
21293
21295
|
}, tickMs);
|
|
21294
21296
|
timer.unref?.();
|
|
21297
|
+
const settle = () => {
|
|
21298
|
+
const t = now();
|
|
21299
|
+
const gap = t - last;
|
|
21300
|
+
if (gap >= gapMs) {
|
|
21301
|
+
suspended += gap - tickMs;
|
|
21302
|
+
last = t;
|
|
21303
|
+
}
|
|
21304
|
+
};
|
|
21295
21305
|
return {
|
|
21296
|
-
suspendedMs: () =>
|
|
21306
|
+
suspendedMs: () => {
|
|
21307
|
+
settle();
|
|
21308
|
+
return suspended;
|
|
21309
|
+
},
|
|
21310
|
+
settle,
|
|
21297
21311
|
dispose: () => clearI(timer)
|
|
21298
21312
|
};
|
|
21299
21313
|
}
|
|
@@ -21368,20 +21382,22 @@ function transition(state, event) {
|
|
|
21368
21382
|
|
|
21369
21383
|
// src/bridge-tombstone.ts
|
|
21370
21384
|
var DEFAULT_TOMBSTONE_TTL_MS = 36e5;
|
|
21371
|
-
function writeTombstone(tombstones, directorSessionId, buf, now) {
|
|
21385
|
+
function writeTombstone(tombstones, directorSessionId, buf, now, suspendedMsNow) {
|
|
21372
21386
|
tombstones.set(directorSessionId, {
|
|
21373
21387
|
turnId: buf.turnId,
|
|
21374
21388
|
lastSeq: buf.lastSeq,
|
|
21375
|
-
expiredAt: now
|
|
21389
|
+
expiredAt: now,
|
|
21390
|
+
expiredSuspendedMs: suspendedMsNow
|
|
21376
21391
|
});
|
|
21377
21392
|
}
|
|
21378
21393
|
function lookupTombstone(tombstones, directorSessionId) {
|
|
21379
21394
|
return tombstones.get(directorSessionId) ?? null;
|
|
21380
21395
|
}
|
|
21381
|
-
function sweepExpiredTombstones(tombstones, now, ttlMs = DEFAULT_TOMBSTONE_TTL_MS) {
|
|
21396
|
+
function sweepExpiredTombstones(tombstones, now, ttlMs = DEFAULT_TOMBSTONE_TTL_MS, suspendedMsNow) {
|
|
21382
21397
|
let removed = 0;
|
|
21383
21398
|
for (const [sid, t] of tombstones) {
|
|
21384
|
-
|
|
21399
|
+
const suspendedSinceWrite = Math.max(0, suspendedMsNow - t.expiredSuspendedMs);
|
|
21400
|
+
if (now - t.expiredAt - suspendedSinceWrite > ttlMs) {
|
|
21385
21401
|
tombstones.delete(sid);
|
|
21386
21402
|
removed += 1;
|
|
21387
21403
|
}
|
|
@@ -36479,6 +36495,7 @@ var openApiOperationIds = {
|
|
|
36479
36495
|
requeueAsyncJob: "requeueAsyncJob",
|
|
36480
36496
|
createGithubBranch: "createGithubBranch",
|
|
36481
36497
|
createGithubPullRequest: "createGithubPullRequest",
|
|
36498
|
+
getGithubPullRequest: "getGithubPullRequest",
|
|
36482
36499
|
getGithubWorkflowRunLatest: "getGithubWorkflowRunLatest",
|
|
36483
36500
|
getGithubLatestDeployment: "getGithubLatestDeployment",
|
|
36484
36501
|
mintGithubPushToken: "mintGithubPushToken",
|
|
@@ -36506,6 +36523,8 @@ var openApiOperationIds = {
|
|
|
36506
36523
|
getProject: "getProject",
|
|
36507
36524
|
getProjectSnapshot: "getProjectSnapshot",
|
|
36508
36525
|
getTask: "getTask",
|
|
36526
|
+
archiveTask: "archiveTask",
|
|
36527
|
+
unarchiveTask: "unarchiveTask",
|
|
36509
36528
|
getWikiPage: "getWikiPage",
|
|
36510
36529
|
handleGithubInstallCallback: "handleGithubInstallCallback",
|
|
36511
36530
|
listExecutionArtifacts: "listExecutionArtifacts",
|
|
@@ -36538,7 +36557,9 @@ var openApiOperationIds = {
|
|
|
36538
36557
|
markSpecReady: "markSpecReady",
|
|
36539
36558
|
resetDirectorThread: "resetDirectorThread",
|
|
36540
36559
|
claimExecutionRun: "claimExecutionRun",
|
|
36560
|
+
reclaimExecutionRunLease: "reclaimExecutionRunLease",
|
|
36541
36561
|
requeueExecutionRun: "requeueExecutionRun",
|
|
36562
|
+
restartExecutionRun: "restartExecutionRun",
|
|
36542
36563
|
resolveHandoff: "resolveHandoff",
|
|
36543
36564
|
touchExecutionRun: "touchExecutionRun",
|
|
36544
36565
|
issueAgentCredential: "issueAgentCredential",
|
|
@@ -36556,6 +36577,7 @@ var openApiOperationIds = {
|
|
|
36556
36577
|
listArtifactFiles: "listArtifactFiles",
|
|
36557
36578
|
getOperatorSettings: "getOperatorSettings",
|
|
36558
36579
|
updateOperatorSettings: "updateOperatorSettings",
|
|
36580
|
+
recordOperatorPresence: "recordOperatorPresence",
|
|
36559
36581
|
getAgentPool: "getAgentPool",
|
|
36560
36582
|
putAgentPool: "putAgentPool",
|
|
36561
36583
|
getConsensusConfig: "getConsensusConfig",
|
|
@@ -36600,6 +36622,8 @@ var mrrlinOperatorSettingsSchema = external_exports.object({
|
|
|
36600
36622
|
// to it (the web active-project selection is otherwise only a browser cookie). Null = not
|
|
36601
36623
|
// selected yet.
|
|
36602
36624
|
activeProjectSlug: external_exports.string().min(1).nullable(),
|
|
36625
|
+
// The last time an authenticated BROWSER operator session was observed. Null means never seen.
|
|
36626
|
+
lastPresenceAt: external_exports.string().datetime().nullable(),
|
|
36603
36627
|
createdAt: external_exports.string().datetime(),
|
|
36604
36628
|
updatedAt: external_exports.string().datetime()
|
|
36605
36629
|
});
|
|
@@ -36611,6 +36635,7 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
|
|
|
36611
36635
|
message: "OperatorSettingsUpdate requires at least one field"
|
|
36612
36636
|
});
|
|
36613
36637
|
var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
|
|
36638
|
+
var operatorPresenceResponseSchema = external_exports.object({ data: external_exports.object({ lastPresenceAt: external_exports.string().datetime() }) });
|
|
36614
36639
|
var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
|
|
36615
36640
|
var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
|
|
36616
36641
|
var agentPoolSnapshotSchema = external_exports.object({
|
|
@@ -36748,7 +36773,13 @@ var mrrlinTaskDomainSchema = external_exports.enum(["code", "seo", "marketing",
|
|
|
36748
36773
|
var mrrlinSpecApprovedSchema = external_exports.enum(["pending", "approved", "skipped", "skipped_legacy"]);
|
|
36749
36774
|
var mrrlinTaskSourceSchema = external_exports.enum(["fixture-import", "api"]);
|
|
36750
36775
|
var mrrlinPlanStatusSchema = external_exports.enum(["active", "paused", "done", "archived"]);
|
|
36751
|
-
var mrrlinTaskEventTypeSchema = external_exports.enum([
|
|
36776
|
+
var mrrlinTaskEventTypeSchema = external_exports.enum([
|
|
36777
|
+
"autonomy_set",
|
|
36778
|
+
"director_thread_reset",
|
|
36779
|
+
"note",
|
|
36780
|
+
"archived",
|
|
36781
|
+
"restored"
|
|
36782
|
+
]);
|
|
36752
36783
|
var mrrlinTaskEventCreateKindSchema = external_exports.enum(["note"]);
|
|
36753
36784
|
var githubRepositorySelectionSchema = external_exports.enum(["all", "selected"]);
|
|
36754
36785
|
var mrrlinTaskIdSchema = external_exports.string().regex(/^GT-\d+$/);
|
|
@@ -36812,6 +36843,14 @@ var mrrlinExecutionRunStatusSchema = external_exports.enum([
|
|
|
36812
36843
|
"handoff_queued",
|
|
36813
36844
|
"cancelled"
|
|
36814
36845
|
]);
|
|
36846
|
+
var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
|
|
36847
|
+
"local_checkout_missing",
|
|
36848
|
+
"task_blocked",
|
|
36849
|
+
"default_branch_unresolved",
|
|
36850
|
+
"execution_turn_timeout",
|
|
36851
|
+
"deliverable_gate_exhausted",
|
|
36852
|
+
"unknown"
|
|
36853
|
+
]);
|
|
36815
36854
|
var mrrlinExecutionArtifactKindSchema = external_exports.enum([
|
|
36816
36855
|
"plan",
|
|
36817
36856
|
"checkpoint",
|
|
@@ -36826,7 +36865,8 @@ var mrrlinExecutionArtifactKindSchema = external_exports.enum([
|
|
|
36826
36865
|
"verification",
|
|
36827
36866
|
"produced_url",
|
|
36828
36867
|
"resume_spec",
|
|
36829
|
-
"deliverable"
|
|
36868
|
+
"deliverable",
|
|
36869
|
+
"pull_request_state"
|
|
36830
36870
|
]);
|
|
36831
36871
|
var mrrlinDeployAttemptPayloadSchema = external_exports.object({
|
|
36832
36872
|
env: external_exports.enum(["dev", "prod"]),
|
|
@@ -36862,6 +36902,9 @@ var taxonomyCategorySchema = external_exports.string().min(1).regex(/^[a-z][a-z0
|
|
|
36862
36902
|
var taxonomySubcategorySchema = external_exports.string().min(1).regex(/^[a-z][a-z0-9-]*$/);
|
|
36863
36903
|
var mrrlinTaskAssigneeSchema = external_exports.union([external_exports.string().regex(/^@/), external_exports.string().email()]).nullable();
|
|
36864
36904
|
var mrrlinTaskSchema = external_exports.object({
|
|
36905
|
+
// Output-optional (not defaulted-required) so existing task fixtures/mocks across the
|
|
36906
|
+
// codebase need no churn; DB rows always resolve it to `string | null` via taskRowToTask.
|
|
36907
|
+
archivedAt: external_exports.string().datetime().nullable().optional(),
|
|
36865
36908
|
assignee: mrrlinTaskAssigneeSchema,
|
|
36866
36909
|
autoDeploy: mrrlinAutoDeploySchema,
|
|
36867
36910
|
// ADR 0020. OUTPUT-optional (not defaulted-required) so hardcoded task fixtures/mocks across the
|
|
@@ -36976,6 +37019,27 @@ var mrrlinGithubPullRequestSchema = external_exports.object({
|
|
|
36976
37019
|
title: external_exports.string().min(1),
|
|
36977
37020
|
url: external_exports.string().url()
|
|
36978
37021
|
});
|
|
37022
|
+
var mrrlinGithubPullRequestCheckStateSchema = external_exports.enum([
|
|
37023
|
+
"passing",
|
|
37024
|
+
"failing",
|
|
37025
|
+
"running",
|
|
37026
|
+
"none",
|
|
37027
|
+
"unknown"
|
|
37028
|
+
]);
|
|
37029
|
+
var mrrlinGithubPullRequestCheckSourceSchema = external_exports.enum(["check_run", "commit_status"]);
|
|
37030
|
+
var mrrlinGithubPullRequestObservedSchema = external_exports.object({
|
|
37031
|
+
checks: mrrlinGithubPullRequestCheckStateSchema,
|
|
37032
|
+
failingChecks: external_exports.array(external_exports.object({
|
|
37033
|
+
name: external_exports.string().min(1),
|
|
37034
|
+
source: mrrlinGithubPullRequestCheckSourceSchema
|
|
37035
|
+
})),
|
|
37036
|
+
headSha: external_exports.string().min(1),
|
|
37037
|
+
merged: external_exports.boolean(),
|
|
37038
|
+
mergedAt: external_exports.string().datetime().nullable(),
|
|
37039
|
+
number: external_exports.number().int().positive(),
|
|
37040
|
+
state: external_exports.string().min(1),
|
|
37041
|
+
url: external_exports.string().url()
|
|
37042
|
+
});
|
|
36979
37043
|
var mrrlinGithubWorkflowSchema = external_exports.object({
|
|
36980
37044
|
id: external_exports.union([external_exports.number().int().positive(), external_exports.string().min(1)]),
|
|
36981
37045
|
name: external_exports.string().min(1),
|
|
@@ -37109,6 +37173,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
|
|
|
37109
37173
|
}
|
|
37110
37174
|
});
|
|
37111
37175
|
var mrrlinTaskListFiltersSchema = external_exports.object({
|
|
37176
|
+
archived: external_exports.enum(["true", "false", "all"]).optional(),
|
|
37112
37177
|
assignee: external_exports.string().startsWith("@").optional(),
|
|
37113
37178
|
autoDeploy: mrrlinAutoDeploySchema.optional(),
|
|
37114
37179
|
autonomyLevel: mrrlinAutonomyLevelSchema.optional(),
|
|
@@ -37271,6 +37336,9 @@ var githubCreatePullRequestRequestSchema = external_exports.object({
|
|
|
37271
37336
|
var githubCreatePullRequestResponseSchema = external_exports.object({
|
|
37272
37337
|
data: mrrlinGithubPullRequestSchema
|
|
37273
37338
|
});
|
|
37339
|
+
var githubPullRequestNumberParamSchema = external_exports.object({
|
|
37340
|
+
pullNumber: external_exports.coerce.number().int().positive()
|
|
37341
|
+
});
|
|
37274
37342
|
var githubWorkflowIdParamSchema = external_exports.object({
|
|
37275
37343
|
workflowId: external_exports.string().min(1)
|
|
37276
37344
|
});
|
|
@@ -37284,6 +37352,9 @@ var githubWorkflowDispatchResponseSchema = external_exports.object({
|
|
|
37284
37352
|
var githubWorkflowListResponseSchema = external_exports.object({
|
|
37285
37353
|
data: external_exports.array(mrrlinGithubWorkflowSchema)
|
|
37286
37354
|
});
|
|
37355
|
+
var githubPullRequestResponseSchema = external_exports.object({
|
|
37356
|
+
data: mrrlinGithubPullRequestObservedSchema
|
|
37357
|
+
});
|
|
37287
37358
|
var githubWorkflowRunQuerySchema = external_exports.object({
|
|
37288
37359
|
workflowId: external_exports.string().min(1),
|
|
37289
37360
|
branch: external_exports.string().min(1),
|
|
@@ -37384,6 +37455,7 @@ var mrrlinExecutionRunSchema = external_exports.object({
|
|
|
37384
37455
|
actor: external_exports.string().min(1),
|
|
37385
37456
|
baseSha: external_exports.string().min(1).nullable(),
|
|
37386
37457
|
branch: external_exports.string().min(1).nullable(),
|
|
37458
|
+
blockingReason: mrrlinRunBlockingReasonCodeSchema.nullable(),
|
|
37387
37459
|
checkpointCursor: external_exports.string().min(1).nullable(),
|
|
37388
37460
|
claimedAt: external_exports.string().datetime().nullable(),
|
|
37389
37461
|
codeExecution: external_exports.boolean(),
|
|
@@ -37398,6 +37470,7 @@ var mrrlinExecutionRunSchema = external_exports.object({
|
|
|
37398
37470
|
lastActor: external_exports.string().min(1).nullable(),
|
|
37399
37471
|
leaseId: external_exports.string().min(1).nullable(),
|
|
37400
37472
|
prerequisites: external_exports.array(mrrlinRunPrerequisiteSchema),
|
|
37473
|
+
prState: external_exports.enum(["open", "merged", "closed"]).optional(),
|
|
37401
37474
|
prodEnvUrl: external_exports.string().min(1).nullable(),
|
|
37402
37475
|
prodRunUrl: external_exports.string().min(1).nullable(),
|
|
37403
37476
|
prodWorkflowId: external_exports.number().int().positive().nullable(),
|
|
@@ -37427,6 +37500,7 @@ var mrrlinExecutionRunCreateSchema = external_exports.object({
|
|
|
37427
37500
|
var mrrlinExecutionRunUpdateSchema = external_exports.object({
|
|
37428
37501
|
baseSha: external_exports.string().min(1).nullable().optional(),
|
|
37429
37502
|
branch: external_exports.string().min(1).nullable().optional(),
|
|
37503
|
+
blockingReason: mrrlinRunBlockingReasonCodeSchema.nullable().optional(),
|
|
37430
37504
|
checkpointCursor: external_exports.string().min(1).nullable().optional(),
|
|
37431
37505
|
devEnvUrl: external_exports.string().min(1).nullable().optional(),
|
|
37432
37506
|
devRunUrl: external_exports.string().min(1).nullable().optional(),
|
|
@@ -37453,6 +37527,7 @@ var mrrlinResumeSpecPayloadSchema = external_exports.object({
|
|
|
37453
37527
|
class: external_exports.literal("tool_assisted"),
|
|
37454
37528
|
question: external_exports.string().min(1),
|
|
37455
37529
|
prerequisites: external_exports.array(external_exports.object({ mcp: external_exports.string().min(1), reason: external_exports.string().min(1) })).default([]),
|
|
37530
|
+
reasonCode: mrrlinRunBlockingReasonCodeSchema.optional(),
|
|
37456
37531
|
steps: external_exports.array(external_exports.string().min(1)).min(1),
|
|
37457
37532
|
runId: mrrlinExecutionRunIdSchema,
|
|
37458
37533
|
artifactId: mrrlinExecutionArtifactIdSchema,
|
|
@@ -37482,7 +37557,13 @@ var mrrlinResolveHandoffSchema = external_exports.object({
|
|
|
37482
37557
|
}
|
|
37483
37558
|
});
|
|
37484
37559
|
var resolveHandoffResponseSchema = external_exports.object({ data: mrrlinExecutionRunSchema });
|
|
37485
|
-
var createExecutionRunHandoffSchema = external_exports.object({
|
|
37560
|
+
var createExecutionRunHandoffSchema = external_exports.object({
|
|
37561
|
+
reason: external_exports.string().min(1),
|
|
37562
|
+
reasonCode: mrrlinRunBlockingReasonCodeSchema.optional()
|
|
37563
|
+
});
|
|
37564
|
+
var mrrlinRestartExecutionRunSchema = external_exports.object({
|
|
37565
|
+
restartedBy: external_exports.string().min(1)
|
|
37566
|
+
});
|
|
37486
37567
|
var mrrlinExecutionRunTouchSchema = external_exports.object({
|
|
37487
37568
|
leaseId: external_exports.string().min(1)
|
|
37488
37569
|
});
|
|
@@ -37829,7 +37910,11 @@ var schedulerPendingTasksResponseSchema = external_exports.object({
|
|
|
37829
37910
|
// disclosure that some exist, so a misconfigured `github.operator` (e.g. it doesn't match the
|
|
37830
37911
|
// person actually running the project) is visible as a large number instead of the autopilot
|
|
37831
37912
|
// silently doing nothing.
|
|
37832
|
-
excludedByOwnershipCount: external_exports.number().int().min(0)
|
|
37913
|
+
excludedByOwnershipCount: external_exports.number().int().min(0),
|
|
37914
|
+
// Additive: true when the operator-web-presence gate is the reason nothing new is starting
|
|
37915
|
+
// (autonomyPaused takes precedence when both apply). Computed by the same shared helper the
|
|
37916
|
+
// tick uses, so this page can never claim a task is starting when the tick would decline it.
|
|
37917
|
+
presenceGated: external_exports.boolean()
|
|
37833
37918
|
})
|
|
37834
37919
|
});
|
|
37835
37920
|
var wakeDirectorRequestSchema = external_exports.object({
|
|
@@ -38343,6 +38428,10 @@ function createMrrlinClient(config2) {
|
|
|
38343
38428
|
const body = await request(`${projectPath(projectSlug)}/github/workflow-runs/latest?${params.toString()}`);
|
|
38344
38429
|
return githubWorkflowRunResponseSchema.parse(body).data;
|
|
38345
38430
|
},
|
|
38431
|
+
async getGithubPullRequest(projectSlug, pullNumber) {
|
|
38432
|
+
const body = await request(`${projectPath(projectSlug)}/github/pull-requests/${encodeURIComponent(String(pullNumber))}`);
|
|
38433
|
+
return githubPullRequestResponseSchema.parse(body).data;
|
|
38434
|
+
},
|
|
38346
38435
|
async getGithubLatestDeployment(projectSlug, query) {
|
|
38347
38436
|
const params = new URLSearchParams({ environment: query.environment });
|
|
38348
38437
|
const body = await request(`${projectPath(projectSlug)}/github/deployments/latest?${params.toString()}`);
|
|
@@ -38386,6 +38475,7 @@ function createMrrlinClient(config2) {
|
|
|
38386
38475
|
},
|
|
38387
38476
|
async listTasks(projectSlug, filters = {}) {
|
|
38388
38477
|
const path19 = appendQuery(`${projectPath(projectSlug)}/tasks`, {
|
|
38478
|
+
archived: filters.archived,
|
|
38389
38479
|
assignee: filters.assignee,
|
|
38390
38480
|
autonomyLevel: filters.autonomyLevel,
|
|
38391
38481
|
blockedBy: filters.blockedBy,
|
|
@@ -38443,6 +38533,18 @@ function createMrrlinClient(config2) {
|
|
|
38443
38533
|
const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/director-thread/reset`, { method: "POST" });
|
|
38444
38534
|
return taskResponseSchema.parse(body).data;
|
|
38445
38535
|
},
|
|
38536
|
+
async archiveTask(projectSlug, taskId) {
|
|
38537
|
+
const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/archive`, {
|
|
38538
|
+
method: "POST"
|
|
38539
|
+
});
|
|
38540
|
+
return taskResponseSchema.parse(body).data;
|
|
38541
|
+
},
|
|
38542
|
+
async unarchiveTask(projectSlug, taskId) {
|
|
38543
|
+
const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/unarchive`, {
|
|
38544
|
+
method: "POST"
|
|
38545
|
+
});
|
|
38546
|
+
return taskResponseSchema.parse(body).data;
|
|
38547
|
+
},
|
|
38446
38548
|
async updateWikiPage(projectSlug, pageId, patch) {
|
|
38447
38549
|
const body = await request(`${projectPath(projectSlug)}/wiki/${encodeURIComponent(pageId)}`, {
|
|
38448
38550
|
body: JSON.stringify(patch),
|
|
@@ -38663,7 +38765,7 @@ function createMrrlinClient(config2) {
|
|
|
38663
38765
|
async requeueExecutionRun(projectSlug, runId, staleSeconds) {
|
|
38664
38766
|
try {
|
|
38665
38767
|
const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/requeue`, {
|
|
38666
|
-
body: JSON.stringify({ staleSeconds }),
|
|
38768
|
+
body: JSON.stringify(mrrlinExecutionRunRequeueSchema.parse({ staleSeconds })),
|
|
38667
38769
|
headers: { "content-type": "application/json" },
|
|
38668
38770
|
method: "POST"
|
|
38669
38771
|
});
|
|
@@ -38674,6 +38776,28 @@ function createMrrlinClient(config2) {
|
|
|
38674
38776
|
throw error51;
|
|
38675
38777
|
}
|
|
38676
38778
|
},
|
|
38779
|
+
async reclaimExecutionRunLease(projectSlug, runId, staleSeconds) {
|
|
38780
|
+
try {
|
|
38781
|
+
const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/reclaim-lease`, {
|
|
38782
|
+
body: JSON.stringify(mrrlinExecutionRunRequeueSchema.parse({ staleSeconds })),
|
|
38783
|
+
headers: { "content-type": "application/json" },
|
|
38784
|
+
method: "POST"
|
|
38785
|
+
});
|
|
38786
|
+
return executionRunResponseSchema.parse(body).data;
|
|
38787
|
+
} catch (error51) {
|
|
38788
|
+
if (error51 instanceof MrrlinApiError && error51.status === 409 && error51.code === "RUN_LEASE_NOT_STALE")
|
|
38789
|
+
return null;
|
|
38790
|
+
throw error51;
|
|
38791
|
+
}
|
|
38792
|
+
},
|
|
38793
|
+
async restartExecutionRun(projectSlug, runId, input) {
|
|
38794
|
+
const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/restart`, {
|
|
38795
|
+
body: JSON.stringify(mrrlinRestartExecutionRunSchema.parse(input)),
|
|
38796
|
+
headers: { "content-type": "application/json" },
|
|
38797
|
+
method: "POST"
|
|
38798
|
+
});
|
|
38799
|
+
return executionRunResponseSchema.parse(body).data;
|
|
38800
|
+
},
|
|
38677
38801
|
async touchExecutionRun(projectSlug, runId, leaseId) {
|
|
38678
38802
|
try {
|
|
38679
38803
|
const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/touch`, {
|
|
@@ -38808,6 +38932,10 @@ function createMrrlinClient(config2) {
|
|
|
38808
38932
|
});
|
|
38809
38933
|
return operatorSettingsResponseSchema.parse(body).data;
|
|
38810
38934
|
},
|
|
38935
|
+
async recordOperatorPresence() {
|
|
38936
|
+
const body = await request(`/operator/presence`, { method: "POST" });
|
|
38937
|
+
return operatorPresenceResponseSchema.parse(body).data;
|
|
38938
|
+
},
|
|
38811
38939
|
async getOperatorAgentPool() {
|
|
38812
38940
|
const body = await request(`/operator/agent-pool`);
|
|
38813
38941
|
return agentPoolResponseSchema.parse(body).data;
|
|
@@ -39322,7 +39450,7 @@ function startCodexProbe(deps = {}) {
|
|
|
39322
39450
|
}
|
|
39323
39451
|
|
|
39324
39452
|
// src/_generated/version.ts
|
|
39325
|
-
var PKG_VERSION = "0.3.
|
|
39453
|
+
var PKG_VERSION = "0.3.56";
|
|
39326
39454
|
|
|
39327
39455
|
// src/api-base-url-migrate.ts
|
|
39328
39456
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -42149,6 +42277,7 @@ ${lines2.join("\n")}`;
|
|
|
42149
42277
|
|
|
42150
42278
|
// src/execution-consumer.ts
|
|
42151
42279
|
var DEFAULT_STALE_SECONDS = 300;
|
|
42280
|
+
var DEFAULT_MAX_LEASE_RECLAIMS = 3;
|
|
42152
42281
|
var DEFAULT_TOUCH_INTERVAL_MS = 6e4;
|
|
42153
42282
|
var DEFAULT_CLAIM_INTERVAL_SECONDS = 30;
|
|
42154
42283
|
var DEFAULT_EXEC_TURN_IDLE_TIMEOUT_SECONDS = 600;
|
|
@@ -42239,8 +42368,8 @@ function buildCompletionTurnPrompt(taskId, currentStatus) {
|
|
|
42239
42368
|
"If the work is NOT complete \u2014 be honest, do not close incomplete work:",
|
|
42240
42369
|
"1. Call update_task with the status that matches why it is not complete, and explain what remains",
|
|
42241
42370
|
" in the notes:",
|
|
42242
|
-
" - if the code work itself is done and only
|
|
42243
|
-
" - if you are blocked and need an operator decision before the work can continue, use status=
|
|
42371
|
+
" - if the code work itself is done and only an open PR still awaits operator merge or follow-up, use status=awaiting_verification;",
|
|
42372
|
+
" - if you are blocked and need an operator decision before the work can continue, use status=handoff_needed.",
|
|
42244
42373
|
"2. Append a handoff_judgement artifact explaining what the operator needs to do.",
|
|
42245
42374
|
"",
|
|
42246
42375
|
"You must pick one of these paths \u2014 do not leave the task in_progress."
|
|
@@ -42264,9 +42393,9 @@ var BASE_INSTRUCTIONS_PREFIX = [
|
|
|
42264
42393
|
"- For a CODE task the run opens a draft PR for you after your turn; opening that PR is NOT",
|
|
42265
42394
|
" completion. Do NOT set closed_done for code work \u2014 leave the task open; the run parks at",
|
|
42266
42395
|
" `pull_request` (waiting for merge) and the operator merges. Closure happens after merge.",
|
|
42267
|
-
"- Pick the right open status when you stop a CODE task: if the code work itself is done and only
|
|
42268
|
-
" PR
|
|
42269
|
-
" must decide before the work can continue, leave it at `
|
|
42396
|
+
"- Pick the right open status when you stop a CODE task: if the code work itself is done and only an",
|
|
42397
|
+
" already-open PR still awaits operator merge or follow-up, leave the task at `awaiting_verification`;",
|
|
42398
|
+
" if you are blocked and a human must decide before the work can continue, leave it at `handoff_needed`.",
|
|
42270
42399
|
"",
|
|
42271
42400
|
"File artifacts:",
|
|
42272
42401
|
"- When your output is a file (markdown report, HTML, screenshot), upload it with",
|
|
@@ -42512,14 +42641,19 @@ async function assertRunCompletionReady(deps, projectSlug, taskId, runId, allowe
|
|
|
42512
42641
|
task
|
|
42513
42642
|
};
|
|
42514
42643
|
}
|
|
42515
|
-
const
|
|
42516
|
-
const
|
|
42517
|
-
|
|
42644
|
+
const runs = await deps.client.listExecutionRuns(projectSlug, { taskId });
|
|
42645
|
+
const runIds = new Set(runs.map((run3) => run3.id));
|
|
42646
|
+
runIds.add(runId);
|
|
42647
|
+
const artifactsNested = await Promise.all(
|
|
42648
|
+
[...runIds].map(async (candidateRunId) => deps.client.listExecutionArtifacts(projectSlug, candidateRunId))
|
|
42649
|
+
);
|
|
42650
|
+
const hasEvidence = artifactsNested.flat().some(
|
|
42651
|
+
(artifact) => COMPLETION_EVIDENCE_KINDS.has(artifact.kind) && !isBridgeGeneratedArtifact(artifact) && runIds.has(artifact.runId)
|
|
42518
42652
|
);
|
|
42519
42653
|
if (!hasEvidence) {
|
|
42520
42654
|
return {
|
|
42521
42655
|
ok: false,
|
|
42522
|
-
reason: `Codex turn completed and task ${task.id} closed, but run ${runId} has no durable completion evidence artifact.`,
|
|
42656
|
+
reason: `Codex turn completed and task ${task.id} closed, but task lineage for run ${runId} has no durable completion evidence artifact.`,
|
|
42523
42657
|
task
|
|
42524
42658
|
};
|
|
42525
42659
|
}
|
|
@@ -42662,7 +42796,8 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42662
42796
|
task,
|
|
42663
42797
|
runId,
|
|
42664
42798
|
leaseId,
|
|
42665
|
-
`task_blocked: run ${runId} targets ${task.id} but blocker tasks could not be loaded: ${missingBlockers.join(", ")}
|
|
42799
|
+
`task_blocked: run ${runId} targets ${task.id} but blocker tasks could not be loaded: ${missingBlockers.join(", ")}`,
|
|
42800
|
+
"task_blocked"
|
|
42666
42801
|
);
|
|
42667
42802
|
return;
|
|
42668
42803
|
}
|
|
@@ -42674,7 +42809,8 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42674
42809
|
task,
|
|
42675
42810
|
runId,
|
|
42676
42811
|
leaseId,
|
|
42677
|
-
`task_blocked: run ${runId} targets ${task.id} but active blockers remain open: ${activeBlockers.join(", ")}
|
|
42812
|
+
`task_blocked: run ${runId} targets ${task.id} but active blockers remain open: ${activeBlockers.join(", ")}`,
|
|
42813
|
+
"task_blocked"
|
|
42678
42814
|
);
|
|
42679
42815
|
return;
|
|
42680
42816
|
}
|
|
@@ -42711,7 +42847,7 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42711
42847
|
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?"
|
|
42712
42848
|
3. When the operator confirms a path, call register_local_checkout({ projectSlug: "${projectSlug}", path: "<absolute path>", repo: "<this project's GitHub repo>" }).
|
|
42713
42849
|
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.`;
|
|
42714
|
-
await failRun(deps, projectSlug, task, runId, leaseId, reason);
|
|
42850
|
+
await failRun(deps, projectSlug, task, runId, leaseId, reason, "local_checkout_missing");
|
|
42715
42851
|
return;
|
|
42716
42852
|
}
|
|
42717
42853
|
try {
|
|
@@ -42745,7 +42881,15 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42745
42881
|
} catch (error51) {
|
|
42746
42882
|
const detail = error51 instanceof Error ? error51.message : String(error51);
|
|
42747
42883
|
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.)`;
|
|
42748
|
-
await failRun(
|
|
42884
|
+
await failRun(
|
|
42885
|
+
deps,
|
|
42886
|
+
projectSlug,
|
|
42887
|
+
task,
|
|
42888
|
+
runId,
|
|
42889
|
+
leaseId,
|
|
42890
|
+
reason,
|
|
42891
|
+
detail.includes("default_branch_unresolved") ? "default_branch_unresolved" : void 0
|
|
42892
|
+
);
|
|
42749
42893
|
return;
|
|
42750
42894
|
}
|
|
42751
42895
|
} else {
|
|
@@ -42784,7 +42928,15 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42784
42928
|
if (!resumed) {
|
|
42785
42929
|
const primingBudgetMs = remainingTurnBudgetMs();
|
|
42786
42930
|
if (primingBudgetMs <= 0) {
|
|
42787
|
-
await failRun(
|
|
42931
|
+
await failRun(
|
|
42932
|
+
deps,
|
|
42933
|
+
projectSlug,
|
|
42934
|
+
task,
|
|
42935
|
+
runId,
|
|
42936
|
+
leaseId,
|
|
42937
|
+
executionTurnBudgetMessage(execRunMaxMs()),
|
|
42938
|
+
"execution_turn_timeout"
|
|
42939
|
+
);
|
|
42788
42940
|
return;
|
|
42789
42941
|
}
|
|
42790
42942
|
const primed = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, bundle, primingBudgetMs, "priming");
|
|
@@ -42799,7 +42951,15 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
42799
42951
|
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.`;
|
|
42800
42952
|
const workBudgetMs = remainingTurnBudgetMs();
|
|
42801
42953
|
if (workBudgetMs <= 0) {
|
|
42802
|
-
await failRun(
|
|
42954
|
+
await failRun(
|
|
42955
|
+
deps,
|
|
42956
|
+
projectSlug,
|
|
42957
|
+
task,
|
|
42958
|
+
runId,
|
|
42959
|
+
leaseId,
|
|
42960
|
+
executionTurnBudgetMessage(execRunMaxMs()),
|
|
42961
|
+
"execution_turn_timeout"
|
|
42962
|
+
);
|
|
42803
42963
|
return;
|
|
42804
42964
|
}
|
|
42805
42965
|
const outcome = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, executionPrompt, workBudgetMs, "work");
|
|
@@ -42966,6 +43126,13 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
42966
43126
|
pullRequestUrl: pr.url,
|
|
42967
43127
|
status: "dev_deploy"
|
|
42968
43128
|
});
|
|
43129
|
+
await reconcileTaskAwaitingMerge(
|
|
43130
|
+
deps,
|
|
43131
|
+
projectSlug,
|
|
43132
|
+
readiness.task,
|
|
43133
|
+
runId,
|
|
43134
|
+
`PR opened (${pr.url}); deploy gate is running, but the operator still owns the merge decision.`
|
|
43135
|
+
);
|
|
42969
43136
|
} else {
|
|
42970
43137
|
await deps.client.updateExecutionRun(projectSlug, runId, {
|
|
42971
43138
|
leaseId,
|
|
@@ -43221,7 +43388,7 @@ async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
|
|
|
43221
43388
|
await deps.client.resetDirectorThread(projectSlug, taskId).catch(() => {
|
|
43222
43389
|
});
|
|
43223
43390
|
}
|
|
43224
|
-
async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
|
|
43391
|
+
async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCode) {
|
|
43225
43392
|
let currentRun;
|
|
43226
43393
|
try {
|
|
43227
43394
|
currentRun = await deps.client.getExecutionRun(projectSlug, runId);
|
|
@@ -43280,13 +43447,21 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
|
|
|
43280
43447
|
}
|
|
43281
43448
|
}
|
|
43282
43449
|
try {
|
|
43283
|
-
await deps.client.createExecutionRunHandoff(projectSlug, runId, {
|
|
43450
|
+
await deps.client.createExecutionRunHandoff(projectSlug, runId, {
|
|
43451
|
+
reason,
|
|
43452
|
+
...reasonCode ? { reasonCode } : {}
|
|
43453
|
+
});
|
|
43284
43454
|
} catch {
|
|
43285
43455
|
await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
|
|
43286
43456
|
}
|
|
43287
43457
|
}
|
|
43288
43458
|
async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
|
|
43289
43459
|
if (task.status === "handoff_needed") return;
|
|
43460
|
+
const run3 = await deps.client.getExecutionRun(projectSlug, runId).catch(() => null);
|
|
43461
|
+
if (run3?.prState === "merged") {
|
|
43462
|
+
console.log(JSON.stringify({ event: "pr_merge_already_observed", projectSlug, runId, taskId: task.id }));
|
|
43463
|
+
return;
|
|
43464
|
+
}
|
|
43290
43465
|
await deps.client.appendExecutionArtifact(projectSlug, runId, {
|
|
43291
43466
|
id: newArtifactId(),
|
|
43292
43467
|
kind: "handoff_judgement",
|
|
@@ -43388,6 +43563,27 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
|
|
|
43388
43563
|
pending = [];
|
|
43389
43564
|
}
|
|
43390
43565
|
for (const run3 of pending) {
|
|
43566
|
+
const updatedAtMs = run3.updatedAt ? Date.parse(run3.updatedAt) : Number.NaN;
|
|
43567
|
+
const staleBeforeMs = Date.now() - deps.staleSeconds * 1e3;
|
|
43568
|
+
if (run3.leaseId) {
|
|
43569
|
+
if (!Number.isFinite(updatedAtMs) || updatedAtMs >= staleBeforeMs) continue;
|
|
43570
|
+
try {
|
|
43571
|
+
const reclaimed = await deps.client.reclaimExecutionRunLease(slug, run3.id, deps.staleSeconds);
|
|
43572
|
+
if (!reclaimed) continue;
|
|
43573
|
+
} catch (error51) {
|
|
43574
|
+
if (error51 instanceof MrrlinApiError && error51.status === 404) continue;
|
|
43575
|
+
if (error51 instanceof MrrlinApiError && error51.status === 409 && error51.code === "RUN_LEASE_RECLAIM_EXHAUSTED") {
|
|
43576
|
+
await deps.client.createInboxItem(slug, {
|
|
43577
|
+
id: `II-${run3.id}-lease-stuck`,
|
|
43578
|
+
kind: "handoff",
|
|
43579
|
+
taskId: run3.taskId,
|
|
43580
|
+
body: `Execution run ${run3.id} exceeded its stale-lease reclaim budget (${maxLeaseReclaims()} attempts) and still is not making progress.`
|
|
43581
|
+
}).catch(() => {
|
|
43582
|
+
});
|
|
43583
|
+
}
|
|
43584
|
+
continue;
|
|
43585
|
+
}
|
|
43586
|
+
}
|
|
43391
43587
|
await drive(slug, run3.id);
|
|
43392
43588
|
}
|
|
43393
43589
|
if (deps.deployGate) {
|
|
@@ -43456,6 +43652,9 @@ function claimIntervalMs() {
|
|
|
43456
43652
|
const jitter = base * 0.25 * (Math.random() * 2 - 1);
|
|
43457
43653
|
return Math.round((base + jitter) * 1e3);
|
|
43458
43654
|
}
|
|
43655
|
+
function maxLeaseReclaims() {
|
|
43656
|
+
return readPositiveIntEnv("MRRLIN_MAX_LEASE_RECLAIMS", DEFAULT_MAX_LEASE_RECLAIMS);
|
|
43657
|
+
}
|
|
43459
43658
|
|
|
43460
43659
|
// src/async-job-consumer.ts
|
|
43461
43660
|
var import_node_crypto6 = __toESM(require("node:crypto"), 1);
|
|
@@ -47913,6 +48112,103 @@ var CheckoutRegistry = class {
|
|
|
47913
48112
|
}
|
|
47914
48113
|
};
|
|
47915
48114
|
|
|
48115
|
+
// src/checkout-handlers.ts
|
|
48116
|
+
var import_node_child_process10 = require("node:child_process");
|
|
48117
|
+
var import_node_fs12 = require("node:fs");
|
|
48118
|
+
|
|
48119
|
+
// src/git-remote-match.ts
|
|
48120
|
+
function normalizeRemote(url2) {
|
|
48121
|
+
const trimmed = url2.trim().replace(/\.git$/i, "");
|
|
48122
|
+
const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
|
|
48123
|
+
const pathPart = scp ? scp[1] : (() => {
|
|
48124
|
+
try {
|
|
48125
|
+
return new URL(trimmed).pathname.replace(/^\/+/, "");
|
|
48126
|
+
} catch {
|
|
48127
|
+
return null;
|
|
48128
|
+
}
|
|
48129
|
+
})();
|
|
48130
|
+
if (!pathPart) return null;
|
|
48131
|
+
const segs = pathPart.split("/").filter(Boolean);
|
|
48132
|
+
if (segs.length < 2) return null;
|
|
48133
|
+
return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
|
|
48134
|
+
}
|
|
48135
|
+
function remoteMatchesRepo(remoteUrl, repoFullName) {
|
|
48136
|
+
const a = normalizeRemote(remoteUrl);
|
|
48137
|
+
return a !== null && a === repoFullName.trim().toLowerCase();
|
|
48138
|
+
}
|
|
48139
|
+
|
|
48140
|
+
// src/checkout-handlers.ts
|
|
48141
|
+
function validateCheckoutPath(checkoutPath, repo) {
|
|
48142
|
+
const trimmedPath = checkoutPath.trim();
|
|
48143
|
+
if (!trimmedPath.startsWith("/")) {
|
|
48144
|
+
return {
|
|
48145
|
+
ok: false,
|
|
48146
|
+
code: "not_absolute",
|
|
48147
|
+
detail: `Path must be absolute, got: ${trimmedPath}`
|
|
48148
|
+
};
|
|
48149
|
+
}
|
|
48150
|
+
let resolvedPath;
|
|
48151
|
+
try {
|
|
48152
|
+
resolvedPath = (0, import_node_fs12.realpathSync)(trimmedPath);
|
|
48153
|
+
} catch {
|
|
48154
|
+
return {
|
|
48155
|
+
ok: false,
|
|
48156
|
+
code: "not_found",
|
|
48157
|
+
detail: `Path does not exist or is not accessible: ${trimmedPath}`
|
|
48158
|
+
};
|
|
48159
|
+
}
|
|
48160
|
+
let remoteUrl;
|
|
48161
|
+
try {
|
|
48162
|
+
remoteUrl = (0, import_node_child_process10.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
|
|
48163
|
+
encoding: "utf8",
|
|
48164
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
48165
|
+
timeout: 15e3,
|
|
48166
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
48167
|
+
}).trim();
|
|
48168
|
+
} catch {
|
|
48169
|
+
return {
|
|
48170
|
+
ok: false,
|
|
48171
|
+
code: "not_a_git_repo",
|
|
48172
|
+
detail: `Path is not a git repository or has no origin remote: ${resolvedPath}`
|
|
48173
|
+
};
|
|
48174
|
+
}
|
|
48175
|
+
if (!remoteMatchesRepo(remoteUrl, repo)) {
|
|
48176
|
+
return {
|
|
48177
|
+
ok: false,
|
|
48178
|
+
code: "remote_mismatch",
|
|
48179
|
+
detail: `Origin remote ${remoteUrl} does not match repo ${repo}.`
|
|
48180
|
+
};
|
|
48181
|
+
}
|
|
48182
|
+
return { ok: true, resolvedPath };
|
|
48183
|
+
}
|
|
48184
|
+
function readCheckoutState(registry3, projectSlug, machineLabel) {
|
|
48185
|
+
const entry = registry3.get(projectSlug);
|
|
48186
|
+
if (!entry) return null;
|
|
48187
|
+
return checkoutStateData(projectSlug, entry, machineLabel);
|
|
48188
|
+
}
|
|
48189
|
+
function registerCheckout(registry3, input, machineLabel, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
48190
|
+
const validated = validateCheckoutPath(input.path, input.repo);
|
|
48191
|
+
if (!validated.ok) return validated;
|
|
48192
|
+
registry3.confirm(input.projectSlug, validated.resolvedPath, now);
|
|
48193
|
+
return {
|
|
48194
|
+
ok: true,
|
|
48195
|
+
data: {
|
|
48196
|
+
projectSlug: input.projectSlug,
|
|
48197
|
+
path: validated.resolvedPath,
|
|
48198
|
+
confirmedAt: now,
|
|
48199
|
+
machineLabel
|
|
48200
|
+
}
|
|
48201
|
+
};
|
|
48202
|
+
}
|
|
48203
|
+
function checkoutStateData(projectSlug, entry, machineLabel) {
|
|
48204
|
+
return {
|
|
48205
|
+
projectSlug,
|
|
48206
|
+
path: entry.path,
|
|
48207
|
+
confirmedAt: entry.confirmedAt,
|
|
48208
|
+
machineLabel
|
|
48209
|
+
};
|
|
48210
|
+
}
|
|
48211
|
+
|
|
47916
48212
|
// src/executor-server.ts
|
|
47917
48213
|
async function startExecutorServer(opts) {
|
|
47918
48214
|
const manager = new CodexServerManager();
|
|
@@ -48120,7 +48416,7 @@ function extractVerificationSection(markdown) {
|
|
|
48120
48416
|
}
|
|
48121
48417
|
|
|
48122
48418
|
// src/bridge-log.ts
|
|
48123
|
-
var
|
|
48419
|
+
var import_node_fs13 = require("node:fs");
|
|
48124
48420
|
var import_node_path16 = require("node:path");
|
|
48125
48421
|
|
|
48126
48422
|
// src/redact.ts
|
|
@@ -48208,7 +48504,7 @@ function createBridgeLogger(stateDir, env = process.env) {
|
|
|
48208
48504
|
if (!resolveBridgeLogEnabled(env)) return NOOP_LOGGER;
|
|
48209
48505
|
const logsDir = (0, import_node_path16.join)(stateDir, "logs");
|
|
48210
48506
|
try {
|
|
48211
|
-
(0,
|
|
48507
|
+
(0, import_node_fs13.mkdirSync)(logsDir, { recursive: true });
|
|
48212
48508
|
} catch {
|
|
48213
48509
|
return NOOP_LOGGER;
|
|
48214
48510
|
}
|
|
@@ -48225,7 +48521,7 @@ function createBridgeLogger(stateDir, env = process.env) {
|
|
|
48225
48521
|
...rec.ms !== void 0 ? { ms: rec.ms } : {},
|
|
48226
48522
|
payload: redactMessage(rec.payload, { includePrompts })
|
|
48227
48523
|
}) + "\n";
|
|
48228
|
-
(0,
|
|
48524
|
+
(0, import_node_fs13.appendFileSync)(fileFor(), line);
|
|
48229
48525
|
} catch {
|
|
48230
48526
|
}
|
|
48231
48527
|
};
|
|
@@ -48238,7 +48534,7 @@ function createBridgeLogger(stateDir, env = process.env) {
|
|
|
48238
48534
|
}
|
|
48239
48535
|
|
|
48240
48536
|
// src/external-agents-scrub.ts
|
|
48241
|
-
var
|
|
48537
|
+
var import_node_fs14 = __toESM(require("node:fs"), 1);
|
|
48242
48538
|
var import_node_os8 = __toESM(require("node:os"), 1);
|
|
48243
48539
|
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
48244
48540
|
var REDACTION = "[REDACTED]";
|
|
@@ -48249,7 +48545,7 @@ function readKeysEnv() {
|
|
|
48249
48545
|
const filePath = KEYS_ENV_PATH();
|
|
48250
48546
|
let mtimeMs = 0;
|
|
48251
48547
|
try {
|
|
48252
|
-
mtimeMs =
|
|
48548
|
+
mtimeMs = import_node_fs14.default.statSync(filePath).mtimeMs;
|
|
48253
48549
|
} catch {
|
|
48254
48550
|
mtimeMs = 0;
|
|
48255
48551
|
}
|
|
@@ -48258,7 +48554,7 @@ function readKeysEnv() {
|
|
|
48258
48554
|
}
|
|
48259
48555
|
const map2 = /* @__PURE__ */ new Map();
|
|
48260
48556
|
try {
|
|
48261
|
-
const content =
|
|
48557
|
+
const content = import_node_fs14.default.readFileSync(filePath, "utf8");
|
|
48262
48558
|
for (const rawLine of content.split(/\r?\n/)) {
|
|
48263
48559
|
const line = rawLine.trim();
|
|
48264
48560
|
if (line === "" || line.startsWith("#")) continue;
|
|
@@ -48650,7 +48946,7 @@ function addBridgeModel(target, model) {
|
|
|
48650
48946
|
}
|
|
48651
48947
|
function readTextIfExists(filePath) {
|
|
48652
48948
|
try {
|
|
48653
|
-
return
|
|
48949
|
+
return import_node_fs15.default.readFileSync(filePath, "utf8");
|
|
48654
48950
|
} catch {
|
|
48655
48951
|
return null;
|
|
48656
48952
|
}
|
|
@@ -48729,7 +49025,7 @@ function readCodexFallbackModels() {
|
|
|
48729
49025
|
}
|
|
48730
49026
|
const automationsDir = import_node_path18.default.join(codexHome, "automations");
|
|
48731
49027
|
try {
|
|
48732
|
-
for (const entry of
|
|
49028
|
+
for (const entry of import_node_fs15.default.readdirSync(automationsDir, { withFileTypes: true })) {
|
|
48733
49029
|
if (!entry.isDirectory()) continue;
|
|
48734
49030
|
for (const slug of readKnownGptSlugsFromTomlFile(import_node_path18.default.join(automationsDir, entry.name, "automation.toml"))) {
|
|
48735
49031
|
addBridgeModel(
|
|
@@ -48753,7 +49049,7 @@ function readCodexFallbackModels() {
|
|
|
48753
49049
|
function neutralCheckoutCwd(stateDir) {
|
|
48754
49050
|
const dir = import_node_path18.default.join(stateDir, "no-checkout");
|
|
48755
49051
|
try {
|
|
48756
|
-
|
|
49052
|
+
import_node_fs15.default.mkdirSync(dir, { recursive: true });
|
|
48757
49053
|
} catch {
|
|
48758
49054
|
}
|
|
48759
49055
|
return dir;
|
|
@@ -48772,6 +49068,21 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
48772
49068
|
"transcript. Just call the tools and reply with the outcome \u2014 start your reply with the result.",
|
|
48773
49069
|
"When creating or updating tasks, always provide `autonomyLevel` and `autonomyReason`.",
|
|
48774
49070
|
"",
|
|
49071
|
+
"Tool-use contract \u2014 typed MCP tools are the ONLY channel for Mrrlin data:",
|
|
49072
|
+
"- Every read or write of Mrrlin data \u2014 tasks, plans, wiki pages, inbox items, execution runs,",
|
|
49073
|
+
" projects \u2014 must go through the corresponding typed MCP tool. Never use `commandExecution`,",
|
|
49074
|
+
" `curl`, `python`, or any direct HTTP call to the Mrrlin API.",
|
|
49075
|
+
"- A single `create_task` call creates a task completely; it accepts `title`, `notes`, `category`,",
|
|
49076
|
+
" `subcategory`, `impact`, `autonomyLevel`, `planId`, `parentTaskId`, and more in one call.",
|
|
49077
|
+
" Do not create then patch, and do not re-read the task afterwards unless the operator asked",
|
|
49078
|
+
" for confirmation.",
|
|
49079
|
+
"- `commandExecution` is for the operator's own machine and repositories \u2014 running builds, tests,",
|
|
49080
|
+
" `git`, inspecting local files \u2014 and for public third-party web checks. It is not a transport",
|
|
49081
|
+
" for this product's API.",
|
|
49082
|
+
"- If no typed tool exists for a needed operation, state that to the operator instead of reaching",
|
|
49083
|
+
" for the API by hand.",
|
|
49084
|
+
"- Do not spend turns re-reading state you just wrote.",
|
|
49085
|
+
"",
|
|
48775
49086
|
"Turn lifecycle \u2014 what you can and cannot do between turns:",
|
|
48776
49087
|
"- Your turn ends the moment you stop producing output. You CANNOT start a new turn yourself:",
|
|
48777
49088
|
" no timer and no scheduler wakes you up. Only a new operator message starts the next turn.",
|
|
@@ -48845,9 +49156,10 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
48845
49156
|
' Never answer "nothing found" from plans alone.',
|
|
48846
49157
|
'- Only report "nothing found" after the q-variants AND a full-list scan are empty, and then',
|
|
48847
49158
|
" state exactly what you searched.",
|
|
48848
|
-
"- For public HTTP/HTML verification through `commandExecution`, prefer built-in
|
|
48849
|
-
" as `curl`, `python3` stdlib `urllib.request`, and `html.parser`; do not assume
|
|
48850
|
-
" Python packages such as `requests` or `bs4` are installed.",
|
|
49159
|
+
"- For public/third-party HTTP/HTML verification through `commandExecution`, prefer built-in",
|
|
49160
|
+
" tooling such as `curl`, `python3` stdlib `urllib.request`, and `html.parser`; do not assume",
|
|
49161
|
+
" third-party Python packages such as `requests` or `bs4` are installed. This permission covers",
|
|
49162
|
+
" third-party sites ONLY \u2014 never call the Mrrlin API from `commandExecution`.",
|
|
48851
49163
|
"",
|
|
48852
49164
|
"Handoff contract \u2014 durable operator action MUST NOT live only in chat:",
|
|
48853
49165
|
"- If you conclude that a specific task needs human input, account access, manual evidence,",
|
|
@@ -49058,7 +49370,7 @@ function resolveDefaultBranch(checkoutPath) {
|
|
|
49058
49370
|
if (!checkoutPath) return "main";
|
|
49059
49371
|
const runGit = (args) => {
|
|
49060
49372
|
try {
|
|
49061
|
-
return (0,
|
|
49373
|
+
return (0, import_node_child_process11.execFileSync)("git", ["-C", checkoutPath, ...args], {
|
|
49062
49374
|
encoding: "utf8",
|
|
49063
49375
|
stdio: ["ignore", "pipe", "ignore"],
|
|
49064
49376
|
timeout: 15e3,
|
|
@@ -49090,7 +49402,7 @@ async function runOrphanWorktreeSweep(client, opts) {
|
|
|
49090
49402
|
const cutoff = Date.now() - worktreeRetentionMs();
|
|
49091
49403
|
const isReapable = (worktreePath) => {
|
|
49092
49404
|
try {
|
|
49093
|
-
return
|
|
49405
|
+
return import_node_fs15.default.statSync(worktreePath).mtimeMs < cutoff;
|
|
49094
49406
|
} catch {
|
|
49095
49407
|
return false;
|
|
49096
49408
|
}
|
|
@@ -49394,7 +49706,9 @@ function createBridgeMessageHandler(deps) {
|
|
|
49394
49706
|
logResumeRejected(req.directorSessionId, reason);
|
|
49395
49707
|
return;
|
|
49396
49708
|
}
|
|
49397
|
-
const
|
|
49709
|
+
const parked = t?.supersededReplay ?? null;
|
|
49710
|
+
const buf = req.acceptSuperseded === true && parked && parked.turnId === req.turnId ? parked : t?.replay ?? null;
|
|
49711
|
+
const servingSuperseded = buf !== null && buf === parked;
|
|
49398
49712
|
if (!buf) {
|
|
49399
49713
|
sendMsg({ type: "resume", status: "no-buffer", reason: "wrong-session" });
|
|
49400
49714
|
logResumeRejected(req.directorSessionId, "wrong-session");
|
|
@@ -49425,15 +49739,17 @@ function createBridgeMessageHandler(deps) {
|
|
|
49425
49739
|
buf.state = result.next;
|
|
49426
49740
|
incrementBindEpoch(buf);
|
|
49427
49741
|
buf.replaying = true;
|
|
49428
|
-
|
|
49429
|
-
|
|
49430
|
-
|
|
49431
|
-
|
|
49432
|
-
|
|
49433
|
-
|
|
49434
|
-
|
|
49435
|
-
|
|
49436
|
-
|
|
49742
|
+
if (!servingSuperseded) {
|
|
49743
|
+
deps.bindSessionSink?.(req.directorSessionId, send);
|
|
49744
|
+
boundSessions.add(req.directorSessionId);
|
|
49745
|
+
if (buf.triggerDetach) {
|
|
49746
|
+
const detach = buf.triggerDetach;
|
|
49747
|
+
const epochAtBind = buf.bindEpoch;
|
|
49748
|
+
socket.once("close", () => {
|
|
49749
|
+
if (buf.bindEpoch !== epochAtBind) return;
|
|
49750
|
+
detach();
|
|
49751
|
+
});
|
|
49752
|
+
}
|
|
49437
49753
|
}
|
|
49438
49754
|
const willStreamLive = result.next === "live";
|
|
49439
49755
|
sendMsg({
|
|
@@ -49510,7 +49826,8 @@ function createBridgeMessageHandler(deps) {
|
|
|
49510
49826
|
}
|
|
49511
49827
|
const msg = parsed;
|
|
49512
49828
|
const sid = sessionIdOf(msg);
|
|
49513
|
-
|
|
49829
|
+
const skipEagerBindForSupersededResume = msg.type === "resume" && msg.acceptSuperseded === true;
|
|
49830
|
+
if (sid && deps.bindSessionSink && !boundSessions.has(sid) && !skipEagerBindForSupersededResume) {
|
|
49514
49831
|
deps.bindSessionSink(sid, send);
|
|
49515
49832
|
boundSessions.add(sid);
|
|
49516
49833
|
}
|
|
@@ -49532,6 +49849,57 @@ function createBridgeMessageHandler(deps) {
|
|
|
49532
49849
|
sendForSpan({ type: "pairing-qr", ...result });
|
|
49533
49850
|
return;
|
|
49534
49851
|
}
|
|
49852
|
+
if (msg.type === "checkout-state") {
|
|
49853
|
+
if (!deps.checkoutRegistry) {
|
|
49854
|
+
sendForSpan({ type: "checkout-state", ok: false, error: { code: "bridge_offline", detail: "checkout registry is not configured on this bridge" } });
|
|
49855
|
+
return;
|
|
49856
|
+
}
|
|
49857
|
+
const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
|
|
49858
|
+
if (!projectSlug) {
|
|
49859
|
+
sendForSpan({ type: "checkout-state", ok: false, error: { code: "invalid_input", fields: { projectSlug: "required" } } });
|
|
49860
|
+
return;
|
|
49861
|
+
}
|
|
49862
|
+
const state = readCheckoutState(deps.checkoutRegistry, projectSlug, import_node_os9.default.hostname());
|
|
49863
|
+
if (!state) {
|
|
49864
|
+
sendForSpan({ type: "checkout-state", ok: false, error: { code: "checkout_missing" } });
|
|
49865
|
+
return;
|
|
49866
|
+
}
|
|
49867
|
+
sendForSpan({ type: "checkout-state", ok: true, data: state });
|
|
49868
|
+
return;
|
|
49869
|
+
}
|
|
49870
|
+
if (msg.type === "checkout-register") {
|
|
49871
|
+
if (!deps.checkoutRegistry) {
|
|
49872
|
+
sendForSpan({ type: "checkout-register", ok: false, error: { code: "bridge_offline", detail: "checkout registry is not configured on this bridge" } });
|
|
49873
|
+
return;
|
|
49874
|
+
}
|
|
49875
|
+
const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
|
|
49876
|
+
const repo = typeof msg.repo === "string" ? msg.repo.trim() : "";
|
|
49877
|
+
if (!projectSlug || !repo) {
|
|
49878
|
+
sendForSpan({
|
|
49879
|
+
type: "checkout-register",
|
|
49880
|
+
ok: false,
|
|
49881
|
+
error: {
|
|
49882
|
+
code: "invalid_input",
|
|
49883
|
+
fields: {
|
|
49884
|
+
...!projectSlug ? { projectSlug: "required" } : {},
|
|
49885
|
+
...!repo ? { repo: "required" } : {}
|
|
49886
|
+
}
|
|
49887
|
+
}
|
|
49888
|
+
});
|
|
49889
|
+
return;
|
|
49890
|
+
}
|
|
49891
|
+
const result = registerCheckout(
|
|
49892
|
+
deps.checkoutRegistry,
|
|
49893
|
+
{ projectSlug, path: typeof msg.path === "string" ? msg.path : "", repo },
|
|
49894
|
+
import_node_os9.default.hostname()
|
|
49895
|
+
);
|
|
49896
|
+
if (!result.ok) {
|
|
49897
|
+
sendForSpan({ type: "checkout-register", ok: false, error: { code: result.code, detail: result.detail } });
|
|
49898
|
+
return;
|
|
49899
|
+
}
|
|
49900
|
+
sendForSpan({ type: "checkout-register", ok: true, data: result.data });
|
|
49901
|
+
return;
|
|
49902
|
+
}
|
|
49535
49903
|
if (msg.type === "agents-state") {
|
|
49536
49904
|
if (!deps.runExternalAgentsCli) {
|
|
49537
49905
|
sendForSpan({ type: "agents-state", ok: false, error: { code: "bridge_offline", detail: "external-agents is not configured on this bridge" } });
|
|
@@ -49841,6 +50209,10 @@ function createBridgeMessageHandler(deps) {
|
|
|
49841
50209
|
recordInterrupted(oldBuf, Date.now(), deps.suspendedMs?.() ?? 0);
|
|
49842
50210
|
}
|
|
49843
50211
|
}
|
|
50212
|
+
const droppedParked = takenOver.supersededReplay;
|
|
50213
|
+
if (oldBuf) {
|
|
50214
|
+
takenOver.supersededReplay = oldBuf;
|
|
50215
|
+
}
|
|
49844
50216
|
takenOver.cancel?.();
|
|
49845
50217
|
void withTimeout2(
|
|
49846
50218
|
codexClient.turn.interrupt({ threadId: takenOver.threadId }),
|
|
@@ -49858,7 +50230,13 @@ function createBridgeMessageHandler(deps) {
|
|
|
49858
50230
|
spanId,
|
|
49859
50231
|
sessionId: directorSessionId,
|
|
49860
50232
|
type: "<turn-takeover>",
|
|
49861
|
-
payload: {
|
|
50233
|
+
payload: {
|
|
50234
|
+
sessionId: directorSessionId,
|
|
50235
|
+
clean: cleared,
|
|
50236
|
+
oldTurnId: oldBuf?.turnId ?? null,
|
|
50237
|
+
parkedTurnId: oldBuf?.turnId ?? null,
|
|
50238
|
+
droppedParkedTurnId: droppedParked?.turnId ?? null
|
|
50239
|
+
}
|
|
49862
50240
|
});
|
|
49863
50241
|
}
|
|
49864
50242
|
const desiredModel = normalizeModel(msg.model);
|
|
@@ -49870,10 +50248,12 @@ function createBridgeMessageHandler(deps) {
|
|
|
49870
50248
|
threadId: "",
|
|
49871
50249
|
mapper: new CodexBridgeEventMapper(),
|
|
49872
50250
|
lastUsedAt: Date.now(),
|
|
50251
|
+
lastUsedAtSuspendedMs: deps.suspendedMs?.() ?? 0,
|
|
49873
50252
|
inFlight: false,
|
|
49874
50253
|
model: desiredModel,
|
|
49875
50254
|
reasoningEffort: desiredEffort,
|
|
49876
|
-
replay: null
|
|
50255
|
+
replay: null,
|
|
50256
|
+
supersededReplay: null
|
|
49877
50257
|
};
|
|
49878
50258
|
await acquireThread(codexClient, {
|
|
49879
50259
|
threadId: null,
|
|
@@ -49939,6 +50319,7 @@ function createBridgeMessageHandler(deps) {
|
|
|
49939
50319
|
...deps.ackLingerMs !== void 0 ? { ackLingerMs: deps.ackLingerMs } : {}
|
|
49940
50320
|
});
|
|
49941
50321
|
thread.lastUsedAt = Date.now();
|
|
50322
|
+
thread.lastUsedAtSuspendedMs = deps.suspendedMs?.() ?? 0;
|
|
49942
50323
|
const threadId = thread.threadId;
|
|
49943
50324
|
const currentReplayBuf = thread.replay;
|
|
49944
50325
|
sendForSpan({
|
|
@@ -50157,6 +50538,7 @@ ${effectiveText}` : effectiveText;
|
|
|
50157
50538
|
thread.inFlight = false;
|
|
50158
50539
|
}
|
|
50159
50540
|
thread.lastUsedAt = Date.now();
|
|
50541
|
+
thread.lastUsedAtSuspendedMs = deps.suspendedMs?.() ?? 0;
|
|
50160
50542
|
deps.persistThreads();
|
|
50161
50543
|
deps.releaseCodex();
|
|
50162
50544
|
}
|
|
@@ -50182,6 +50564,56 @@ function createBridgeShutdown(deps) {
|
|
|
50182
50564
|
deps.exit(0);
|
|
50183
50565
|
};
|
|
50184
50566
|
}
|
|
50567
|
+
function shouldReapIdleThread(thread, now, suspendedMsNow, idleThreadTtlMs) {
|
|
50568
|
+
const suspendedSinceUse = Math.max(0, suspendedMsNow - thread.lastUsedAtSuspendedMs);
|
|
50569
|
+
return now - thread.lastUsedAt - suspendedSinceUse > idleThreadTtlMs;
|
|
50570
|
+
}
|
|
50571
|
+
function sweepIdleThreads(threads, opts) {
|
|
50572
|
+
const suspendedNow = opts.suspendedMs();
|
|
50573
|
+
const reaped = [];
|
|
50574
|
+
for (const [directorSessionId, thread] of threads.entries()) {
|
|
50575
|
+
if (thread.inFlight) continue;
|
|
50576
|
+
if (thread.replay && !shouldGc(thread.replay, opts.now, suspendedNow)) continue;
|
|
50577
|
+
if (thread.supersededReplay && !shouldGc(thread.supersededReplay, opts.now, suspendedNow)) continue;
|
|
50578
|
+
if (!shouldReapIdleThread(thread, opts.now, suspendedNow, opts.idleThreadTtlMs)) continue;
|
|
50579
|
+
threads.delete(directorSessionId);
|
|
50580
|
+
reaped.push(directorSessionId);
|
|
50581
|
+
}
|
|
50582
|
+
return reaped;
|
|
50583
|
+
}
|
|
50584
|
+
function sweepReplayBuffers(threads, tombstones, opts) {
|
|
50585
|
+
const suspendedNow = opts.suspendedMs();
|
|
50586
|
+
let retainedBuffers = 0;
|
|
50587
|
+
let retainedBytes = 0;
|
|
50588
|
+
for (const [directorSessionId, thread] of threads) {
|
|
50589
|
+
if (thread.replay && shouldGc(thread.replay, opts.now, suspendedNow)) {
|
|
50590
|
+
const turnId = thread.replay.turnId;
|
|
50591
|
+
const lastSeq = thread.replay.lastSeq;
|
|
50592
|
+
const expiredUnacked = thread.replay.ackedAt === null;
|
|
50593
|
+
writeTombstone(tombstones, directorSessionId, thread.replay, opts.now, suspendedNow);
|
|
50594
|
+
opts.onTombstone?.({ directorSessionId, turnId, lastSeq, expiredUnacked });
|
|
50595
|
+
thread.replay = null;
|
|
50596
|
+
} else if (thread.replay) {
|
|
50597
|
+
retainedBuffers += 1;
|
|
50598
|
+
retainedBytes += thread.replay.bytesUsed;
|
|
50599
|
+
}
|
|
50600
|
+
if (thread.supersededReplay && shouldGc(thread.supersededReplay, opts.now, suspendedNow)) {
|
|
50601
|
+
const turnId = thread.supersededReplay.turnId;
|
|
50602
|
+
thread.supersededReplay = null;
|
|
50603
|
+
opts.onSupersededDropped?.({ directorSessionId, turnId });
|
|
50604
|
+
} else if (thread.supersededReplay) {
|
|
50605
|
+
retainedBuffers += 1;
|
|
50606
|
+
retainedBytes += thread.supersededReplay.bytesUsed;
|
|
50607
|
+
}
|
|
50608
|
+
}
|
|
50609
|
+
const tombstonesRemoved = sweepExpiredTombstones(
|
|
50610
|
+
tombstones,
|
|
50611
|
+
opts.now,
|
|
50612
|
+
opts.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS,
|
|
50613
|
+
suspendedNow
|
|
50614
|
+
);
|
|
50615
|
+
return { retainedBuffers, retainedBytes, tombstonesRemoved };
|
|
50616
|
+
}
|
|
50185
50617
|
function parseConfig() {
|
|
50186
50618
|
const host = (process.env.MRRLIN_DIRECTOR_BRIDGE_HOST ?? "127.0.0.1").trim() || "127.0.0.1";
|
|
50187
50619
|
const port = readEnvInt("MRRLIN_DIRECTOR_BRIDGE_PORT", 8791);
|
|
@@ -50195,7 +50627,7 @@ function parseConfig() {
|
|
|
50195
50627
|
}
|
|
50196
50628
|
function scanForOtherBridges() {
|
|
50197
50629
|
try {
|
|
50198
|
-
const result = (0,
|
|
50630
|
+
const result = (0, import_node_child_process11.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
|
|
50199
50631
|
encoding: "utf8"
|
|
50200
50632
|
});
|
|
50201
50633
|
if (result.status !== 0) return [];
|
|
@@ -50375,13 +50807,13 @@ async function startDirectorBridge() {
|
|
|
50375
50807
|
// egressDomains is retained in the seam signature (verification-turn.ts still computes the
|
|
50376
50808
|
// env host) but no longer constrains anything — the executor is unrestricted.
|
|
50377
50809
|
startSandbox: async (_egressDomains) => {
|
|
50378
|
-
const cwd =
|
|
50810
|
+
const cwd = import_node_fs15.default.mkdtempSync(import_node_path18.default.join(import_node_os9.default.tmpdir(), "mrrlin-verify-"));
|
|
50379
50811
|
let handle;
|
|
50380
50812
|
try {
|
|
50381
50813
|
handle = await startExecutorServer({ worktree: cwd });
|
|
50382
50814
|
} catch (error51) {
|
|
50383
50815
|
try {
|
|
50384
|
-
|
|
50816
|
+
import_node_fs15.default.rmSync(cwd, { recursive: true, force: true });
|
|
50385
50817
|
} catch {
|
|
50386
50818
|
}
|
|
50387
50819
|
throw error51;
|
|
@@ -50392,7 +50824,7 @@ async function startDirectorBridge() {
|
|
|
50392
50824
|
dispose: () => {
|
|
50393
50825
|
handle.dispose();
|
|
50394
50826
|
try {
|
|
50395
|
-
|
|
50827
|
+
import_node_fs15.default.rmSync(cwd, { recursive: true, force: true });
|
|
50396
50828
|
} catch {
|
|
50397
50829
|
}
|
|
50398
50830
|
}
|
|
@@ -50504,10 +50936,12 @@ async function startDirectorBridge() {
|
|
|
50504
50936
|
threadId: entry.threadId,
|
|
50505
50937
|
mapper: new CodexBridgeEventMapper(),
|
|
50506
50938
|
lastUsedAt: entry.lastUsedAt,
|
|
50939
|
+
lastUsedAtSuspendedMs: suspensionDetector.suspendedMs(),
|
|
50507
50940
|
inFlight: false,
|
|
50508
50941
|
model: entry.model,
|
|
50509
50942
|
reasoningEffort: normalizeReasoningEffort(entry.reasoningEffort),
|
|
50510
|
-
replay: null
|
|
50943
|
+
replay: null,
|
|
50944
|
+
supersededReplay: null
|
|
50511
50945
|
});
|
|
50512
50946
|
}
|
|
50513
50947
|
const persistThreads = () => {
|
|
@@ -50532,17 +50966,12 @@ async function startDirectorBridge() {
|
|
|
50532
50966
|
}
|
|
50533
50967
|
};
|
|
50534
50968
|
const cleanupTimer = setInterval(() => {
|
|
50535
|
-
const
|
|
50536
|
-
|
|
50537
|
-
|
|
50538
|
-
|
|
50539
|
-
|
|
50540
|
-
|
|
50541
|
-
threads.delete(directorSessionId);
|
|
50542
|
-
changed = true;
|
|
50543
|
-
}
|
|
50544
|
-
}
|
|
50545
|
-
if (changed) persistThreads();
|
|
50969
|
+
const reaped = sweepIdleThreads(threads, {
|
|
50970
|
+
now: Date.now(),
|
|
50971
|
+
suspendedMs: () => suspensionDetector.suspendedMs(),
|
|
50972
|
+
idleThreadTtlMs: config2.idleThreadTtlMs
|
|
50973
|
+
});
|
|
50974
|
+
if (reaped.length > 0) persistThreads();
|
|
50546
50975
|
}, 6e4);
|
|
50547
50976
|
cleanupTimer.unref?.();
|
|
50548
50977
|
let remoteRelay = null;
|
|
@@ -50603,7 +51032,7 @@ async function startDirectorBridge() {
|
|
|
50603
51032
|
const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
|
|
50604
51033
|
let bridgeBinShaShort = "unknown00000";
|
|
50605
51034
|
try {
|
|
50606
|
-
bridgeBinShaShort = import_node_crypto8.default.createHash("sha256").update(
|
|
51035
|
+
bridgeBinShaShort = import_node_crypto8.default.createHash("sha256").update(import_node_fs15.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
|
|
50607
51036
|
} catch {
|
|
50608
51037
|
}
|
|
50609
51038
|
attachBridgeWss(server, {
|
|
@@ -50737,28 +51166,27 @@ async function startDirectorBridge() {
|
|
|
50737
51166
|
})
|
|
50738
51167
|
);
|
|
50739
51168
|
enqueueRun(async () => {
|
|
50740
|
-
const
|
|
50741
|
-
|
|
50742
|
-
|
|
50743
|
-
|
|
50744
|
-
|
|
50745
|
-
if (t.replay && shouldGc(t.replay, now, suspendedNow)) {
|
|
50746
|
-
const turnId = t.replay.turnId;
|
|
50747
|
-
const lastSeq = t.replay.lastSeq;
|
|
50748
|
-
const expiredUnacked = t.replay.ackedAt === null;
|
|
50749
|
-
writeTombstone(tombstones, sid, t.replay, now);
|
|
51169
|
+
const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
|
|
51170
|
+
now: Date.now(),
|
|
51171
|
+
suspendedMs: () => suspensionDetector.suspendedMs(),
|
|
51172
|
+
tombstoneTtlMs: DEFAULT_TOMBSTONE_TTL_MS,
|
|
51173
|
+
onTombstone: ({ directorSessionId, turnId, lastSeq, expiredUnacked }) => {
|
|
50750
51174
|
bridgeLogger.logOut({
|
|
50751
51175
|
spanId: "gc",
|
|
50752
|
-
sessionId:
|
|
51176
|
+
sessionId: directorSessionId,
|
|
50753
51177
|
type: expiredUnacked ? "<result-expired>" : "<tombstone-written>",
|
|
50754
|
-
payload: { sessionId:
|
|
51178
|
+
payload: { sessionId: directorSessionId, turnId, lastSeq }
|
|
51179
|
+
});
|
|
51180
|
+
},
|
|
51181
|
+
onSupersededDropped: ({ directorSessionId, turnId }) => {
|
|
51182
|
+
bridgeLogger.logOut({
|
|
51183
|
+
spanId: "gc",
|
|
51184
|
+
sessionId: directorSessionId,
|
|
51185
|
+
type: "<superseded-dropped>",
|
|
51186
|
+
payload: { sessionId: directorSessionId, turnId }
|
|
50755
51187
|
});
|
|
50756
|
-
t.replay = null;
|
|
50757
|
-
} else if (t.replay) {
|
|
50758
|
-
retainedBuffers += 1;
|
|
50759
|
-
retainedBytes += t.replay.bytesUsed;
|
|
50760
51188
|
}
|
|
50761
|
-
}
|
|
51189
|
+
});
|
|
50762
51190
|
if (retainedBuffers > 0) {
|
|
50763
51191
|
bridgeLogger.logOut({
|
|
50764
51192
|
spanId: "gc",
|
|
@@ -50767,13 +51195,12 @@ async function startDirectorBridge() {
|
|
|
50767
51195
|
payload: { retainedBuffers, retainedBytes }
|
|
50768
51196
|
});
|
|
50769
51197
|
}
|
|
50770
|
-
|
|
50771
|
-
if (removed > 0) {
|
|
51198
|
+
if (tombstonesRemoved > 0) {
|
|
50772
51199
|
bridgeLogger.logOut({
|
|
50773
51200
|
spanId: "gc",
|
|
50774
51201
|
sessionId: null,
|
|
50775
51202
|
type: "<tombstone-expired>",
|
|
50776
|
-
payload: { removed }
|
|
51203
|
+
payload: { removed: tombstonesRemoved }
|
|
50777
51204
|
});
|
|
50778
51205
|
}
|
|
50779
51206
|
});
|
|
@@ -50997,13 +51424,13 @@ function readOrCreateBridgeToken(stateDir) {
|
|
|
50997
51424
|
if (explicit) return explicit;
|
|
50998
51425
|
const tokenPath = import_node_path18.default.join(stateDir, "token.txt");
|
|
50999
51426
|
try {
|
|
51000
|
-
const existing =
|
|
51427
|
+
const existing = import_node_fs15.default.readFileSync(tokenPath, "utf8").trim();
|
|
51001
51428
|
if (existing) return existing;
|
|
51002
51429
|
} catch {
|
|
51003
51430
|
}
|
|
51004
|
-
|
|
51431
|
+
import_node_fs15.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
|
|
51005
51432
|
const token = import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
51006
|
-
|
|
51433
|
+
import_node_fs15.default.writeFileSync(tokenPath, `${token}
|
|
51007
51434
|
`, { mode: 384 });
|
|
51008
51435
|
return token;
|
|
51009
51436
|
}
|
|
@@ -54387,10 +54814,8 @@ var StdioServerTransport = class {
|
|
|
54387
54814
|
// src/tools.ts
|
|
54388
54815
|
var import_node_crypto11 = require("node:crypto");
|
|
54389
54816
|
var import_promises3 = require("node:fs/promises");
|
|
54390
|
-
var import_node_fs15 = require("node:fs");
|
|
54391
54817
|
var import_node_path20 = __toESM(require("node:path"), 1);
|
|
54392
54818
|
var import_node_os10 = __toESM(require("node:os"), 1);
|
|
54393
|
-
var import_node_child_process12 = require("node:child_process");
|
|
54394
54819
|
|
|
54395
54820
|
// ../../packages/wiki/dist/index.js
|
|
54396
54821
|
var import_promises2 = __toESM(require("node:fs/promises"), 1);
|
|
@@ -54649,15 +55074,35 @@ function firstTaskIdToken(text) {
|
|
|
54649
55074
|
}
|
|
54650
55075
|
|
|
54651
55076
|
// src/consensus/spec-gate.ts
|
|
55077
|
+
function mapAcceptedIssueToClarification(issue2) {
|
|
55078
|
+
const text = issue2.text.toLowerCase();
|
|
55079
|
+
let kind;
|
|
55080
|
+
if (issue2.category === "scope") {
|
|
55081
|
+
const directionCues = ["only", "or also", "narrower", "broader", "include", "exclude", "versus", "vs"];
|
|
55082
|
+
kind = directionCues.some((cue) => text.includes(cue)) ? "scope_direction" : "missing_requirement";
|
|
55083
|
+
} else {
|
|
55084
|
+
const acceptanceCues = ["acceptance", "done", "success", "criteria", "satisfy"];
|
|
55085
|
+
kind = acceptanceCues.some((cue) => text.includes(cue)) ? "acceptance_criteria_detail" : "default_choice";
|
|
55086
|
+
}
|
|
55087
|
+
return {
|
|
55088
|
+
kind,
|
|
55089
|
+
diagnostic: false,
|
|
55090
|
+
rawQuestion: issue2.text,
|
|
55091
|
+
whyBlocking: `reviewer raised a ${issue2.category} issue`,
|
|
55092
|
+
category: issue2.category
|
|
55093
|
+
};
|
|
55094
|
+
}
|
|
54652
55095
|
async function runSpecGate(_target, deps, opts) {
|
|
54653
55096
|
if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
|
|
54654
55097
|
const intent = await deps.readIntent();
|
|
54655
55098
|
if (intent.taskId !== null && (intent.seed === null || intent.seed.trim() === "")) {
|
|
54656
55099
|
return {
|
|
54657
55100
|
outcome: "CONSENSUS_BLOCKED",
|
|
54658
|
-
|
|
55101
|
+
operatorClarifications: [
|
|
54659
55102
|
{
|
|
54660
|
-
|
|
55103
|
+
kind: "missing_requirement",
|
|
55104
|
+
diagnostic: false,
|
|
55105
|
+
rawQuestion: "What are the requirements for this task? There is no operator request on record, so the spec cannot be validated against your intent \u2014 please state what this task must do.",
|
|
54661
55106
|
whyBlocking: "operator_seed_request is empty",
|
|
54662
55107
|
category: "scope"
|
|
54663
55108
|
}
|
|
@@ -54671,9 +55116,11 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
54671
55116
|
if (normalizeDomain(intent.domain) !== "code" && parseAcceptanceCriteria(spec.markdownBody) === null) {
|
|
54672
55117
|
return {
|
|
54673
55118
|
outcome: "CONSENSUS_BLOCKED",
|
|
54674
|
-
|
|
55119
|
+
operatorClarifications: [
|
|
54675
55120
|
{
|
|
54676
|
-
|
|
55121
|
+
kind: "acceptance_criteria_detail",
|
|
55122
|
+
diagnostic: false,
|
|
55123
|
+
rawQuestion: "This spec has no machine-usable acceptance criteria. Add an `## Acceptance criteria` section written as a markdown checklist (`- [ ] \u2026` items) describing exactly what the finished deliverable must satisfy \u2014 the review of the produced work is scored against it.",
|
|
54677
55124
|
whyBlocking: "non-code spec is missing a parseable `## Acceptance criteria` checklist",
|
|
54678
55125
|
category: "scope"
|
|
54679
55126
|
}
|
|
@@ -54723,9 +55170,11 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
54723
55170
|
);
|
|
54724
55171
|
return {
|
|
54725
55172
|
outcome: "CONSENSUS_BLOCKED",
|
|
54726
|
-
|
|
55173
|
+
diagnostics: [
|
|
54727
55174
|
{
|
|
54728
|
-
|
|
55175
|
+
kind: "spec_identity_mismatch",
|
|
55176
|
+
diagnostic: true,
|
|
55177
|
+
rawQuestion: `SPEC_IDENTITY_MISMATCH: this spec page gates ${intent.taskId}, but its heading identifies a different task \u2014 rewrite the spec heading and content for ${intent.taskId}, or re-link the task.`,
|
|
54729
55178
|
whyBlocking: "converged spec content belongs to a different task than the one it gates",
|
|
54730
55179
|
category: "scope"
|
|
54731
55180
|
}
|
|
@@ -54745,12 +55194,8 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
54745
55194
|
}
|
|
54746
55195
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
54747
55196
|
const accepted = result.history.at(-1)?.accepted ?? [];
|
|
54748
|
-
const
|
|
54749
|
-
|
|
54750
|
-
whyBlocking: `reviewer raised a ${issue2.category} issue`,
|
|
54751
|
-
category: issue2.category
|
|
54752
|
-
}));
|
|
54753
|
-
return clarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", clarifications } : { outcome: "CONSENSUS_BLOCKED" };
|
|
55197
|
+
const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
|
|
55198
|
+
return operatorClarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", operatorClarifications } : { outcome: "CONSENSUS_BLOCKED" };
|
|
54754
55199
|
}
|
|
54755
55200
|
return { outcome: result.outcome };
|
|
54756
55201
|
}
|
|
@@ -54828,7 +55273,7 @@ async function runCodeGate(_target, deps) {
|
|
|
54828
55273
|
}
|
|
54829
55274
|
|
|
54830
55275
|
// src/consensus/multi-llm-head.ts
|
|
54831
|
-
var
|
|
55276
|
+
var import_node_child_process12 = require("node:child_process");
|
|
54832
55277
|
function errMessage3(err) {
|
|
54833
55278
|
return err instanceof Error ? err.message : String(err);
|
|
54834
55279
|
}
|
|
@@ -54870,7 +55315,7 @@ function runExternalAgents(bin, args, label, timeoutMs, spawn3) {
|
|
|
54870
55315
|
});
|
|
54871
55316
|
}
|
|
54872
55317
|
async function pickPanel(input, deps) {
|
|
54873
|
-
const spawn3 = deps?.spawn ??
|
|
55318
|
+
const spawn3 = deps?.spawn ?? import_node_child_process12.spawn;
|
|
54874
55319
|
const { command, prefixArgs } = resolveExternalAgentsCliInvocation(deps?.externalAgentsBin);
|
|
54875
55320
|
const args = [
|
|
54876
55321
|
"pick",
|
|
@@ -54891,7 +55336,7 @@ async function pickPanel(input, deps) {
|
|
|
54891
55336
|
return { ok: true, agents };
|
|
54892
55337
|
}
|
|
54893
55338
|
async function dispatchHead(agentId, prompt, timeoutMs, deps) {
|
|
54894
|
-
const spawn3 = deps?.spawn ??
|
|
55339
|
+
const spawn3 = deps?.spawn ?? import_node_child_process12.spawn;
|
|
54895
55340
|
const { command, prefixArgs } = resolveExternalAgentsCliInvocation(deps?.externalAgentsBin);
|
|
54896
55341
|
const args = ["dispatch", agentId, "--json", prompt];
|
|
54897
55342
|
const r = await runExternalAgents(command, [...prefixArgs, ...args], "dispatch", timeoutMs, spawn3);
|
|
@@ -55175,6 +55620,22 @@ function buildGateQuestionOptions(question, category) {
|
|
|
55175
55620
|
}
|
|
55176
55621
|
];
|
|
55177
55622
|
}
|
|
55623
|
+
var HUMANIZE_TEMPLATE_BY_KIND = {
|
|
55624
|
+
scope_direction: "The spec can be read in two different ways. Which direction should it follow?",
|
|
55625
|
+
default_choice: "One important detail is still ambiguous. Which interpretation should the spec use?",
|
|
55626
|
+
missing_requirement: "A required part of the task is still missing from the spec. What should it say?",
|
|
55627
|
+
acceptance_criteria_detail: "The direction is clear, but the spec still needs a sharper success check. What should count as done?"
|
|
55628
|
+
};
|
|
55629
|
+
var HUMANIZE_CONTEXT_CAP = 200;
|
|
55630
|
+
function humanizeClarificationBody(clarification) {
|
|
55631
|
+
const prefix = HUMANIZE_TEMPLATE_BY_KIND[clarification.kind];
|
|
55632
|
+
let context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
55633
|
+
if (context.length > HUMANIZE_CONTEXT_CAP) {
|
|
55634
|
+
const cutIdx = context.lastIndexOf(" ", HUMANIZE_CONTEXT_CAP);
|
|
55635
|
+
context = context.slice(0, cutIdx > 0 ? cutIdx : HUMANIZE_CONTEXT_CAP).trim() + "\u2026";
|
|
55636
|
+
}
|
|
55637
|
+
return context ? `${prefix} ${context}` : prefix;
|
|
55638
|
+
}
|
|
55178
55639
|
var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
|
|
55179
55640
|
async function runNativeSpecGate(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
55180
55641
|
const deps = builders.buildSpecGateDeps({
|
|
@@ -55215,6 +55676,100 @@ async function cachedResult(client, input) {
|
|
|
55215
55676
|
}
|
|
55216
55677
|
return null;
|
|
55217
55678
|
}
|
|
55679
|
+
async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
55680
|
+
const operatorClarifications = result.operatorClarifications ?? [];
|
|
55681
|
+
const diagnostics = result.diagnostics ?? [];
|
|
55682
|
+
if (operatorClarifications.length > 0) {
|
|
55683
|
+
try {
|
|
55684
|
+
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
55685
|
+
if (page) {
|
|
55686
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
55687
|
+
id: page.id,
|
|
55688
|
+
taskId: page.taskId ?? null
|
|
55689
|
+
});
|
|
55690
|
+
await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
|
|
55691
|
+
contentHash: domainScopedReviewHash(page.contentHash, domain2),
|
|
55692
|
+
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
55693
|
+
rounds: 0
|
|
55694
|
+
});
|
|
55695
|
+
}
|
|
55696
|
+
} catch (error52) {
|
|
55697
|
+
process.stderr.write(
|
|
55698
|
+
`[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
|
|
55699
|
+
`
|
|
55700
|
+
);
|
|
55701
|
+
}
|
|
55702
|
+
}
|
|
55703
|
+
let filedQuestionCount = 0;
|
|
55704
|
+
if (operatorClarifications.length > 0) {
|
|
55705
|
+
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
55706
|
+
const taskId = page?.taskId ?? null;
|
|
55707
|
+
if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
|
|
55708
|
+
let planId = null;
|
|
55709
|
+
if (!taskId) {
|
|
55710
|
+
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
55711
|
+
planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
|
|
55712
|
+
}
|
|
55713
|
+
if (taskId) {
|
|
55714
|
+
const scopeId = taskId;
|
|
55715
|
+
const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
55716
|
+
for (const clarification of operatorClarifications) {
|
|
55717
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
55718
|
+
try {
|
|
55719
|
+
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
55720
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
55721
|
+
id,
|
|
55722
|
+
kind: "question",
|
|
55723
|
+
taskId: scopeId,
|
|
55724
|
+
body: humanizeClarificationBody(clarification),
|
|
55725
|
+
origin: "gate",
|
|
55726
|
+
questionOptions,
|
|
55727
|
+
...groupId ? { groupId } : {}
|
|
55728
|
+
});
|
|
55729
|
+
} catch {
|
|
55730
|
+
}
|
|
55731
|
+
}
|
|
55732
|
+
filedQuestionCount = operatorClarifications.length;
|
|
55733
|
+
} else if (planId) {
|
|
55734
|
+
const scopeId = planId;
|
|
55735
|
+
const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
55736
|
+
for (const clarification of operatorClarifications) {
|
|
55737
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
|
|
55738
|
+
try {
|
|
55739
|
+
const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
|
|
55740
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
55741
|
+
id,
|
|
55742
|
+
kind: "question",
|
|
55743
|
+
planId: scopeId,
|
|
55744
|
+
body: humanizeClarificationBody(clarification),
|
|
55745
|
+
origin: "gate",
|
|
55746
|
+
questionOptions,
|
|
55747
|
+
...groupId ? { groupId } : {}
|
|
55748
|
+
});
|
|
55749
|
+
} catch {
|
|
55750
|
+
}
|
|
55751
|
+
}
|
|
55752
|
+
filedQuestionCount = operatorClarifications.length;
|
|
55753
|
+
}
|
|
55754
|
+
}
|
|
55755
|
+
let error51;
|
|
55756
|
+
if (filedQuestionCount > 0) {
|
|
55757
|
+
error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
|
|
55758
|
+
} else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
|
|
55759
|
+
error51 = "Consensus blocked: the spec content does not match its linked task. This requires fixing the spec/task relationship directly, not an inbox answer.";
|
|
55760
|
+
} else if (operatorClarifications.length > 0) {
|
|
55761
|
+
error51 = `Consensus blocked on ${operatorClarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
|
|
55762
|
+
} else {
|
|
55763
|
+
error51 = "Consensus blocked: reviewers did not converge within budget.";
|
|
55764
|
+
}
|
|
55765
|
+
return {
|
|
55766
|
+
kind: "failed",
|
|
55767
|
+
failureReason: "consensus_blocked",
|
|
55768
|
+
error: error51,
|
|
55769
|
+
filedQuestionCount,
|
|
55770
|
+
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
55771
|
+
};
|
|
55772
|
+
}
|
|
55218
55773
|
async function run(ctx, input) {
|
|
55219
55774
|
const result = await runNativeSpecGate(ctx, input);
|
|
55220
55775
|
if (process.env[SHADOW_ENV_FLAG]) {
|
|
@@ -55243,95 +55798,7 @@ async function run(ctx, input) {
|
|
|
55243
55798
|
} catch {
|
|
55244
55799
|
}
|
|
55245
55800
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
55246
|
-
|
|
55247
|
-
if (clarifications.length > 0) {
|
|
55248
|
-
try {
|
|
55249
|
-
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
55250
|
-
if (page) {
|
|
55251
|
-
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
55252
|
-
id: page.id,
|
|
55253
|
-
taskId: page.taskId ?? null
|
|
55254
|
-
});
|
|
55255
|
-
await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
|
|
55256
|
-
contentHash: domainScopedReviewHash(page.contentHash, domain2),
|
|
55257
|
-
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
55258
|
-
rounds: 0
|
|
55259
|
-
});
|
|
55260
|
-
}
|
|
55261
|
-
} catch (error52) {
|
|
55262
|
-
process.stderr.write(
|
|
55263
|
-
`[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
|
|
55264
|
-
`
|
|
55265
|
-
);
|
|
55266
|
-
}
|
|
55267
|
-
}
|
|
55268
|
-
let filedQuestionCount = 0;
|
|
55269
|
-
if (clarifications.length > 0) {
|
|
55270
|
-
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
55271
|
-
const taskId = page?.taskId ?? null;
|
|
55272
|
-
if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
|
|
55273
|
-
let planId = null;
|
|
55274
|
-
if (!taskId) {
|
|
55275
|
-
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
55276
|
-
planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
|
|
55277
|
-
}
|
|
55278
|
-
if (taskId) {
|
|
55279
|
-
const scopeId = taskId;
|
|
55280
|
-
const groupId = clarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(clarifications.map((c) => c.question).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
55281
|
-
for (const clarification of clarifications) {
|
|
55282
|
-
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
|
|
55283
|
-
try {
|
|
55284
|
-
const questionOptions = buildGateQuestionOptions(clarification.question, clarification.category);
|
|
55285
|
-
await ctx.client.createInboxItem(input.projectSlug, {
|
|
55286
|
-
id,
|
|
55287
|
-
kind: "question",
|
|
55288
|
-
taskId: scopeId,
|
|
55289
|
-
body: clarification.question,
|
|
55290
|
-
origin: "gate",
|
|
55291
|
-
questionOptions,
|
|
55292
|
-
...groupId ? { groupId } : {}
|
|
55293
|
-
});
|
|
55294
|
-
} catch {
|
|
55295
|
-
}
|
|
55296
|
-
}
|
|
55297
|
-
filedQuestionCount = clarifications.length;
|
|
55298
|
-
} else if (planId) {
|
|
55299
|
-
const scopeId = planId;
|
|
55300
|
-
const groupId = clarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(clarifications.map((c) => c.question).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
55301
|
-
for (const clarification of clarifications) {
|
|
55302
|
-
const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
|
|
55303
|
-
try {
|
|
55304
|
-
const questionOptions = buildGateQuestionOptions(clarification.question, clarification.category);
|
|
55305
|
-
await ctx.client.createInboxItem(input.projectSlug, {
|
|
55306
|
-
id,
|
|
55307
|
-
kind: "question",
|
|
55308
|
-
planId: scopeId,
|
|
55309
|
-
body: clarification.question,
|
|
55310
|
-
origin: "gate",
|
|
55311
|
-
questionOptions,
|
|
55312
|
-
...groupId ? { groupId } : {}
|
|
55313
|
-
});
|
|
55314
|
-
} catch {
|
|
55315
|
-
}
|
|
55316
|
-
}
|
|
55317
|
-
filedQuestionCount = clarifications.length;
|
|
55318
|
-
}
|
|
55319
|
-
}
|
|
55320
|
-
let error51;
|
|
55321
|
-
if (filedQuestionCount > 0) {
|
|
55322
|
-
error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
|
|
55323
|
-
} else if (clarifications.length > 0) {
|
|
55324
|
-
error51 = `Consensus blocked on ${clarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
|
|
55325
|
-
} else {
|
|
55326
|
-
error51 = "Consensus blocked: reviewers did not converge within budget.";
|
|
55327
|
-
}
|
|
55328
|
-
return {
|
|
55329
|
-
kind: "failed",
|
|
55330
|
-
failureReason: "consensus_blocked",
|
|
55331
|
-
error: error51,
|
|
55332
|
-
filedQuestionCount,
|
|
55333
|
-
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
55334
|
-
};
|
|
55801
|
+
return handleConsensusBlocked(result, ctx, input, resolvedTaskId);
|
|
55335
55802
|
}
|
|
55336
55803
|
switch (result.outcome) {
|
|
55337
55804
|
case "CONVERGED":
|
|
@@ -55527,27 +55994,6 @@ async function enqueueAsyncTool(client, descriptor2, input) {
|
|
|
55527
55994
|
}
|
|
55528
55995
|
}
|
|
55529
55996
|
|
|
55530
|
-
// src/git-remote-match.ts
|
|
55531
|
-
function normalizeRemote(url2) {
|
|
55532
|
-
const trimmed = url2.trim().replace(/\.git$/i, "");
|
|
55533
|
-
const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
|
|
55534
|
-
const pathPart = scp ? scp[1] : (() => {
|
|
55535
|
-
try {
|
|
55536
|
-
return new URL(trimmed).pathname.replace(/^\/+/, "");
|
|
55537
|
-
} catch {
|
|
55538
|
-
return null;
|
|
55539
|
-
}
|
|
55540
|
-
})();
|
|
55541
|
-
if (!pathPart) return null;
|
|
55542
|
-
const segs = pathPart.split("/").filter(Boolean);
|
|
55543
|
-
if (segs.length < 2) return null;
|
|
55544
|
-
return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
|
|
55545
|
-
}
|
|
55546
|
-
function remoteMatchesRepo(remoteUrl, repoFullName) {
|
|
55547
|
-
const a = normalizeRemote(remoteUrl);
|
|
55548
|
-
return a !== null && a === repoFullName.trim().toLowerCase();
|
|
55549
|
-
}
|
|
55550
|
-
|
|
55551
55997
|
// src/tool-names.ts
|
|
55552
55998
|
var mcpToolNames = {
|
|
55553
55999
|
appendExecutionArtifact: "append_execution_artifact",
|
|
@@ -55601,12 +56047,16 @@ var mcpToolNames = {
|
|
|
55601
56047
|
updatePlan: "update_plan",
|
|
55602
56048
|
updateTask: "update_task",
|
|
55603
56049
|
resetDirectorThread: "reset_director_thread",
|
|
56050
|
+
archiveTask: "archive_task",
|
|
56051
|
+
unarchiveTask: "unarchive_task",
|
|
55604
56052
|
updateWikiPage: "update_wiki_page",
|
|
55605
56053
|
markSpecReady: "mark_spec_ready",
|
|
55606
56054
|
runSpecConsensus: "run_spec_consensus",
|
|
55607
56055
|
runCodeReviewerGate: "run_code_reviewer_gate",
|
|
55608
56056
|
uploadArtifact: "upload_artifact",
|
|
55609
56057
|
listArtifactFiles: "list_artifact_files",
|
|
56058
|
+
reclaimExecutionRunLease: "reclaim_execution_run_lease",
|
|
56059
|
+
restartExecutionRun: "restart_execution_run",
|
|
55610
56060
|
resolveHandoff: "resolve_handoff",
|
|
55611
56061
|
recordProducedUrl: "record_produced_url",
|
|
55612
56062
|
registerLocalCheckout: "register_local_checkout",
|
|
@@ -56116,6 +56566,8 @@ var getTaskInputSchema = taskScopedSchema;
|
|
|
56116
56566
|
var createTaskInputSchema = projectScopedSchema.merge(mrrlinTaskCreateSchema);
|
|
56117
56567
|
var updateTaskInputSchema = taskScopedSchema.merge(mrrlinTaskUpdateSchema);
|
|
56118
56568
|
var resetDirectorThreadInputSchema = taskScopedSchema;
|
|
56569
|
+
var archiveTaskInputSchema = taskScopedSchema;
|
|
56570
|
+
var unarchiveTaskInputSchema = taskScopedSchema;
|
|
56119
56571
|
var listPlansInputSchema = projectScopedSchema;
|
|
56120
56572
|
var createPlanInputSchema = projectScopedSchema.merge(mrrlinPlanCreateSchema);
|
|
56121
56573
|
var updatePlanInputSchema = planScopedSchema.merge(mrrlinPlanUpdateSchema);
|
|
@@ -56203,6 +56655,8 @@ var listArtifactFilesInputSchema = projectScopedSchema.merge(
|
|
|
56203
56655
|
runId: mrrlinExecutionRunIdSchema.optional()
|
|
56204
56656
|
})
|
|
56205
56657
|
);
|
|
56658
|
+
var reclaimExecutionRunLeaseInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinExecutionRunRequeueSchema);
|
|
56659
|
+
var restartExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinRestartExecutionRunSchema);
|
|
56206
56660
|
var listInboxItemsInputSchema = projectScopedSchema.merge(mrrlinInboxItemListFiltersSchema);
|
|
56207
56661
|
var createInboxItemInputSchema = projectScopedSchema.merge(mrrlinInboxItemCreateSchema);
|
|
56208
56662
|
var decideInboxItemInputSchema = projectScopedSchema.extend({ itemId: mrrlinInboxItemIdSchema }).merge(mrrlinInboxItemDecideSchema);
|
|
@@ -56268,6 +56722,8 @@ var mcpToolInputSchemas = {
|
|
|
56268
56722
|
[mcpToolNames.updatePlan]: updatePlanInputSchema,
|
|
56269
56723
|
[mcpToolNames.updateTask]: updateTaskInputSchema,
|
|
56270
56724
|
[mcpToolNames.resetDirectorThread]: resetDirectorThreadInputSchema,
|
|
56725
|
+
[mcpToolNames.archiveTask]: archiveTaskInputSchema,
|
|
56726
|
+
[mcpToolNames.unarchiveTask]: unarchiveTaskInputSchema,
|
|
56271
56727
|
[mcpToolNames.updateWikiPage]: updateWikiPageInputSchema,
|
|
56272
56728
|
[mcpToolNames.markSpecReady]: markSpecReadyInputSchema,
|
|
56273
56729
|
[mcpToolNames.runSpecConsensus]: runSpecConsensusInputSchema,
|
|
@@ -56277,6 +56733,8 @@ var mcpToolInputSchemas = {
|
|
|
56277
56733
|
[mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
|
|
56278
56734
|
[mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
|
|
56279
56735
|
[mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
|
|
56736
|
+
[mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
|
|
56737
|
+
[mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
|
|
56280
56738
|
[mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
|
|
56281
56739
|
[mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
|
|
56282
56740
|
[mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
|
|
@@ -56331,6 +56789,8 @@ var mcpToolOperationIds = {
|
|
|
56331
56789
|
[mcpToolNames.updatePlan]: openApiOperationIds.updatePlan,
|
|
56332
56790
|
[mcpToolNames.updateTask]: openApiOperationIds.updateTask,
|
|
56333
56791
|
[mcpToolNames.resetDirectorThread]: openApiOperationIds.resetDirectorThread,
|
|
56792
|
+
[mcpToolNames.archiveTask]: openApiOperationIds.archiveTask,
|
|
56793
|
+
[mcpToolNames.unarchiveTask]: openApiOperationIds.unarchiveTask,
|
|
56334
56794
|
[mcpToolNames.updateWikiPage]: openApiOperationIds.updateWikiPage,
|
|
56335
56795
|
[mcpToolNames.markSpecReady]: openApiOperationIds.markSpecReady,
|
|
56336
56796
|
[mcpToolNames.listInboxItems]: openApiOperationIds.listInboxItems,
|
|
@@ -56338,6 +56798,8 @@ var mcpToolOperationIds = {
|
|
|
56338
56798
|
[mcpToolNames.decideInboxItem]: openApiOperationIds.decideInboxItem,
|
|
56339
56799
|
[mcpToolNames.uploadArtifact]: openApiOperationIds.createArtifactFile,
|
|
56340
56800
|
[mcpToolNames.listArtifactFiles]: openApiOperationIds.listArtifactFiles,
|
|
56801
|
+
[mcpToolNames.reclaimExecutionRunLease]: openApiOperationIds.reclaimExecutionRunLease,
|
|
56802
|
+
[mcpToolNames.restartExecutionRun]: openApiOperationIds.restartExecutionRun,
|
|
56341
56803
|
[mcpToolNames.resolveHandoff]: openApiOperationIds.resolveHandoff
|
|
56342
56804
|
};
|
|
56343
56805
|
var mcpToolDescriptions = {
|
|
@@ -56378,6 +56840,8 @@ var mcpToolDescriptions = {
|
|
|
56378
56840
|
[mcpToolNames.updatePlan]: "Update a plan/epic.",
|
|
56379
56841
|
[mcpToolNames.updateTask]: "Update a task. Honors DB invariants: closed_no_result requires linked Evidence wiki pages. Set directorThreadId here to persist the worker's resumable provider thread handle.",
|
|
56380
56842
|
[mcpToolNames.resetDirectorThread]: "Clear a task's persisted Director execution thread handle so the next execution run starts a fresh provider thread. Records a director_thread_reset task event.",
|
|
56843
|
+
[mcpToolNames.archiveTask]: "Archive a task: hides it from default reads, removes it from plan progress and scheduler consideration, cancels its non-terminal execution runs, and closes its open inbox items. Reversible \u2014 the project owner can restore it with unarchive_task. Records an 'archived' task event.",
|
|
56844
|
+
[mcpToolNames.unarchiveTask]: "Restore an archived task to its original status and visibility. Owner-only \u2014 refused with NOT_PROJECT_OWNER for any other caller. Does not resurrect the execution runs or inbox items that were cancelled/closed when the task was archived. Records a 'restored' task event.",
|
|
56381
56845
|
[mcpToolNames.updateWikiPage]: "Update a markdown Wiki page.",
|
|
56382
56846
|
[mcpToolNames.markSpecReady]: "Mark a wiki page (folder=Specs) as ready (or not) for execution-run gating. The Director sets this true after validating the spec; any edit to the page's markdown auto-resets it to false.",
|
|
56383
56847
|
[mcpToolNames.runSpecConsensus]: 'Enqueue the consensus review gate over a Specs/{id} page. Returns IMMEDIATELY with one of: already_converged (a CONVERGED review already exists for the current content \u2014 proceed straight to create_execution_run, do NOT call mark_spec_ready, it is already set), already_reviewed (a terminal CONSENSUS_BLOCKED for this content), already_running (a consensus job is in flight), or queued (a new job was launched). The review runs in the background; its result/blocker arrives in the inbox (info on CONVERGED, handoff on failure) and is surfaced into the open chat. Do NOT call again for the same spec and do NOT poll. On queued/already_running, tell the user it was launched and end the turn. Pass extraHeads: ["researcher"] for specs referencing external libraries/APIs, or ["debugger"] for root-cause/investigation specs to add opt-in specialist reviewers on top of the fixed four. For a NON-code task (domain seo/marketing/content, ADR 0020): the low-risk lanes `seo`, `marketing`, and `content` proceed to `create_execution_run` after consensus, using the same flow as code tasks. Report the spec\'s review outcome to the operator.',
|
|
@@ -56399,6 +56863,8 @@ var mcpToolDescriptions = {
|
|
|
56399
56863
|
[mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
|
|
56400
56864
|
[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.",
|
|
56401
56865
|
[mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
|
|
56866
|
+
[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.",
|
|
56867
|
+
[mcpToolNames.restartExecutionRun]: "Restart a parked execution run (handoff_requested or handoff_queued) back to pending so the queue can resume it.",
|
|
56402
56868
|
[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.",
|
|
56403
56869
|
[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.",
|
|
56404
56870
|
[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."
|
|
@@ -56808,6 +57274,20 @@ function createMrrlinTools(options) {
|
|
|
56808
57274
|
"Unable to reset director thread.",
|
|
56809
57275
|
async (c, { projectSlug, taskId }) => await c.resetDirectorThread(projectSlug, taskId)
|
|
56810
57276
|
),
|
|
57277
|
+
[mcpToolNames.archiveTask]: makeTool(
|
|
57278
|
+
client,
|
|
57279
|
+
mcpToolNames.archiveTask,
|
|
57280
|
+
"TASK_ARCHIVE_FAILED",
|
|
57281
|
+
"Unable to archive task.",
|
|
57282
|
+
async (c, { projectSlug, taskId }) => await c.archiveTask(projectSlug, taskId)
|
|
57283
|
+
),
|
|
57284
|
+
[mcpToolNames.unarchiveTask]: makeTool(
|
|
57285
|
+
client,
|
|
57286
|
+
mcpToolNames.unarchiveTask,
|
|
57287
|
+
"TASK_UNARCHIVE_FAILED",
|
|
57288
|
+
"Unable to unarchive task.",
|
|
57289
|
+
async (c, { projectSlug, taskId }) => await c.unarchiveTask(projectSlug, taskId)
|
|
57290
|
+
),
|
|
56811
57291
|
[mcpToolNames.updateWikiPage]: makeTool(
|
|
56812
57292
|
client,
|
|
56813
57293
|
mcpToolNames.updateWikiPage,
|
|
@@ -57142,6 +57622,20 @@ function createMrrlinTools(options) {
|
|
|
57142
57622
|
"Unable to list artifact files.",
|
|
57143
57623
|
async (c, { projectSlug, taskId, runId }) => await c.listArtifactFiles(projectSlug, { runId, taskId })
|
|
57144
57624
|
),
|
|
57625
|
+
[mcpToolNames.reclaimExecutionRunLease]: makeTool(
|
|
57626
|
+
client,
|
|
57627
|
+
mcpToolNames.reclaimExecutionRunLease,
|
|
57628
|
+
"EXECUTION_RUN_RECLAIM_FAILED",
|
|
57629
|
+
"Unable to reclaim the execution run lease.",
|
|
57630
|
+
async (c, { projectSlug, runId, staleSeconds }) => await c.reclaimExecutionRunLease(projectSlug, runId, staleSeconds)
|
|
57631
|
+
),
|
|
57632
|
+
[mcpToolNames.restartExecutionRun]: makeTool(
|
|
57633
|
+
client,
|
|
57634
|
+
mcpToolNames.restartExecutionRun,
|
|
57635
|
+
"EXECUTION_RUN_RESTART_FAILED",
|
|
57636
|
+
"Unable to restart the execution run.",
|
|
57637
|
+
async (c, { projectSlug, runId, ...input }) => await c.restartExecutionRun(projectSlug, runId, input)
|
|
57638
|
+
),
|
|
57145
57639
|
[mcpToolNames.resolveHandoff]: makeTool(
|
|
57146
57640
|
client,
|
|
57147
57641
|
mcpToolNames.resolveHandoff,
|
|
@@ -57155,47 +57649,19 @@ function createMrrlinTools(options) {
|
|
|
57155
57649
|
"LOCAL_CHECKOUT_REGISTER_FAILED",
|
|
57156
57650
|
"Unable to register local checkout.",
|
|
57157
57651
|
async (_c, { projectSlug, path: checkoutPath, repo }) => {
|
|
57158
|
-
const trimmedPath = checkoutPath.trim();
|
|
57159
|
-
if (!import_node_path20.default.isAbsolute(trimmedPath)) {
|
|
57160
|
-
throw new McpToolCodedError(
|
|
57161
|
-
"LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
|
|
57162
|
-
`Path must be absolute, got: ${trimmedPath}`
|
|
57163
|
-
);
|
|
57164
|
-
}
|
|
57165
|
-
let resolvedPath;
|
|
57166
|
-
try {
|
|
57167
|
-
resolvedPath = (0, import_node_fs15.realpathSync)(trimmedPath);
|
|
57168
|
-
} catch {
|
|
57169
|
-
throw new McpToolCodedError(
|
|
57170
|
-
"LOCAL_CHECKOUT_PATH_NOT_FOUND",
|
|
57171
|
-
`Path does not exist or is not accessible: ${trimmedPath}`
|
|
57172
|
-
);
|
|
57173
|
-
}
|
|
57174
|
-
let remoteUrl;
|
|
57175
|
-
try {
|
|
57176
|
-
remoteUrl = (0, import_node_child_process12.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
|
|
57177
|
-
encoding: "utf8",
|
|
57178
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
57179
|
-
timeout: 15e3,
|
|
57180
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
57181
|
-
}).trim();
|
|
57182
|
-
} catch {
|
|
57183
|
-
throw new McpToolCodedError(
|
|
57184
|
-
"LOCAL_CHECKOUT_NOT_A_GIT_REPO",
|
|
57185
|
-
`Path is not a git repository or has no origin remote: ${resolvedPath}`
|
|
57186
|
-
);
|
|
57187
|
-
}
|
|
57188
|
-
if (!remoteMatchesRepo(remoteUrl, repo)) {
|
|
57189
|
-
throw new McpToolCodedError(
|
|
57190
|
-
"LOCAL_CHECKOUT_REMOTE_MISMATCH",
|
|
57191
|
-
`Origin remote ${remoteUrl} does not match repo ${repo}.`
|
|
57192
|
-
);
|
|
57193
|
-
}
|
|
57194
57652
|
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");
|
|
57195
57653
|
const registry3 = new CheckoutRegistry(stateDir);
|
|
57196
|
-
const
|
|
57197
|
-
|
|
57198
|
-
|
|
57654
|
+
const result = registerCheckout(registry3, { projectSlug, path: checkoutPath, repo }, import_node_os10.default.hostname());
|
|
57655
|
+
if (!result.ok) {
|
|
57656
|
+
const codeMap = {
|
|
57657
|
+
not_absolute: "LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
|
|
57658
|
+
not_found: "LOCAL_CHECKOUT_PATH_NOT_FOUND",
|
|
57659
|
+
not_a_git_repo: "LOCAL_CHECKOUT_NOT_A_GIT_REPO",
|
|
57660
|
+
remote_mismatch: "LOCAL_CHECKOUT_REMOTE_MISMATCH"
|
|
57661
|
+
};
|
|
57662
|
+
throw new McpToolCodedError(codeMap[result.code], result.detail);
|
|
57663
|
+
}
|
|
57664
|
+
return result.data;
|
|
57199
57665
|
}
|
|
57200
57666
|
),
|
|
57201
57667
|
[mcpToolNames.retrieveContext]: makeTool(
|