@mrrlin-dev/mcp 0.3.27 → 0.3.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.cjs +911 -50
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -20645,7 +20645,7 @@ var require_dist = __commonJS({
20645
20645
 
20646
20646
  // src/bin.ts
20647
20647
  var import_node_path19 = __toESM(require("node:path"), 1);
20648
- var import_node_child_process9 = require("node:child_process");
20648
+ var import_node_child_process12 = require("node:child_process");
20649
20649
  var import_node_fs13 = require("node:fs");
20650
20650
  var import_node_os11 = require("node:os");
20651
20651
  var import_node_path20 = __toESM(require("node:path"), 1);
@@ -20747,6 +20747,9 @@ function mrrlinTable(binPath, distribution, serveEnv) {
20747
20747
  }
20748
20748
  return { ...base, startup_timeout_sec: 30 };
20749
20749
  }
20750
+ function externalAgentsTable() {
20751
+ return { command: "external-agents-mcp", args: [], startup_timeout_sec: 30 };
20752
+ }
20750
20753
  function browserTable(binPath, distribution, homeDir) {
20751
20754
  const base = distribution === "npm" ? { command: "mrrlin-mcp", args: ["browser-serve"] } : { command: "node", args: [binPath, "browser-serve"] };
20752
20755
  return { ...base, startup_timeout_sec: 30, env: { PLAYWRIGHT_BROWSERS_PATH: browserPaths(homeDir).browsersPath } };
@@ -20767,6 +20770,11 @@ function managedBlocks(options, distribution) {
20767
20770
  id: "mrrlin-browser",
20768
20771
  expected: browserTable(options.binPath, distribution, homeDir),
20769
20772
  blockRe: blockRegex("mrrlin-browser")
20773
+ },
20774
+ {
20775
+ id: "external-agents",
20776
+ expected: externalAgentsTable(),
20777
+ blockRe: blockRegex("external-agents")
20770
20778
  }
20771
20779
  ];
20772
20780
  }
@@ -20981,7 +20989,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
20981
20989
  var import_node_http = __toESM(require("node:http"), 1);
20982
20990
  var import_node_os7 = __toESM(require("node:os"), 1);
20983
20991
  var import_node_path13 = __toESM(require("node:path"), 1);
20984
- var import_node_child_process7 = require("node:child_process");
20992
+ var import_node_child_process9 = require("node:child_process");
20985
20993
  var import_node_crypto7 = __toESM(require("node:crypto"), 1);
20986
20994
 
20987
20995
  // src/turn-replay-buffer.ts
@@ -36377,6 +36385,8 @@ var openApiOperationIds = {
36377
36385
  listArtifactFiles: "listArtifactFiles",
36378
36386
  getOperatorSettings: "getOperatorSettings",
36379
36387
  updateOperatorSettings: "updateOperatorSettings",
36388
+ getAgentPool: "getAgentPool",
36389
+ putAgentPool: "putAgentPool",
36380
36390
  getConsensusConfig: "getConsensusConfig",
36381
36391
  updateWikiPageCas: "updateWikiPageCas",
36382
36392
  promoteSpecReadyCas: "promoteSpecReadyCas",
@@ -36432,6 +36442,27 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
36432
36442
  var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
36433
36443
  var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
36434
36444
  var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
36445
+ var agentPoolSnapshotSchema = external_exports.object({
36446
+ collected_at: external_exports.string(),
36447
+ status: external_exports.enum(["ok", "unavailable"]),
36448
+ agents: external_exports.array(external_exports.object({
36449
+ id: external_exports.string(),
36450
+ provider: external_exports.string(),
36451
+ tier: external_exports.string(),
36452
+ tags: external_exports.array(external_exports.string()),
36453
+ state: external_exports.string(),
36454
+ calls: external_exports.number(),
36455
+ tokens_in: external_exports.number(),
36456
+ tokens_out: external_exports.number(),
36457
+ success_rate: external_exports.number(),
36458
+ last_used_at: external_exports.string().nullable()
36459
+ })),
36460
+ totals: external_exports.object({ calls: external_exports.number(), tokens_in: external_exports.number(), tokens_out: external_exports.number() }),
36461
+ estimated_savings_usd: external_exports.number()
36462
+ });
36463
+ var agentPoolResponseSchema = external_exports.object({
36464
+ data: external_exports.object({ snapshot: agentPoolSnapshotSchema.nullable() })
36465
+ });
36435
36466
 
36436
36467
  // ../../packages/schemas/dist/specConsensusReview.js
36437
36468
  var mrrlinSpecConsensusReviewSchema = external_exports.object({
@@ -37615,7 +37646,8 @@ var mrrlinInboxItemDecideSchema = external_exports.object({
37615
37646
  var mrrlinInboxItemListFiltersSchema = external_exports.object({
37616
37647
  kind: mrrlinInboxItemKindSchema.optional(),
37617
37648
  readState: external_exports.enum(["read", "unread"]).optional(),
37618
- status: mrrlinInboxItemStatusSchema.optional()
37649
+ status: mrrlinInboxItemStatusSchema.optional(),
37650
+ taskId: mrrlinTaskIdSchema.optional()
37619
37651
  });
37620
37652
  var inboxItemReadResponseSchema = external_exports.object({
37621
37653
  data: external_exports.object({
@@ -38222,7 +38254,8 @@ function createMrrlinClient(config2) {
38222
38254
  const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
38223
38255
  kind: filters.kind,
38224
38256
  readState: filters.readState,
38225
- status: filters.status
38257
+ status: filters.status,
38258
+ taskId: filters.taskId
38226
38259
  });
38227
38260
  const body = await request(url2);
38228
38261
  return inboxItemListResponseSchema.parse(body).data;
@@ -38406,6 +38439,18 @@ function createMrrlinClient(config2) {
38406
38439
  });
38407
38440
  return operatorSettingsResponseSchema.parse(body).data;
38408
38441
  },
38442
+ async getOperatorAgentPool() {
38443
+ const body = await request(`/operator/agent-pool`);
38444
+ return agentPoolResponseSchema.parse(body).data;
38445
+ },
38446
+ async putOperatorAgentPool(snapshot) {
38447
+ const body = await request(`/operator/agent-pool`, {
38448
+ body: JSON.stringify(snapshot),
38449
+ headers: { "content-type": "application/json" },
38450
+ method: "POST"
38451
+ });
38452
+ return agentPoolResponseSchema.parse(body).data;
38453
+ },
38409
38454
  async getConsensusConfig(projectSlug) {
38410
38455
  const body = await request(`${projectPath(projectSlug)}/consensus-config`);
38411
38456
  return consensusConfigResponseSchema.parse(body).data;
@@ -38788,8 +38833,92 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
38788
38833
  };
38789
38834
  }
38790
38835
 
38836
+ // src/codex-version.ts
38837
+ var import_node_child_process3 = require("node:child_process");
38838
+ var import_semver2 = __toESM(require_semver2(), 1);
38839
+ var SEMVER_RE = /\b\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?\b/;
38840
+ var CODEX_PACKAGE = process.env.MRRLIN_CODEX_PACKAGE?.trim() || "@openai/codex";
38841
+ var distTagsUrl = (pkg) => `https://registry.npmjs.org/-/package/${encodeURIComponent(pkg)}/dist-tags`;
38842
+ function parseCodexVersion(raw) {
38843
+ const m = raw.match(SEMVER_RE);
38844
+ return m ? m[0] : null;
38845
+ }
38846
+ var defaultRun = (args) => {
38847
+ const r = (0, import_node_child_process3.spawnSync)("codex", args, { encoding: "utf8", timeout: 8e3, shell: true });
38848
+ return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
38849
+ };
38850
+ function getInstalledCodexVersion(run3) {
38851
+ const r = run3(["--version"]);
38852
+ if (r.status !== 0) return null;
38853
+ return parseCodexVersion(r.stdout || r.stderr);
38854
+ }
38855
+ async function fetchLatestCodexVersion(fetchImpl = globalThis.fetch, pkg = CODEX_PACKAGE) {
38856
+ try {
38857
+ const controller = new AbortController();
38858
+ const t = setTimeout(() => controller.abort(), 5e3);
38859
+ let res;
38860
+ try {
38861
+ res = await fetchImpl(distTagsUrl(pkg), {
38862
+ headers: { Accept: "application/json" },
38863
+ signal: controller.signal
38864
+ });
38865
+ } finally {
38866
+ clearTimeout(t);
38867
+ }
38868
+ if (!res.ok) return null;
38869
+ const body = await res.json();
38870
+ const latest = body?.latest;
38871
+ return typeof latest === "string" && import_semver2.default.valid(latest) ? latest : null;
38872
+ } catch {
38873
+ return null;
38874
+ }
38875
+ }
38876
+ async function computeCodexUpdateState(run3, fetchImpl = globalThis.fetch, pkg = CODEX_PACKAGE) {
38877
+ const version2 = getInstalledCodexVersion(run3);
38878
+ if (!version2 || !import_semver2.default.valid(version2)) return { version: version2, latestVersion: null, updateAvailable: false };
38879
+ const latestVersion = await fetchLatestCodexVersion(fetchImpl, pkg);
38880
+ if (!latestVersion) return { version: version2, latestVersion: null, updateAvailable: false };
38881
+ return { version: version2, latestVersion, updateAvailable: import_semver2.default.gt(latestVersion, version2) };
38882
+ }
38883
+ function codexUpdateCommandFor(state) {
38884
+ return state.updateAvailable ? "codex update" : null;
38885
+ }
38886
+ var DEFAULT_PROBE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
38887
+ var DEFAULT_STARTUP_DELAY_MS = 1e4;
38888
+ function startCodexProbe(deps = {}) {
38889
+ const run3 = deps.run ?? defaultRun;
38890
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
38891
+ const pkg = deps.pkg ?? CODEX_PACKAGE;
38892
+ const intervalMs = deps.intervalMs ?? DEFAULT_PROBE_INTERVAL_MS;
38893
+ const startupDelayMs = deps.startupDelayMs ?? DEFAULT_STARTUP_DELAY_MS;
38894
+ const setIntervalFn = deps.setIntervalImpl ?? setInterval;
38895
+ const clearIntervalFn = deps.clearIntervalImpl ?? clearInterval;
38896
+ const setTimeoutFn = deps.setTimeoutImpl ?? setTimeout;
38897
+ const clearTimeoutFn = deps.clearTimeoutImpl ?? clearTimeout;
38898
+ let state = { version: null, latestVersion: null, updateAvailable: false };
38899
+ const refreshNow = async () => {
38900
+ try {
38901
+ state = await computeCodexUpdateState(run3, fetchImpl, pkg);
38902
+ } catch {
38903
+ }
38904
+ };
38905
+ const startupTimer = setTimeoutFn(() => void refreshNow(), startupDelayMs);
38906
+ startupTimer.unref?.();
38907
+ const timer = setIntervalFn(() => void refreshNow(), intervalMs);
38908
+ timer.unref?.();
38909
+ return {
38910
+ getState: () => state,
38911
+ getUpdateCommand: () => codexUpdateCommandFor(state),
38912
+ refreshNow,
38913
+ stop: () => {
38914
+ clearTimeoutFn(startupTimer);
38915
+ clearIntervalFn(timer);
38916
+ }
38917
+ };
38918
+ }
38919
+
38791
38920
  // src/_generated/version.ts
38792
- var PKG_VERSION = "0.3.27";
38921
+ var PKG_VERSION = "0.3.34";
38793
38922
 
38794
38923
  // src/api-base-url.ts
38795
38924
  var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
@@ -38967,8 +39096,192 @@ function startSchedulerTickLoop(deps) {
38967
39096
  };
38968
39097
  }
38969
39098
 
39099
+ // src/agent-pool-loop.ts
39100
+ var import_node_child_process4 = require("node:child_process");
39101
+
39102
+ // src/agent-pool-snapshot.ts
39103
+ function buildAgentPoolSnapshot(statusRows, stats, opts) {
39104
+ if (!opts.available) {
39105
+ return {
39106
+ collected_at: opts.collectedAt,
39107
+ status: "unavailable",
39108
+ agents: [],
39109
+ totals: { calls: 0, tokens_in: 0, tokens_out: 0 },
39110
+ estimated_savings_usd: 0
39111
+ };
39112
+ }
39113
+ const costPer1M = opts.paidCostPer1MTokens ?? 3;
39114
+ const agents = statusRows.map((row) => {
39115
+ const agentStats = stats.by_agent[row.id];
39116
+ const count2 = agentStats?.count ?? 0;
39117
+ const tokensIn = agentStats?.tokens_in ?? 0;
39118
+ const tokensOut = agentStats?.tokens_out ?? 0;
39119
+ let successRate = 0;
39120
+ if (count2 > 0) {
39121
+ const successOutcomes = agentStats?.outcomes.success ?? 0;
39122
+ successRate = Number((successOutcomes / count2 * 100).toFixed(2)) / 100;
39123
+ }
39124
+ return {
39125
+ id: row.id,
39126
+ provider: row.provider,
39127
+ tier: row.tier,
39128
+ tags: row.tags,
39129
+ state: row.state ?? "unknown",
39130
+ calls: count2,
39131
+ tokens_in: tokensIn,
39132
+ tokens_out: tokensOut,
39133
+ success_rate: successRate,
39134
+ last_used_at: row.last_used_at ?? null
39135
+ };
39136
+ });
39137
+ let totalCalls = 0;
39138
+ let totalTokensIn = 0;
39139
+ let totalTokensOut = 0;
39140
+ for (const entry of Object.values(stats.by_agent)) {
39141
+ totalCalls += entry.count;
39142
+ totalTokensIn += entry.tokens_in;
39143
+ totalTokensOut += entry.tokens_out;
39144
+ }
39145
+ let savedTokens = 0;
39146
+ for (const row of statusRows) {
39147
+ if (row.tags.includes("free")) {
39148
+ const agentStats = stats.by_agent[row.id];
39149
+ if (agentStats) {
39150
+ savedTokens += agentStats.tokens_in + agentStats.tokens_out;
39151
+ }
39152
+ }
39153
+ }
39154
+ const estimatedSavings = Number((savedTokens / 1e6 * costPer1M).toFixed(2));
39155
+ return {
39156
+ collected_at: opts.collectedAt,
39157
+ status: "ok",
39158
+ agents,
39159
+ totals: {
39160
+ calls: totalCalls,
39161
+ tokens_in: totalTokensIn,
39162
+ tokens_out: totalTokensOut
39163
+ },
39164
+ estimated_savings_usd: estimatedSavings
39165
+ };
39166
+ }
39167
+
39168
+ // src/agent-pool-collector.ts
39169
+ async function collectAgentPoolSnapshot(deps) {
39170
+ let statusRows;
39171
+ let stats;
39172
+ try {
39173
+ const statusResult = await deps.runCli(["status", "--json"]);
39174
+ if (statusResult.code !== 0) throw new Error("non-zero exit on status");
39175
+ let statusParsed;
39176
+ try {
39177
+ statusParsed = JSON.parse(statusResult.stdout);
39178
+ } catch {
39179
+ throw new Error("invalid status json");
39180
+ }
39181
+ if (!Array.isArray(statusParsed)) throw new Error("status not array");
39182
+ statusRows = statusParsed;
39183
+ const statsResult = await deps.runCli(["stats", "--json"]);
39184
+ if (statsResult.code !== 0) throw new Error("non-zero exit on stats");
39185
+ try {
39186
+ stats = JSON.parse(statsResult.stdout);
39187
+ } catch {
39188
+ throw new Error("invalid stats json");
39189
+ }
39190
+ } catch {
39191
+ return buildAgentPoolSnapshot(
39192
+ [],
39193
+ {},
39194
+ { collectedAt: deps.now(), available: false }
39195
+ );
39196
+ }
39197
+ return buildAgentPoolSnapshot(
39198
+ statusRows,
39199
+ stats,
39200
+ { collectedAt: deps.now(), paidCostPer1MTokens: deps.paidCostPer1MTokens, available: true }
39201
+ );
39202
+ }
39203
+
39204
+ // src/agent-pool-loop.ts
39205
+ var DEFAULT_INTERVAL_MS2 = 6e4;
39206
+ function agentPoolLoopDisabled(env = process.env) {
39207
+ return env["MRRLIN_BRIDGE_AGENT_POOL_DISABLE"] === "1";
39208
+ }
39209
+ function defaultRunCli(args) {
39210
+ const cli = process.env["EXTERNAL_AGENTS_CLI"];
39211
+ const [cmd, baseArgs] = cli ? ["node", [cli, ...args]] : ["external-agents", args];
39212
+ return new Promise((resolve) => {
39213
+ let stdout = "";
39214
+ let settled = false;
39215
+ const done = (code) => {
39216
+ if (!settled) {
39217
+ settled = true;
39218
+ resolve({ stdout, code });
39219
+ }
39220
+ };
39221
+ try {
39222
+ const child = (0, import_node_child_process4.spawn)(cmd, baseArgs, { stdio: ["ignore", "pipe", "ignore"] });
39223
+ const timer = setTimeout(() => {
39224
+ try {
39225
+ child.kill("SIGKILL");
39226
+ } catch {
39227
+ }
39228
+ done(1);
39229
+ }, 5e3);
39230
+ timer.unref?.();
39231
+ child.stdout.on("data", (d) => {
39232
+ stdout += d.toString();
39233
+ if (stdout.length > 2e5) stdout = stdout.slice(-2e5);
39234
+ });
39235
+ child.on("error", () => {
39236
+ clearTimeout(timer);
39237
+ done(1);
39238
+ });
39239
+ child.on("close", (code) => {
39240
+ clearTimeout(timer);
39241
+ done(code ?? 1);
39242
+ });
39243
+ } catch {
39244
+ done(1);
39245
+ }
39246
+ });
39247
+ }
39248
+ async function runAgentPoolCycle(deps) {
39249
+ try {
39250
+ const snapshot = await collectAgentPoolSnapshot({
39251
+ runCli: deps.runCli ?? defaultRunCli,
39252
+ now: deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
39253
+ });
39254
+ await deps.push(snapshot);
39255
+ } catch (err) {
39256
+ deps.onError?.(err);
39257
+ }
39258
+ }
39259
+ function startAgentPoolLoop(deps) {
39260
+ const env = deps.env ?? process.env;
39261
+ const intervalMs = deps.intervalMs ?? Number(env["MRRLIN_BRIDGE_AGENT_POOL_INTERVAL_MS"] ?? DEFAULT_INTERVAL_MS2);
39262
+ let stopped = false;
39263
+ let timer = null;
39264
+ const schedule = () => {
39265
+ if (stopped) return;
39266
+ timer = setTimeout(() => {
39267
+ void runAgentPoolCycle(deps).then(() => schedule());
39268
+ }, intervalMs);
39269
+ timer.unref?.();
39270
+ };
39271
+ timer = setTimeout(() => {
39272
+ void runAgentPoolCycle(deps).then(() => schedule());
39273
+ }, 1e3);
39274
+ timer.unref?.();
39275
+ return {
39276
+ stop() {
39277
+ stopped = true;
39278
+ if (timer) clearTimeout(timer);
39279
+ }
39280
+ };
39281
+ }
39282
+
38970
39283
  // src/execution-consumer.ts
38971
- var import_node_child_process6 = require("node:child_process");
39284
+ var import_node_child_process8 = require("node:child_process");
38972
39285
  var import_node_crypto4 = __toESM(require("node:crypto"), 1);
38973
39286
  var import_node_path6 = __toESM(require("node:path"), 1);
38974
39287
  var import_node_util = require("node:util");
@@ -38991,6 +39304,9 @@ function normalizeDomain(value) {
38991
39304
  function resolveHeads(domain2) {
38992
39305
  return [...HEADS_BY_DOMAIN[normalizeDomain(domain2)]];
38993
39306
  }
39307
+ function allReviewHeads() {
39308
+ return [...new Set(Object.values(HEADS_BY_DOMAIN).flat())];
39309
+ }
38994
39310
  var PERSONAS_BY_DOMAIN = {
38995
39311
  seo: "seo-writer",
38996
39312
  content: "content-author",
@@ -39009,7 +39325,7 @@ var import_node_path3 = __toESM(require("node:path"), 1);
39009
39325
  var import_node_url2 = require("node:url");
39010
39326
 
39011
39327
  // src/consensus/codex-exec.ts
39012
- var import_node_child_process3 = require("node:child_process");
39328
+ var import_node_child_process5 = require("node:child_process");
39013
39329
  function extractFinalMessage(stdout) {
39014
39330
  let last = null;
39015
39331
  for (const rawLine of stdout.split(/\r?\n/)) {
@@ -39054,7 +39370,7 @@ function firstString(...values) {
39054
39370
  return null;
39055
39371
  }
39056
39372
  function runCodexExec(input, deps) {
39057
- const spawn2 = deps?.spawn ?? import_node_child_process3.spawn;
39373
+ const spawn3 = deps?.spawn ?? import_node_child_process5.spawn;
39058
39374
  const executable = input.codexExecutable ?? "codex";
39059
39375
  const sandbox = input.sandbox ?? "read-only";
39060
39376
  const fullPrompt = input.developerInstructions.trim() ? `${input.developerInstructions.trim()}
@@ -39086,7 +39402,7 @@ ${input.prompt}` : input.prompt;
39086
39402
  };
39087
39403
  let child;
39088
39404
  try {
39089
- child = spawn2(executable, args, options);
39405
+ child = spawn3(executable, args, options);
39090
39406
  } catch (err) {
39091
39407
  finish({ ok: false, error: `Failed to spawn codex exec: ${errMessage(err)}` });
39092
39408
  return;
@@ -39193,7 +39509,7 @@ function parseAcceptanceCriteria(markdown) {
39193
39509
  }
39194
39510
 
39195
39511
  // src/consensus/code-gate-git.ts
39196
- var import_node_child_process4 = require("node:child_process");
39512
+ var import_node_child_process6 = require("node:child_process");
39197
39513
  var DIFF_MAX_BYTES = 2e5;
39198
39514
  var FASTGATE_OUTPUT_MAX_BYTES = 16e3;
39199
39515
  var GIT_TIMEOUT_MS = 6e4;
@@ -39209,7 +39525,7 @@ var defaultRunCmd = (cmd, args, opts) => new Promise((resolve) => {
39209
39525
  };
39210
39526
  let child;
39211
39527
  try {
39212
- child = (0, import_node_child_process4.spawn)(cmd, args, {
39528
+ child = (0, import_node_child_process6.spawn)(cmd, args, {
39213
39529
  cwd: opts.cwd,
39214
39530
  env: process.env,
39215
39531
  shell: false,
@@ -39455,7 +39771,7 @@ function reviserIdentityInstructions(taskId, title) {
39455
39771
  }
39456
39772
  function buildSpecGateDeps(args) {
39457
39773
  const { client, projectSlug, specPageId, cwd, codexExecutable, extraHeadCount = 0 } = args;
39458
- const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 3);
39774
+ const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
39459
39775
  const maxHeadCalls = rounds * (4 + extraHeadCount + 1);
39460
39776
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
39461
39777
  let intentPromise = null;
@@ -39609,7 +39925,7 @@ ${artifact}`,
39609
39925
  }
39610
39926
  function buildCodeGateDeps(args) {
39611
39927
  const { client, projectSlug, baseRef, cwd, codexExecutable, extraHeads } = args;
39612
- const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 3);
39928
+ const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
39613
39929
  const heads = ["code-reviewer", ...extraHeads ?? []];
39614
39930
  const maxHeadCalls = rounds * (heads.length + 1);
39615
39931
  const headTimeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
@@ -39690,7 +40006,7 @@ function renderCriteriaBlock(criteria) {
39690
40006
  }
39691
40007
  async function buildDeliverableGateDeps(args) {
39692
40008
  const { client, projectSlug, deliverablePageId, cwd, codexExecutable, runId } = args;
39693
- const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 3);
40009
+ const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
39694
40010
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
39695
40011
  const page = await client.getWikiPage(projectSlug, deliverablePageId);
39696
40012
  const taskId = page.taskId ?? null;
@@ -40613,7 +40929,7 @@ async function awaitTurnWithDeadline(opts) {
40613
40929
  }
40614
40930
 
40615
40931
  // src/worktree.ts
40616
- var import_node_child_process5 = require("node:child_process");
40932
+ var import_node_child_process7 = require("node:child_process");
40617
40933
  var import_node_fs4 = require("node:fs");
40618
40934
  var import_node_os3 = require("node:os");
40619
40935
  var import_node_path5 = require("node:path");
@@ -40641,9 +40957,9 @@ async function withCheckoutLock(locksDir, slug, fn) {
40641
40957
  function worktreeBranch(runId) {
40642
40958
  return `mrrlin/run-${runId}`;
40643
40959
  }
40644
- var defaultGitExec = (args, env) => (0, import_node_child_process5.execFileSync)("git", args, { encoding: "utf8", timeout: 3e4, env });
40960
+ var defaultGitExec = (args, env) => (0, import_node_child_process7.execFileSync)("git", args, { encoding: "utf8", timeout: 3e4, env });
40645
40961
  function git(checkout, args) {
40646
- return (0, import_node_child_process5.execFileSync)("git", ["-C", checkout, ...args], {
40962
+ return (0, import_node_child_process7.execFileSync)("git", ["-C", checkout, ...args], {
40647
40963
  encoding: "utf8",
40648
40964
  timeout: 3e4,
40649
40965
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
@@ -40848,6 +41164,38 @@ function isExecutionTurnTimeoutReason(reason) {
40848
41164
  function contextMaxArtifacts() {
40849
41165
  return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_CONTEXT_MAX_ARTIFACTS", DEFAULT_CONTEXT_MAX_ARTIFACTS);
40850
41166
  }
41167
+ var DEFAULT_COMPLETION_TURN_BUDGET_MS = 6e4;
41168
+ var DEFAULT_COMPLETION_TURN_MIN_BUDGET_MS = 1e4;
41169
+ function completionTurnBudgetMs() {
41170
+ return readPositiveIntEnv("MRRLIN_COMPLETION_TURN_BUDGET_MS", DEFAULT_COMPLETION_TURN_BUDGET_MS);
41171
+ }
41172
+ function completionTurnMinBudgetMs() {
41173
+ return readPositiveIntEnv("MRRLIN_COMPLETION_TURN_MIN_BUDGET_MS", DEFAULT_COMPLETION_TURN_MIN_BUDGET_MS);
41174
+ }
41175
+ function isCompletionTurnDisabled() {
41176
+ return (process.env.MRRLIN_COMPLETION_TURN_DISABLED ?? "").trim() === "1";
41177
+ }
41178
+ function buildCompletionTurnPrompt(taskId, currentStatus) {
41179
+ return [
41180
+ `Your work turn ended but task ${taskId} is still in status "${currentStatus}".`,
41181
+ "",
41182
+ "Decide honestly whether the work is complete:",
41183
+ "",
41184
+ "If the work IS genuinely complete:",
41185
+ "1. Update the task notes with a markdown summary of what you accomplished",
41186
+ " (see the notes format contract in your base instructions).",
41187
+ "2. Call update_task with status=closed_done.",
41188
+ "3. Append a completion-evidence artifact (checkpoint or diff_summary)",
41189
+ " to the open run if you haven't already.",
41190
+ "",
41191
+ "If the work is NOT complete \u2014 be honest, do not close incomplete work:",
41192
+ "1. Call update_task with the appropriate status (needs_human, awaiting_verification, etc.)",
41193
+ " and explain what remains in the notes.",
41194
+ "2. Append a handoff_judgement artifact explaining what the operator needs to do.",
41195
+ "",
41196
+ "You must pick one of these paths \u2014 do not leave the task in_progress."
41197
+ ].join("\n");
41198
+ }
40851
41199
  var BASE_INSTRUCTIONS_PREFIX = [
40852
41200
  "You are the Mrrlin Director driving a task toward completion autonomously.",
40853
41201
  "You can call MCP tools to read and write durable project state.",
@@ -40863,6 +41211,9 @@ var BASE_INSTRUCTIONS_PREFIX = [
40863
41211
  "- Close the task with `update_task` status=closed_done ONLY when it is genuinely complete",
40864
41212
  " AND you appended at least one completion-evidence artifact. Otherwise leave the task",
40865
41213
  " open so the run fails into an operator handoff instead of pretending success.",
41214
+ "- For a CODE task the run opens a draft PR for you after your turn; opening that PR is NOT",
41215
+ " completion. Do NOT set closed_done for code work \u2014 leave the task open; the run parks at",
41216
+ " `pull_request` (waiting for merge) and the operator merges. Closure happens after merge.",
40866
41217
  "",
40867
41218
  "File artifacts:",
40868
41219
  "- When your output is a file (markdown report, HTML, screenshot), upload it with",
@@ -40882,6 +41233,23 @@ var BASE_INSTRUCTIONS_PREFIX = [
40882
41233
  " and lose login/cookie state across runs.",
40883
41234
  "- If tool listings show duplicate names across servers, prefer the `mrrlin-browser` one",
40884
41235
  " unconditionally. Do not switch mid-run.",
41236
+ "",
41237
+ "Task description quality contract \u2014 every task you create or update via create_task or update_task:",
41238
+ "- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
41239
+ ' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
41240
+ ' Bad: "GSC coverage triage crawled-not-indexed + 404"',
41241
+ "- Notes (markdown): write for a human operator scanning a task list.",
41242
+ " First line: one plain-text sentence summarizing the task (no heading).",
41243
+ " Then scale the structure to complexity:",
41244
+ " For simple tasks \u2014 the one-line summary may be enough. No headings required.",
41245
+ " For non-trivial tasks, use these sections as needed:",
41246
+ " ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
41247
+ " ## Scope \u2014 concrete actions and acceptance criteria.",
41248
+ " ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
41249
+ " When closing a task (update_task status=closed_done), APPEND a Result section:",
41250
+ " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
41251
+ " Do NOT remove earlier sections \u2014 they remain useful for context.",
41252
+ " Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
40885
41253
  ""
40886
41254
  ];
40887
41255
  var CONSUMER_BASE_INSTRUCTIONS = [
@@ -40896,7 +41264,23 @@ var EXECUTOR_BASE_INSTRUCTIONS = [
40896
41264
  "files and running git/build/test commands WITHIN this worktree. Keep all changes inside the",
40897
41265
  "worktree and do not read the operator's personal secrets or files outside it \u2014 this is a rule",
40898
41266
  "you must follow, not something the environment enforces. Be concise. When you have made the",
40899
- "next increment of progress, stop the turn."
41267
+ "next increment of progress, stop the turn.",
41268
+ // ADR 0022 Phase 3: the executor is the orchestrator, not the sole worker. It has the
41269
+ // external-agents `pick`/`dispatch` MCP tools (a pool of cheap/free models) and should
41270
+ // delegate the bulk substantive work to them in parallel, keeping itself as judge. This
41271
+ // minimises the expensive default model's own token spend while the verify step preserves
41272
+ // quality. If external-agents isn't installed the tools are simply absent — these lines then
41273
+ // read as advice the model can't act on, and it does the work itself (graceful, no-op).
41274
+ "You are the ORCHESTRATOR and JUDGE, not the sole worker. If the external-agents `pick` and",
41275
+ "`dispatch` tools are available, delegate substantive, parallelizable subtasks \u2014 file edits,",
41276
+ "boilerplate, tests, research, mechanical refactors \u2014 to pool subagents via `dispatch`, several",
41277
+ "in parallel where possible, to minimise your own token spend. Keep for yourself only what needs",
41278
+ "your judgment: decomposition, integration, hard reasoning, and final decisions. You MUST verify",
41279
+ "every delegated result against the actual files and tests before it lands \u2014 pool models are",
41280
+ "lower-reliability and a wrong delegated edit is YOUR responsibility; never commit unverified",
41281
+ "delegated output. Delegation is the default for routine bulk work, but do trivial tasks yourself",
41282
+ "when dispatch overhead exceeds the work. If dispatch fails or the pool is unavailable, do the",
41283
+ "work yourself \u2014 never block the run on the pool."
40900
41284
  ].join("\n");
40901
41285
  function artifactKindForEvent(event) {
40902
41286
  switch (event.type) {
@@ -40935,7 +41319,7 @@ var BLAST_RADIUS_FLOOR_PATTERNS = [/^supabase\/migrations\//, /^packages\/db\/sr
40935
41319
  function isBlastRadiusFloorPath(path15) {
40936
41320
  return BLAST_RADIUS_FLOOR_PATTERNS.some((re) => re.test(path15));
40937
41321
  }
40938
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process6.execFile);
41322
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process8.execFile);
40939
41323
  async function defaultListRunDiffPaths(opts) {
40940
41324
  const parse5 = (stdout) => stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
40941
41325
  try {
@@ -41315,10 +41699,44 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
41315
41699
  await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
41316
41700
  return;
41317
41701
  }
41318
- const readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
41702
+ let readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
41319
41703
  if (!readiness.ok) {
41320
- await failRun(deps, projectSlug, readiness.task, runId, leaseId, readiness.reason);
41321
- return;
41704
+ const completionBudget = Math.min(completionTurnBudgetMs(), remainingTurnBudgetMs());
41705
+ if (isCompletionTurnDisabled() || completionBudget < completionTurnMinBudgetMs()) {
41706
+ await failRun(deps, projectSlug, readiness.task, runId, leaseId, readiness.reason);
41707
+ return;
41708
+ }
41709
+ console.warn(
41710
+ JSON.stringify({
41711
+ event: "completion_turn_triggered",
41712
+ projectSlug,
41713
+ runId,
41714
+ taskId: task.id,
41715
+ previousStatus: readiness.task.status
41716
+ })
41717
+ );
41718
+ const completionPrompt = buildCompletionTurnPrompt(task.id, readiness.task.status);
41719
+ const completionOutcome = await driveTurn(
41720
+ deps,
41721
+ codex,
41722
+ projectSlug,
41723
+ runId,
41724
+ leaseId,
41725
+ threadId,
41726
+ completionPrompt,
41727
+ completionBudget,
41728
+ "completion"
41729
+ );
41730
+ if (!completionOutcome.ok) {
41731
+ await resetWedgedThread(deps, projectSlug, task.id, completionOutcome.interruptFailed);
41732
+ await failRun(deps, projectSlug, readiness.task, runId, leaseId, completionOutcome.error);
41733
+ return;
41734
+ }
41735
+ readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
41736
+ if (!readiness.ok) {
41737
+ await failRun(deps, projectSlug, readiness.task, runId, leaseId, readiness.reason);
41738
+ return;
41739
+ }
41322
41740
  }
41323
41741
  if (useExecutor) {
41324
41742
  if (!worktreePath) {
@@ -41410,6 +41828,13 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
41410
41828
  pullRequestUrl: pr.url,
41411
41829
  status: "pull_request"
41412
41830
  });
41831
+ await reconcileTaskAwaitingMerge(
41832
+ deps,
41833
+ projectSlug,
41834
+ readiness.task,
41835
+ runId,
41836
+ `PR opened (${pr.url}); autoDeploy is off \u2014 the operator merges when ready.`
41837
+ );
41413
41838
  return;
41414
41839
  }
41415
41840
  if (deps.deployGate) {
@@ -41424,7 +41849,7 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
41424
41849
  pullRequestUrl: pr.url,
41425
41850
  status: "pull_request"
41426
41851
  });
41427
- await notifyOperatorHandoff(
41852
+ await reconcileTaskAwaitingMerge(
41428
41853
  deps,
41429
41854
  projectSlug,
41430
41855
  readiness.task,
@@ -41716,6 +42141,34 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
41716
42141
  await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
41717
42142
  }
41718
42143
  }
42144
+ async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
42145
+ if (task.status === "handoff_needed") return;
42146
+ await deps.client.appendExecutionArtifact(projectSlug, runId, {
42147
+ id: newArtifactId(),
42148
+ kind: "handoff_judgement",
42149
+ payload: {
42150
+ verdict: "approved",
42151
+ reason,
42152
+ summary: `Execution run ${runId} opened a PR and is awaiting operator action.`,
42153
+ operatorQuestion: `Task ${task.id} (${task.title}) has an open PR: ${reason}`,
42154
+ source: "execution-consumer"
42155
+ }
42156
+ }).catch(() => {
42157
+ });
42158
+ try {
42159
+ await deps.client.updateTask(projectSlug, task.id, { status: "handoff_needed" });
42160
+ } catch (error51) {
42161
+ console.warn(
42162
+ JSON.stringify({
42163
+ event: "reconcile_awaiting_merge_failed",
42164
+ projectSlug,
42165
+ taskId: task.id,
42166
+ runId,
42167
+ error: error51 instanceof Error ? error51.message : String(error51)
42168
+ })
42169
+ );
42170
+ }
42171
+ }
41719
42172
  async function notifyOperatorHandoff(deps, projectSlug, task, runId, reason) {
41720
42173
  try {
41721
42174
  await deps.client.appendExecutionArtifact(projectSlug, runId, {
@@ -41915,13 +42368,15 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
41915
42368
  }).catch(() => {
41916
42369
  });
41917
42370
  const delivery = descriptor2.onComplete(outcome, input);
41918
- await deps.client.createInboxItem(projectSlug, {
41919
- id: delivery.id ?? `II-${import_node_crypto5.default.randomUUID()}`,
41920
- kind: delivery.kind,
41921
- body: delivery.body,
41922
- ...delivery.taskId ? { taskId: delivery.taskId } : {}
41923
- }).catch(() => {
41924
- });
42371
+ if (!delivery.suppressInbox) {
42372
+ await deps.client.createInboxItem(projectSlug, {
42373
+ id: delivery.id ?? `II-${import_node_crypto5.default.randomUUID()}`,
42374
+ kind: delivery.kind,
42375
+ body: delivery.body,
42376
+ ...delivery.taskId ? { taskId: delivery.taskId } : {}
42377
+ }).catch(() => {
42378
+ });
42379
+ }
41925
42380
  if (descriptor2.wakeDirector) deps.wake(projectSlug, claimed.directorSessionId, delivery.body, delivery.kind);
41926
42381
  }
41927
42382
  function deriveHash() {
@@ -46768,6 +47223,23 @@ function buildThreadStartParams(msg, config2, desiredModel) {
46768
47223
  'or "let me call the \u2026 tool" in ANY language. The operator already sees tool calls in the',
46769
47224
  "transcript. Just call the tools and reply with the outcome \u2014 start your reply with the result.",
46770
47225
  "When creating or updating tasks, always provide `autonomyLevel` and `autonomyReason`.",
47226
+ "",
47227
+ "Task description quality contract \u2014 every task you create or update via create_task or update_task:",
47228
+ "- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
47229
+ ' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
47230
+ ' Bad: "GSC coverage triage crawled-not-indexed + 404"',
47231
+ "- Notes (markdown): write for a human operator scanning a task list.",
47232
+ " First line: one plain-text sentence summarizing the task (no heading).",
47233
+ " Then scale the structure to complexity:",
47234
+ " For simple tasks \u2014 the one-line summary may be enough. No headings required.",
47235
+ " For non-trivial tasks, use these sections as needed:",
47236
+ " ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
47237
+ " ## Scope \u2014 concrete actions and acceptance criteria.",
47238
+ " ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
47239
+ " When closing a task (update_task status=closed_done), APPEND a Result section:",
47240
+ " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
47241
+ " Do NOT remove earlier sections \u2014 they remain useful for context.",
47242
+ " Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
46771
47243
  "When you create a task out of an operator request, set `operatorSeedRequest` to the operator's",
46772
47244
  "VERBATIM words (their request as they wrote it \u2014 NOT your paraphrase or synthesized notes). It is",
46773
47245
  "the spec gate's independent record of intent; omit it only when there is genuinely no operator",
@@ -47017,7 +47489,7 @@ function resolveDefaultBranch(checkoutPath) {
47017
47489
  if (!checkoutPath) return "main";
47018
47490
  const runGit = (args) => {
47019
47491
  try {
47020
- return (0, import_node_child_process7.execFileSync)("git", ["-C", checkoutPath, ...args], {
47492
+ return (0, import_node_child_process9.execFileSync)("git", ["-C", checkoutPath, ...args], {
47021
47493
  encoding: "utf8",
47022
47494
  stdio: ["ignore", "pipe", "ignore"],
47023
47495
  timeout: 15e3,
@@ -47176,7 +47648,7 @@ function sessionIdOf(msg) {
47176
47648
  return typeof id === "string" ? id : null;
47177
47649
  }
47178
47650
  function attachBridgeWss(server, opts) {
47179
- const { token, allowedOrigins, onReady, logger, onPaired, bridgeInstanceId, selfUpdate } = opts;
47651
+ const { token, allowedOrigins, onReady, logger, onPaired, bridgeInstanceId, selfUpdate, codex } = opts;
47180
47652
  const wss = new import_websocket_server.default({ server, path: "/ws" });
47181
47653
  wss.on("connection", (socket, request) => {
47182
47654
  const origin = request.headers.origin;
@@ -47241,7 +47713,15 @@ function attachBridgeWss(server, opts) {
47241
47713
  version: selfUpdate.version,
47242
47714
  binSha: selfUpdate.binSha,
47243
47715
  latestVersion: selfUpdate.getLatestVersion()
47244
- } : {}
47716
+ } : {},
47717
+ ...codex ? (() => {
47718
+ const st = codex.getState();
47719
+ return {
47720
+ codexVersion: st.version,
47721
+ codexLatestVersion: st.latestVersion,
47722
+ codexUpdateCommand: codex.getUpdateCommand()
47723
+ };
47724
+ })() : {}
47245
47725
  });
47246
47726
  onPaired?.();
47247
47727
  onReady?.(socket, request, send, logger ? { logger, span } : void 0);
@@ -47265,13 +47745,14 @@ async function waitFor(pred, timeoutMs) {
47265
47745
  return pred();
47266
47746
  }
47267
47747
  function attachSeqMetadata(msg, seq, turnId) {
47268
- if (msg.type === "event" || msg.type === "rateLimits" || msg.type === "error") {
47748
+ if (msg.type === "event" || msg.type === "rateLimits" || msg.type === "error" || msg.type === "turn-executing") {
47269
47749
  return { ...msg, seq, turnId };
47270
47750
  }
47271
47751
  return msg;
47272
47752
  }
47273
47753
  function createBridgeMessageHandler(deps) {
47274
47754
  const tombstones = deps.tombstones ?? /* @__PURE__ */ new Map();
47755
+ const pendingTurnSessions = /* @__PURE__ */ new Set();
47275
47756
  return (socket, _request, send, logCtx) => {
47276
47757
  const boundSessions = /* @__PURE__ */ new Set();
47277
47758
  socket.on("close", () => {
@@ -47523,6 +48004,14 @@ function createBridgeMessageHandler(deps) {
47523
48004
  interruptBuf.state = stopResult.next;
47524
48005
  recordInterrupted(interruptBuf, Date.now(), deps.suspendedMs?.() ?? 0);
47525
48006
  }
48007
+ } else if (interruptBuf && interruptBuf.state === "live" && !existing.cancel) {
48008
+ const stopResult = transition("live", { kind: "stop" });
48009
+ if (stopResult.next !== "invalid") {
48010
+ interruptBuf.state = stopResult.next;
48011
+ recordInterrupted(interruptBuf, Date.now(), deps.suspendedMs?.() ?? 0);
48012
+ existing.inFlight = false;
48013
+ pendingTurnSessions.delete(directorSessionId2);
48014
+ }
47526
48015
  }
47527
48016
  const codexClient2 = await deps.acquireCodex();
47528
48017
  try {
@@ -47581,6 +48070,18 @@ function createBridgeMessageHandler(deps) {
47581
48070
  sendForSpan({ type: "error", directorSessionId, error: "message is required." });
47582
48071
  return;
47583
48072
  }
48073
+ const busyThread = deps.threads.get(directorSessionId);
48074
+ if (msg.takeover !== true && (pendingTurnSessions.has(directorSessionId) || busyThread?.inFlight)) {
48075
+ sendForSpan({
48076
+ type: "error",
48077
+ directorSessionId,
48078
+ code: "busy",
48079
+ ...busyThread?.replay ? { inFlightTurnId: busyThread.replay.turnId } : {},
48080
+ error: "Director session is busy."
48081
+ });
48082
+ return;
48083
+ }
48084
+ pendingTurnSessions.add(directorSessionId);
47584
48085
  const codexClient = await deps.acquireCodex();
47585
48086
  let thread = deps.threads.get(directorSessionId);
47586
48087
  if (thread?.inFlight && msg.takeover === true) {
@@ -47666,6 +48167,7 @@ function createBridgeMessageHandler(deps) {
47666
48167
  if (/unauthor|not logged in|login|401|auth/i.test(acquireErrMsg)) {
47667
48168
  deps.setCodexAuth?.("expired");
47668
48169
  }
48170
+ pendingTurnSessions.delete(directorSessionId);
47669
48171
  deps.releaseCodex();
47670
48172
  return;
47671
48173
  }
@@ -47677,10 +48179,12 @@ function createBridgeMessageHandler(deps) {
47677
48179
  ...thread.replay ? { inFlightTurnId: thread.replay.turnId } : {},
47678
48180
  error: "Director session is busy."
47679
48181
  });
48182
+ pendingTurnSessions.delete(directorSessionId);
47680
48183
  deps.releaseCodex();
47681
48184
  return;
47682
48185
  }
47683
48186
  thread.inFlight = true;
48187
+ pendingTurnSessions.delete(directorSessionId);
47684
48188
  thread.replay = createReplayBuffer({
47685
48189
  turnId: (0, import_node_crypto7.randomUUID)(),
47686
48190
  spanId,
@@ -47689,6 +48193,7 @@ function createBridgeMessageHandler(deps) {
47689
48193
  });
47690
48194
  thread.lastUsedAt = Date.now();
47691
48195
  const threadId = thread.threadId;
48196
+ const currentReplayBuf = thread.replay;
47692
48197
  sendForSpan({
47693
48198
  type: "turn-started",
47694
48199
  directorSessionId,
@@ -47730,6 +48235,15 @@ function createBridgeMessageHandler(deps) {
47730
48235
  };
47731
48236
  try {
47732
48237
  await deps.runExclusive(async () => {
48238
+ if (currentReplayBuf.state === "interrupted" || currentReplayBuf.state === "completed") {
48239
+ logCtx?.logger.logOut({
48240
+ spanId,
48241
+ sessionId: directorSessionId,
48242
+ type: "<queued-turn-cancelled-drained>",
48243
+ payload: { sessionId: directorSessionId, turnId: currentReplayBuf.turnId }
48244
+ });
48245
+ return;
48246
+ }
47733
48247
  let resolveTurn = null;
47734
48248
  let rejectTurn = null;
47735
48249
  const turnFinished = new Promise((resolve, reject) => {
@@ -47836,6 +48350,7 @@ ${effectiveText}` : effectiveText;
47836
48350
  if (imageDataUri) {
47837
48351
  turnInput.push({ type: "image", url: imageDataUri });
47838
48352
  }
48353
+ sendInTurn({ type: "turn-executing", directorSessionId, turnId: currentReplayBuf.turnId });
47839
48354
  await codexClient.turn.start({
47840
48355
  threadId: thread.threadId,
47841
48356
  input: turnInput,
@@ -47891,7 +48406,9 @@ ${effectiveText}` : effectiveText;
47891
48406
  }
47892
48407
  }
47893
48408
  } finally {
47894
- thread.inFlight = false;
48409
+ if (thread.replay === currentReplayBuf) {
48410
+ thread.inFlight = false;
48411
+ }
47895
48412
  thread.lastUsedAt = Date.now();
47896
48413
  deps.persistThreads();
47897
48414
  deps.releaseCodex();
@@ -47931,7 +48448,7 @@ function parseConfig() {
47931
48448
  }
47932
48449
  function scanForOtherBridges() {
47933
48450
  try {
47934
- const result = (0, import_node_child_process7.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
48451
+ const result = (0, import_node_child_process9.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
47935
48452
  encoding: "utf8"
47936
48453
  });
47937
48454
  if (result.status !== 0) return [];
@@ -47984,6 +48501,7 @@ async function startDirectorBridge() {
47984
48501
  );
47985
48502
  }
47986
48503
  });
48504
+ const codexProbeHandle = startCodexProbe();
47987
48505
  const consumerState = { inFlightRunId: null, lastClaimAt: null };
47988
48506
  const health = createServiceHealth();
47989
48507
  const apiBaseUrl = readConfiguredApiBaseUrl();
@@ -48341,6 +48859,10 @@ async function startDirectorBridge() {
48341
48859
  version: PKG_VERSION,
48342
48860
  binSha: bridgeBinShaShort,
48343
48861
  getLatestVersion: () => selfUpdateHandle.getLatestVersion()
48862
+ },
48863
+ codex: {
48864
+ getState: () => codexProbeHandle.getState(),
48865
+ getUpdateCommand: () => codexProbeHandle.getUpdateCommand()
48344
48866
  }
48345
48867
  });
48346
48868
  if (agentSecret && relayUrl) {
@@ -48512,6 +49034,16 @@ async function startDirectorBridge() {
48512
49034
  "[mrrlin-mcp director-bridge] scheduler tick loop disabled (MRRLIN_BRIDGE_SCHEDULER_TICK_DISABLE=1).\n"
48513
49035
  );
48514
49036
  }
49037
+ let agentPoolLoopHandle = null;
49038
+ if (tokenProvider && !agentPoolLoopDisabled(process.env)) {
49039
+ agentPoolLoopHandle = startAgentPoolLoop({
49040
+ push: (snapshot) => client.putOperatorAgentPool(snapshot),
49041
+ onError: (err) => process.stderr.write(
49042
+ `[mrrlin-mcp director-bridge] agent-pool snapshot push failed: ${err instanceof Error ? err.message : String(err)}
49043
+ `
49044
+ )
49045
+ });
49046
+ }
48515
49047
  await new Promise((resolve, reject) => {
48516
49048
  server.on("error", (err) => {
48517
49049
  if (err.code === "EADDRINUSE") {
@@ -48553,8 +49085,10 @@ async function startDirectorBridge() {
48553
49085
  if (claimTimer) clearTimeout(claimTimer);
48554
49086
  },
48555
49087
  () => schedulerTickHandle?.stop(),
49088
+ () => agentPoolLoopHandle?.stop(),
48556
49089
  () => codexKeepalive.dropNow(),
48557
- () => selfUpdateHandle.stop()
49090
+ () => selfUpdateHandle.stop(),
49091
+ () => codexProbeHandle.stop()
48558
49092
  ],
48559
49093
  shutdownCodex: () => codexServerManager.shutdown(),
48560
49094
  exit: (code) => process.exit(code),
@@ -52081,7 +52615,7 @@ var import_promises3 = require("node:fs/promises");
52081
52615
  var import_node_fs12 = require("node:fs");
52082
52616
  var import_node_path15 = __toESM(require("node:path"), 1);
52083
52617
  var import_node_os8 = __toESM(require("node:os"), 1);
52084
- var import_node_child_process8 = require("node:child_process");
52618
+ var import_node_child_process11 = require("node:child_process");
52085
52619
 
52086
52620
  // ../../packages/wiki/dist/index.js
52087
52621
  var import_promises2 = __toESM(require("node:fs/promises"), 1);
@@ -52518,6 +53052,278 @@ async function runCodeGate(_target, deps) {
52518
53052
  }
52519
53053
  }
52520
53054
 
53055
+ // src/consensus/multi-llm-head.ts
53056
+ var import_node_child_process10 = require("node:child_process");
53057
+ function errMessage3(err) {
53058
+ return err instanceof Error ? err.message : String(err);
53059
+ }
53060
+ function runExternalAgents(bin, args, timeoutMs, spawn3) {
53061
+ return new Promise((resolve) => {
53062
+ let settled = false;
53063
+ const timerRef = { id: void 0 };
53064
+ const finish = (r) => {
53065
+ if (settled) return;
53066
+ settled = true;
53067
+ if (timerRef.id) clearTimeout(timerRef.id);
53068
+ resolve(r);
53069
+ };
53070
+ const options = {
53071
+ stdio: ["ignore", "pipe", "pipe"],
53072
+ env: process.env,
53073
+ shell: false
53074
+ // SECURITY: prompt is an argv element; never hand it to a shell.
53075
+ };
53076
+ let child;
53077
+ try {
53078
+ child = spawn3(bin, args, options);
53079
+ } catch (err) {
53080
+ finish({ code: null, stdout: "", error: `spawn ${bin} failed: ${errMessage3(err)}` });
53081
+ return;
53082
+ }
53083
+ let stdout = "";
53084
+ timerRef.id = setTimeout(() => {
53085
+ child.kill("SIGKILL");
53086
+ finish({ code: null, stdout, error: `external-agents ${args[0]} timed out after ${timeoutMs}ms` });
53087
+ }, timeoutMs);
53088
+ child.stdout?.on("data", (chunk) => {
53089
+ stdout += chunk.toString();
53090
+ });
53091
+ child.stderr?.on("data", () => {
53092
+ });
53093
+ child.on("error", (err) => finish({ code: null, stdout, error: `external-agents ${args[0]} error: ${errMessage3(err)}` }));
53094
+ child.on("close", (code) => finish({ code, stdout }));
53095
+ });
53096
+ }
53097
+ async function pickPanel(input, deps) {
53098
+ const spawn3 = deps?.spawn ?? import_node_child_process10.spawn;
53099
+ const bin = deps?.externalAgentsBin ?? "external-agents";
53100
+ const args = [
53101
+ "pick",
53102
+ "--tier-prefer",
53103
+ input.tierPrefer ?? "strong",
53104
+ "--n",
53105
+ String(input.n),
53106
+ "--min-distinct-providers",
53107
+ String(input.n),
53108
+ "--exclude-providers",
53109
+ input.excludeProvider
53110
+ ];
53111
+ const r = await runExternalAgents(bin, args, input.timeoutMs, spawn3);
53112
+ if (r.error) return { ok: false, error: r.error };
53113
+ if (r.code === 3) return { ok: true, agents: [] };
53114
+ if (r.code !== 0) return { ok: false, error: `external-agents pick exited ${r.code}` };
53115
+ const agents = r.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
53116
+ return { ok: true, agents };
53117
+ }
53118
+ async function dispatchHead(agentId, prompt, timeoutMs, deps) {
53119
+ const spawn3 = deps?.spawn ?? import_node_child_process10.spawn;
53120
+ const bin = deps?.externalAgentsBin ?? "external-agents";
53121
+ const args = ["dispatch", agentId, "--json", prompt];
53122
+ const r = await runExternalAgents(bin, args, timeoutMs, spawn3);
53123
+ if (r.error) return { ok: false, error: r.error };
53124
+ const lines2 = r.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
53125
+ const jsonLine = [...lines2].reverse().find((l) => l[0] === "{");
53126
+ if (!jsonLine) return { ok: false, error: `dispatch ${agentId}: no JSON on stdout (exit ${r.code})` };
53127
+ let parsed;
53128
+ try {
53129
+ parsed = JSON.parse(jsonLine);
53130
+ } catch (err) {
53131
+ return { ok: false, error: `dispatch ${agentId}: unparseable JSON (${errMessage3(err)})` };
53132
+ }
53133
+ if (parsed.outcome !== "success") {
53134
+ return { ok: false, error: `dispatch ${agentId}: outcome=${parsed.outcome} (exit ${parsed.exit_code})` };
53135
+ }
53136
+ if (typeof parsed.text !== "string" || parsed.text.trim() === "") {
53137
+ return { ok: false, error: `dispatch ${agentId}: empty text on success` };
53138
+ }
53139
+ return { ok: true, text: parsed.text };
53140
+ }
53141
+ async function buildMultiHeadRunner(input, deps) {
53142
+ const picked = await pickPanel(
53143
+ { n: input.personas.length, excludeProvider: input.excludeProvider, tierPrefer: input.tierPrefer, timeoutMs: input.pickTimeoutMs },
53144
+ deps
53145
+ );
53146
+ if (!picked.ok) return { ok: false, error: picked.error };
53147
+ if (picked.agents.length === 0) return { ok: false, error: "pick returned no agents" };
53148
+ const agents = picked.agents;
53149
+ const assignment = {};
53150
+ input.personas.forEach((persona, i) => {
53151
+ const agent = agents[i % agents.length];
53152
+ if (agent) assignment[persona] = agent;
53153
+ });
53154
+ const runHead = async (persona, artifact, dismissed) => {
53155
+ const agentId = assignment[persona];
53156
+ if (!agentId) return { ok: false, error: `no agent assigned for persona ${persona}` };
53157
+ const instructions = input.personaInstructions[persona] ?? "";
53158
+ const dismissedBlock = dismissed.trim() ? `
53159
+
53160
+ ${dismissed.trim()}` : "";
53161
+ const prompt = instructions.trim() ? `${instructions.trim()}
53162
+
53163
+ ${artifact}${dismissedBlock}` : `${artifact}${dismissedBlock}`;
53164
+ return dispatchHead(agentId, prompt, input.dispatchTimeoutMs, deps);
53165
+ };
53166
+ return { ok: true, runner: { agents, assignment, runHead } };
53167
+ }
53168
+
53169
+ // src/consensus/multi-llm-shadow.ts
53170
+ var SHADOW_LOG_PREFIX = "__MULTI_LLM_SHADOW__";
53171
+ function rollUpVerdict(verdicts) {
53172
+ if (verdicts.includes("REJECT")) return "REJECT";
53173
+ if (verdicts.includes("REQUEST_CHANGES")) return "REQUEST_CHANGES";
53174
+ return "APPROVE";
53175
+ }
53176
+ async function runShadowPanel(input, deps) {
53177
+ const base = { ran: false, correlationId: input.correlationId, productionVerdict: input.productionVerdict };
53178
+ const built = await buildMultiHeadRunner(
53179
+ {
53180
+ personas: input.heads,
53181
+ personaInstructions: input.personaInstructions,
53182
+ excludeProvider: input.excludeProvider,
53183
+ tierPrefer: "strong",
53184
+ pickTimeoutMs: input.pickTimeoutMs,
53185
+ dispatchTimeoutMs: input.dispatchTimeoutMs
53186
+ },
53187
+ deps
53188
+ );
53189
+ if (!built.ok) return { ...base, reason: built.error };
53190
+ const verdicts = [];
53191
+ const perHead = await Promise.all(
53192
+ input.heads.map(async (persona) => {
53193
+ const agent = built.runner.assignment[persona] ?? "(unassigned)";
53194
+ try {
53195
+ const res = await built.runner.runHead(persona, input.artifact, "");
53196
+ if (res.ok) {
53197
+ const parsed = parseVerdict(res.text);
53198
+ return { persona, agent, verdict: parsed.verdict, ok: true };
53199
+ }
53200
+ } catch {
53201
+ }
53202
+ return { persona, agent, verdict: "ERROR", ok: false };
53203
+ })
53204
+ );
53205
+ for (const h of perHead) {
53206
+ if (h.ok && h.verdict !== "ERROR") verdicts.push(h.verdict);
53207
+ }
53208
+ if (verdicts.length === 0) {
53209
+ return { ...base, agents: built.runner.agents, perHead, reason: "no head produced a parseable verdict" };
53210
+ }
53211
+ const shadowVerdict = rollUpVerdict(verdicts);
53212
+ return {
53213
+ ran: true,
53214
+ correlationId: input.correlationId,
53215
+ agents: built.runner.agents,
53216
+ perHead,
53217
+ shadowVerdict,
53218
+ productionVerdict: input.productionVerdict,
53219
+ agrees: shadowVerdict === input.productionVerdict
53220
+ };
53221
+ }
53222
+ async function runShadowPanelSafe(input, deps, logger = (line) => console.error(line)) {
53223
+ let record2;
53224
+ try {
53225
+ record2 = await runShadowPanel(input, deps);
53226
+ } catch (err) {
53227
+ record2 = {
53228
+ ran: false,
53229
+ correlationId: input.correlationId,
53230
+ productionVerdict: input.productionVerdict,
53231
+ reason: `shadow panel threw: ${err instanceof Error ? err.message : String(err)}`
53232
+ };
53233
+ }
53234
+ try {
53235
+ logger(`${SHADOW_LOG_PREFIX} ${JSON.stringify(record2)}`);
53236
+ } catch {
53237
+ }
53238
+ return record2;
53239
+ }
53240
+
53241
+ // src/consensus/multi-llm-shadow-hook.ts
53242
+ var SHADOW_ENV_FLAG = "MRRLIN_CONSENSUS_MULTI_LLM_SHADOW";
53243
+ var ORCHESTRATOR_PROVIDER = "openai";
53244
+ var PICK_TIMEOUT_MS = 15e3;
53245
+ var DISPATCH_TIMEOUT_MS = 6e4;
53246
+ function productionVerdictFromOutcome(outcome) {
53247
+ if (outcome === "CONVERGED") return "APPROVE";
53248
+ if (outcome === "CONSENSUS_BLOCKED") return "REQUEST_CHANGES";
53249
+ return null;
53250
+ }
53251
+ async function maybeRunShadow(input, deps, logger) {
53252
+ const env = input.env ?? process.env;
53253
+ if (!env[SHADOW_ENV_FLAG]) return false;
53254
+ const productionVerdict = productionVerdictFromOutcome(input.outcome);
53255
+ if (productionVerdict === null) return false;
53256
+ try {
53257
+ const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
53258
+ const personaInstructions = {};
53259
+ for (const name of heads) {
53260
+ try {
53261
+ personaInstructions[name] = loadPersona(name);
53262
+ } catch {
53263
+ personaInstructions[name] = "";
53264
+ }
53265
+ }
53266
+ await runShadowPanelSafe(
53267
+ {
53268
+ heads,
53269
+ personaInstructions,
53270
+ artifact: input.artifact,
53271
+ productionVerdict,
53272
+ excludeProvider: ORCHESTRATOR_PROVIDER,
53273
+ pickTimeoutMs: PICK_TIMEOUT_MS,
53274
+ dispatchTimeoutMs: DISPATCH_TIMEOUT_MS,
53275
+ correlationId: input.correlationId
53276
+ },
53277
+ deps,
53278
+ logger
53279
+ );
53280
+ } catch {
53281
+ }
53282
+ return true;
53283
+ }
53284
+
53285
+ // src/consensus/multi-llm-activate.ts
53286
+ var MULTI_LLM_ENV_FLAG = "MRRLIN_CONSENSUS_MULTI_LLM";
53287
+ var ORCHESTRATOR_PROVIDER2 = "openai";
53288
+ var PICK_TIMEOUT_MS2 = 15e3;
53289
+ var DISPATCH_TIMEOUT_MS2 = 6e4;
53290
+ function multiLlmActive(env = process.env) {
53291
+ if (env["NODE_ENV"] === "production") return false;
53292
+ const flag = env[MULTI_LLM_ENV_FLAG];
53293
+ if (flag === "0" || flag === "false" || flag === "off") return false;
53294
+ return Boolean(flag) || env["MRRLIN_STAGING"] === "1";
53295
+ }
53296
+ async function resolveActiveRunHead(input, deps) {
53297
+ if (!multiLlmActive(input.env ?? process.env)) return null;
53298
+ try {
53299
+ const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
53300
+ if (heads.length === 0) return null;
53301
+ const personaInstructions = {};
53302
+ for (const name of heads) {
53303
+ try {
53304
+ personaInstructions[name] = loadPersona(name);
53305
+ } catch {
53306
+ personaInstructions[name] = "";
53307
+ }
53308
+ }
53309
+ const built = await buildMultiHeadRunner(
53310
+ {
53311
+ personas: heads,
53312
+ personaInstructions,
53313
+ excludeProvider: ORCHESTRATOR_PROVIDER2,
53314
+ tierPrefer: "strong",
53315
+ pickTimeoutMs: PICK_TIMEOUT_MS2,
53316
+ dispatchTimeoutMs: DISPATCH_TIMEOUT_MS2
53317
+ },
53318
+ deps
53319
+ );
53320
+ if (!built.ok) return null;
53321
+ return built.runner.runHead;
53322
+ } catch {
53323
+ return null;
53324
+ }
53325
+ }
53326
+
52521
53327
  // src/async-tools/consensus.ts
52522
53328
  var import_node_crypto8 = require("node:crypto");
52523
53329
  function buildGateQuestionOptions(category) {
@@ -52581,13 +53387,37 @@ async function run(ctx, input) {
52581
53387
  codexExecutable: ctx.codexExecutable,
52582
53388
  extraHeadCount: input.extraHeads?.length ?? 0
52583
53389
  });
53390
+ const activeRunHead = await resolveActiveRunHead({
53391
+ heads: [...allReviewHeads(), ...input.extraHeads ?? []]
53392
+ });
53393
+ if (activeRunHead) deps.runHead = activeRunHead;
52584
53394
  const result = await runSpecGate(
52585
53395
  { projectSlug: input.projectSlug, specPageId: input.pageId },
52586
53396
  deps,
52587
53397
  input.extraHeads && input.extraHeads.length > 0 ? { extraHeads: input.extraHeads } : void 0
52588
53398
  );
53399
+ if (process.env[SHADOW_ENV_FLAG]) {
53400
+ try {
53401
+ const specPage = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
53402
+ if (specPage) {
53403
+ const shadowDomain = await resolveSpecDomain(ctx.client, input.projectSlug, {
53404
+ id: specPage.id,
53405
+ taskId: specPage.taskId ?? null
53406
+ });
53407
+ await maybeRunShadow({
53408
+ outcome: result.outcome,
53409
+ artifact: specPage.markdownBody,
53410
+ domain: shadowDomain,
53411
+ extraHeads: input.extraHeads,
53412
+ correlationId: `Specs/${input.pageId}`
53413
+ });
53414
+ }
53415
+ } catch {
53416
+ }
53417
+ }
52589
53418
  if (result.outcome === "CONSENSUS_BLOCKED") {
52590
53419
  const clarifications = result.clarifications ?? [];
53420
+ let filedQuestionCount = 0;
52591
53421
  if (clarifications.length > 0) {
52592
53422
  const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
52593
53423
  const taskId = page?.taskId ?? null;
@@ -52612,6 +53442,7 @@ async function run(ctx, input) {
52612
53442
  } catch {
52613
53443
  }
52614
53444
  }
53445
+ filedQuestionCount = clarifications.length;
52615
53446
  } else if (planId) {
52616
53447
  for (const clarification of clarifications) {
52617
53448
  const scopeId = planId;
@@ -52628,13 +53459,18 @@ async function run(ctx, input) {
52628
53459
  } catch {
52629
53460
  }
52630
53461
  }
53462
+ filedQuestionCount = clarifications.length;
52631
53463
  }
52632
53464
  }
52633
- return {
52634
- kind: "failed",
52635
- failureReason: "consensus_blocked",
52636
- error: clarifications.length > 0 ? `Consensus blocked on ${clarifications.length} clarification(s); filed as operator question(s).` : "Consensus blocked: reviewers did not converge within budget."
52637
- };
53465
+ let error51;
53466
+ if (filedQuestionCount > 0) {
53467
+ error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
53468
+ } else if (clarifications.length > 0) {
53469
+ 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.`;
53470
+ } else {
53471
+ error51 = "Consensus blocked: reviewers did not converge within budget.";
53472
+ }
53473
+ return { kind: "failed", failureReason: "consensus_blocked", error: error51, filedQuestionCount };
52638
53474
  }
52639
53475
  switch (result.outcome) {
52640
53476
  case "CONVERGED":
@@ -52670,6 +53506,14 @@ function onComplete2(outcome, input) {
52670
53506
  body: `Consensus CONVERGED for Specs/${input.pageId}; spec_ready set. Ready to create an execution run.`
52671
53507
  };
52672
53508
  }
53509
+ if (outcome.kind === "failed" && (outcome.filedQuestionCount ?? 0) > 0) {
53510
+ const n = outcome.filedQuestionCount ?? 0;
53511
+ return {
53512
+ kind: "info",
53513
+ suppressInbox: true,
53514
+ body: `Consensus blocked on Specs/${input.pageId}; filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.`
53515
+ };
53516
+ }
52673
53517
  return {
52674
53518
  kind: "handoff",
52675
53519
  body: `Consensus could not complete for Specs/${input.pageId}: ${outcome.error} Needs operator attention.`
@@ -54170,7 +55014,24 @@ function createMrrlinTools(options) {
54170
55014
  codexExecutable: process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0,
54171
55015
  extraHeads
54172
55016
  });
54173
- return await runCodeGate({ projectSlug, baseRef }, deps);
55017
+ const activeCodeRunHead = await resolveActiveRunHead({
55018
+ heads: ["code-reviewer", ...extraHeads ?? []]
55019
+ });
55020
+ if (activeCodeRunHead) deps.runHead = activeCodeRunHead;
55021
+ const codeGateResult = await runCodeGate({ projectSlug, baseRef }, deps);
55022
+ if (process.env[SHADOW_ENV_FLAG]) {
55023
+ try {
55024
+ const diff = await readDiff({ cwd: process.cwd(), baseRef, run: defaultRunCmd });
55025
+ await maybeRunShadow({
55026
+ outcome: codeGateResult.outcome,
55027
+ artifact: diff,
55028
+ heads: ["code-reviewer", ...extraHeads ?? []],
55029
+ correlationId: `code:${baseRef}`
55030
+ });
55031
+ } catch {
55032
+ }
55033
+ }
55034
+ return codeGateResult;
54174
55035
  }
54175
55036
  ),
54176
55037
  [mcpToolNames.listExecutionRuns]: makeTool(
@@ -54421,7 +55282,7 @@ function createMrrlinTools(options) {
54421
55282
  }
54422
55283
  let remoteUrl;
54423
55284
  try {
54424
- remoteUrl = (0, import_node_child_process8.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
55285
+ remoteUrl = (0, import_node_child_process11.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
54425
55286
  encoding: "utf8",
54426
55287
  stdio: ["ignore", "pipe", "ignore"],
54427
55288
  timeout: 15e3,
@@ -54773,7 +55634,7 @@ async function pathExists3(target) {
54773
55634
  return false;
54774
55635
  }
54775
55636
  }
54776
- var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.mrrlin(?:-browser)?(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
55637
+ var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.(?:mrrlin(?:-browser)?|external-agents)(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
54777
55638
  async function uninstallCodex(options = {}) {
54778
55639
  const codexHome = resolveCodexHome2(options);
54779
55640
  const configPath = import_node_path18.default.join(codexHome, "config.toml");
@@ -54795,7 +55656,7 @@ async function uninstallCodex(options = {}) {
54795
55656
  };
54796
55657
  throw Object.assign(new Error(err.detail), err);
54797
55658
  }
54798
- if (parsed?.mcp_servers?.["mrrlin"] === void 0 && parsed?.mcp_servers?.["mrrlin-browser"] === void 0) {
55659
+ if (parsed?.mcp_servers?.["mrrlin"] === void 0 && parsed?.mcp_servers?.["mrrlin-browser"] === void 0 && parsed?.mcp_servers?.["external-agents"] === void 0) {
54799
55660
  return { action: "noop", configPath: realPath, prompts: await removePrompts() };
54800
55661
  }
54801
55662
  const stripped = existing.replace(MRRLIN_BLOCK_RE, (match) => match.startsWith("\n") ? "\n" : "").replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "");
@@ -54825,13 +55686,13 @@ async function uninstallPrompts(codexHome, names) {
54825
55686
 
54826
55687
  // src/bin.ts
54827
55688
  function pm2Runner(args) {
54828
- const r = (0, import_node_child_process9.spawnSync)("pm2", args, { encoding: "utf8" });
55689
+ const r = (0, import_node_child_process12.spawnSync)("pm2", args, { encoding: "utf8" });
54829
55690
  process.stderr.write(r.stdout ?? "");
54830
55691
  process.stderr.write(r.stderr ?? "");
54831
55692
  return { code: r.status ?? 1, stdout: r.stdout ?? "" };
54832
55693
  }
54833
55694
  function whichBin(bin) {
54834
- const r = (0, import_node_child_process9.spawnSync)("which", [bin], { encoding: "utf8" });
55695
+ const r = (0, import_node_child_process12.spawnSync)("which", [bin], { encoding: "utf8" });
54835
55696
  const out = (r.stdout ?? "").trim();
54836
55697
  return r.status === 0 && out ? out.split("\n")[0] ?? null : null;
54837
55698
  }
@@ -55007,7 +55868,7 @@ async function main() {
55007
55868
  }
55008
55869
  case "browser-serve": {
55009
55870
  const { command: command2, args, env } = browserServeArgs();
55010
- const r = (0, import_node_child_process9.spawnSync)(command2, args, { stdio: "inherit", env: { ...process.env, ...env } });
55871
+ const r = (0, import_node_child_process12.spawnSync)(command2, args, { stdio: "inherit", env: { ...process.env, ...env } });
55011
55872
  process.exitCode = r.status ?? 1;
55012
55873
  return;
55013
55874
  }