@mrrlin-dev/mcp 0.3.27 → 0.3.36
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 +928 -56
- package/package.json +3 -3
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
|
|
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
|
}
|
|
@@ -20802,10 +20810,11 @@ function renderBlock(block) {
|
|
|
20802
20810
|
var KNOWN_MRRLIN_ENV_KEYS = /* @__PURE__ */ new Set(["MRRLIN_STAGING", "MRRLIN_API_BASE_URL", "MRRLIN_API_ALLOW_REMOTE"]);
|
|
20803
20811
|
function commandArgsTimeoutEqual(candidate, expected) {
|
|
20804
20812
|
if (candidate.command !== expected.command) return false;
|
|
20805
|
-
|
|
20806
|
-
if (
|
|
20813
|
+
const actualArgs = candidate.args === void 0 && expected.args.length === 0 ? [] : Array.isArray(candidate.args) ? candidate.args : null;
|
|
20814
|
+
if (actualArgs === null) return false;
|
|
20815
|
+
if (actualArgs.length !== expected.args.length) return false;
|
|
20807
20816
|
for (let index = 0; index < expected.args.length; index += 1) {
|
|
20808
|
-
if (
|
|
20817
|
+
if (actualArgs[index] !== expected.args[index]) return false;
|
|
20809
20818
|
}
|
|
20810
20819
|
if (candidate.startup_timeout_sec !== void 0 && candidate.startup_timeout_sec !== expected.startup_timeout_sec) {
|
|
20811
20820
|
return false;
|
|
@@ -20981,7 +20990,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
|
20981
20990
|
var import_node_http = __toESM(require("node:http"), 1);
|
|
20982
20991
|
var import_node_os7 = __toESM(require("node:os"), 1);
|
|
20983
20992
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
20984
|
-
var
|
|
20993
|
+
var import_node_child_process9 = require("node:child_process");
|
|
20985
20994
|
var import_node_crypto7 = __toESM(require("node:crypto"), 1);
|
|
20986
20995
|
|
|
20987
20996
|
// src/turn-replay-buffer.ts
|
|
@@ -36377,6 +36386,8 @@ var openApiOperationIds = {
|
|
|
36377
36386
|
listArtifactFiles: "listArtifactFiles",
|
|
36378
36387
|
getOperatorSettings: "getOperatorSettings",
|
|
36379
36388
|
updateOperatorSettings: "updateOperatorSettings",
|
|
36389
|
+
getAgentPool: "getAgentPool",
|
|
36390
|
+
putAgentPool: "putAgentPool",
|
|
36380
36391
|
getConsensusConfig: "getConsensusConfig",
|
|
36381
36392
|
updateWikiPageCas: "updateWikiPageCas",
|
|
36382
36393
|
promoteSpecReadyCas: "promoteSpecReadyCas",
|
|
@@ -36432,6 +36443,27 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
|
|
|
36432
36443
|
var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
|
|
36433
36444
|
var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
|
|
36434
36445
|
var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
|
|
36446
|
+
var agentPoolSnapshotSchema = external_exports.object({
|
|
36447
|
+
collected_at: external_exports.string(),
|
|
36448
|
+
status: external_exports.enum(["ok", "unavailable"]),
|
|
36449
|
+
agents: external_exports.array(external_exports.object({
|
|
36450
|
+
id: external_exports.string(),
|
|
36451
|
+
provider: external_exports.string(),
|
|
36452
|
+
tier: external_exports.string(),
|
|
36453
|
+
tags: external_exports.array(external_exports.string()),
|
|
36454
|
+
state: external_exports.string(),
|
|
36455
|
+
calls: external_exports.number(),
|
|
36456
|
+
tokens_in: external_exports.number(),
|
|
36457
|
+
tokens_out: external_exports.number(),
|
|
36458
|
+
success_rate: external_exports.number(),
|
|
36459
|
+
last_used_at: external_exports.string().nullable()
|
|
36460
|
+
})),
|
|
36461
|
+
totals: external_exports.object({ calls: external_exports.number(), tokens_in: external_exports.number(), tokens_out: external_exports.number() }),
|
|
36462
|
+
estimated_savings_usd: external_exports.number()
|
|
36463
|
+
});
|
|
36464
|
+
var agentPoolResponseSchema = external_exports.object({
|
|
36465
|
+
data: external_exports.object({ snapshot: agentPoolSnapshotSchema.nullable() })
|
|
36466
|
+
});
|
|
36435
36467
|
|
|
36436
36468
|
// ../../packages/schemas/dist/specConsensusReview.js
|
|
36437
36469
|
var mrrlinSpecConsensusReviewSchema = external_exports.object({
|
|
@@ -36854,6 +36886,7 @@ var mrrlinTaskCreateSchema = external_exports.object({
|
|
|
36854
36886
|
impact: mrrlinTaskImpactSchema.default("medium"),
|
|
36855
36887
|
notes: external_exports.string().nullable().optional(),
|
|
36856
36888
|
operatorSeedRequest: external_exports.string().max(8e3).nullable().optional(),
|
|
36889
|
+
parentTaskId: external_exports.string().nullable().optional(),
|
|
36857
36890
|
planId: mrrlinPlanIdSchema.nullable().optional(),
|
|
36858
36891
|
specWikiPageId: mrrlinWikiPageIdSchema.nullable().optional(),
|
|
36859
36892
|
tags: mrrlinTaskTagsSchema.optional(),
|
|
@@ -37615,7 +37648,8 @@ var mrrlinInboxItemDecideSchema = external_exports.object({
|
|
|
37615
37648
|
var mrrlinInboxItemListFiltersSchema = external_exports.object({
|
|
37616
37649
|
kind: mrrlinInboxItemKindSchema.optional(),
|
|
37617
37650
|
readState: external_exports.enum(["read", "unread"]).optional(),
|
|
37618
|
-
status: mrrlinInboxItemStatusSchema.optional()
|
|
37651
|
+
status: mrrlinInboxItemStatusSchema.optional(),
|
|
37652
|
+
taskId: mrrlinTaskIdSchema.optional()
|
|
37619
37653
|
});
|
|
37620
37654
|
var inboxItemReadResponseSchema = external_exports.object({
|
|
37621
37655
|
data: external_exports.object({
|
|
@@ -38222,7 +38256,8 @@ function createMrrlinClient(config2) {
|
|
|
38222
38256
|
const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
|
|
38223
38257
|
kind: filters.kind,
|
|
38224
38258
|
readState: filters.readState,
|
|
38225
|
-
status: filters.status
|
|
38259
|
+
status: filters.status,
|
|
38260
|
+
taskId: filters.taskId
|
|
38226
38261
|
});
|
|
38227
38262
|
const body = await request(url2);
|
|
38228
38263
|
return inboxItemListResponseSchema.parse(body).data;
|
|
@@ -38406,6 +38441,18 @@ function createMrrlinClient(config2) {
|
|
|
38406
38441
|
});
|
|
38407
38442
|
return operatorSettingsResponseSchema.parse(body).data;
|
|
38408
38443
|
},
|
|
38444
|
+
async getOperatorAgentPool() {
|
|
38445
|
+
const body = await request(`/operator/agent-pool`);
|
|
38446
|
+
return agentPoolResponseSchema.parse(body).data;
|
|
38447
|
+
},
|
|
38448
|
+
async putOperatorAgentPool(snapshot) {
|
|
38449
|
+
const body = await request(`/operator/agent-pool`, {
|
|
38450
|
+
body: JSON.stringify(snapshot),
|
|
38451
|
+
headers: { "content-type": "application/json" },
|
|
38452
|
+
method: "POST"
|
|
38453
|
+
});
|
|
38454
|
+
return agentPoolResponseSchema.parse(body).data;
|
|
38455
|
+
},
|
|
38409
38456
|
async getConsensusConfig(projectSlug) {
|
|
38410
38457
|
const body = await request(`${projectPath(projectSlug)}/consensus-config`);
|
|
38411
38458
|
return consensusConfigResponseSchema.parse(body).data;
|
|
@@ -38788,8 +38835,92 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
|
|
|
38788
38835
|
};
|
|
38789
38836
|
}
|
|
38790
38837
|
|
|
38838
|
+
// src/codex-version.ts
|
|
38839
|
+
var import_node_child_process3 = require("node:child_process");
|
|
38840
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
38841
|
+
var SEMVER_RE = /\b\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?\b/;
|
|
38842
|
+
var CODEX_PACKAGE = process.env.MRRLIN_CODEX_PACKAGE?.trim() || "@openai/codex";
|
|
38843
|
+
var distTagsUrl = (pkg) => `https://registry.npmjs.org/-/package/${encodeURIComponent(pkg)}/dist-tags`;
|
|
38844
|
+
function parseCodexVersion(raw) {
|
|
38845
|
+
const m = raw.match(SEMVER_RE);
|
|
38846
|
+
return m ? m[0] : null;
|
|
38847
|
+
}
|
|
38848
|
+
var defaultRun = (args) => {
|
|
38849
|
+
const r = (0, import_node_child_process3.spawnSync)("codex", args, { encoding: "utf8", timeout: 8e3, shell: true });
|
|
38850
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
38851
|
+
};
|
|
38852
|
+
function getInstalledCodexVersion(run3) {
|
|
38853
|
+
const r = run3(["--version"]);
|
|
38854
|
+
if (r.status !== 0) return null;
|
|
38855
|
+
return parseCodexVersion(r.stdout || r.stderr);
|
|
38856
|
+
}
|
|
38857
|
+
async function fetchLatestCodexVersion(fetchImpl = globalThis.fetch, pkg = CODEX_PACKAGE) {
|
|
38858
|
+
try {
|
|
38859
|
+
const controller = new AbortController();
|
|
38860
|
+
const t = setTimeout(() => controller.abort(), 5e3);
|
|
38861
|
+
let res;
|
|
38862
|
+
try {
|
|
38863
|
+
res = await fetchImpl(distTagsUrl(pkg), {
|
|
38864
|
+
headers: { Accept: "application/json" },
|
|
38865
|
+
signal: controller.signal
|
|
38866
|
+
});
|
|
38867
|
+
} finally {
|
|
38868
|
+
clearTimeout(t);
|
|
38869
|
+
}
|
|
38870
|
+
if (!res.ok) return null;
|
|
38871
|
+
const body = await res.json();
|
|
38872
|
+
const latest = body?.latest;
|
|
38873
|
+
return typeof latest === "string" && import_semver2.default.valid(latest) ? latest : null;
|
|
38874
|
+
} catch {
|
|
38875
|
+
return null;
|
|
38876
|
+
}
|
|
38877
|
+
}
|
|
38878
|
+
async function computeCodexUpdateState(run3, fetchImpl = globalThis.fetch, pkg = CODEX_PACKAGE) {
|
|
38879
|
+
const version2 = getInstalledCodexVersion(run3);
|
|
38880
|
+
if (!version2 || !import_semver2.default.valid(version2)) return { version: version2, latestVersion: null, updateAvailable: false };
|
|
38881
|
+
const latestVersion = await fetchLatestCodexVersion(fetchImpl, pkg);
|
|
38882
|
+
if (!latestVersion) return { version: version2, latestVersion: null, updateAvailable: false };
|
|
38883
|
+
return { version: version2, latestVersion, updateAvailable: import_semver2.default.gt(latestVersion, version2) };
|
|
38884
|
+
}
|
|
38885
|
+
function codexUpdateCommandFor(state) {
|
|
38886
|
+
return state.updateAvailable ? "codex update" : null;
|
|
38887
|
+
}
|
|
38888
|
+
var DEFAULT_PROBE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
38889
|
+
var DEFAULT_STARTUP_DELAY_MS = 1e4;
|
|
38890
|
+
function startCodexProbe(deps = {}) {
|
|
38891
|
+
const run3 = deps.run ?? defaultRun;
|
|
38892
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
38893
|
+
const pkg = deps.pkg ?? CODEX_PACKAGE;
|
|
38894
|
+
const intervalMs = deps.intervalMs ?? DEFAULT_PROBE_INTERVAL_MS;
|
|
38895
|
+
const startupDelayMs = deps.startupDelayMs ?? DEFAULT_STARTUP_DELAY_MS;
|
|
38896
|
+
const setIntervalFn = deps.setIntervalImpl ?? setInterval;
|
|
38897
|
+
const clearIntervalFn = deps.clearIntervalImpl ?? clearInterval;
|
|
38898
|
+
const setTimeoutFn = deps.setTimeoutImpl ?? setTimeout;
|
|
38899
|
+
const clearTimeoutFn = deps.clearTimeoutImpl ?? clearTimeout;
|
|
38900
|
+
let state = { version: null, latestVersion: null, updateAvailable: false };
|
|
38901
|
+
const refreshNow = async () => {
|
|
38902
|
+
try {
|
|
38903
|
+
state = await computeCodexUpdateState(run3, fetchImpl, pkg);
|
|
38904
|
+
} catch {
|
|
38905
|
+
}
|
|
38906
|
+
};
|
|
38907
|
+
const startupTimer = setTimeoutFn(() => void refreshNow(), startupDelayMs);
|
|
38908
|
+
startupTimer.unref?.();
|
|
38909
|
+
const timer = setIntervalFn(() => void refreshNow(), intervalMs);
|
|
38910
|
+
timer.unref?.();
|
|
38911
|
+
return {
|
|
38912
|
+
getState: () => state,
|
|
38913
|
+
getUpdateCommand: () => codexUpdateCommandFor(state),
|
|
38914
|
+
refreshNow,
|
|
38915
|
+
stop: () => {
|
|
38916
|
+
clearTimeoutFn(startupTimer);
|
|
38917
|
+
clearIntervalFn(timer);
|
|
38918
|
+
}
|
|
38919
|
+
};
|
|
38920
|
+
}
|
|
38921
|
+
|
|
38791
38922
|
// src/_generated/version.ts
|
|
38792
|
-
var PKG_VERSION = "0.3.
|
|
38923
|
+
var PKG_VERSION = "0.3.36";
|
|
38793
38924
|
|
|
38794
38925
|
// src/api-base-url.ts
|
|
38795
38926
|
var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
|
|
@@ -38967,8 +39098,192 @@ function startSchedulerTickLoop(deps) {
|
|
|
38967
39098
|
};
|
|
38968
39099
|
}
|
|
38969
39100
|
|
|
39101
|
+
// src/agent-pool-loop.ts
|
|
39102
|
+
var import_node_child_process4 = require("node:child_process");
|
|
39103
|
+
|
|
39104
|
+
// src/agent-pool-snapshot.ts
|
|
39105
|
+
function buildAgentPoolSnapshot(statusRows, stats, opts) {
|
|
39106
|
+
if (!opts.available) {
|
|
39107
|
+
return {
|
|
39108
|
+
collected_at: opts.collectedAt,
|
|
39109
|
+
status: "unavailable",
|
|
39110
|
+
agents: [],
|
|
39111
|
+
totals: { calls: 0, tokens_in: 0, tokens_out: 0 },
|
|
39112
|
+
estimated_savings_usd: 0
|
|
39113
|
+
};
|
|
39114
|
+
}
|
|
39115
|
+
const costPer1M = opts.paidCostPer1MTokens ?? 3;
|
|
39116
|
+
const agents = statusRows.map((row) => {
|
|
39117
|
+
const agentStats = stats.by_agent[row.id];
|
|
39118
|
+
const count2 = agentStats?.count ?? 0;
|
|
39119
|
+
const tokensIn = agentStats?.tokens_in ?? 0;
|
|
39120
|
+
const tokensOut = agentStats?.tokens_out ?? 0;
|
|
39121
|
+
let successRate = 0;
|
|
39122
|
+
if (count2 > 0) {
|
|
39123
|
+
const successOutcomes = agentStats?.outcomes.success ?? 0;
|
|
39124
|
+
successRate = Number((successOutcomes / count2 * 100).toFixed(2)) / 100;
|
|
39125
|
+
}
|
|
39126
|
+
return {
|
|
39127
|
+
id: row.id,
|
|
39128
|
+
provider: row.provider,
|
|
39129
|
+
tier: row.tier,
|
|
39130
|
+
tags: row.tags,
|
|
39131
|
+
state: row.state ?? "unknown",
|
|
39132
|
+
calls: count2,
|
|
39133
|
+
tokens_in: tokensIn,
|
|
39134
|
+
tokens_out: tokensOut,
|
|
39135
|
+
success_rate: successRate,
|
|
39136
|
+
last_used_at: row.last_used_at ?? null
|
|
39137
|
+
};
|
|
39138
|
+
});
|
|
39139
|
+
let totalCalls = 0;
|
|
39140
|
+
let totalTokensIn = 0;
|
|
39141
|
+
let totalTokensOut = 0;
|
|
39142
|
+
for (const entry of Object.values(stats.by_agent)) {
|
|
39143
|
+
totalCalls += entry.count;
|
|
39144
|
+
totalTokensIn += entry.tokens_in;
|
|
39145
|
+
totalTokensOut += entry.tokens_out;
|
|
39146
|
+
}
|
|
39147
|
+
let savedTokens = 0;
|
|
39148
|
+
for (const row of statusRows) {
|
|
39149
|
+
if (row.tags.includes("free")) {
|
|
39150
|
+
const agentStats = stats.by_agent[row.id];
|
|
39151
|
+
if (agentStats) {
|
|
39152
|
+
savedTokens += agentStats.tokens_in + agentStats.tokens_out;
|
|
39153
|
+
}
|
|
39154
|
+
}
|
|
39155
|
+
}
|
|
39156
|
+
const estimatedSavings = Number((savedTokens / 1e6 * costPer1M).toFixed(2));
|
|
39157
|
+
return {
|
|
39158
|
+
collected_at: opts.collectedAt,
|
|
39159
|
+
status: "ok",
|
|
39160
|
+
agents,
|
|
39161
|
+
totals: {
|
|
39162
|
+
calls: totalCalls,
|
|
39163
|
+
tokens_in: totalTokensIn,
|
|
39164
|
+
tokens_out: totalTokensOut
|
|
39165
|
+
},
|
|
39166
|
+
estimated_savings_usd: estimatedSavings
|
|
39167
|
+
};
|
|
39168
|
+
}
|
|
39169
|
+
|
|
39170
|
+
// src/agent-pool-collector.ts
|
|
39171
|
+
async function collectAgentPoolSnapshot(deps) {
|
|
39172
|
+
let statusRows;
|
|
39173
|
+
let stats;
|
|
39174
|
+
try {
|
|
39175
|
+
const statusResult = await deps.runCli(["status", "--json"]);
|
|
39176
|
+
if (statusResult.code !== 0) throw new Error("non-zero exit on status");
|
|
39177
|
+
let statusParsed;
|
|
39178
|
+
try {
|
|
39179
|
+
statusParsed = JSON.parse(statusResult.stdout);
|
|
39180
|
+
} catch {
|
|
39181
|
+
throw new Error("invalid status json");
|
|
39182
|
+
}
|
|
39183
|
+
if (!Array.isArray(statusParsed)) throw new Error("status not array");
|
|
39184
|
+
statusRows = statusParsed;
|
|
39185
|
+
const statsResult = await deps.runCli(["stats", "--json"]);
|
|
39186
|
+
if (statsResult.code !== 0) throw new Error("non-zero exit on stats");
|
|
39187
|
+
try {
|
|
39188
|
+
stats = JSON.parse(statsResult.stdout);
|
|
39189
|
+
} catch {
|
|
39190
|
+
throw new Error("invalid stats json");
|
|
39191
|
+
}
|
|
39192
|
+
} catch {
|
|
39193
|
+
return buildAgentPoolSnapshot(
|
|
39194
|
+
[],
|
|
39195
|
+
{},
|
|
39196
|
+
{ collectedAt: deps.now(), available: false }
|
|
39197
|
+
);
|
|
39198
|
+
}
|
|
39199
|
+
return buildAgentPoolSnapshot(
|
|
39200
|
+
statusRows,
|
|
39201
|
+
stats,
|
|
39202
|
+
{ collectedAt: deps.now(), paidCostPer1MTokens: deps.paidCostPer1MTokens, available: true }
|
|
39203
|
+
);
|
|
39204
|
+
}
|
|
39205
|
+
|
|
39206
|
+
// src/agent-pool-loop.ts
|
|
39207
|
+
var DEFAULT_INTERVAL_MS2 = 6e4;
|
|
39208
|
+
function agentPoolLoopDisabled(env = process.env) {
|
|
39209
|
+
return env["MRRLIN_BRIDGE_AGENT_POOL_DISABLE"] === "1";
|
|
39210
|
+
}
|
|
39211
|
+
function defaultRunCli(args) {
|
|
39212
|
+
const cli = process.env["EXTERNAL_AGENTS_CLI"];
|
|
39213
|
+
const [cmd, baseArgs] = cli ? ["node", [cli, ...args]] : ["external-agents", args];
|
|
39214
|
+
return new Promise((resolve) => {
|
|
39215
|
+
let stdout = "";
|
|
39216
|
+
let settled = false;
|
|
39217
|
+
const done = (code) => {
|
|
39218
|
+
if (!settled) {
|
|
39219
|
+
settled = true;
|
|
39220
|
+
resolve({ stdout, code });
|
|
39221
|
+
}
|
|
39222
|
+
};
|
|
39223
|
+
try {
|
|
39224
|
+
const child = (0, import_node_child_process4.spawn)(cmd, baseArgs, { stdio: ["ignore", "pipe", "ignore"] });
|
|
39225
|
+
const timer = setTimeout(() => {
|
|
39226
|
+
try {
|
|
39227
|
+
child.kill("SIGKILL");
|
|
39228
|
+
} catch {
|
|
39229
|
+
}
|
|
39230
|
+
done(1);
|
|
39231
|
+
}, 5e3);
|
|
39232
|
+
timer.unref?.();
|
|
39233
|
+
child.stdout.on("data", (d) => {
|
|
39234
|
+
stdout += d.toString();
|
|
39235
|
+
if (stdout.length > 2e5) stdout = stdout.slice(-2e5);
|
|
39236
|
+
});
|
|
39237
|
+
child.on("error", () => {
|
|
39238
|
+
clearTimeout(timer);
|
|
39239
|
+
done(1);
|
|
39240
|
+
});
|
|
39241
|
+
child.on("close", (code) => {
|
|
39242
|
+
clearTimeout(timer);
|
|
39243
|
+
done(code ?? 1);
|
|
39244
|
+
});
|
|
39245
|
+
} catch {
|
|
39246
|
+
done(1);
|
|
39247
|
+
}
|
|
39248
|
+
});
|
|
39249
|
+
}
|
|
39250
|
+
async function runAgentPoolCycle(deps) {
|
|
39251
|
+
try {
|
|
39252
|
+
const snapshot = await collectAgentPoolSnapshot({
|
|
39253
|
+
runCli: deps.runCli ?? defaultRunCli,
|
|
39254
|
+
now: deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
|
|
39255
|
+
});
|
|
39256
|
+
await deps.push(snapshot);
|
|
39257
|
+
} catch (err) {
|
|
39258
|
+
deps.onError?.(err);
|
|
39259
|
+
}
|
|
39260
|
+
}
|
|
39261
|
+
function startAgentPoolLoop(deps) {
|
|
39262
|
+
const env = deps.env ?? process.env;
|
|
39263
|
+
const intervalMs = deps.intervalMs ?? Number(env["MRRLIN_BRIDGE_AGENT_POOL_INTERVAL_MS"] ?? DEFAULT_INTERVAL_MS2);
|
|
39264
|
+
let stopped = false;
|
|
39265
|
+
let timer = null;
|
|
39266
|
+
const schedule = () => {
|
|
39267
|
+
if (stopped) return;
|
|
39268
|
+
timer = setTimeout(() => {
|
|
39269
|
+
void runAgentPoolCycle(deps).then(() => schedule());
|
|
39270
|
+
}, intervalMs);
|
|
39271
|
+
timer.unref?.();
|
|
39272
|
+
};
|
|
39273
|
+
timer = setTimeout(() => {
|
|
39274
|
+
void runAgentPoolCycle(deps).then(() => schedule());
|
|
39275
|
+
}, 1e3);
|
|
39276
|
+
timer.unref?.();
|
|
39277
|
+
return {
|
|
39278
|
+
stop() {
|
|
39279
|
+
stopped = true;
|
|
39280
|
+
if (timer) clearTimeout(timer);
|
|
39281
|
+
}
|
|
39282
|
+
};
|
|
39283
|
+
}
|
|
39284
|
+
|
|
38970
39285
|
// src/execution-consumer.ts
|
|
38971
|
-
var
|
|
39286
|
+
var import_node_child_process8 = require("node:child_process");
|
|
38972
39287
|
var import_node_crypto4 = __toESM(require("node:crypto"), 1);
|
|
38973
39288
|
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
38974
39289
|
var import_node_util = require("node:util");
|
|
@@ -38991,6 +39306,9 @@ function normalizeDomain(value) {
|
|
|
38991
39306
|
function resolveHeads(domain2) {
|
|
38992
39307
|
return [...HEADS_BY_DOMAIN[normalizeDomain(domain2)]];
|
|
38993
39308
|
}
|
|
39309
|
+
function allReviewHeads() {
|
|
39310
|
+
return [...new Set(Object.values(HEADS_BY_DOMAIN).flat())];
|
|
39311
|
+
}
|
|
38994
39312
|
var PERSONAS_BY_DOMAIN = {
|
|
38995
39313
|
seo: "seo-writer",
|
|
38996
39314
|
content: "content-author",
|
|
@@ -39009,7 +39327,7 @@ var import_node_path3 = __toESM(require("node:path"), 1);
|
|
|
39009
39327
|
var import_node_url2 = require("node:url");
|
|
39010
39328
|
|
|
39011
39329
|
// src/consensus/codex-exec.ts
|
|
39012
|
-
var
|
|
39330
|
+
var import_node_child_process5 = require("node:child_process");
|
|
39013
39331
|
function extractFinalMessage(stdout) {
|
|
39014
39332
|
let last = null;
|
|
39015
39333
|
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
@@ -39054,7 +39372,7 @@ function firstString(...values) {
|
|
|
39054
39372
|
return null;
|
|
39055
39373
|
}
|
|
39056
39374
|
function runCodexExec(input, deps) {
|
|
39057
|
-
const
|
|
39375
|
+
const spawn3 = deps?.spawn ?? import_node_child_process5.spawn;
|
|
39058
39376
|
const executable = input.codexExecutable ?? "codex";
|
|
39059
39377
|
const sandbox = input.sandbox ?? "read-only";
|
|
39060
39378
|
const fullPrompt = input.developerInstructions.trim() ? `${input.developerInstructions.trim()}
|
|
@@ -39086,7 +39404,7 @@ ${input.prompt}` : input.prompt;
|
|
|
39086
39404
|
};
|
|
39087
39405
|
let child;
|
|
39088
39406
|
try {
|
|
39089
|
-
child =
|
|
39407
|
+
child = spawn3(executable, args, options);
|
|
39090
39408
|
} catch (err) {
|
|
39091
39409
|
finish({ ok: false, error: `Failed to spawn codex exec: ${errMessage(err)}` });
|
|
39092
39410
|
return;
|
|
@@ -39193,7 +39511,7 @@ function parseAcceptanceCriteria(markdown) {
|
|
|
39193
39511
|
}
|
|
39194
39512
|
|
|
39195
39513
|
// src/consensus/code-gate-git.ts
|
|
39196
|
-
var
|
|
39514
|
+
var import_node_child_process6 = require("node:child_process");
|
|
39197
39515
|
var DIFF_MAX_BYTES = 2e5;
|
|
39198
39516
|
var FASTGATE_OUTPUT_MAX_BYTES = 16e3;
|
|
39199
39517
|
var GIT_TIMEOUT_MS = 6e4;
|
|
@@ -39209,7 +39527,7 @@ var defaultRunCmd = (cmd, args, opts) => new Promise((resolve) => {
|
|
|
39209
39527
|
};
|
|
39210
39528
|
let child;
|
|
39211
39529
|
try {
|
|
39212
|
-
child = (0,
|
|
39530
|
+
child = (0, import_node_child_process6.spawn)(cmd, args, {
|
|
39213
39531
|
cwd: opts.cwd,
|
|
39214
39532
|
env: process.env,
|
|
39215
39533
|
shell: false,
|
|
@@ -39455,7 +39773,7 @@ function reviserIdentityInstructions(taskId, title) {
|
|
|
39455
39773
|
}
|
|
39456
39774
|
function buildSpecGateDeps(args) {
|
|
39457
39775
|
const { client, projectSlug, specPageId, cwd, codexExecutable, extraHeadCount = 0 } = args;
|
|
39458
|
-
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ??
|
|
39776
|
+
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
39459
39777
|
const maxHeadCalls = rounds * (4 + extraHeadCount + 1);
|
|
39460
39778
|
const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
39461
39779
|
let intentPromise = null;
|
|
@@ -39469,13 +39787,22 @@ function buildSpecGateDeps(args) {
|
|
|
39469
39787
|
const taskId = await resolveGatedTaskId(client, projectSlug, { id: page.id, taskId: page.taskId ?? null });
|
|
39470
39788
|
if (taskId === null) {
|
|
39471
39789
|
const project = await client.getProject(projectSlug);
|
|
39472
|
-
return { taskId: null, seed: null, title: null, domain: project.defaultDomain };
|
|
39790
|
+
return { taskId: null, seed: null, title: null, domain: project.defaultDomain, notes: null };
|
|
39473
39791
|
}
|
|
39474
39792
|
const task = await client.getTask(projectSlug, taskId);
|
|
39475
39793
|
if (!task) {
|
|
39476
39794
|
throw new Error(`gated task ${projectSlug}/${taskId} not found for spec ${specPageId} \u2014 cannot gate`);
|
|
39477
39795
|
}
|
|
39478
|
-
|
|
39796
|
+
const notes = task.notes ?? null;
|
|
39797
|
+
const rawSeed = task.operatorSeedRequest ?? null;
|
|
39798
|
+
let resolvedSeed = rawSeed;
|
|
39799
|
+
if (rawSeed === null || rawSeed.trim() === "") {
|
|
39800
|
+
const parsed = notes ? parseAcceptanceCriteria(notes) : null;
|
|
39801
|
+
if (parsed !== null && parsed.items.length > 0) {
|
|
39802
|
+
resolvedSeed = "(No operator statement on record for this task. Derived from the task's own notes \u2014 the following acceptance criteria were extracted verbatim:)\n\n" + parsed.items.map((c) => `- ${c}`).join("\n");
|
|
39803
|
+
}
|
|
39804
|
+
}
|
|
39805
|
+
return { taskId, seed: resolvedSeed, title: task.title ?? "", domain: task.domain, notes };
|
|
39479
39806
|
})();
|
|
39480
39807
|
}
|
|
39481
39808
|
return intentPromise;
|
|
@@ -39609,7 +39936,7 @@ ${artifact}`,
|
|
|
39609
39936
|
}
|
|
39610
39937
|
function buildCodeGateDeps(args) {
|
|
39611
39938
|
const { client, projectSlug, baseRef, cwd, codexExecutable, extraHeads } = args;
|
|
39612
|
-
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ??
|
|
39939
|
+
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
39613
39940
|
const heads = ["code-reviewer", ...extraHeads ?? []];
|
|
39614
39941
|
const maxHeadCalls = rounds * (heads.length + 1);
|
|
39615
39942
|
const headTimeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
@@ -39690,7 +40017,7 @@ function renderCriteriaBlock(criteria) {
|
|
|
39690
40017
|
}
|
|
39691
40018
|
async function buildDeliverableGateDeps(args) {
|
|
39692
40019
|
const { client, projectSlug, deliverablePageId, cwd, codexExecutable, runId } = args;
|
|
39693
|
-
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ??
|
|
40020
|
+
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
39694
40021
|
const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
39695
40022
|
const page = await client.getWikiPage(projectSlug, deliverablePageId);
|
|
39696
40023
|
const taskId = page.taskId ?? null;
|
|
@@ -40613,7 +40940,7 @@ async function awaitTurnWithDeadline(opts) {
|
|
|
40613
40940
|
}
|
|
40614
40941
|
|
|
40615
40942
|
// src/worktree.ts
|
|
40616
|
-
var
|
|
40943
|
+
var import_node_child_process7 = require("node:child_process");
|
|
40617
40944
|
var import_node_fs4 = require("node:fs");
|
|
40618
40945
|
var import_node_os3 = require("node:os");
|
|
40619
40946
|
var import_node_path5 = require("node:path");
|
|
@@ -40641,9 +40968,9 @@ async function withCheckoutLock(locksDir, slug, fn) {
|
|
|
40641
40968
|
function worktreeBranch(runId) {
|
|
40642
40969
|
return `mrrlin/run-${runId}`;
|
|
40643
40970
|
}
|
|
40644
|
-
var defaultGitExec = (args, env) => (0,
|
|
40971
|
+
var defaultGitExec = (args, env) => (0, import_node_child_process7.execFileSync)("git", args, { encoding: "utf8", timeout: 3e4, env });
|
|
40645
40972
|
function git(checkout, args) {
|
|
40646
|
-
return (0,
|
|
40973
|
+
return (0, import_node_child_process7.execFileSync)("git", ["-C", checkout, ...args], {
|
|
40647
40974
|
encoding: "utf8",
|
|
40648
40975
|
timeout: 3e4,
|
|
40649
40976
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
@@ -40848,6 +41175,38 @@ function isExecutionTurnTimeoutReason(reason) {
|
|
|
40848
41175
|
function contextMaxArtifacts() {
|
|
40849
41176
|
return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_CONTEXT_MAX_ARTIFACTS", DEFAULT_CONTEXT_MAX_ARTIFACTS);
|
|
40850
41177
|
}
|
|
41178
|
+
var DEFAULT_COMPLETION_TURN_BUDGET_MS = 6e4;
|
|
41179
|
+
var DEFAULT_COMPLETION_TURN_MIN_BUDGET_MS = 1e4;
|
|
41180
|
+
function completionTurnBudgetMs() {
|
|
41181
|
+
return readPositiveIntEnv("MRRLIN_COMPLETION_TURN_BUDGET_MS", DEFAULT_COMPLETION_TURN_BUDGET_MS);
|
|
41182
|
+
}
|
|
41183
|
+
function completionTurnMinBudgetMs() {
|
|
41184
|
+
return readPositiveIntEnv("MRRLIN_COMPLETION_TURN_MIN_BUDGET_MS", DEFAULT_COMPLETION_TURN_MIN_BUDGET_MS);
|
|
41185
|
+
}
|
|
41186
|
+
function isCompletionTurnDisabled() {
|
|
41187
|
+
return (process.env.MRRLIN_COMPLETION_TURN_DISABLED ?? "").trim() === "1";
|
|
41188
|
+
}
|
|
41189
|
+
function buildCompletionTurnPrompt(taskId, currentStatus) {
|
|
41190
|
+
return [
|
|
41191
|
+
`Your work turn ended but task ${taskId} is still in status "${currentStatus}".`,
|
|
41192
|
+
"",
|
|
41193
|
+
"Decide honestly whether the work is complete:",
|
|
41194
|
+
"",
|
|
41195
|
+
"If the work IS genuinely complete:",
|
|
41196
|
+
"1. Update the task notes with a markdown summary of what you accomplished",
|
|
41197
|
+
" (see the notes format contract in your base instructions).",
|
|
41198
|
+
"2. Call update_task with status=closed_done.",
|
|
41199
|
+
"3. Append a completion-evidence artifact (checkpoint or diff_summary)",
|
|
41200
|
+
" to the open run if you haven't already.",
|
|
41201
|
+
"",
|
|
41202
|
+
"If the work is NOT complete \u2014 be honest, do not close incomplete work:",
|
|
41203
|
+
"1. Call update_task with the appropriate status (needs_human, awaiting_verification, etc.)",
|
|
41204
|
+
" and explain what remains in the notes.",
|
|
41205
|
+
"2. Append a handoff_judgement artifact explaining what the operator needs to do.",
|
|
41206
|
+
"",
|
|
41207
|
+
"You must pick one of these paths \u2014 do not leave the task in_progress."
|
|
41208
|
+
].join("\n");
|
|
41209
|
+
}
|
|
40851
41210
|
var BASE_INSTRUCTIONS_PREFIX = [
|
|
40852
41211
|
"You are the Mrrlin Director driving a task toward completion autonomously.",
|
|
40853
41212
|
"You can call MCP tools to read and write durable project state.",
|
|
@@ -40863,6 +41222,9 @@ var BASE_INSTRUCTIONS_PREFIX = [
|
|
|
40863
41222
|
"- Close the task with `update_task` status=closed_done ONLY when it is genuinely complete",
|
|
40864
41223
|
" AND you appended at least one completion-evidence artifact. Otherwise leave the task",
|
|
40865
41224
|
" open so the run fails into an operator handoff instead of pretending success.",
|
|
41225
|
+
"- For a CODE task the run opens a draft PR for you after your turn; opening that PR is NOT",
|
|
41226
|
+
" completion. Do NOT set closed_done for code work \u2014 leave the task open; the run parks at",
|
|
41227
|
+
" `pull_request` (waiting for merge) and the operator merges. Closure happens after merge.",
|
|
40866
41228
|
"",
|
|
40867
41229
|
"File artifacts:",
|
|
40868
41230
|
"- When your output is a file (markdown report, HTML, screenshot), upload it with",
|
|
@@ -40882,6 +41244,23 @@ var BASE_INSTRUCTIONS_PREFIX = [
|
|
|
40882
41244
|
" and lose login/cookie state across runs.",
|
|
40883
41245
|
"- If tool listings show duplicate names across servers, prefer the `mrrlin-browser` one",
|
|
40884
41246
|
" unconditionally. Do not switch mid-run.",
|
|
41247
|
+
"",
|
|
41248
|
+
"Task description quality contract \u2014 every task you create or update via create_task or update_task:",
|
|
41249
|
+
"- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
|
|
41250
|
+
' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
|
|
41251
|
+
' Bad: "GSC coverage triage crawled-not-indexed + 404"',
|
|
41252
|
+
"- Notes (markdown): write for a human operator scanning a task list.",
|
|
41253
|
+
" First line: one plain-text sentence summarizing the task (no heading).",
|
|
41254
|
+
" Then scale the structure to complexity:",
|
|
41255
|
+
" For simple tasks \u2014 the one-line summary may be enough. No headings required.",
|
|
41256
|
+
" For non-trivial tasks, use these sections as needed:",
|
|
41257
|
+
" ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
|
|
41258
|
+
" ## Scope \u2014 concrete actions and acceptance criteria.",
|
|
41259
|
+
" ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
|
|
41260
|
+
" When closing a task (update_task status=closed_done), APPEND a Result section:",
|
|
41261
|
+
" ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
|
|
41262
|
+
" Do NOT remove earlier sections \u2014 they remain useful for context.",
|
|
41263
|
+
" Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
|
|
40885
41264
|
""
|
|
40886
41265
|
];
|
|
40887
41266
|
var CONSUMER_BASE_INSTRUCTIONS = [
|
|
@@ -40896,7 +41275,23 @@ var EXECUTOR_BASE_INSTRUCTIONS = [
|
|
|
40896
41275
|
"files and running git/build/test commands WITHIN this worktree. Keep all changes inside the",
|
|
40897
41276
|
"worktree and do not read the operator's personal secrets or files outside it \u2014 this is a rule",
|
|
40898
41277
|
"you must follow, not something the environment enforces. Be concise. When you have made the",
|
|
40899
|
-
"next increment of progress, stop the turn."
|
|
41278
|
+
"next increment of progress, stop the turn.",
|
|
41279
|
+
// ADR 0022 Phase 3: the executor is the orchestrator, not the sole worker. It has the
|
|
41280
|
+
// external-agents `pick`/`dispatch` MCP tools (a pool of cheap/free models) and should
|
|
41281
|
+
// delegate the bulk substantive work to them in parallel, keeping itself as judge. This
|
|
41282
|
+
// minimises the expensive default model's own token spend while the verify step preserves
|
|
41283
|
+
// quality. If external-agents isn't installed the tools are simply absent — these lines then
|
|
41284
|
+
// read as advice the model can't act on, and it does the work itself (graceful, no-op).
|
|
41285
|
+
"You are the ORCHESTRATOR and JUDGE, not the sole worker. If the external-agents `pick` and",
|
|
41286
|
+
"`dispatch` tools are available, delegate substantive, parallelizable subtasks \u2014 file edits,",
|
|
41287
|
+
"boilerplate, tests, research, mechanical refactors \u2014 to pool subagents via `dispatch`, several",
|
|
41288
|
+
"in parallel where possible, to minimise your own token spend. Keep for yourself only what needs",
|
|
41289
|
+
"your judgment: decomposition, integration, hard reasoning, and final decisions. You MUST verify",
|
|
41290
|
+
"every delegated result against the actual files and tests before it lands \u2014 pool models are",
|
|
41291
|
+
"lower-reliability and a wrong delegated edit is YOUR responsibility; never commit unverified",
|
|
41292
|
+
"delegated output. Delegation is the default for routine bulk work, but do trivial tasks yourself",
|
|
41293
|
+
"when dispatch overhead exceeds the work. If dispatch fails or the pool is unavailable, do the",
|
|
41294
|
+
"work yourself \u2014 never block the run on the pool."
|
|
40900
41295
|
].join("\n");
|
|
40901
41296
|
function artifactKindForEvent(event) {
|
|
40902
41297
|
switch (event.type) {
|
|
@@ -40935,7 +41330,7 @@ var BLAST_RADIUS_FLOOR_PATTERNS = [/^supabase\/migrations\//, /^packages\/db\/sr
|
|
|
40935
41330
|
function isBlastRadiusFloorPath(path15) {
|
|
40936
41331
|
return BLAST_RADIUS_FLOOR_PATTERNS.some((re) => re.test(path15));
|
|
40937
41332
|
}
|
|
40938
|
-
var execFileAsync = (0, import_node_util.promisify)(
|
|
41333
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process8.execFile);
|
|
40939
41334
|
async function defaultListRunDiffPaths(opts) {
|
|
40940
41335
|
const parse5 = (stdout) => stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
40941
41336
|
try {
|
|
@@ -41315,10 +41710,44 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
41315
41710
|
await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
|
|
41316
41711
|
return;
|
|
41317
41712
|
}
|
|
41318
|
-
|
|
41713
|
+
let readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
|
|
41319
41714
|
if (!readiness.ok) {
|
|
41320
|
-
|
|
41321
|
-
|
|
41715
|
+
const completionBudget = Math.min(completionTurnBudgetMs(), remainingTurnBudgetMs());
|
|
41716
|
+
if (isCompletionTurnDisabled() || completionBudget < completionTurnMinBudgetMs()) {
|
|
41717
|
+
await failRun(deps, projectSlug, readiness.task, runId, leaseId, readiness.reason);
|
|
41718
|
+
return;
|
|
41719
|
+
}
|
|
41720
|
+
console.warn(
|
|
41721
|
+
JSON.stringify({
|
|
41722
|
+
event: "completion_turn_triggered",
|
|
41723
|
+
projectSlug,
|
|
41724
|
+
runId,
|
|
41725
|
+
taskId: task.id,
|
|
41726
|
+
previousStatus: readiness.task.status
|
|
41727
|
+
})
|
|
41728
|
+
);
|
|
41729
|
+
const completionPrompt = buildCompletionTurnPrompt(task.id, readiness.task.status);
|
|
41730
|
+
const completionOutcome = await driveTurn(
|
|
41731
|
+
deps,
|
|
41732
|
+
codex,
|
|
41733
|
+
projectSlug,
|
|
41734
|
+
runId,
|
|
41735
|
+
leaseId,
|
|
41736
|
+
threadId,
|
|
41737
|
+
completionPrompt,
|
|
41738
|
+
completionBudget,
|
|
41739
|
+
"completion"
|
|
41740
|
+
);
|
|
41741
|
+
if (!completionOutcome.ok) {
|
|
41742
|
+
await resetWedgedThread(deps, projectSlug, task.id, completionOutcome.interruptFailed);
|
|
41743
|
+
await failRun(deps, projectSlug, readiness.task, runId, leaseId, completionOutcome.error);
|
|
41744
|
+
return;
|
|
41745
|
+
}
|
|
41746
|
+
readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
|
|
41747
|
+
if (!readiness.ok) {
|
|
41748
|
+
await failRun(deps, projectSlug, readiness.task, runId, leaseId, readiness.reason);
|
|
41749
|
+
return;
|
|
41750
|
+
}
|
|
41322
41751
|
}
|
|
41323
41752
|
if (useExecutor) {
|
|
41324
41753
|
if (!worktreePath) {
|
|
@@ -41410,6 +41839,13 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
41410
41839
|
pullRequestUrl: pr.url,
|
|
41411
41840
|
status: "pull_request"
|
|
41412
41841
|
});
|
|
41842
|
+
await reconcileTaskAwaitingMerge(
|
|
41843
|
+
deps,
|
|
41844
|
+
projectSlug,
|
|
41845
|
+
readiness.task,
|
|
41846
|
+
runId,
|
|
41847
|
+
`PR opened (${pr.url}); autoDeploy is off \u2014 the operator merges when ready.`
|
|
41848
|
+
);
|
|
41413
41849
|
return;
|
|
41414
41850
|
}
|
|
41415
41851
|
if (deps.deployGate) {
|
|
@@ -41424,7 +41860,7 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
41424
41860
|
pullRequestUrl: pr.url,
|
|
41425
41861
|
status: "pull_request"
|
|
41426
41862
|
});
|
|
41427
|
-
await
|
|
41863
|
+
await reconcileTaskAwaitingMerge(
|
|
41428
41864
|
deps,
|
|
41429
41865
|
projectSlug,
|
|
41430
41866
|
readiness.task,
|
|
@@ -41716,6 +42152,34 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
|
|
|
41716
42152
|
await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
|
|
41717
42153
|
}
|
|
41718
42154
|
}
|
|
42155
|
+
async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
|
|
42156
|
+
if (task.status === "handoff_needed") return;
|
|
42157
|
+
await deps.client.appendExecutionArtifact(projectSlug, runId, {
|
|
42158
|
+
id: newArtifactId(),
|
|
42159
|
+
kind: "handoff_judgement",
|
|
42160
|
+
payload: {
|
|
42161
|
+
verdict: "approved",
|
|
42162
|
+
reason,
|
|
42163
|
+
summary: `Execution run ${runId} opened a PR and is awaiting operator action.`,
|
|
42164
|
+
operatorQuestion: `Task ${task.id} (${task.title}) has an open PR: ${reason}`,
|
|
42165
|
+
source: "execution-consumer"
|
|
42166
|
+
}
|
|
42167
|
+
}).catch(() => {
|
|
42168
|
+
});
|
|
42169
|
+
try {
|
|
42170
|
+
await deps.client.updateTask(projectSlug, task.id, { status: "handoff_needed" });
|
|
42171
|
+
} catch (error51) {
|
|
42172
|
+
console.warn(
|
|
42173
|
+
JSON.stringify({
|
|
42174
|
+
event: "reconcile_awaiting_merge_failed",
|
|
42175
|
+
projectSlug,
|
|
42176
|
+
taskId: task.id,
|
|
42177
|
+
runId,
|
|
42178
|
+
error: error51 instanceof Error ? error51.message : String(error51)
|
|
42179
|
+
})
|
|
42180
|
+
);
|
|
42181
|
+
}
|
|
42182
|
+
}
|
|
41719
42183
|
async function notifyOperatorHandoff(deps, projectSlug, task, runId, reason) {
|
|
41720
42184
|
try {
|
|
41721
42185
|
await deps.client.appendExecutionArtifact(projectSlug, runId, {
|
|
@@ -41915,13 +42379,15 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
|
|
|
41915
42379
|
}).catch(() => {
|
|
41916
42380
|
});
|
|
41917
42381
|
const delivery = descriptor2.onComplete(outcome, input);
|
|
41918
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
|
|
41924
|
-
|
|
42382
|
+
if (!delivery.suppressInbox) {
|
|
42383
|
+
await deps.client.createInboxItem(projectSlug, {
|
|
42384
|
+
id: delivery.id ?? `II-${import_node_crypto5.default.randomUUID()}`,
|
|
42385
|
+
kind: delivery.kind,
|
|
42386
|
+
body: delivery.body,
|
|
42387
|
+
...delivery.taskId ? { taskId: delivery.taskId } : {}
|
|
42388
|
+
}).catch(() => {
|
|
42389
|
+
});
|
|
42390
|
+
}
|
|
41925
42391
|
if (descriptor2.wakeDirector) deps.wake(projectSlug, claimed.directorSessionId, delivery.body, delivery.kind);
|
|
41926
42392
|
}
|
|
41927
42393
|
function deriveHash() {
|
|
@@ -46768,6 +47234,23 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
46768
47234
|
'or "let me call the \u2026 tool" in ANY language. The operator already sees tool calls in the',
|
|
46769
47235
|
"transcript. Just call the tools and reply with the outcome \u2014 start your reply with the result.",
|
|
46770
47236
|
"When creating or updating tasks, always provide `autonomyLevel` and `autonomyReason`.",
|
|
47237
|
+
"",
|
|
47238
|
+
"Task description quality contract \u2014 every task you create or update via create_task or update_task:",
|
|
47239
|
+
"- Title: short, actionable, human-readable. Describe what needs to happen, not internal jargon.",
|
|
47240
|
+
' Good: "Audit crawled-not-indexed pages and fix 404 errors"',
|
|
47241
|
+
' Bad: "GSC coverage triage crawled-not-indexed + 404"',
|
|
47242
|
+
"- Notes (markdown): write for a human operator scanning a task list.",
|
|
47243
|
+
" First line: one plain-text sentence summarizing the task (no heading).",
|
|
47244
|
+
" Then scale the structure to complexity:",
|
|
47245
|
+
" For simple tasks \u2014 the one-line summary may be enough. No headings required.",
|
|
47246
|
+
" For non-trivial tasks, use these sections as needed:",
|
|
47247
|
+
" ## Context \u2014 why this task exists; link to parent/trigger task if follow-up.",
|
|
47248
|
+
" ## Scope \u2014 concrete actions and acceptance criteria.",
|
|
47249
|
+
" ## References \u2014 links, data, relevant findings (omit if nothing to reference).",
|
|
47250
|
+
" When closing a task (update_task status=closed_done), APPEND a Result section:",
|
|
47251
|
+
" ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
|
|
47252
|
+
" Do NOT remove earlier sections \u2014 they remain useful for context.",
|
|
47253
|
+
" Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
|
|
46771
47254
|
"When you create a task out of an operator request, set `operatorSeedRequest` to the operator's",
|
|
46772
47255
|
"VERBATIM words (their request as they wrote it \u2014 NOT your paraphrase or synthesized notes). It is",
|
|
46773
47256
|
"the spec gate's independent record of intent; omit it only when there is genuinely no operator",
|
|
@@ -47017,7 +47500,7 @@ function resolveDefaultBranch(checkoutPath) {
|
|
|
47017
47500
|
if (!checkoutPath) return "main";
|
|
47018
47501
|
const runGit = (args) => {
|
|
47019
47502
|
try {
|
|
47020
|
-
return (0,
|
|
47503
|
+
return (0, import_node_child_process9.execFileSync)("git", ["-C", checkoutPath, ...args], {
|
|
47021
47504
|
encoding: "utf8",
|
|
47022
47505
|
stdio: ["ignore", "pipe", "ignore"],
|
|
47023
47506
|
timeout: 15e3,
|
|
@@ -47176,7 +47659,7 @@ function sessionIdOf(msg) {
|
|
|
47176
47659
|
return typeof id === "string" ? id : null;
|
|
47177
47660
|
}
|
|
47178
47661
|
function attachBridgeWss(server, opts) {
|
|
47179
|
-
const { token, allowedOrigins, onReady, logger, onPaired, bridgeInstanceId, selfUpdate } = opts;
|
|
47662
|
+
const { token, allowedOrigins, onReady, logger, onPaired, bridgeInstanceId, selfUpdate, codex } = opts;
|
|
47180
47663
|
const wss = new import_websocket_server.default({ server, path: "/ws" });
|
|
47181
47664
|
wss.on("connection", (socket, request) => {
|
|
47182
47665
|
const origin = request.headers.origin;
|
|
@@ -47241,7 +47724,15 @@ function attachBridgeWss(server, opts) {
|
|
|
47241
47724
|
version: selfUpdate.version,
|
|
47242
47725
|
binSha: selfUpdate.binSha,
|
|
47243
47726
|
latestVersion: selfUpdate.getLatestVersion()
|
|
47244
|
-
} : {}
|
|
47727
|
+
} : {},
|
|
47728
|
+
...codex ? (() => {
|
|
47729
|
+
const st = codex.getState();
|
|
47730
|
+
return {
|
|
47731
|
+
codexVersion: st.version,
|
|
47732
|
+
codexLatestVersion: st.latestVersion,
|
|
47733
|
+
codexUpdateCommand: codex.getUpdateCommand()
|
|
47734
|
+
};
|
|
47735
|
+
})() : {}
|
|
47245
47736
|
});
|
|
47246
47737
|
onPaired?.();
|
|
47247
47738
|
onReady?.(socket, request, send, logger ? { logger, span } : void 0);
|
|
@@ -47265,13 +47756,14 @@ async function waitFor(pred, timeoutMs) {
|
|
|
47265
47756
|
return pred();
|
|
47266
47757
|
}
|
|
47267
47758
|
function attachSeqMetadata(msg, seq, turnId) {
|
|
47268
|
-
if (msg.type === "event" || msg.type === "rateLimits" || msg.type === "error") {
|
|
47759
|
+
if (msg.type === "event" || msg.type === "rateLimits" || msg.type === "error" || msg.type === "turn-executing") {
|
|
47269
47760
|
return { ...msg, seq, turnId };
|
|
47270
47761
|
}
|
|
47271
47762
|
return msg;
|
|
47272
47763
|
}
|
|
47273
47764
|
function createBridgeMessageHandler(deps) {
|
|
47274
47765
|
const tombstones = deps.tombstones ?? /* @__PURE__ */ new Map();
|
|
47766
|
+
const pendingTurnSessions = /* @__PURE__ */ new Set();
|
|
47275
47767
|
return (socket, _request, send, logCtx) => {
|
|
47276
47768
|
const boundSessions = /* @__PURE__ */ new Set();
|
|
47277
47769
|
socket.on("close", () => {
|
|
@@ -47523,6 +48015,14 @@ function createBridgeMessageHandler(deps) {
|
|
|
47523
48015
|
interruptBuf.state = stopResult.next;
|
|
47524
48016
|
recordInterrupted(interruptBuf, Date.now(), deps.suspendedMs?.() ?? 0);
|
|
47525
48017
|
}
|
|
48018
|
+
} else if (interruptBuf && interruptBuf.state === "live" && !existing.cancel) {
|
|
48019
|
+
const stopResult = transition("live", { kind: "stop" });
|
|
48020
|
+
if (stopResult.next !== "invalid") {
|
|
48021
|
+
interruptBuf.state = stopResult.next;
|
|
48022
|
+
recordInterrupted(interruptBuf, Date.now(), deps.suspendedMs?.() ?? 0);
|
|
48023
|
+
existing.inFlight = false;
|
|
48024
|
+
pendingTurnSessions.delete(directorSessionId2);
|
|
48025
|
+
}
|
|
47526
48026
|
}
|
|
47527
48027
|
const codexClient2 = await deps.acquireCodex();
|
|
47528
48028
|
try {
|
|
@@ -47581,6 +48081,18 @@ function createBridgeMessageHandler(deps) {
|
|
|
47581
48081
|
sendForSpan({ type: "error", directorSessionId, error: "message is required." });
|
|
47582
48082
|
return;
|
|
47583
48083
|
}
|
|
48084
|
+
const busyThread = deps.threads.get(directorSessionId);
|
|
48085
|
+
if (msg.takeover !== true && (pendingTurnSessions.has(directorSessionId) || busyThread?.inFlight)) {
|
|
48086
|
+
sendForSpan({
|
|
48087
|
+
type: "error",
|
|
48088
|
+
directorSessionId,
|
|
48089
|
+
code: "busy",
|
|
48090
|
+
...busyThread?.replay ? { inFlightTurnId: busyThread.replay.turnId } : {},
|
|
48091
|
+
error: "Director session is busy."
|
|
48092
|
+
});
|
|
48093
|
+
return;
|
|
48094
|
+
}
|
|
48095
|
+
pendingTurnSessions.add(directorSessionId);
|
|
47584
48096
|
const codexClient = await deps.acquireCodex();
|
|
47585
48097
|
let thread = deps.threads.get(directorSessionId);
|
|
47586
48098
|
if (thread?.inFlight && msg.takeover === true) {
|
|
@@ -47666,6 +48178,7 @@ function createBridgeMessageHandler(deps) {
|
|
|
47666
48178
|
if (/unauthor|not logged in|login|401|auth/i.test(acquireErrMsg)) {
|
|
47667
48179
|
deps.setCodexAuth?.("expired");
|
|
47668
48180
|
}
|
|
48181
|
+
pendingTurnSessions.delete(directorSessionId);
|
|
47669
48182
|
deps.releaseCodex();
|
|
47670
48183
|
return;
|
|
47671
48184
|
}
|
|
@@ -47677,10 +48190,12 @@ function createBridgeMessageHandler(deps) {
|
|
|
47677
48190
|
...thread.replay ? { inFlightTurnId: thread.replay.turnId } : {},
|
|
47678
48191
|
error: "Director session is busy."
|
|
47679
48192
|
});
|
|
48193
|
+
pendingTurnSessions.delete(directorSessionId);
|
|
47680
48194
|
deps.releaseCodex();
|
|
47681
48195
|
return;
|
|
47682
48196
|
}
|
|
47683
48197
|
thread.inFlight = true;
|
|
48198
|
+
pendingTurnSessions.delete(directorSessionId);
|
|
47684
48199
|
thread.replay = createReplayBuffer({
|
|
47685
48200
|
turnId: (0, import_node_crypto7.randomUUID)(),
|
|
47686
48201
|
spanId,
|
|
@@ -47689,6 +48204,7 @@ function createBridgeMessageHandler(deps) {
|
|
|
47689
48204
|
});
|
|
47690
48205
|
thread.lastUsedAt = Date.now();
|
|
47691
48206
|
const threadId = thread.threadId;
|
|
48207
|
+
const currentReplayBuf = thread.replay;
|
|
47692
48208
|
sendForSpan({
|
|
47693
48209
|
type: "turn-started",
|
|
47694
48210
|
directorSessionId,
|
|
@@ -47730,6 +48246,15 @@ function createBridgeMessageHandler(deps) {
|
|
|
47730
48246
|
};
|
|
47731
48247
|
try {
|
|
47732
48248
|
await deps.runExclusive(async () => {
|
|
48249
|
+
if (currentReplayBuf.state === "interrupted" || currentReplayBuf.state === "completed") {
|
|
48250
|
+
logCtx?.logger.logOut({
|
|
48251
|
+
spanId,
|
|
48252
|
+
sessionId: directorSessionId,
|
|
48253
|
+
type: "<queued-turn-cancelled-drained>",
|
|
48254
|
+
payload: { sessionId: directorSessionId, turnId: currentReplayBuf.turnId }
|
|
48255
|
+
});
|
|
48256
|
+
return;
|
|
48257
|
+
}
|
|
47733
48258
|
let resolveTurn = null;
|
|
47734
48259
|
let rejectTurn = null;
|
|
47735
48260
|
const turnFinished = new Promise((resolve, reject) => {
|
|
@@ -47836,6 +48361,7 @@ ${effectiveText}` : effectiveText;
|
|
|
47836
48361
|
if (imageDataUri) {
|
|
47837
48362
|
turnInput.push({ type: "image", url: imageDataUri });
|
|
47838
48363
|
}
|
|
48364
|
+
sendInTurn({ type: "turn-executing", directorSessionId, turnId: currentReplayBuf.turnId });
|
|
47839
48365
|
await codexClient.turn.start({
|
|
47840
48366
|
threadId: thread.threadId,
|
|
47841
48367
|
input: turnInput,
|
|
@@ -47891,7 +48417,9 @@ ${effectiveText}` : effectiveText;
|
|
|
47891
48417
|
}
|
|
47892
48418
|
}
|
|
47893
48419
|
} finally {
|
|
47894
|
-
thread.
|
|
48420
|
+
if (thread.replay === currentReplayBuf) {
|
|
48421
|
+
thread.inFlight = false;
|
|
48422
|
+
}
|
|
47895
48423
|
thread.lastUsedAt = Date.now();
|
|
47896
48424
|
deps.persistThreads();
|
|
47897
48425
|
deps.releaseCodex();
|
|
@@ -47931,7 +48459,7 @@ function parseConfig() {
|
|
|
47931
48459
|
}
|
|
47932
48460
|
function scanForOtherBridges() {
|
|
47933
48461
|
try {
|
|
47934
|
-
const result = (0,
|
|
48462
|
+
const result = (0, import_node_child_process9.spawnSync)("pgrep", ["-f", "mrrlin-mcp director-bridge"], {
|
|
47935
48463
|
encoding: "utf8"
|
|
47936
48464
|
});
|
|
47937
48465
|
if (result.status !== 0) return [];
|
|
@@ -47984,6 +48512,7 @@ async function startDirectorBridge() {
|
|
|
47984
48512
|
);
|
|
47985
48513
|
}
|
|
47986
48514
|
});
|
|
48515
|
+
const codexProbeHandle = startCodexProbe();
|
|
47987
48516
|
const consumerState = { inFlightRunId: null, lastClaimAt: null };
|
|
47988
48517
|
const health = createServiceHealth();
|
|
47989
48518
|
const apiBaseUrl = readConfiguredApiBaseUrl();
|
|
@@ -48341,6 +48870,10 @@ async function startDirectorBridge() {
|
|
|
48341
48870
|
version: PKG_VERSION,
|
|
48342
48871
|
binSha: bridgeBinShaShort,
|
|
48343
48872
|
getLatestVersion: () => selfUpdateHandle.getLatestVersion()
|
|
48873
|
+
},
|
|
48874
|
+
codex: {
|
|
48875
|
+
getState: () => codexProbeHandle.getState(),
|
|
48876
|
+
getUpdateCommand: () => codexProbeHandle.getUpdateCommand()
|
|
48344
48877
|
}
|
|
48345
48878
|
});
|
|
48346
48879
|
if (agentSecret && relayUrl) {
|
|
@@ -48512,6 +49045,16 @@ async function startDirectorBridge() {
|
|
|
48512
49045
|
"[mrrlin-mcp director-bridge] scheduler tick loop disabled (MRRLIN_BRIDGE_SCHEDULER_TICK_DISABLE=1).\n"
|
|
48513
49046
|
);
|
|
48514
49047
|
}
|
|
49048
|
+
let agentPoolLoopHandle = null;
|
|
49049
|
+
if (tokenProvider && !agentPoolLoopDisabled(process.env)) {
|
|
49050
|
+
agentPoolLoopHandle = startAgentPoolLoop({
|
|
49051
|
+
push: (snapshot) => client.putOperatorAgentPool(snapshot),
|
|
49052
|
+
onError: (err) => process.stderr.write(
|
|
49053
|
+
`[mrrlin-mcp director-bridge] agent-pool snapshot push failed: ${err instanceof Error ? err.message : String(err)}
|
|
49054
|
+
`
|
|
49055
|
+
)
|
|
49056
|
+
});
|
|
49057
|
+
}
|
|
48515
49058
|
await new Promise((resolve, reject) => {
|
|
48516
49059
|
server.on("error", (err) => {
|
|
48517
49060
|
if (err.code === "EADDRINUSE") {
|
|
@@ -48553,8 +49096,10 @@ async function startDirectorBridge() {
|
|
|
48553
49096
|
if (claimTimer) clearTimeout(claimTimer);
|
|
48554
49097
|
},
|
|
48555
49098
|
() => schedulerTickHandle?.stop(),
|
|
49099
|
+
() => agentPoolLoopHandle?.stop(),
|
|
48556
49100
|
() => codexKeepalive.dropNow(),
|
|
48557
|
-
() => selfUpdateHandle.stop()
|
|
49101
|
+
() => selfUpdateHandle.stop(),
|
|
49102
|
+
() => codexProbeHandle.stop()
|
|
48558
49103
|
],
|
|
48559
49104
|
shutdownCodex: () => codexServerManager.shutdown(),
|
|
48560
49105
|
exit: (code) => process.exit(code),
|
|
@@ -52081,7 +52626,7 @@ var import_promises3 = require("node:fs/promises");
|
|
|
52081
52626
|
var import_node_fs12 = require("node:fs");
|
|
52082
52627
|
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
52083
52628
|
var import_node_os8 = __toESM(require("node:os"), 1);
|
|
52084
|
-
var
|
|
52629
|
+
var import_node_child_process11 = require("node:child_process");
|
|
52085
52630
|
|
|
52086
52631
|
// ../../packages/wiki/dist/index.js
|
|
52087
52632
|
var import_promises2 = __toESM(require("node:fs/promises"), 1);
|
|
@@ -52518,6 +53063,278 @@ async function runCodeGate(_target, deps) {
|
|
|
52518
53063
|
}
|
|
52519
53064
|
}
|
|
52520
53065
|
|
|
53066
|
+
// src/consensus/multi-llm-head.ts
|
|
53067
|
+
var import_node_child_process10 = require("node:child_process");
|
|
53068
|
+
function errMessage3(err) {
|
|
53069
|
+
return err instanceof Error ? err.message : String(err);
|
|
53070
|
+
}
|
|
53071
|
+
function runExternalAgents(bin, args, timeoutMs, spawn3) {
|
|
53072
|
+
return new Promise((resolve) => {
|
|
53073
|
+
let settled = false;
|
|
53074
|
+
const timerRef = { id: void 0 };
|
|
53075
|
+
const finish = (r) => {
|
|
53076
|
+
if (settled) return;
|
|
53077
|
+
settled = true;
|
|
53078
|
+
if (timerRef.id) clearTimeout(timerRef.id);
|
|
53079
|
+
resolve(r);
|
|
53080
|
+
};
|
|
53081
|
+
const options = {
|
|
53082
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
53083
|
+
env: process.env,
|
|
53084
|
+
shell: false
|
|
53085
|
+
// SECURITY: prompt is an argv element; never hand it to a shell.
|
|
53086
|
+
};
|
|
53087
|
+
let child;
|
|
53088
|
+
try {
|
|
53089
|
+
child = spawn3(bin, args, options);
|
|
53090
|
+
} catch (err) {
|
|
53091
|
+
finish({ code: null, stdout: "", error: `spawn ${bin} failed: ${errMessage3(err)}` });
|
|
53092
|
+
return;
|
|
53093
|
+
}
|
|
53094
|
+
let stdout = "";
|
|
53095
|
+
timerRef.id = setTimeout(() => {
|
|
53096
|
+
child.kill("SIGKILL");
|
|
53097
|
+
finish({ code: null, stdout, error: `external-agents ${args[0]} timed out after ${timeoutMs}ms` });
|
|
53098
|
+
}, timeoutMs);
|
|
53099
|
+
child.stdout?.on("data", (chunk) => {
|
|
53100
|
+
stdout += chunk.toString();
|
|
53101
|
+
});
|
|
53102
|
+
child.stderr?.on("data", () => {
|
|
53103
|
+
});
|
|
53104
|
+
child.on("error", (err) => finish({ code: null, stdout, error: `external-agents ${args[0]} error: ${errMessage3(err)}` }));
|
|
53105
|
+
child.on("close", (code) => finish({ code, stdout }));
|
|
53106
|
+
});
|
|
53107
|
+
}
|
|
53108
|
+
async function pickPanel(input, deps) {
|
|
53109
|
+
const spawn3 = deps?.spawn ?? import_node_child_process10.spawn;
|
|
53110
|
+
const bin = deps?.externalAgentsBin ?? "external-agents";
|
|
53111
|
+
const args = [
|
|
53112
|
+
"pick",
|
|
53113
|
+
"--tier-prefer",
|
|
53114
|
+
input.tierPrefer ?? "strong",
|
|
53115
|
+
"--n",
|
|
53116
|
+
String(input.n),
|
|
53117
|
+
"--min-distinct-providers",
|
|
53118
|
+
String(input.n),
|
|
53119
|
+
"--exclude-providers",
|
|
53120
|
+
input.excludeProvider
|
|
53121
|
+
];
|
|
53122
|
+
const r = await runExternalAgents(bin, args, input.timeoutMs, spawn3);
|
|
53123
|
+
if (r.error) return { ok: false, error: r.error };
|
|
53124
|
+
if (r.code === 3) return { ok: true, agents: [] };
|
|
53125
|
+
if (r.code !== 0) return { ok: false, error: `external-agents pick exited ${r.code}` };
|
|
53126
|
+
const agents = r.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
53127
|
+
return { ok: true, agents };
|
|
53128
|
+
}
|
|
53129
|
+
async function dispatchHead(agentId, prompt, timeoutMs, deps) {
|
|
53130
|
+
const spawn3 = deps?.spawn ?? import_node_child_process10.spawn;
|
|
53131
|
+
const bin = deps?.externalAgentsBin ?? "external-agents";
|
|
53132
|
+
const args = ["dispatch", agentId, "--json", prompt];
|
|
53133
|
+
const r = await runExternalAgents(bin, args, timeoutMs, spawn3);
|
|
53134
|
+
if (r.error) return { ok: false, error: r.error };
|
|
53135
|
+
const lines2 = r.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
53136
|
+
const jsonLine = [...lines2].reverse().find((l) => l[0] === "{");
|
|
53137
|
+
if (!jsonLine) return { ok: false, error: `dispatch ${agentId}: no JSON on stdout (exit ${r.code})` };
|
|
53138
|
+
let parsed;
|
|
53139
|
+
try {
|
|
53140
|
+
parsed = JSON.parse(jsonLine);
|
|
53141
|
+
} catch (err) {
|
|
53142
|
+
return { ok: false, error: `dispatch ${agentId}: unparseable JSON (${errMessage3(err)})` };
|
|
53143
|
+
}
|
|
53144
|
+
if (parsed.outcome !== "success") {
|
|
53145
|
+
return { ok: false, error: `dispatch ${agentId}: outcome=${parsed.outcome} (exit ${parsed.exit_code})` };
|
|
53146
|
+
}
|
|
53147
|
+
if (typeof parsed.text !== "string" || parsed.text.trim() === "") {
|
|
53148
|
+
return { ok: false, error: `dispatch ${agentId}: empty text on success` };
|
|
53149
|
+
}
|
|
53150
|
+
return { ok: true, text: parsed.text };
|
|
53151
|
+
}
|
|
53152
|
+
async function buildMultiHeadRunner(input, deps) {
|
|
53153
|
+
const picked = await pickPanel(
|
|
53154
|
+
{ n: input.personas.length, excludeProvider: input.excludeProvider, tierPrefer: input.tierPrefer, timeoutMs: input.pickTimeoutMs },
|
|
53155
|
+
deps
|
|
53156
|
+
);
|
|
53157
|
+
if (!picked.ok) return { ok: false, error: picked.error };
|
|
53158
|
+
if (picked.agents.length === 0) return { ok: false, error: "pick returned no agents" };
|
|
53159
|
+
const agents = picked.agents;
|
|
53160
|
+
const assignment = {};
|
|
53161
|
+
input.personas.forEach((persona, i) => {
|
|
53162
|
+
const agent = agents[i % agents.length];
|
|
53163
|
+
if (agent) assignment[persona] = agent;
|
|
53164
|
+
});
|
|
53165
|
+
const runHead = async (persona, artifact, dismissed) => {
|
|
53166
|
+
const agentId = assignment[persona];
|
|
53167
|
+
if (!agentId) return { ok: false, error: `no agent assigned for persona ${persona}` };
|
|
53168
|
+
const instructions = input.personaInstructions[persona] ?? "";
|
|
53169
|
+
const dismissedBlock = dismissed.trim() ? `
|
|
53170
|
+
|
|
53171
|
+
${dismissed.trim()}` : "";
|
|
53172
|
+
const prompt = instructions.trim() ? `${instructions.trim()}
|
|
53173
|
+
|
|
53174
|
+
${artifact}${dismissedBlock}` : `${artifact}${dismissedBlock}`;
|
|
53175
|
+
return dispatchHead(agentId, prompt, input.dispatchTimeoutMs, deps);
|
|
53176
|
+
};
|
|
53177
|
+
return { ok: true, runner: { agents, assignment, runHead } };
|
|
53178
|
+
}
|
|
53179
|
+
|
|
53180
|
+
// src/consensus/multi-llm-shadow.ts
|
|
53181
|
+
var SHADOW_LOG_PREFIX = "__MULTI_LLM_SHADOW__";
|
|
53182
|
+
function rollUpVerdict(verdicts) {
|
|
53183
|
+
if (verdicts.includes("REJECT")) return "REJECT";
|
|
53184
|
+
if (verdicts.includes("REQUEST_CHANGES")) return "REQUEST_CHANGES";
|
|
53185
|
+
return "APPROVE";
|
|
53186
|
+
}
|
|
53187
|
+
async function runShadowPanel(input, deps) {
|
|
53188
|
+
const base = { ran: false, correlationId: input.correlationId, productionVerdict: input.productionVerdict };
|
|
53189
|
+
const built = await buildMultiHeadRunner(
|
|
53190
|
+
{
|
|
53191
|
+
personas: input.heads,
|
|
53192
|
+
personaInstructions: input.personaInstructions,
|
|
53193
|
+
excludeProvider: input.excludeProvider,
|
|
53194
|
+
tierPrefer: "strong",
|
|
53195
|
+
pickTimeoutMs: input.pickTimeoutMs,
|
|
53196
|
+
dispatchTimeoutMs: input.dispatchTimeoutMs
|
|
53197
|
+
},
|
|
53198
|
+
deps
|
|
53199
|
+
);
|
|
53200
|
+
if (!built.ok) return { ...base, reason: built.error };
|
|
53201
|
+
const verdicts = [];
|
|
53202
|
+
const perHead = await Promise.all(
|
|
53203
|
+
input.heads.map(async (persona) => {
|
|
53204
|
+
const agent = built.runner.assignment[persona] ?? "(unassigned)";
|
|
53205
|
+
try {
|
|
53206
|
+
const res = await built.runner.runHead(persona, input.artifact, "");
|
|
53207
|
+
if (res.ok) {
|
|
53208
|
+
const parsed = parseVerdict(res.text);
|
|
53209
|
+
return { persona, agent, verdict: parsed.verdict, ok: true };
|
|
53210
|
+
}
|
|
53211
|
+
} catch {
|
|
53212
|
+
}
|
|
53213
|
+
return { persona, agent, verdict: "ERROR", ok: false };
|
|
53214
|
+
})
|
|
53215
|
+
);
|
|
53216
|
+
for (const h of perHead) {
|
|
53217
|
+
if (h.ok && h.verdict !== "ERROR") verdicts.push(h.verdict);
|
|
53218
|
+
}
|
|
53219
|
+
if (verdicts.length === 0) {
|
|
53220
|
+
return { ...base, agents: built.runner.agents, perHead, reason: "no head produced a parseable verdict" };
|
|
53221
|
+
}
|
|
53222
|
+
const shadowVerdict = rollUpVerdict(verdicts);
|
|
53223
|
+
return {
|
|
53224
|
+
ran: true,
|
|
53225
|
+
correlationId: input.correlationId,
|
|
53226
|
+
agents: built.runner.agents,
|
|
53227
|
+
perHead,
|
|
53228
|
+
shadowVerdict,
|
|
53229
|
+
productionVerdict: input.productionVerdict,
|
|
53230
|
+
agrees: shadowVerdict === input.productionVerdict
|
|
53231
|
+
};
|
|
53232
|
+
}
|
|
53233
|
+
async function runShadowPanelSafe(input, deps, logger = (line) => console.error(line)) {
|
|
53234
|
+
let record2;
|
|
53235
|
+
try {
|
|
53236
|
+
record2 = await runShadowPanel(input, deps);
|
|
53237
|
+
} catch (err) {
|
|
53238
|
+
record2 = {
|
|
53239
|
+
ran: false,
|
|
53240
|
+
correlationId: input.correlationId,
|
|
53241
|
+
productionVerdict: input.productionVerdict,
|
|
53242
|
+
reason: `shadow panel threw: ${err instanceof Error ? err.message : String(err)}`
|
|
53243
|
+
};
|
|
53244
|
+
}
|
|
53245
|
+
try {
|
|
53246
|
+
logger(`${SHADOW_LOG_PREFIX} ${JSON.stringify(record2)}`);
|
|
53247
|
+
} catch {
|
|
53248
|
+
}
|
|
53249
|
+
return record2;
|
|
53250
|
+
}
|
|
53251
|
+
|
|
53252
|
+
// src/consensus/multi-llm-shadow-hook.ts
|
|
53253
|
+
var SHADOW_ENV_FLAG = "MRRLIN_CONSENSUS_MULTI_LLM_SHADOW";
|
|
53254
|
+
var ORCHESTRATOR_PROVIDER = "openai";
|
|
53255
|
+
var PICK_TIMEOUT_MS = 15e3;
|
|
53256
|
+
var DISPATCH_TIMEOUT_MS = 6e4;
|
|
53257
|
+
function productionVerdictFromOutcome(outcome) {
|
|
53258
|
+
if (outcome === "CONVERGED") return "APPROVE";
|
|
53259
|
+
if (outcome === "CONSENSUS_BLOCKED") return "REQUEST_CHANGES";
|
|
53260
|
+
return null;
|
|
53261
|
+
}
|
|
53262
|
+
async function maybeRunShadow(input, deps, logger) {
|
|
53263
|
+
const env = input.env ?? process.env;
|
|
53264
|
+
if (!env[SHADOW_ENV_FLAG]) return false;
|
|
53265
|
+
const productionVerdict = productionVerdictFromOutcome(input.outcome);
|
|
53266
|
+
if (productionVerdict === null) return false;
|
|
53267
|
+
try {
|
|
53268
|
+
const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
|
|
53269
|
+
const personaInstructions = {};
|
|
53270
|
+
for (const name of heads) {
|
|
53271
|
+
try {
|
|
53272
|
+
personaInstructions[name] = loadPersona(name);
|
|
53273
|
+
} catch {
|
|
53274
|
+
personaInstructions[name] = "";
|
|
53275
|
+
}
|
|
53276
|
+
}
|
|
53277
|
+
await runShadowPanelSafe(
|
|
53278
|
+
{
|
|
53279
|
+
heads,
|
|
53280
|
+
personaInstructions,
|
|
53281
|
+
artifact: input.artifact,
|
|
53282
|
+
productionVerdict,
|
|
53283
|
+
excludeProvider: ORCHESTRATOR_PROVIDER,
|
|
53284
|
+
pickTimeoutMs: PICK_TIMEOUT_MS,
|
|
53285
|
+
dispatchTimeoutMs: DISPATCH_TIMEOUT_MS,
|
|
53286
|
+
correlationId: input.correlationId
|
|
53287
|
+
},
|
|
53288
|
+
deps,
|
|
53289
|
+
logger
|
|
53290
|
+
);
|
|
53291
|
+
} catch {
|
|
53292
|
+
}
|
|
53293
|
+
return true;
|
|
53294
|
+
}
|
|
53295
|
+
|
|
53296
|
+
// src/consensus/multi-llm-activate.ts
|
|
53297
|
+
var MULTI_LLM_ENV_FLAG = "MRRLIN_CONSENSUS_MULTI_LLM";
|
|
53298
|
+
var ORCHESTRATOR_PROVIDER2 = "openai";
|
|
53299
|
+
var PICK_TIMEOUT_MS2 = 15e3;
|
|
53300
|
+
var DISPATCH_TIMEOUT_MS2 = 6e4;
|
|
53301
|
+
function multiLlmActive(env = process.env) {
|
|
53302
|
+
if (env["NODE_ENV"] === "production") return false;
|
|
53303
|
+
const flag = env[MULTI_LLM_ENV_FLAG];
|
|
53304
|
+
if (flag === "0" || flag === "false" || flag === "off") return false;
|
|
53305
|
+
return Boolean(flag) || env["MRRLIN_STAGING"] === "1";
|
|
53306
|
+
}
|
|
53307
|
+
async function resolveActiveRunHead(input, deps) {
|
|
53308
|
+
if (!multiLlmActive(input.env ?? process.env)) return null;
|
|
53309
|
+
try {
|
|
53310
|
+
const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
|
|
53311
|
+
if (heads.length === 0) return null;
|
|
53312
|
+
const personaInstructions = {};
|
|
53313
|
+
for (const name of heads) {
|
|
53314
|
+
try {
|
|
53315
|
+
personaInstructions[name] = loadPersona(name);
|
|
53316
|
+
} catch {
|
|
53317
|
+
personaInstructions[name] = "";
|
|
53318
|
+
}
|
|
53319
|
+
}
|
|
53320
|
+
const built = await buildMultiHeadRunner(
|
|
53321
|
+
{
|
|
53322
|
+
personas: heads,
|
|
53323
|
+
personaInstructions,
|
|
53324
|
+
excludeProvider: ORCHESTRATOR_PROVIDER2,
|
|
53325
|
+
tierPrefer: "strong",
|
|
53326
|
+
pickTimeoutMs: PICK_TIMEOUT_MS2,
|
|
53327
|
+
dispatchTimeoutMs: DISPATCH_TIMEOUT_MS2
|
|
53328
|
+
},
|
|
53329
|
+
deps
|
|
53330
|
+
);
|
|
53331
|
+
if (!built.ok) return null;
|
|
53332
|
+
return built.runner.runHead;
|
|
53333
|
+
} catch {
|
|
53334
|
+
return null;
|
|
53335
|
+
}
|
|
53336
|
+
}
|
|
53337
|
+
|
|
52521
53338
|
// src/async-tools/consensus.ts
|
|
52522
53339
|
var import_node_crypto8 = require("node:crypto");
|
|
52523
53340
|
function buildGateQuestionOptions(category) {
|
|
@@ -52581,13 +53398,37 @@ async function run(ctx, input) {
|
|
|
52581
53398
|
codexExecutable: ctx.codexExecutable,
|
|
52582
53399
|
extraHeadCount: input.extraHeads?.length ?? 0
|
|
52583
53400
|
});
|
|
53401
|
+
const activeRunHead = await resolveActiveRunHead({
|
|
53402
|
+
heads: [...allReviewHeads(), ...input.extraHeads ?? []]
|
|
53403
|
+
});
|
|
53404
|
+
if (activeRunHead) deps.runHead = activeRunHead;
|
|
52584
53405
|
const result = await runSpecGate(
|
|
52585
53406
|
{ projectSlug: input.projectSlug, specPageId: input.pageId },
|
|
52586
53407
|
deps,
|
|
52587
53408
|
input.extraHeads && input.extraHeads.length > 0 ? { extraHeads: input.extraHeads } : void 0
|
|
52588
53409
|
);
|
|
53410
|
+
if (process.env[SHADOW_ENV_FLAG]) {
|
|
53411
|
+
try {
|
|
53412
|
+
const specPage = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
53413
|
+
if (specPage) {
|
|
53414
|
+
const shadowDomain = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
53415
|
+
id: specPage.id,
|
|
53416
|
+
taskId: specPage.taskId ?? null
|
|
53417
|
+
});
|
|
53418
|
+
await maybeRunShadow({
|
|
53419
|
+
outcome: result.outcome,
|
|
53420
|
+
artifact: specPage.markdownBody,
|
|
53421
|
+
domain: shadowDomain,
|
|
53422
|
+
extraHeads: input.extraHeads,
|
|
53423
|
+
correlationId: `Specs/${input.pageId}`
|
|
53424
|
+
});
|
|
53425
|
+
}
|
|
53426
|
+
} catch {
|
|
53427
|
+
}
|
|
53428
|
+
}
|
|
52589
53429
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
52590
53430
|
const clarifications = result.clarifications ?? [];
|
|
53431
|
+
let filedQuestionCount = 0;
|
|
52591
53432
|
if (clarifications.length > 0) {
|
|
52592
53433
|
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
52593
53434
|
const taskId = page?.taskId ?? null;
|
|
@@ -52612,6 +53453,7 @@ async function run(ctx, input) {
|
|
|
52612
53453
|
} catch {
|
|
52613
53454
|
}
|
|
52614
53455
|
}
|
|
53456
|
+
filedQuestionCount = clarifications.length;
|
|
52615
53457
|
} else if (planId) {
|
|
52616
53458
|
for (const clarification of clarifications) {
|
|
52617
53459
|
const scopeId = planId;
|
|
@@ -52628,13 +53470,18 @@ async function run(ctx, input) {
|
|
|
52628
53470
|
} catch {
|
|
52629
53471
|
}
|
|
52630
53472
|
}
|
|
53473
|
+
filedQuestionCount = clarifications.length;
|
|
52631
53474
|
}
|
|
52632
53475
|
}
|
|
52633
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
|
|
52637
|
-
|
|
53476
|
+
let error51;
|
|
53477
|
+
if (filedQuestionCount > 0) {
|
|
53478
|
+
error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
|
|
53479
|
+
} else if (clarifications.length > 0) {
|
|
53480
|
+
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.`;
|
|
53481
|
+
} else {
|
|
53482
|
+
error51 = "Consensus blocked: reviewers did not converge within budget.";
|
|
53483
|
+
}
|
|
53484
|
+
return { kind: "failed", failureReason: "consensus_blocked", error: error51, filedQuestionCount };
|
|
52638
53485
|
}
|
|
52639
53486
|
switch (result.outcome) {
|
|
52640
53487
|
case "CONVERGED":
|
|
@@ -52670,6 +53517,14 @@ function onComplete2(outcome, input) {
|
|
|
52670
53517
|
body: `Consensus CONVERGED for Specs/${input.pageId}; spec_ready set. Ready to create an execution run.`
|
|
52671
53518
|
};
|
|
52672
53519
|
}
|
|
53520
|
+
if (outcome.kind === "failed" && (outcome.filedQuestionCount ?? 0) > 0) {
|
|
53521
|
+
const n = outcome.filedQuestionCount ?? 0;
|
|
53522
|
+
return {
|
|
53523
|
+
kind: "info",
|
|
53524
|
+
suppressInbox: true,
|
|
53525
|
+
body: `Consensus blocked on Specs/${input.pageId}; filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.`
|
|
53526
|
+
};
|
|
53527
|
+
}
|
|
52673
53528
|
return {
|
|
52674
53529
|
kind: "handoff",
|
|
52675
53530
|
body: `Consensus could not complete for Specs/${input.pageId}: ${outcome.error} Needs operator attention.`
|
|
@@ -53629,7 +54484,7 @@ var mcpToolDescriptions = {
|
|
|
53629
54484
|
[mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline.",
|
|
53630
54485
|
[mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
|
|
53631
54486
|
[mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
|
|
53632
|
-
[mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee.",
|
|
54487
|
+
[mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own.",
|
|
53633
54488
|
[mcpToolNames.getProject]: "Fetch a single project summary by slug.",
|
|
53634
54489
|
[mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
|
|
53635
54490
|
[mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",
|
|
@@ -54170,7 +55025,24 @@ function createMrrlinTools(options) {
|
|
|
54170
55025
|
codexExecutable: process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0,
|
|
54171
55026
|
extraHeads
|
|
54172
55027
|
});
|
|
54173
|
-
|
|
55028
|
+
const activeCodeRunHead = await resolveActiveRunHead({
|
|
55029
|
+
heads: ["code-reviewer", ...extraHeads ?? []]
|
|
55030
|
+
});
|
|
55031
|
+
if (activeCodeRunHead) deps.runHead = activeCodeRunHead;
|
|
55032
|
+
const codeGateResult = await runCodeGate({ projectSlug, baseRef }, deps);
|
|
55033
|
+
if (process.env[SHADOW_ENV_FLAG]) {
|
|
55034
|
+
try {
|
|
55035
|
+
const diff = await readDiff({ cwd: process.cwd(), baseRef, run: defaultRunCmd });
|
|
55036
|
+
await maybeRunShadow({
|
|
55037
|
+
outcome: codeGateResult.outcome,
|
|
55038
|
+
artifact: diff,
|
|
55039
|
+
heads: ["code-reviewer", ...extraHeads ?? []],
|
|
55040
|
+
correlationId: `code:${baseRef}`
|
|
55041
|
+
});
|
|
55042
|
+
} catch {
|
|
55043
|
+
}
|
|
55044
|
+
}
|
|
55045
|
+
return codeGateResult;
|
|
54174
55046
|
}
|
|
54175
55047
|
),
|
|
54176
55048
|
[mcpToolNames.listExecutionRuns]: makeTool(
|
|
@@ -54421,7 +55293,7 @@ function createMrrlinTools(options) {
|
|
|
54421
55293
|
}
|
|
54422
55294
|
let remoteUrl;
|
|
54423
55295
|
try {
|
|
54424
|
-
remoteUrl = (0,
|
|
55296
|
+
remoteUrl = (0, import_node_child_process11.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
|
|
54425
55297
|
encoding: "utf8",
|
|
54426
55298
|
stdio: ["ignore", "pipe", "ignore"],
|
|
54427
55299
|
timeout: 15e3,
|
|
@@ -54773,7 +55645,7 @@ async function pathExists3(target) {
|
|
|
54773
55645
|
return false;
|
|
54774
55646
|
}
|
|
54775
55647
|
}
|
|
54776
|
-
var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.mrrlin(?:-browser)
|
|
55648
|
+
var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.(?:mrrlin(?:-browser)?|external-agents)(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
|
|
54777
55649
|
async function uninstallCodex(options = {}) {
|
|
54778
55650
|
const codexHome = resolveCodexHome2(options);
|
|
54779
55651
|
const configPath = import_node_path18.default.join(codexHome, "config.toml");
|
|
@@ -54795,7 +55667,7 @@ async function uninstallCodex(options = {}) {
|
|
|
54795
55667
|
};
|
|
54796
55668
|
throw Object.assign(new Error(err.detail), err);
|
|
54797
55669
|
}
|
|
54798
|
-
if (parsed?.mcp_servers?.["mrrlin"] === void 0 && parsed?.mcp_servers?.["mrrlin-browser"] === void 0) {
|
|
55670
|
+
if (parsed?.mcp_servers?.["mrrlin"] === void 0 && parsed?.mcp_servers?.["mrrlin-browser"] === void 0 && parsed?.mcp_servers?.["external-agents"] === void 0) {
|
|
54799
55671
|
return { action: "noop", configPath: realPath, prompts: await removePrompts() };
|
|
54800
55672
|
}
|
|
54801
55673
|
const stripped = existing.replace(MRRLIN_BLOCK_RE, (match) => match.startsWith("\n") ? "\n" : "").replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "");
|
|
@@ -54825,13 +55697,13 @@ async function uninstallPrompts(codexHome, names) {
|
|
|
54825
55697
|
|
|
54826
55698
|
// src/bin.ts
|
|
54827
55699
|
function pm2Runner(args) {
|
|
54828
|
-
const r = (0,
|
|
55700
|
+
const r = (0, import_node_child_process12.spawnSync)("pm2", args, { encoding: "utf8" });
|
|
54829
55701
|
process.stderr.write(r.stdout ?? "");
|
|
54830
55702
|
process.stderr.write(r.stderr ?? "");
|
|
54831
55703
|
return { code: r.status ?? 1, stdout: r.stdout ?? "" };
|
|
54832
55704
|
}
|
|
54833
55705
|
function whichBin(bin) {
|
|
54834
|
-
const r = (0,
|
|
55706
|
+
const r = (0, import_node_child_process12.spawnSync)("which", [bin], { encoding: "utf8" });
|
|
54835
55707
|
const out = (r.stdout ?? "").trim();
|
|
54836
55708
|
return r.status === 0 && out ? out.split("\n")[0] ?? null : null;
|
|
54837
55709
|
}
|
|
@@ -55007,7 +55879,7 @@ async function main() {
|
|
|
55007
55879
|
}
|
|
55008
55880
|
case "browser-serve": {
|
|
55009
55881
|
const { command: command2, args, env } = browserServeArgs();
|
|
55010
|
-
const r = (0,
|
|
55882
|
+
const r = (0, import_node_child_process12.spawnSync)(command2, args, { stdio: "inherit", env: { ...process.env, ...env } });
|
|
55011
55883
|
process.exitCode = r.status ?? 1;
|
|
55012
55884
|
return;
|
|
55013
55885
|
}
|