@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.
- package/dist/bin.cjs +911 -50
- 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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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,
|
|
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"] ??
|
|
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"] ??
|
|
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"] ??
|
|
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
|
|
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,
|
|
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,
|
|
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)(
|
|
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
|
-
|
|
41702
|
+
let readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId);
|
|
41319
41703
|
if (!readiness.ok) {
|
|
41320
|
-
|
|
41321
|
-
|
|
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
|
|
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
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
|
|
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,
|
|
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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
}
|