@mrrlin-dev/mcp 0.4.10 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.cjs +656 -157
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -8166,7 +8166,7 @@ var require_graceful_fs = __commonJS({
8166
8166
  function patch(fs11) {
8167
8167
  polyfills(fs11);
8168
8168
  fs11.gracefulify = patch;
8169
- fs11.createReadStream = createReadStream;
8169
+ fs11.createReadStream = createReadStream2;
8170
8170
  fs11.createWriteStream = createWriteStream;
8171
8171
  var fs$readFile = fs11.readFile;
8172
8172
  fs11.readFile = readFile9;
@@ -8376,7 +8376,7 @@ var require_graceful_fs = __commonJS({
8376
8376
  }
8377
8377
  });
8378
8378
  }
8379
- function createReadStream(path22, options) {
8379
+ function createReadStream2(path22, options) {
8380
8380
  return new fs11.ReadStream(path22, options);
8381
8381
  }
8382
8382
  function createWriteStream(path22, options) {
@@ -20644,11 +20644,11 @@ var require_dist = __commonJS({
20644
20644
  });
20645
20645
 
20646
20646
  // src/bin.ts
20647
- var import_node_path30 = __toESM(require("node:path"), 1);
20647
+ var import_node_path31 = __toESM(require("node:path"), 1);
20648
20648
  var import_node_child_process18 = require("node:child_process");
20649
- var import_node_fs17 = require("node:fs");
20649
+ var import_node_fs18 = require("node:fs");
20650
20650
  var import_node_os15 = require("node:os");
20651
- var import_node_path31 = __toESM(require("node:path"), 1);
20651
+ var import_node_path32 = __toESM(require("node:path"), 1);
20652
20652
 
20653
20653
  // src/install-codex.ts
20654
20654
  var import_promises = __toESM(require("node:fs/promises"), 1);
@@ -21471,10 +21471,10 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
21471
21471
  }
21472
21472
 
21473
21473
  // src/director-bridge.ts
21474
- var import_node_fs16 = __toESM(require("node:fs"), 1);
21474
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
21475
21475
  var import_node_http = __toESM(require("node:http"), 1);
21476
21476
  var import_node_os11 = __toESM(require("node:os"), 1);
21477
- var import_node_path23 = __toESM(require("node:path"), 1);
21477
+ var import_node_path24 = __toESM(require("node:path"), 1);
21478
21478
  var import_node_child_process15 = require("node:child_process");
21479
21479
  var import_node_crypto9 = __toESM(require("node:crypto"), 1);
21480
21480
 
@@ -36828,6 +36828,8 @@ var openApiOperationIds = {
36828
36828
  getArtifactFile: "getArtifactFile",
36829
36829
  downloadArtifactFile: "downloadArtifactFile",
36830
36830
  listArtifactFiles: "listArtifactFiles",
36831
+ listArtifactFileExcerpts: "listArtifactFileExcerpts",
36832
+ getArtifactFileContent: "getArtifactFileContent",
36831
36833
  getOperatorSettings: "getOperatorSettings",
36832
36834
  updateOperatorSettings: "updateOperatorSettings",
36833
36835
  recordOperatorPresence: "recordOperatorPresence",
@@ -36842,7 +36844,8 @@ var openApiOperationIds = {
36842
36844
  recordSpecConsensusReview: "recordSpecConsensusReview",
36843
36845
  getSpecConsensusReview: "getSpecConsensusReview",
36844
36846
  onboardingKickoffTransition: "onboardingKickoffTransition",
36845
- ingestTelemetry: "ingestTelemetry"
36847
+ ingestTelemetry: "ingestTelemetry",
36848
+ getTelemetry: "getTelemetry"
36846
36849
  };
36847
36850
 
36848
36851
  // ../../packages/schemas/dist/session.js
@@ -37284,6 +37287,20 @@ function totalSize(items) {
37284
37287
  return items.reduce((sum, i) => sum + i.size, 0);
37285
37288
  }
37286
37289
 
37290
+ // ../../packages/schemas/dist/inboxProposal.js
37291
+ function isRunnableProposal(item) {
37292
+ if (item.kind !== "proposal") {
37293
+ return false;
37294
+ }
37295
+ if (item.actionPayload === null || item.actionPayload === void 0) {
37296
+ return false;
37297
+ }
37298
+ if (item.taskId === null || item.taskId === void 0 || item.taskId === "") {
37299
+ return false;
37300
+ }
37301
+ return true;
37302
+ }
37303
+
37287
37304
  // ../../packages/schemas/dist/index.js
37288
37305
  var mrrlinTaskStatusSchema = external_exports.enum([
37289
37306
  "backlog",
@@ -38460,6 +38477,13 @@ var artifactFileCreateResponseSchema = external_exports.object({
38460
38477
  })
38461
38478
  });
38462
38479
  var artifactFileListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinArtifactFilePublicSchema) });
38480
+ var mrrlinArtifactFileExcerptSchema = external_exports.object({
38481
+ excerpt: external_exports.string(),
38482
+ id: external_exports.string().uuid()
38483
+ });
38484
+ var mrrlinArtifactFileExcerptsResponseSchema = external_exports.object({
38485
+ data: external_exports.array(mrrlinArtifactFileExcerptSchema)
38486
+ });
38463
38487
  var artifactFileExpiredResponseSchema = external_exports.object({
38464
38488
  artifact: external_exports.object({
38465
38489
  class: mrrlinArtifactFileClassSchema,
@@ -40175,6 +40199,46 @@ function createMrrlinClient(config2) {
40175
40199
  }
40176
40200
  return response.json();
40177
40201
  }
40202
+ async function requestText(path22, init) {
40203
+ const method = init?.method ?? "GET";
40204
+ const headers = new Headers(init?.headers ?? {});
40205
+ if (token && !headers.has("authorization")) {
40206
+ headers.set("authorization", `Bearer ${token}`);
40207
+ }
40208
+ const response = await customFetch(`${baseUrl}${path22}`, { ...init, headers });
40209
+ if (!response.ok) {
40210
+ const body = await response.text();
40211
+ let code = `HTTP_${response.status}`;
40212
+ let violations = null;
40213
+ let message = `Mrrlin API ${method} ${path22} failed: ${response.status}`;
40214
+ try {
40215
+ const parsed = JSON.parse(body);
40216
+ const violationsParsed = apiViolationsErrorSchema.safeParse(parsed);
40217
+ if (violationsParsed.success) {
40218
+ code = violationsParsed.data.code;
40219
+ violations = violationsParsed.data.violations;
40220
+ message = violationsParsed.data.message;
40221
+ } else {
40222
+ const errorParsed = apiErrorSchema.safeParse(parsed);
40223
+ if (errorParsed.success) {
40224
+ code = errorParsed.data.code;
40225
+ message = errorParsed.data.message;
40226
+ }
40227
+ }
40228
+ } catch {
40229
+ }
40230
+ throw new MrrlinApiError({
40231
+ code,
40232
+ method,
40233
+ path: path22,
40234
+ responseBody: body,
40235
+ status: response.status,
40236
+ violations,
40237
+ message
40238
+ });
40239
+ }
40240
+ return response.text();
40241
+ }
40178
40242
  return {
40179
40243
  async listGithubInstallations() {
40180
40244
  const body = await request(`/github/installations`);
@@ -40894,6 +40958,14 @@ function createMrrlinClient(config2) {
40894
40958
  const body = await request(`${projectPath(projectSlug)}/artifact-files${suffix}`);
40895
40959
  return artifactFileListResponseSchema.parse(body).data;
40896
40960
  },
40961
+ async listArtifactFileExcerpts(projectSlug, taskId) {
40962
+ const params = new URLSearchParams({ taskId });
40963
+ const body = await request(`${projectPath(projectSlug)}/artifact-file-excerpts?${params.toString()}`);
40964
+ return mrrlinArtifactFileExcerptsResponseSchema.parse(body).data;
40965
+ },
40966
+ async getArtifactFileContent(projectSlug, artifactFileId) {
40967
+ return requestText(`${projectPath(projectSlug)}/artifact-files/${encodeURIComponent(artifactFileId)}/content`);
40968
+ },
40897
40969
  async getProjectSchedulerPolicy(projectSlug) {
40898
40970
  const body = await request(`${projectPath(projectSlug)}/scheduler/policy`);
40899
40971
  return projectSchedulerPolicyResponseSchema.parse(body).data;
@@ -41292,16 +41364,16 @@ function resolveBinPath(opts) {
41292
41364
  return null;
41293
41365
  }
41294
41366
  });
41295
- const existsSync4 = opts.existsSync ?? import_node_fs2.existsSync;
41367
+ const existsSync5 = opts.existsSync ?? import_node_fs2.existsSync;
41296
41368
  const resolved = whichSync(BIN_NAME);
41297
- if (resolved && existsSync4(resolved)) {
41369
+ if (resolved && existsSync5(resolved)) {
41298
41370
  return { path: resolved, source: "which" };
41299
41371
  }
41300
41372
  const envPath = (opts.env ?? process.env).PATH ?? "";
41301
41373
  for (const dir of envPath.split(import_node_path5.default.delimiter)) {
41302
41374
  if (!dir) continue;
41303
41375
  const candidate = import_node_path5.default.join(dir, BIN_NAME);
41304
- if (existsSync4(candidate)) {
41376
+ if (existsSync5(candidate)) {
41305
41377
  return { path: candidate, source: "path-walk" };
41306
41378
  }
41307
41379
  }
@@ -41551,7 +41623,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41551
41623
  }
41552
41624
 
41553
41625
  // src/_generated/version.ts
41554
- var PKG_VERSION = "0.4.10";
41626
+ var PKG_VERSION = "0.4.15";
41555
41627
 
41556
41628
  // src/api-base-url-migrate.ts
41557
41629
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -42114,6 +42186,9 @@ function startSchedulerTickLoop(deps) {
42114
42186
  // src/retention/pruner.ts
42115
42187
  var import_promises4 = require("node:fs/promises");
42116
42188
  var import_node_path9 = __toESM(require("node:path"), 1);
42189
+ function protectedDayFloor(protectedSinceMs) {
42190
+ return new Date(protectedSinceMs).toISOString().slice(0, 10);
42191
+ }
42117
42192
  async function prune(stateDir, bounds) {
42118
42193
  const logsDir = import_node_path9.default.join(stateDir, "logs");
42119
42194
  let entries;
@@ -42122,7 +42197,7 @@ async function prune(stateDir, bounds) {
42122
42197
  } catch (err) {
42123
42198
  if (err?.code === "ENOENT") {
42124
42199
  await emitPruneLog(stateDir, [], {}, 0, null);
42125
- return { daysRemoved: [], reasonByDay: {}, bytesRemoved: 0, oldestDayRemaining: null };
42200
+ return { daysRemoved: [], reasonByDay: {}, bytesRemoved: 0, oldestDayRemaining: null, budgetOvershootBytes: 0 };
42126
42201
  }
42127
42202
  throw err;
42128
42203
  }
@@ -42166,12 +42241,15 @@ async function prune(stateDir, bounds) {
42166
42241
  }
42167
42242
  return true;
42168
42243
  }
42244
+ const protectedFromDay = bounds.protectedSinceMs === void 0 ? null : protectedDayFloor(bounds.protectedSinceMs);
42245
+ const isProtected = (day) => protectedFromDay !== null && day >= protectedFromDay;
42169
42246
  const budgetBytes = bounds.budgetMb * 1024 * 1024;
42170
42247
  let totalSize2 = files.reduce((sum, f) => sum + f.size, 0);
42171
42248
  let i = 0;
42172
42249
  while (totalSize2 > budgetBytes && i < files.length) {
42173
42250
  const candidate = files[i];
42174
42251
  if (!candidate) break;
42252
+ if (isProtected(candidate.day)) break;
42175
42253
  if (!await isDaySafeToDelete(candidate.day)) {
42176
42254
  i += 1;
42177
42255
  continue;
@@ -42189,16 +42267,18 @@ async function prune(stateDir, bounds) {
42189
42267
  for (const info of files) {
42190
42268
  const fileDate = /* @__PURE__ */ new Date(`${info.day}T00:00:00Z`);
42191
42269
  const ageInDays = (now - fileDate.getTime()) / msPerDay;
42192
- if (ageInDays > bounds.ageDays && await isDaySafeToDelete(info.day)) {
42270
+ if (ageInDays > bounds.ageDays && !isProtected(info.day) && await isDaySafeToDelete(info.day)) {
42193
42271
  await (0, import_promises4.unlink)(info.fullPath);
42194
42272
  daysRemoved.push(info.day);
42195
42273
  reasonByDay[info.day] = "age";
42196
42274
  bytesRemoved += info.size;
42275
+ totalSize2 -= info.size;
42197
42276
  continue;
42198
42277
  }
42199
42278
  stillRemaining.push(info);
42200
42279
  }
42201
42280
  const oldestDayRemaining = stillRemaining.length > 0 ? stillRemaining[0]?.day ?? null : null;
42281
+ const budgetOvershootBytes = Math.max(0, totalSize2 - budgetBytes);
42202
42282
  await emitPruneLog(stateDir, daysRemoved, reasonByDay, bytesRemoved, oldestDayRemaining);
42203
42283
  if (daysRemoved.length > 0) {
42204
42284
  const sessionsPath = import_node_path9.default.join(logsDir, ".sessions.json");
@@ -42220,7 +42300,7 @@ async function prune(stateDir, bounds) {
42220
42300
  await (0, import_promises4.rename)(tmpPath, sessionsPath);
42221
42301
  }
42222
42302
  }
42223
- return { daysRemoved, reasonByDay, bytesRemoved, oldestDayRemaining };
42303
+ return { daysRemoved, reasonByDay, bytesRemoved, oldestDayRemaining, budgetOvershootBytes };
42224
42304
  }
42225
42305
  async function emitPruneLog(stateDir, daysRemoved, reasonByDay, bytesRemoved, oldestDayRemaining) {
42226
42306
  const logsDir = import_node_path9.default.join(stateDir, "logs");
@@ -44029,50 +44109,102 @@ async function uploadPendingDays(stateDir, client, bridgeHealth, gateCtx) {
44029
44109
  const fullUnprocessed = pending.map((p) => ({ projectSlug: p.projectSlug, day: p.day }));
44030
44110
  pending.sort((a, b) => a.day.localeCompare(b.day));
44031
44111
  for (const cur of pending) {
44032
- let raw;
44112
+ const failurePath = (0, import_node_path12.join)(telemetryDir, `.failed-${cur.projectSlug}-${cur.day}.json`);
44113
+ let failureState = null;
44033
44114
  try {
44034
- raw = await (0, import_promises6.readFile)(cur.rowsPath, "utf8");
44035
- } catch (err) {
44036
- console.error(`uploadPendingDays: failed to read ${cur.rowsPath}`, err);
44037
- continue;
44038
- }
44039
- const rows = [];
44040
- for (const line of raw.split("\n")) {
44041
- if (!line.trim()) continue;
44042
- try {
44043
- rows.push({ ...JSON.parse(line), gateMarker: "gated" });
44044
- } catch {
44045
- }
44115
+ const data = await (0, import_promises6.readFile)(failurePath, "utf8");
44116
+ failureState = JSON.parse(data);
44117
+ } catch {
44118
+ failureState = null;
44046
44119
  }
44047
- const batch = {
44048
- day: cur.day,
44049
- bridgeVersion: PKG_VERSION,
44050
- knownCrashCount,
44051
- unprocessedPartitions: fullUnprocessed.filter(
44052
- (p) => !(p.projectSlug === cur.projectSlug && p.day === cur.day)
44053
- ),
44054
- rows
44055
- };
44056
- const gateResult = await gate(batch, [], gateCtx);
44057
- const gateMarker = gateResult.gated ? "gated" : "projection-only";
44058
- for (const row of gateResult.artifact.rows) row.gateMarker = gateMarker;
44059
- const targetProject = cur.projectSlug === "_unattributed" ? process.env.MRRLIN_TELEMETRY_DEFAULT_PROJECT : cur.projectSlug;
44060
- if (!targetProject) {
44061
- console.error(`uploadPendingDays: no target project resolved for _unattributed/${cur.day}`);
44120
+ const now = Date.now();
44121
+ if (failureState && failureState.nextAttemptAt > now) {
44062
44122
  continue;
44063
44123
  }
44124
+ let success2 = false;
44064
44125
  try {
44126
+ let raw;
44127
+ try {
44128
+ raw = await (0, import_promises6.readFile)(cur.rowsPath, "utf8");
44129
+ } catch (err) {
44130
+ console.error(`uploadPendingDays: failed to read ${cur.rowsPath}`, err);
44131
+ throw err;
44132
+ }
44133
+ const rows = [];
44134
+ for (const line of raw.split("\n")) {
44135
+ if (!line.trim()) continue;
44136
+ try {
44137
+ rows.push({ ...JSON.parse(line), gateMarker: "gated" });
44138
+ } catch {
44139
+ }
44140
+ }
44141
+ const batch = {
44142
+ day: cur.day,
44143
+ bridgeVersion: PKG_VERSION,
44144
+ knownCrashCount,
44145
+ unprocessedPartitions: fullUnprocessed.filter(
44146
+ (p) => !(p.projectSlug === cur.projectSlug && p.day === cur.day)
44147
+ ),
44148
+ rows
44149
+ };
44150
+ const gateResult = await gate(batch, [], gateCtx);
44151
+ const gateMarker = gateResult.gated ? "gated" : "projection-only";
44152
+ for (const row of gateResult.artifact.rows) row.gateMarker = gateMarker;
44153
+ const targetProject = cur.projectSlug === "_unattributed" ? process.env.MRRLIN_TELEMETRY_DEFAULT_PROJECT : cur.projectSlug;
44154
+ if (!targetProject) {
44155
+ console.error(`uploadPendingDays: no target project resolved for _unattributed/${cur.day}`);
44156
+ continue;
44157
+ }
44065
44158
  await client.postTelemetry(targetProject, gateResult.artifact);
44066
- } catch (err) {
44067
- console.error(`uploadPendingDays: postTelemetry failed for ${cur.projectSlug}/${cur.day}`, err);
44068
- continue;
44159
+ success2 = true;
44160
+ } catch {
44161
+ success2 = false;
44069
44162
  }
44070
- const tmpMarker = `${cur.markerPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
44071
- try {
44072
- await (0, import_promises6.writeFile)(tmpMarker, "");
44073
- await (0, import_promises6.rename)(tmpMarker, cur.markerPath);
44074
- } catch (err) {
44075
- console.error(`uploadPendingDays: failed to write marker for ${cur.projectSlug}/${cur.day}`, err);
44163
+ if (success2) {
44164
+ const tmpMarker = `${cur.markerPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
44165
+ try {
44166
+ await (0, import_promises6.writeFile)(tmpMarker, "");
44167
+ await (0, import_promises6.rename)(tmpMarker, cur.markerPath);
44168
+ } catch (err) {
44169
+ console.error(`uploadPendingDays: failed to write marker for ${cur.projectSlug}/${cur.day}`, err);
44170
+ }
44171
+ if (failureState) {
44172
+ try {
44173
+ await (0, import_promises6.unlink)(failurePath);
44174
+ console.log(`uploadPendingDays: partition ${cur.projectSlug}/${cur.day} recovered after ${failureState.failureCount + 1} consecutive failures`);
44175
+ } catch (err) {
44176
+ console.error(`uploadPendingDays: failed to remove failure state for ${cur.projectSlug}/${cur.day}`, err);
44177
+ }
44178
+ }
44179
+ } else {
44180
+ let newFailureCount = 1;
44181
+ if (failureState) {
44182
+ newFailureCount = failureState.failureCount + 1;
44183
+ }
44184
+ const baseDelayMs = 36e5;
44185
+ const maxDelayMs = 864e5;
44186
+ let delayMs2 = 0;
44187
+ if (newFailureCount >= 2) {
44188
+ delayMs2 = baseDelayMs * 2 ** (newFailureCount - 2);
44189
+ if (delayMs2 > maxDelayMs) {
44190
+ delayMs2 = maxDelayMs;
44191
+ }
44192
+ }
44193
+ const nextAttemptAt = now + delayMs2;
44194
+ const newFailureState = {
44195
+ failureCount: newFailureCount,
44196
+ nextAttemptAt
44197
+ };
44198
+ const tmpFailurePath = `${failurePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
44199
+ try {
44200
+ await (0, import_promises6.writeFile)(tmpFailurePath, JSON.stringify(newFailureState));
44201
+ await (0, import_promises6.rename)(tmpFailurePath, failurePath);
44202
+ } catch (writeErr) {
44203
+ console.error(`uploadPendingDays: failed to write failure state for ${cur.projectSlug}/${cur.day}`, writeErr);
44204
+ }
44205
+ if (!failureState) {
44206
+ console.error(`uploadPendingDays: partition ${cur.projectSlug}/${cur.day} entering backoff after first failure`);
44207
+ }
44076
44208
  }
44077
44209
  }
44078
44210
  }
@@ -47878,7 +48010,7 @@ function resolveBridgeLogEnabled(env = process.env) {
47878
48010
  const raw = (env.MRRLIN_DIRECTOR_BRIDGE_LOG ?? "").trim().toLowerCase();
47879
48011
  return !(raw === "0" || raw === "false");
47880
48012
  }
47881
- function resolvePromptsEnabled(env) {
48013
+ function resolvePromptsEnabled(env = process.env) {
47882
48014
  const raw = (env.MRRLIN_DIRECTOR_BRIDGE_LOG_PROMPTS ?? "").trim().toLowerCase();
47883
48015
  return !(raw === "0" || raw === "false");
47884
48016
  }
@@ -52292,7 +52424,8 @@ function boundInboxItems(items) {
52292
52424
  kind: it.kind,
52293
52425
  status: it.status,
52294
52426
  body,
52295
- ...it.taskId ? { taskId: it.taskId } : {}
52427
+ ...it.taskId ? { taskId: it.taskId } : {},
52428
+ ...it.kind === "proposal" ? { runnable: isRunnableProposal(it) } : {}
52296
52429
  };
52297
52430
  const size = enc2.encode(JSON.stringify(projected)).length + 1;
52298
52431
  if (bytes3 + size > MAX_INBOX_PAYLOAD_BYTES && out.length > 0) break;
@@ -52780,19 +52913,179 @@ function extractVerificationSection(markdown) {
52780
52913
  return body.length > 0 ? body : null;
52781
52914
  }
52782
52915
 
52916
+ // src/director-reseed.ts
52917
+ var import_node_fs15 = require("node:fs");
52918
+ var import_node_readline = require("node:readline");
52919
+ var import_node_path22 = require("node:path");
52920
+ function estimateTokens(s) {
52921
+ return Buffer.byteLength(s, "utf8");
52922
+ }
52923
+ function dayFilesInWindow(now, windowMs) {
52924
+ const end = new Date(now.getTime());
52925
+ const start = new Date(now.getTime() - windowMs);
52926
+ const startDay = new Date(Date.UTC(
52927
+ start.getUTCFullYear(),
52928
+ start.getUTCMonth(),
52929
+ start.getUTCDate(),
52930
+ 0,
52931
+ 0,
52932
+ 0,
52933
+ 0
52934
+ ));
52935
+ const files = [];
52936
+ for (let cur = new Date(startDay); cur.getTime() <= end.getTime(); cur.setUTCDate(cur.getUTCDate() + 1)) {
52937
+ const name = `bridge-${cur.getUTCFullYear().toString().padStart(4, "0")}-${(cur.getUTCMonth() + 1).toString().padStart(2, "0")}-${cur.getUTCDate().toString().padStart(2, "0")}.jsonl`;
52938
+ files.push(name);
52939
+ }
52940
+ return files;
52941
+ }
52942
+ function isRecord3(value) {
52943
+ return typeof value === "object" && value !== null;
52944
+ }
52945
+ function extractItems(lines2, sessionIds) {
52946
+ const sessionSet = new Set(sessionIds);
52947
+ const items = [];
52948
+ let skipped2 = 0;
52949
+ for (const raw of lines2) {
52950
+ if (raw.trim() === "") {
52951
+ continue;
52952
+ }
52953
+ let parsed;
52954
+ try {
52955
+ parsed = JSON.parse(raw);
52956
+ } catch {
52957
+ skipped2++;
52958
+ continue;
52959
+ }
52960
+ if (!isRecord3(parsed)) {
52961
+ skipped2++;
52962
+ continue;
52963
+ }
52964
+ const obj = parsed;
52965
+ if (!sessionSet.has(obj.sessionId)) continue;
52966
+ const ts = obj.ts;
52967
+ const type = obj.type;
52968
+ const payload = obj.payload;
52969
+ if (type === "turn" && isRecord3(payload) && typeof payload.message === "string") {
52970
+ items.push({ ts, kind: "user", text: payload.message });
52971
+ } else if (type === "event" && isRecord3(payload) && isRecord3(payload.event) && payload.event.type === "assistant" && typeof payload.event.content === "string") {
52972
+ items.push({ ts, kind: "assistant", text: payload.event.content });
52973
+ } else if (type === "event" && isRecord3(payload) && isRecord3(payload.event) && payload.event.type === "tool_use" && isRecord3(payload.event.tool) && typeof payload.event.tool.name === "string") {
52974
+ items.push({ ts, kind: "tool", text: payload.event.tool.name });
52975
+ }
52976
+ }
52977
+ items.sort((a, b) => a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0);
52978
+ return { items, skipped: skipped2 };
52979
+ }
52980
+ function renderReseedBlock(items, opts) {
52981
+ const buildText = (kept2, droppedCount) => {
52982
+ const header = [
52983
+ "[bridge:reconstructed-history] The conversation below is a RECONSTRUCTION assembled",
52984
+ "from this bridge's own logs. It is NOT your memory: you did not run these tools in",
52985
+ "this thread, and nothing here is a live handle.",
52986
+ "- Every external session it mentions is GONE: browser tabs, logins, shell sessions,",
52987
+ " working directories, temporary files.",
52988
+ "- Tool names appear WITHOUT their arguments and WITHOUT their results, and may come",
52989
+ " from a different provider's tool surface than the one you have now. They are",
52990
+ " history, not a menu.",
52991
+ "- Before acting on anything described here, re-verify it against current state."
52992
+ ].join("\n");
52993
+ const firstKeptTs = kept2[0]?.ts;
52994
+ const truncationNotice = droppedCount > 0 && firstKeptTs !== void 0 ? `- TRUNCATED: the oldest ${droppedCount} items were dropped; this reconstruction starts at ${firstKeptTs}.` : "";
52995
+ const roleMap = {
52996
+ user: "OPERATOR",
52997
+ assistant: "DIRECTOR",
52998
+ tool: "TOOL"
52999
+ };
53000
+ const itemsText = kept2.map((it) => `[${it.ts}] ${roleMap[it.kind]}: ${it.text}`).join("\n");
53001
+ const body = [
53002
+ truncationNotice,
53003
+ "--- reconstructed conversation begins ---",
53004
+ itemsText,
53005
+ "--- reconstructed conversation ends ---",
53006
+ "The operator's actual request for THIS turn is the LAST block in this message, below",
53007
+ "any project-context or bridge-notice blocks that follow this one."
53008
+ ].filter((s) => s !== "").join("\n");
53009
+ return `${header}
53010
+ ${body}`;
53011
+ };
53012
+ if (estimateTokens(buildText(items, 0)) <= opts.maxTokens) {
53013
+ return {
53014
+ text: buildText(items, 0),
53015
+ droppedCount: 0,
53016
+ itemCount: items.length
53017
+ };
53018
+ }
53019
+ const kept = [...items];
53020
+ let dropped = 0;
53021
+ const dropOne = () => {
53022
+ const toolIdx = kept.findIndex((it) => it.kind === "tool");
53023
+ kept.splice(toolIdx >= 0 ? toolIdx : 0, 1);
53024
+ };
53025
+ while (kept.length > 0) {
53026
+ dropOne();
53027
+ dropped++;
53028
+ if (kept.length === 0) return null;
53029
+ const candidateText = buildText(kept, dropped);
53030
+ if (estimateTokens(candidateText) <= opts.maxTokens) {
53031
+ const convCount = kept.filter(
53032
+ (it) => it.kind === "user" || it.kind === "assistant"
53033
+ ).length;
53034
+ if (convCount < opts.minConversationalItems) {
53035
+ return null;
53036
+ }
53037
+ return {
53038
+ text: candidateText,
53039
+ droppedCount: dropped,
53040
+ itemCount: kept.length
53041
+ };
53042
+ }
53043
+ }
53044
+ return null;
53045
+ }
53046
+ var NO_HISTORY_BLOCK = "[bridge:no-history] No prior conversation is available for this chat \u2014 it could not be\nreconstructed. Do NOT infer what was discussed before, and do NOT assume any earlier\ntask is still in progress. If the operator's request below refers to prior context,\nsay plainly that the history is unavailable and ask them to restate what they need.";
53047
+ async function readSessionRows(logsDir, files, sessionIds) {
53048
+ const lines2 = [];
53049
+ let missingFiles = 0;
53050
+ for (const f of files) {
53051
+ const filePath = (0, import_node_path22.join)(logsDir, f);
53052
+ try {
53053
+ const stream = (0, import_node_fs15.createReadStream)(filePath, { encoding: "utf8" });
53054
+ const rl = (0, import_node_readline.createInterface)({ input: stream, crlfDelay: Infinity });
53055
+ try {
53056
+ for await (const line of rl) {
53057
+ if (line.trim() === "") continue;
53058
+ if (sessionIds && sessionIds.length > 0 && !sessionIds.some((id) => line.includes(id))) continue;
53059
+ lines2.push(line);
53060
+ }
53061
+ } finally {
53062
+ rl.close();
53063
+ stream.destroy();
53064
+ }
53065
+ } catch (e) {
53066
+ if (isRecord3(e) && e.code === "ENOENT") {
53067
+ missingFiles++;
53068
+ continue;
53069
+ }
53070
+ throw e;
53071
+ }
53072
+ }
53073
+ return { lines: lines2, missingFiles };
53074
+ }
53075
+
52783
53076
  // src/external-agents-scrub.ts
52784
- var import_node_fs15 = __toESM(require("node:fs"), 1);
53077
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
52785
53078
  var import_node_os10 = __toESM(require("node:os"), 1);
52786
- var import_node_path22 = __toESM(require("node:path"), 1);
53079
+ var import_node_path23 = __toESM(require("node:path"), 1);
52787
53080
  var REDACTION = "[REDACTED]";
52788
53081
  var SHORT_VALUE_THRESHOLD = 8;
52789
- var KEYS_ENV_PATH = () => import_node_path22.default.join(import_node_os10.default.homedir(), ".local", "state", "external-agents", "keys.env");
53082
+ var KEYS_ENV_PATH = () => import_node_path23.default.join(import_node_os10.default.homedir(), ".local", "state", "external-agents", "keys.env");
52790
53083
  var keysEnvCache = null;
52791
53084
  function readKeysEnv() {
52792
53085
  const filePath = KEYS_ENV_PATH();
52793
53086
  let mtimeMs = 0;
52794
53087
  try {
52795
- mtimeMs = import_node_fs15.default.statSync(filePath).mtimeMs;
53088
+ mtimeMs = import_node_fs16.default.statSync(filePath).mtimeMs;
52796
53089
  } catch {
52797
53090
  mtimeMs = 0;
52798
53091
  }
@@ -52801,7 +53094,7 @@ function readKeysEnv() {
52801
53094
  }
52802
53095
  const map2 = /* @__PURE__ */ new Map();
52803
53096
  try {
52804
- const content = import_node_fs15.default.readFileSync(filePath, "utf8");
53097
+ const content = import_node_fs16.default.readFileSync(filePath, "utf8");
52805
53098
  for (const rawLine of content.split(/\r?\n/)) {
52806
53099
  const line = rawLine.trim();
52807
53100
  if (line === "" || line.startsWith("#")) continue;
@@ -53219,7 +53512,7 @@ function addBridgeModel(target, model) {
53219
53512
  }
53220
53513
  function readTextIfExists(filePath) {
53221
53514
  try {
53222
- return import_node_fs16.default.readFileSync(filePath, "utf8");
53515
+ return import_node_fs17.default.readFileSync(filePath, "utf8");
53223
53516
  } catch {
53224
53517
  return null;
53225
53518
  }
@@ -53230,9 +53523,9 @@ function readKnownGptSlugsFromTomlFile(filePath) {
53230
53523
  return Array.from(raw.matchAll(/^\s*model\s*=\s*"([^"]+)"/gm)).map((match) => match[1]?.trim() ?? "").filter(isSelectableModelSlug);
53231
53524
  }
53232
53525
  function readCodexFallbackModels() {
53233
- const codexHome = process.env.CODEX_HOME?.trim() || import_node_path23.default.join(import_node_os11.default.homedir(), ".codex");
53526
+ const codexHome = process.env.CODEX_HOME?.trim() || import_node_path24.default.join(import_node_os11.default.homedir(), ".codex");
53234
53527
  const models = /* @__PURE__ */ new Map();
53235
- const cachePath = import_node_path23.default.join(codexHome, "models_cache.json");
53528
+ const cachePath = import_node_path24.default.join(codexHome, "models_cache.json");
53236
53529
  const cacheRaw = readTextIfExists(cachePath);
53237
53530
  if (cacheRaw) {
53238
53531
  try {
@@ -53282,7 +53575,7 @@ function readCodexFallbackModels() {
53282
53575
  );
53283
53576
  }
53284
53577
  }
53285
- for (const slug of readKnownGptSlugsFromTomlFile(import_node_path23.default.join(codexHome, "config.toml"))) {
53578
+ for (const slug of readKnownGptSlugsFromTomlFile(import_node_path24.default.join(codexHome, "config.toml"))) {
53286
53579
  addBridgeModel(
53287
53580
  models,
53288
53581
  mapBridgeModel({
@@ -53296,11 +53589,11 @@ function readCodexFallbackModels() {
53296
53589
  })
53297
53590
  );
53298
53591
  }
53299
- const automationsDir = import_node_path23.default.join(codexHome, "automations");
53592
+ const automationsDir = import_node_path24.default.join(codexHome, "automations");
53300
53593
  try {
53301
- for (const entry of import_node_fs16.default.readdirSync(automationsDir, { withFileTypes: true })) {
53594
+ for (const entry of import_node_fs17.default.readdirSync(automationsDir, { withFileTypes: true })) {
53302
53595
  if (!entry.isDirectory()) continue;
53303
- for (const slug of readKnownGptSlugsFromTomlFile(import_node_path23.default.join(automationsDir, entry.name, "automation.toml"))) {
53596
+ for (const slug of readKnownGptSlugsFromTomlFile(import_node_path24.default.join(automationsDir, entry.name, "automation.toml"))) {
53304
53597
  addBridgeModel(
53305
53598
  models,
53306
53599
  mapBridgeModel({
@@ -53319,10 +53612,16 @@ function readCodexFallbackModels() {
53319
53612
  }
53320
53613
  return [...models.values()];
53321
53614
  }
53615
+ function selfUpdateInboxItemId(offeredVersion) {
53616
+ if (!offeredVersion) return `II-MCPUPD-${(0, import_node_crypto9.randomUUID)()}`;
53617
+ if (/^[A-Za-z0-9._-]+$/.test(offeredVersion)) return `II-MCPUPD-${offeredVersion}`;
53618
+ const hash3 = import_node_crypto9.default.createHash("sha256").update(offeredVersion).digest("hex").slice(0, 10);
53619
+ return `II-MCPUPD-${offeredVersion.replace(/[^A-Za-z0-9._-]/g, "-")}-${hash3}`;
53620
+ }
53322
53621
  function neutralCheckoutCwd(stateDir) {
53323
- const dir = import_node_path23.default.join(stateDir, "no-checkout");
53622
+ const dir = import_node_path24.default.join(stateDir, "no-checkout");
53324
53623
  try {
53325
- import_node_fs16.default.mkdirSync(dir, { recursive: true });
53624
+ import_node_fs17.default.mkdirSync(dir, { recursive: true });
53326
53625
  } catch {
53327
53626
  }
53328
53627
  return dir;
@@ -53752,7 +54051,7 @@ async function runOrphanWorktreeSweep(client, opts) {
53752
54051
  const cutoff = Date.now() - worktreeRetentionMs();
53753
54052
  const isReapable = (worktreePath) => {
53754
54053
  try {
53755
- return import_node_fs16.default.statSync(worktreePath).mtimeMs < cutoff;
54054
+ return import_node_fs17.default.statSync(worktreePath).mtimeMs < cutoff;
53756
54055
  } catch {
53757
54056
  return false;
53758
54057
  }
@@ -53790,8 +54089,8 @@ async function runOrphanWorktreeSweep(client, opts) {
53790
54089
  }
53791
54090
  }
53792
54091
  function readStateDir() {
53793
- if (process.env.CODEX_HOME) return import_node_path23.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge");
53794
- return import_node_path23.default.join(import_node_os11.default.homedir(), ".mrrlin", "director-bridge");
54092
+ if (process.env.CODEX_HOME) return import_node_path24.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge");
54093
+ return import_node_path24.default.join(import_node_os11.default.homedir(), ".mrrlin", "director-bridge");
53795
54094
  }
53796
54095
  function readAllowedOrigins() {
53797
54096
  const raw = (process.env.MRRLIN_DIRECTOR_BRIDGE_ALLOWED_ORIGINS ?? "").trim();
@@ -54001,7 +54300,7 @@ function describeZeroOutputLimit(signal) {
54001
54300
  }
54002
54301
  function createBridgeMessageHandler(deps) {
54003
54302
  const tombstones = deps.tombstones ?? /* @__PURE__ */ new Map();
54004
- const pendingTurnSessions = /* @__PURE__ */ new Set();
54303
+ const pendingTurnSessions = deps.pendingTurnSessions ?? /* @__PURE__ */ new Set();
54005
54304
  return (socket, _request, send, logCtx) => {
54006
54305
  const boundSessions = /* @__PURE__ */ new Set();
54007
54306
  socket.on("close", () => {
@@ -54675,6 +54974,7 @@ function createBridgeMessageHandler(deps) {
54675
54974
  const desiredModel = normalizeModel(msg.model);
54676
54975
  const desiredEffort = normalizeReasoningEffort(msg.reasoningEffort);
54677
54976
  const resolvedProviderId = turnSeat.id;
54977
+ let startedFreshThread = false;
54678
54978
  try {
54679
54979
  const startParams = deps.buildStartParams(msg, desiredModel);
54680
54980
  if (!thread) {
@@ -54694,6 +54994,7 @@ function createBridgeMessageHandler(deps) {
54694
54994
  thread = newThread;
54695
54995
  deps.threads.set(directorSessionId, thread);
54696
54996
  deps.persistThreads();
54997
+ startedFreshThread = true;
54697
54998
  } else {
54698
54999
  const existingThread = thread;
54699
55000
  const providerMismatch = existingThread.provider !== null && existingThread.provider !== resolvedProviderId;
@@ -54712,16 +55013,7 @@ function createBridgeMessageHandler(deps) {
54712
55013
  }
54713
55014
  existingThread.threadId = result.threadId;
54714
55015
  existingThread.provider = resolvedProviderId;
54715
- if (!resumed) {
54716
- sendForSpan({
54717
- type: "event",
54718
- directorSessionId,
54719
- event: {
54720
- type: "error",
54721
- content: "Previous Director thread expired; started a fresh local thread for this chat."
54722
- }
54723
- });
54724
- }
55016
+ if (!resumed) startedFreshThread = true;
54725
55017
  existingThread.model = desiredModel;
54726
55018
  existingThread.reasoningEffort = desiredEffort;
54727
55019
  }
@@ -54764,6 +55056,16 @@ function createBridgeMessageHandler(deps) {
54764
55056
  directorSessionId,
54765
55057
  turnId: thread.replay.turnId
54766
55058
  });
55059
+ if (startedFreshThread) {
55060
+ sendForSpan({
55061
+ type: "event",
55062
+ directorSessionId,
55063
+ event: {
55064
+ type: "notice",
55065
+ content: "This chat's previous Director thread is gone; continuing on a fresh thread. Earlier context is reconstructed from the bridge log where possible."
55066
+ }
55067
+ });
55068
+ }
54767
55069
  const contextFocusTaskId = msg.context?.focusTaskId?.trim();
54768
55070
  const contextProjectSlug = msg.context?.projectSlug?.trim();
54769
55071
  let answeredQuestions;
@@ -54936,9 +55238,22 @@ function createBridgeMessageHandler(deps) {
54936
55238
  if (updateOffer !== null && updateOfferProjectSlug && deps.fileUpdateInboxItem) {
54937
55239
  void deps.fileUpdateInboxItem(updateOfferProjectSlug);
54938
55240
  }
54939
- const composedPrefix = updateOffer ? contextPrefix ? `${updateOffer}
54940
-
54941
- ${contextPrefix}` : updateOffer : contextPrefix;
55241
+ let reseedPrefix = null;
55242
+ if (startedFreshThread && deps.buildReseedPrefix) {
55243
+ const otherBytes = Buffer.byteLength(contextPrefix ?? "", "utf8") + Buffer.byteLength(updateOffer ?? "", "utf8") + Buffer.byteLength(effectiveText, "utf8");
55244
+ try {
55245
+ reseedPrefix = await deps.buildReseedPrefix({
55246
+ directorSessionId,
55247
+ // Client-supplied, so the elements are validated and not just the shape:
55248
+ // this array selects which sessions' log rows get replayed into a prompt.
55249
+ priorSessionIds: Array.isArray(msg.priorSessionIds) ? msg.priorSessionIds.filter((id) => typeof id === "string" && id.length > 0) : [],
55250
+ otherBytes
55251
+ });
55252
+ } catch {
55253
+ reseedPrefix = null;
55254
+ }
55255
+ }
55256
+ const composedPrefix = [reseedPrefix, contextPrefix, updateOffer].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
54942
55257
  const composed = composedPrefix ? `${composedPrefix}
54943
55258
 
54944
55259
  ${effectiveText}` : effectiveText;
@@ -55039,6 +55354,7 @@ function sweepIdleThreads(threads, opts) {
55039
55354
  const reaped = [];
55040
55355
  for (const [directorSessionId, thread] of threads.entries()) {
55041
55356
  if (thread.inFlight) continue;
55357
+ if (opts.isPending?.(directorSessionId)) continue;
55042
55358
  if (thread.replay && !shouldGc(thread.replay, opts.now, suspendedNow)) continue;
55043
55359
  if (thread.supersededReplay && !shouldGc(thread.supersededReplay, opts.now, suspendedNow)) continue;
55044
55360
  if (!shouldReapIdleThread(thread, opts.now, suspendedNow, opts.idleThreadTtlMs)) continue;
@@ -55047,6 +55363,25 @@ function sweepIdleThreads(threads, opts) {
55047
55363
  }
55048
55364
  return reaped;
55049
55365
  }
55366
+ function evictOverflowThreads(threads, opts) {
55367
+ if (threads.size <= opts.maxThreads) return [];
55368
+ const suspendedNow = opts.suspendedMs();
55369
+ const evictable = [...threads.entries()].filter(([directorSessionId, thread]) => {
55370
+ if (thread.inFlight) return false;
55371
+ if (opts.isPending?.(directorSessionId)) return false;
55372
+ if (thread.replay && !shouldGc(thread.replay, opts.now, suspendedNow)) return false;
55373
+ if (thread.supersededReplay && !shouldGc(thread.supersededReplay, opts.now, suspendedNow)) return false;
55374
+ return true;
55375
+ }).sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt);
55376
+ const overflow = threads.size - opts.maxThreads;
55377
+ const evicted = [];
55378
+ for (const [directorSessionId] of evictable) {
55379
+ if (evicted.length >= overflow) break;
55380
+ threads.delete(directorSessionId);
55381
+ evicted.push(directorSessionId);
55382
+ }
55383
+ return evicted;
55384
+ }
55050
55385
  function sweepReplayBuffers(threads, tombstones, opts) {
55051
55386
  const suspendedNow = opts.suspendedMs();
55052
55387
  let retainedBuffers = 0;
@@ -55088,8 +55423,25 @@ function parseConfig() {
55088
55423
  const token = readOrCreateBridgeToken(stateDir);
55089
55424
  const codexCwd = process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_CWD?.trim() || void 0;
55090
55425
  const codexExecutable = process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0;
55091
- const idleThreadTtlMs = Math.max(6e4, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_IDLE_TTL_SECONDS", 1800) * 1e3);
55092
- return { host, port, stateDir, allowedOrigins, token, codexCwd, codexExecutable, idleThreadTtlMs };
55426
+ const idleThreadTtlMs = Math.max(6e4, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_IDLE_TTL_SECONDS", 172800) * 1e3);
55427
+ const maxThreads = Math.max(1, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_MAX_THREADS", 128));
55428
+ const reseedMaxTokens = Math.max(1e3, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_RESEED_MAX_TOKENS", 48e3));
55429
+ const turnMaxTokens = Math.max(reseedMaxTokens, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_TURN_MAX_TOKENS", 64e3));
55430
+ const reseedMinItems = Math.max(0, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_RESEED_MIN_ITEMS", 4));
55431
+ return {
55432
+ host,
55433
+ port,
55434
+ stateDir,
55435
+ allowedOrigins,
55436
+ token,
55437
+ codexCwd,
55438
+ codexExecutable,
55439
+ idleThreadTtlMs,
55440
+ maxThreads,
55441
+ reseedMaxTokens,
55442
+ turnMaxTokens,
55443
+ reseedMinItems
55444
+ };
55093
55445
  }
55094
55446
  function scanForOtherBridges() {
55095
55447
  try {
@@ -55102,11 +55454,15 @@ function scanForOtherBridges() {
55102
55454
  return [];
55103
55455
  }
55104
55456
  }
55457
+ function checkTelemetryDefaultProject(env, warn) {
55458
+ if (env.MRRLIN_TELEMETRY_DEFAULT_PROJECT?.trim()) return;
55459
+ warn(
55460
+ "[mrrlin-mcp director-bridge] MRRLIN_TELEMETRY_DEFAULT_PROJECT is not set \u2014 telemetry for turns with no project-scoped tool call will not be uploaded. Set it in the pm2 ecosystem config to enable unattributed telemetry upload."
55461
+ );
55462
+ }
55105
55463
  async function startDirectorBridge() {
55106
55464
  const config2 = parseConfig();
55107
- if (!process.env.MRRLIN_TELEMETRY_DEFAULT_PROJECT?.trim()) {
55108
- throw new Error("MRRLIN_TELEMETRY_DEFAULT_PROJECT must be set (non-empty) at bridge startup.");
55109
- }
55465
+ checkTelemetryDefaultProject(process.env, (m) => console.error(m));
55110
55466
  const turnLock = createTurnLock();
55111
55467
  let lastTurnEndedAt = Date.now();
55112
55468
  const wrappedTurnLock = {
@@ -55243,8 +55599,8 @@ async function startDirectorBridge() {
55243
55599
  codexCwd: config2.codexCwd,
55244
55600
  codexExecutable: config2.codexExecutable,
55245
55601
  checkoutRegistry,
55246
- worktreeRoot: import_node_path23.default.join(config2.stateDir, "worktrees"),
55247
- locksDir: import_node_path23.default.join(config2.stateDir, "locks"),
55602
+ worktreeRoot: import_node_path24.default.join(config2.stateDir, "worktrees"),
55603
+ locksDir: import_node_path24.default.join(config2.stateDir, "locks"),
55248
55604
  // Wire the executor server for code-execution runs. Each run gets its own provider process
55249
55605
  // (app-server for codex, a resident CLI child for claude) at the run worktree, running
55250
55606
  // UNRESTRICTED from the real operator config (owner no-sandbox decision — auth + mrrlin MCP
@@ -55288,13 +55644,13 @@ async function startDirectorBridge() {
55288
55644
  // egressDomains is retained in the seam signature (verification-turn.ts still computes the
55289
55645
  // env host) but no longer constrains anything — the executor is unrestricted.
55290
55646
  startSandbox: async (_egressDomains, providerId) => {
55291
- const cwd = import_node_fs16.default.mkdtempSync(import_node_path23.default.join(import_node_os11.default.tmpdir(), "mrrlin-verify-"));
55647
+ const cwd = import_node_fs17.default.mkdtempSync(import_node_path24.default.join(import_node_os11.default.tmpdir(), "mrrlin-verify-"));
55292
55648
  let executor;
55293
55649
  try {
55294
55650
  executor = await startExecutorServer({ worktree: cwd, provider: providerId });
55295
55651
  } catch (error51) {
55296
55652
  try {
55297
- import_node_fs16.default.rmSync(cwd, { recursive: true, force: true });
55653
+ import_node_fs17.default.rmSync(cwd, { recursive: true, force: true });
55298
55654
  } catch {
55299
55655
  }
55300
55656
  throw error51;
@@ -55305,7 +55661,7 @@ async function startDirectorBridge() {
55305
55661
  dispose: () => {
55306
55662
  executor.dispose();
55307
55663
  try {
55308
- import_node_fs16.default.rmSync(cwd, { recursive: true, force: true });
55664
+ import_node_fs17.default.rmSync(cwd, { recursive: true, force: true });
55309
55665
  } catch {
55310
55666
  }
55311
55667
  }
@@ -55450,18 +55806,94 @@ async function startDirectorBridge() {
55450
55806
  );
55451
55807
  }
55452
55808
  };
55809
+ const pendingTurnSessions = /* @__PURE__ */ new Set();
55453
55810
  const cleanupTimer = setInterval(() => {
55811
+ const now = Date.now();
55812
+ const isPending = (directorSessionId) => pendingTurnSessions.has(directorSessionId);
55454
55813
  const reaped = sweepIdleThreads(threads, {
55455
- now: Date.now(),
55814
+ now,
55815
+ suspendedMs: () => suspensionDetector.suspendedMs(),
55816
+ idleThreadTtlMs: config2.idleThreadTtlMs,
55817
+ isPending
55818
+ });
55819
+ const evicted = evictOverflowThreads(threads, {
55820
+ now,
55456
55821
  suspendedMs: () => suspensionDetector.suspendedMs(),
55457
- idleThreadTtlMs: config2.idleThreadTtlMs
55822
+ maxThreads: config2.maxThreads,
55823
+ isPending
55458
55824
  });
55459
- if (reaped.length > 0) persistThreads();
55825
+ for (const sessionId of reaped) {
55826
+ bridgeLogger.logOut({ spanId: "gc", sessionId, type: "<thread-evicted>", payload: { sessionId, reason: "age" } });
55827
+ }
55828
+ for (const sessionId of evicted) {
55829
+ bridgeLogger.logOut({
55830
+ spanId: "gc",
55831
+ sessionId,
55832
+ type: "<thread-evicted>",
55833
+ payload: { sessionId, reason: "count", mapSize: threads.size, maxThreads: config2.maxThreads }
55834
+ });
55835
+ }
55836
+ if (reaped.length > 0 || evicted.length > 0) persistThreads();
55460
55837
  }, 6e4);
55461
55838
  cleanupTimer.unref?.();
55462
55839
  let remoteRelay = null;
55463
55840
  const relayUrl = process.env.MRRLIN_RELAY_URL?.trim();
55464
55841
  const relayConfigured = Boolean(agentSecret && relayUrl);
55842
+ const buildReseedPrefix = async (args) => {
55843
+ const refuse = (reason) => {
55844
+ bridgeLogger.logOut({
55845
+ spanId: "reseed",
55846
+ sessionId: args.directorSessionId,
55847
+ type: "<reseed>",
55848
+ payload: { sessionId: args.directorSessionId, refused: reason }
55849
+ });
55850
+ return NO_HISTORY_BLOCK;
55851
+ };
55852
+ if (!resolveBridgeLogEnabled()) return refuse("log-disabled");
55853
+ if (!resolvePromptsEnabled()) return refuse("prompts-not-logged");
55854
+ const retentionWindowMs = readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30) * 864e5;
55855
+ const reseedWindowMs = Math.min(config2.idleThreadTtlMs, retentionWindowMs);
55856
+ if (reseedWindowMs < config2.idleThreadTtlMs) {
55857
+ bridgeLogger.logOut({
55858
+ spanId: "reseed",
55859
+ sessionId: args.directorSessionId,
55860
+ type: "<reseed-window-clamped>",
55861
+ payload: {
55862
+ idleThreadTtlMs: config2.idleThreadTtlMs,
55863
+ retentionWindowMs,
55864
+ effectiveWindowMs: reseedWindowMs
55865
+ }
55866
+ });
55867
+ }
55868
+ const files = dayFilesInWindow(/* @__PURE__ */ new Date(), reseedWindowMs);
55869
+ const sessionIds = [args.directorSessionId, ...args.priorSessionIds];
55870
+ const { lines: lines2, missingFiles } = await readSessionRows(import_node_path24.default.join(config2.stateDir, "logs"), files, sessionIds);
55871
+ const { items, skipped: skipped2 } = extractItems(lines2, sessionIds);
55872
+ if (items.length === 0) return refuse("no-rows");
55873
+ const budget = Math.max(0, Math.min(config2.reseedMaxTokens, config2.turnMaxTokens - args.otherBytes));
55874
+ const block = renderReseedBlock(items, {
55875
+ maxTokens: budget,
55876
+ minConversationalItems: config2.reseedMinItems
55877
+ });
55878
+ if (!block) return refuse("below-minimum-after-truncation");
55879
+ bridgeLogger.logOut({
55880
+ spanId: "reseed",
55881
+ sessionId: args.directorSessionId,
55882
+ type: "<reseed>",
55883
+ payload: {
55884
+ sessionId: args.directorSessionId,
55885
+ priorSessionIds: args.priorSessionIds.length,
55886
+ dayFiles: files.length,
55887
+ missingFiles,
55888
+ parseSkips: skipped2,
55889
+ itemCount: block.itemCount,
55890
+ droppedCount: block.droppedCount,
55891
+ estimatedTokens: Buffer.byteLength(block.text, "utf8"),
55892
+ budget
55893
+ }
55894
+ });
55895
+ return block.text;
55896
+ };
55465
55897
  const sharedHandlerDeps = {
55466
55898
  acquireCodex: (providerId) => agentKeepalive.acquire(providerId),
55467
55899
  releaseCodex: (providerId) => agentKeepalive.release(providerId),
@@ -55490,7 +55922,7 @@ async function startDirectorBridge() {
55490
55922
  fileUpdateInboxItem: async (projectSlug) => {
55491
55923
  const body = selfUpdateHandle.updateInboxBody();
55492
55924
  if (!body) return;
55493
- const id = `II-MCPUPD-${(0, import_node_crypto9.randomUUID)()}`;
55925
+ const id = selfUpdateInboxItemId(selfUpdateHandle.getLatestVersion());
55494
55926
  try {
55495
55927
  const row = await client.createInboxItem(projectSlug, { id, kind: "info", body });
55496
55928
  process.stderr.write(
@@ -55532,13 +55964,21 @@ async function startDirectorBridge() {
55532
55964
  const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
55533
55965
  let bridgeBinShaShort = "unknown00000";
55534
55966
  try {
55535
- bridgeBinShaShort = import_node_crypto9.default.createHash("sha256").update(import_node_fs16.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
55967
+ bridgeBinShaShort = import_node_crypto9.default.createHash("sha256").update(import_node_fs17.default.readFileSync(process.argv[1] ?? "")).digest("hex").slice(0, 12);
55536
55968
  } catch {
55537
55969
  }
55538
55970
  attachBridgeWss(server, {
55539
55971
  token: config2.token,
55540
55972
  allowedOrigins: config2.allowedOrigins,
55541
- onReady: createBridgeMessageHandler({ ...sharedHandlerDeps, threads, persistThreads, bridgeInstanceId, tombstones }),
55973
+ onReady: createBridgeMessageHandler({
55974
+ ...sharedHandlerDeps,
55975
+ threads,
55976
+ persistThreads,
55977
+ bridgeInstanceId,
55978
+ tombstones,
55979
+ pendingTurnSessions,
55980
+ buildReseedPrefix
55981
+ }),
55542
55982
  logger: bridgeLogger,
55543
55983
  onPaired: () => health.setPaired(true),
55544
55984
  bridgeInstanceId,
@@ -55659,8 +56099,8 @@ async function startDirectorBridge() {
55659
56099
  );
55660
56100
  void runOrphanWorktreeSweep(client, {
55661
56101
  checkoutRegistry,
55662
- worktreeRoot: import_node_path23.default.join(config2.stateDir, "worktrees"),
55663
- locksDir: import_node_path23.default.join(config2.stateDir, "locks")
56102
+ worktreeRoot: import_node_path24.default.join(config2.stateDir, "worktrees"),
56103
+ locksDir: import_node_path24.default.join(config2.stateDir, "locks")
55664
56104
  });
55665
56105
  {
55666
56106
  const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
@@ -55710,10 +56150,24 @@ async function startDirectorBridge() {
55710
56150
  codexExecutable: config2.codexExecutable,
55711
56151
  timeoutMs: readEnvInt("MRRLIN_TELEMETRY_GATE_TIMEOUT_SECONDS", 60) * 1e3
55712
56152
  });
55713
- await prune(config2.stateDir, {
56153
+ const pruneResult = await prune(config2.stateDir, {
55714
56154
  ageDays: readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30),
55715
- budgetMb: readEnvInt("MRRLIN_LOG_RETENTION_BUDGET_MB", 1024)
56155
+ budgetMb: readEnvInt("MRRLIN_LOG_RETENTION_BUDGET_MB", 1024),
56156
+ // The reconstruction window's day-files are off limits. Derived from the thread
56157
+ // TTL so the two numbers cannot drift apart.
56158
+ protectedSinceMs: Date.now() - config2.idleThreadTtlMs
55716
56159
  });
56160
+ if (pruneResult.budgetOvershootBytes > 0) {
56161
+ bridgeLogger.logOut({
56162
+ spanId: "gc",
56163
+ sessionId: null,
56164
+ type: "<log-budget-overshoot>",
56165
+ payload: {
56166
+ overshootBytes: pruneResult.budgetOvershootBytes,
56167
+ reason: "protected re-seed window not pruned"
56168
+ }
56169
+ });
56170
+ }
55717
56171
  } catch (error51) {
55718
56172
  bridgeLogger.logOut({
55719
56173
  spanId: "gc",
@@ -55965,15 +56419,15 @@ function readDispatchBody(req) {
55965
56419
  function readOrCreateBridgeToken(stateDir) {
55966
56420
  const explicit = (process.env.MRRLIN_DIRECTOR_BRIDGE_TOKEN ?? "").trim();
55967
56421
  if (explicit) return explicit;
55968
- const tokenPath = import_node_path23.default.join(stateDir, "token.txt");
56422
+ const tokenPath = import_node_path24.default.join(stateDir, "token.txt");
55969
56423
  try {
55970
- const existing = import_node_fs16.default.readFileSync(tokenPath, "utf8").trim();
56424
+ const existing = import_node_fs17.default.readFileSync(tokenPath, "utf8").trim();
55971
56425
  if (existing) return existing;
55972
56426
  } catch {
55973
56427
  }
55974
- import_node_fs16.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
56428
+ import_node_fs17.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
55975
56429
  const token = import_node_crypto9.default.randomBytes(32).toString("base64url");
55976
- import_node_fs16.default.writeFileSync(tokenPath, `${token}
56430
+ import_node_fs17.default.writeFileSync(tokenPath, `${token}
55977
56431
  `, { mode: 384 });
55978
56432
  return token;
55979
56433
  }
@@ -59359,15 +59813,15 @@ var StdioServerTransport = class {
59359
59813
  // src/tools.ts
59360
59814
  var import_node_crypto12 = require("node:crypto");
59361
59815
  var import_promises10 = require("node:fs/promises");
59362
- var import_node_path26 = __toESM(require("node:path"), 1);
59816
+ var import_node_path27 = __toESM(require("node:path"), 1);
59363
59817
  var import_node_os12 = __toESM(require("node:os"), 1);
59364
59818
 
59365
59819
  // ../../packages/wiki/dist/index.js
59366
59820
  var import_promises8 = __toESM(require("node:fs/promises"), 1);
59367
- var import_node_path24 = __toESM(require("node:path"), 1);
59821
+ var import_node_path25 = __toESM(require("node:path"), 1);
59368
59822
  var INCLUDED_DOC_FOLDERS = /* @__PURE__ */ new Set(["_meta", "explanation", "how-to", "reference", "sdd", "tutorials"]);
59369
59823
  function toPosixPath(input) {
59370
- return input.split(import_node_path24.default.sep).join("/");
59824
+ return input.split(import_node_path25.default.sep).join("/");
59371
59825
  }
59372
59826
  function slugifyWikiSegment(input) {
59373
59827
  return input.trim().toLowerCase().replace(/`/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -59381,12 +59835,12 @@ async function pathExists3(input) {
59381
59835
  }
59382
59836
  }
59383
59837
  async function findWorkspaceRoot(start = process.cwd()) {
59384
- let current2 = import_node_path24.default.resolve(start);
59838
+ let current2 = import_node_path25.default.resolve(start);
59385
59839
  while (true) {
59386
- if (await pathExists3(import_node_path24.default.join(current2, "pnpm-workspace.yaml")) && await pathExists3(import_node_path24.default.join(current2, "docs"))) {
59840
+ if (await pathExists3(import_node_path25.default.join(current2, "pnpm-workspace.yaml")) && await pathExists3(import_node_path25.default.join(current2, "docs"))) {
59387
59841
  return current2;
59388
59842
  }
59389
- const parent = import_node_path24.default.dirname(current2);
59843
+ const parent = import_node_path25.default.dirname(current2);
59390
59844
  if (parent === current2) {
59391
59845
  throw new Error(`Unable to find Mrrlin workspace root from ${start}`);
59392
59846
  }
@@ -59395,28 +59849,28 @@ async function findWorkspaceRoot(start = process.cwd()) {
59395
59849
  }
59396
59850
  async function resolveDocsRoot(input) {
59397
59851
  if (input) {
59398
- return import_node_path24.default.resolve(input);
59852
+ return import_node_path25.default.resolve(input);
59399
59853
  }
59400
59854
  if (process.env.MRRLIN_DOCS_ROOT) {
59401
- return import_node_path24.default.resolve(process.env.MRRLIN_DOCS_ROOT);
59855
+ return import_node_path25.default.resolve(process.env.MRRLIN_DOCS_ROOT);
59402
59856
  }
59403
- return import_node_path24.default.join(await findWorkspaceRoot(), "docs");
59857
+ return import_node_path25.default.join(await findWorkspaceRoot(), "docs");
59404
59858
  }
59405
59859
  async function resolveIndexPath(input) {
59406
59860
  if (input) {
59407
- return import_node_path24.default.resolve(input);
59861
+ return import_node_path25.default.resolve(input);
59408
59862
  }
59409
59863
  if (process.env.MRRLIN_WIKI_INDEX_PATH) {
59410
- return import_node_path24.default.resolve(process.env.MRRLIN_WIKI_INDEX_PATH);
59864
+ return import_node_path25.default.resolve(process.env.MRRLIN_WIKI_INDEX_PATH);
59411
59865
  }
59412
59866
  const workspaceRoot = await findWorkspaceRoot();
59413
- return import_node_path24.default.join(workspaceRoot, "apps", "web", "public", "wiki-index.json");
59867
+ return import_node_path25.default.join(workspaceRoot, "apps", "web", "public", "wiki-index.json");
59414
59868
  }
59415
59869
  async function walkMarkdownFiles(root, dir = root) {
59416
59870
  const entries = await import_promises8.default.readdir(dir, { withFileTypes: true });
59417
59871
  const files = [];
59418
59872
  for (const entry of entries) {
59419
- const absolute = import_node_path24.default.join(dir, entry.name);
59873
+ const absolute = import_node_path25.default.join(dir, entry.name);
59420
59874
  if (entry.isDirectory()) {
59421
59875
  files.push(...await walkMarkdownFiles(root, absolute));
59422
59876
  continue;
@@ -59435,7 +59889,7 @@ function parseTitle(markdown, sourcePath) {
59435
59889
  return title;
59436
59890
  }
59437
59891
  function documentFromFile(docsRoot, absolutePath, markdown) {
59438
- const relativePath = toPosixPath(import_node_path24.default.relative(docsRoot, absolutePath));
59892
+ const relativePath = toPosixPath(import_node_path25.default.relative(docsRoot, absolutePath));
59439
59893
  const [section, fileName] = relativePath.split("/");
59440
59894
  if (!section || !fileName || !INCLUDED_DOC_FOLDERS.has(section)) {
59441
59895
  return null;
@@ -60941,17 +61395,17 @@ var intentCheckDescriptor = createIntentCheckDescriptor();
60941
61395
 
60942
61396
  // src/evidence/assembler.ts
60943
61397
  var import_promises9 = require("node:fs/promises");
60944
- var import_node_path25 = require("node:path");
61398
+ var import_node_path26 = require("node:path");
60945
61399
  function evidenceDedupKey(sessionId, reportId) {
60946
61400
  return `${sessionId}:${reportId}`;
60947
61401
  }
60948
- function isRecord3(v) {
61402
+ function isRecord4(v) {
60949
61403
  return v !== null && typeof v === "object";
60950
61404
  }
60951
61405
  async function readDayLines(stateDir, day, sessionId) {
60952
61406
  let raw;
60953
61407
  try {
60954
- raw = await (0, import_promises9.readFile)((0, import_node_path25.join)(stateDir, "logs", `bridge-${day}.jsonl`), "utf8");
61408
+ raw = await (0, import_promises9.readFile)((0, import_node_path26.join)(stateDir, "logs", `bridge-${day}.jsonl`), "utf8");
60955
61409
  } catch {
60956
61410
  return [];
60957
61411
  }
@@ -60964,7 +61418,7 @@ async function readDayLines(stateDir, day, sessionId) {
60964
61418
  } catch {
60965
61419
  continue;
60966
61420
  }
60967
- if (!isRecord3(rec) || !isRecord3(rec.payload)) continue;
61421
+ if (!isRecord4(rec) || !isRecord4(rec.payload)) continue;
60968
61422
  if (rec.payload.directorSessionId !== sessionId) continue;
60969
61423
  kept.push(rec);
60970
61424
  }
@@ -61025,7 +61479,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
61025
61479
  }
61026
61480
  let windowPruned = false;
61027
61481
  try {
61028
- const raw = await (0, import_promises9.readFile)((0, import_node_path25.join)(stateDir, "logs", ".pruned.jsonl"), "utf8");
61482
+ const raw = await (0, import_promises9.readFile)((0, import_node_path26.join)(stateDir, "logs", ".pruned.jsonl"), "utf8");
61029
61483
  outer: for (const line of raw.split("\n")) {
61030
61484
  if (!line.trim()) continue;
61031
61485
  let rec;
@@ -61034,7 +61488,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
61034
61488
  } catch {
61035
61489
  continue;
61036
61490
  }
61037
- if (!isRecord3(rec) || !Array.isArray(rec.daysRemoved)) continue;
61491
+ if (!isRecord4(rec) || !Array.isArray(rec.daysRemoved)) continue;
61038
61492
  for (const removedDay of rec.daysRemoved) {
61039
61493
  if (daysRead.includes(removedDay)) {
61040
61494
  windowPruned = true;
@@ -62758,7 +63212,7 @@ function createMrrlinTools(options) {
62758
63212
  "ARTIFACT_FILE_UPLOAD_FAILED",
62759
63213
  "Unable to upload artifact file.",
62760
63214
  async (c, { projectSlug, path: filePath, class: artifactClass, taskId, runId, description }) => {
62761
- const extension2 = import_node_path26.default.extname(filePath).toLowerCase();
63215
+ const extension2 = import_node_path27.default.extname(filePath).toLowerCase();
62762
63216
  const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
62763
63217
  if (!contentType) {
62764
63218
  throw new McpToolCodedError(
@@ -62786,7 +63240,7 @@ function createMrrlinTools(options) {
62786
63240
  "Artifact exceeds 5MB; split or summarize instead."
62787
63241
  );
62788
63242
  }
62789
- const filename = import_node_path26.default.basename(filePath);
63243
+ const filename = import_node_path27.default.basename(filePath);
62790
63244
  return uploadArtifactBytes(c, {
62791
63245
  bytes: bytes3,
62792
63246
  class: artifactClass,
@@ -62811,7 +63265,7 @@ function createMrrlinTools(options) {
62811
63265
  `Invalid filename '${filename}'; provide a bare filename with no path separators or '..'.`
62812
63266
  );
62813
63267
  }
62814
- const extension2 = import_node_path26.default.extname(filename).toLowerCase();
63268
+ const extension2 = import_node_path27.default.extname(filename).toLowerCase();
62815
63269
  const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
62816
63270
  if (!contentType) {
62817
63271
  throw new McpToolCodedError(
@@ -62878,7 +63332,7 @@ function createMrrlinTools(options) {
62878
63332
  "LOCAL_CHECKOUT_REGISTER_FAILED",
62879
63333
  "Unable to register local checkout.",
62880
63334
  async (_c, { projectSlug, path: checkoutPath, repo }) => {
62881
- const stateDir = process.env.CODEX_HOME ? import_node_path26.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge") : import_node_path26.default.join(import_node_os12.default.homedir(), ".mrrlin", "director-bridge");
63335
+ const stateDir = process.env.CODEX_HOME ? import_node_path27.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge") : import_node_path27.default.join(import_node_os12.default.homedir(), ".mrrlin", "director-bridge");
62882
63336
  const registry3 = new CheckoutRegistry(stateDir);
62883
63337
  const result = registerCheckout(registry3, { projectSlug, path: checkoutPath, repo }, import_node_os12.default.hostname());
62884
63338
  if (!result.ok) {
@@ -63118,7 +63572,7 @@ function runSetCredential(args) {
63118
63572
 
63119
63573
  // src/install-service.ts
63120
63574
  var import_node_os13 = __toESM(require("node:os"), 1);
63121
- var import_node_path27 = __toESM(require("node:path"), 1);
63575
+ var import_node_path28 = __toESM(require("node:path"), 1);
63122
63576
  function installService(deps) {
63123
63577
  if (!deps.hasSecret()) {
63124
63578
  deps.log("No agent credential found. Issue one in Settings, then run: mrrlin-mcp set-credential '<secret>'.");
@@ -63140,7 +63594,7 @@ function installService(deps) {
63140
63594
  return { ok: false };
63141
63595
  }
63142
63596
  const resolvedBins = bins.filter((b) => b !== null);
63143
- const pathEnv = Array.from(new Set(resolvedBins.map((b) => import_node_path27.default.dirname(b)))).join(":");
63597
+ const pathEnv = Array.from(new Set(resolvedBins.map((b) => import_node_path28.default.dirname(b)))).join(":");
63144
63598
  const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim() || void 0;
63145
63599
  const staging = deps.env.MRRLIN_STAGING === "1" || !apiBaseUrl && bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL);
63146
63600
  const text = renderEcosystemConfig({
@@ -63151,7 +63605,7 @@ function installService(deps) {
63151
63605
  staging,
63152
63606
  apiBaseUrl
63153
63607
  });
63154
- const ecoPath = import_node_path27.default.join(home, ".mrrlin", "ecosystem.config.cjs");
63608
+ const ecoPath = import_node_path28.default.join(home, ".mrrlin", "ecosystem.config.cjs");
63155
63609
  deps.writeFile(ecoPath, text);
63156
63610
  const start = deps.runPm2(["startOrReload", ecoPath]);
63157
63611
  if (start.code !== 0) {
@@ -63163,8 +63617,29 @@ function installService(deps) {
63163
63617
  deps.log(`pm2 save failed (exit ${save.code}).`);
63164
63618
  return { ok: false };
63165
63619
  }
63166
- deps.log(`Service installed (${ecoPath}). For reboot persistence run the printed 'pm2 startup' line ONCE, then reboot and verify 'pm2 status' shows mrrlin-bridge online. If pm2 startup does not stick on macOS, see the runbook's launchd-plist fallback.`);
63620
+ deps.log(`Service installed (${ecoPath}).`);
63167
63621
  deps.runPm2(["startup"]);
63622
+ const hook = deps.bootHookStatus?.();
63623
+ switch (hook?.status) {
63624
+ case void 0:
63625
+ break;
63626
+ case "configured":
63627
+ deps.log(`Boot hook registered (${hook.label}); last run clean.`);
63628
+ break;
63629
+ case "registered_failing":
63630
+ deps.log(
63631
+ `Boot hook registered (${hook.label}) but FAILING - last exit code ${hook.lastExitCode}. The bridge will NOT come back after a reboot. See docs/how-to/run-director-bridge.md.`
63632
+ );
63633
+ break;
63634
+ case "not_configured":
63635
+ deps.log(
63636
+ "No boot hook registered - the bridge will NOT restart after a reboot. See docs/how-to/run-director-bridge.md."
63637
+ );
63638
+ break;
63639
+ case "unknown":
63640
+ deps.log("Boot-hook status not checked on this platform.");
63641
+ break;
63642
+ }
63168
63643
  return { ok: true };
63169
63644
  }
63170
63645
  function uninstallService(deps) {
@@ -63183,12 +63658,12 @@ function uninstallService(deps) {
63183
63658
  // src/uninstall-codex.ts
63184
63659
  var import_promises11 = __toESM(require("node:fs/promises"), 1);
63185
63660
  var import_node_os14 = __toESM(require("node:os"), 1);
63186
- var import_node_path28 = __toESM(require("node:path"), 1);
63661
+ var import_node_path29 = __toESM(require("node:path"), 1);
63187
63662
  var toml3 = __toESM(require_toml(), 1);
63188
63663
  function resolveCodexHome3(options) {
63189
63664
  if (options.codexHome) return options.codexHome;
63190
63665
  if (process.env.CODEX_HOME) return process.env.CODEX_HOME;
63191
- return import_node_path28.default.join(options.homeDir ?? import_node_os14.default.homedir(), ".codex");
63666
+ return import_node_path29.default.join(options.homeDir ?? import_node_os14.default.homedir(), ".codex");
63192
63667
  }
63193
63668
  async function pathExists4(target) {
63194
63669
  try {
@@ -63201,7 +63676,7 @@ async function pathExists4(target) {
63201
63676
  var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.(?:mrrlin(?:-browser)?|external-agents)(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
63202
63677
  async function uninstallCodex(options = {}) {
63203
63678
  const codexHome = resolveCodexHome3(options);
63204
- const configPath = import_node_path28.default.join(codexHome, "config.toml");
63679
+ const configPath = import_node_path29.default.join(codexHome, "config.toml");
63205
63680
  const removePrompts = async () => uninstallPrompts(codexHome, options.promptNames ?? []);
63206
63681
  if (!await pathExists4(configPath)) {
63207
63682
  return { action: "missing", configPath, prompts: await removePrompts() };
@@ -63229,10 +63704,10 @@ async function uninstallCodex(options = {}) {
63229
63704
  }
63230
63705
  async function uninstallPrompts(codexHome, names) {
63231
63706
  if (names.length === 0) return [];
63232
- const promptsDir = import_node_path28.default.join(codexHome, "prompts");
63707
+ const promptsDir = import_node_path29.default.join(codexHome, "prompts");
63233
63708
  const out = [];
63234
63709
  for (const name of names) {
63235
- const promptPath = import_node_path28.default.join(promptsDir, `${name}.md`);
63710
+ const promptPath = import_node_path29.default.join(promptsDir, `${name}.md`);
63236
63711
  try {
63237
63712
  await import_promises11.default.unlink(promptPath);
63238
63713
  out.push({ name, action: "removed", promptPath });
@@ -63250,7 +63725,7 @@ async function uninstallPrompts(codexHome, names) {
63250
63725
 
63251
63726
  // src/extract/command.ts
63252
63727
  var import_promises12 = require("node:fs/promises");
63253
- var import_node_path29 = require("node:path");
63728
+ var import_node_path30 = require("node:path");
63254
63729
  function parseArgs(args) {
63255
63730
  const map2 = /* @__PURE__ */ new Map();
63256
63731
  for (let i = 0; i < args.length; i++) {
@@ -63266,7 +63741,7 @@ function parseArgs(args) {
63266
63741
  }
63267
63742
  return map2;
63268
63743
  }
63269
- function isRecord4(v) {
63744
+ function isRecord5(v) {
63270
63745
  return v !== null && typeof v === "object";
63271
63746
  }
63272
63747
  async function runExtract(args, deps) {
@@ -63287,7 +63762,7 @@ Missing required flag(s): ${missing.join(
63287
63762
  const days = await findSessionDays(deps.stateDir, sessionId);
63288
63763
  const matchingLines = [];
63289
63764
  for (const day of days) {
63290
- const logPath = (0, import_node_path29.join)(deps.stateDir, "logs", `bridge-${day}.jsonl`);
63765
+ const logPath = (0, import_node_path30.join)(deps.stateDir, "logs", `bridge-${day}.jsonl`);
63291
63766
  let raw;
63292
63767
  try {
63293
63768
  raw = await (0, import_promises12.readFile)(logPath, "utf8");
@@ -63303,8 +63778,8 @@ Missing required flag(s): ${missing.join(
63303
63778
  } catch {
63304
63779
  continue;
63305
63780
  }
63306
- const payload = isRecord4(parsed) ? parsed.payload : void 0;
63307
- if (isRecord4(payload) && payload.directorSessionId === sessionId) {
63781
+ const payload = isRecord5(parsed) ? parsed.payload : void 0;
63782
+ if (isRecord5(payload) && payload.directorSessionId === sessionId) {
63308
63783
  matchingLines.push(line);
63309
63784
  }
63310
63785
  }
@@ -63383,6 +63858,29 @@ function whichBin(bin) {
63383
63858
  const out = (r.stdout ?? "").trim();
63384
63859
  return r.status === 0 && out ? out.split("\n")[0] ?? null : null;
63385
63860
  }
63861
+ function bootHookStatus() {
63862
+ if (process.platform !== "darwin") return { status: "unknown" };
63863
+ const uid = process.getuid?.();
63864
+ if (uid === void 0 || uid === 0) return { status: "unknown" };
63865
+ const home = process.env.HOME ?? (0, import_node_os15.homedir)();
63866
+ const agents = import_node_path32.default.join(home, "Library", "LaunchAgents");
63867
+ const candidates = [
63868
+ { label: "com.mrrlin.director-bridge", plist: import_node_path32.default.join(agents, "com.mrrlin.director-bridge.plist") },
63869
+ { label: "com.PM2", plist: import_node_path32.default.join(agents, `pm2.${(0, import_node_os15.userInfo)().username}.plist`) }
63870
+ ];
63871
+ for (const candidate of candidates) {
63872
+ const printed = (0, import_node_child_process18.spawnSync)("launchctl", ["print", `gui/${uid}/${candidate.label}`], { encoding: "utf8" });
63873
+ if (printed.status !== 0) continue;
63874
+ if (!(0, import_node_fs18.existsSync)(candidate.plist)) continue;
63875
+ const out = printed.stdout ?? "";
63876
+ const runs = /^\s*runs\s*=\s*(\d+)/m.exec(out);
63877
+ const exitCode = /^\s*last exit code\s*=\s*(-?\d+)/m.exec(out);
63878
+ if (!exitCode || runs?.[1] === "0") return { status: "configured", label: candidate.label };
63879
+ const code = Number(exitCode[1]);
63880
+ return code === 0 ? { status: "configured", label: candidate.label } : { status: "registered_failing", label: candidate.label, lastExitCode: code };
63881
+ }
63882
+ return { status: "not_configured" };
63883
+ }
63386
63884
  var HELP_TEXT = `mrrlin-mcp - Mrrlin Phase 1 MCP server CLI
63387
63885
 
63388
63886
  Usage:
@@ -63629,8 +64127,8 @@ async function main() {
63629
64127
  }
63630
64128
  }
63631
64129
  try {
63632
- const codexHome = process.env.CODEX_HOME ?? import_node_path31.default.join(process.env.HOME ?? (0, import_node_os15.homedir)(), ".codex");
63633
- (0, import_node_fs17.rmSync)(import_node_path31.default.join(codexHome, "prompts", "report-issue.md"), { force: true });
64130
+ const codexHome = process.env.CODEX_HOME ?? import_node_path32.default.join(process.env.HOME ?? (0, import_node_os15.homedir)(), ".codex");
64131
+ (0, import_node_fs18.rmSync)(import_node_path32.default.join(codexHome, "prompts", "report-issue.md"), { force: true });
63634
64132
  } catch {
63635
64133
  }
63636
64134
  const claudeResult = await installClaude({ binPath, force, serveEnv });
@@ -63691,12 +64189,13 @@ async function main() {
63691
64189
  env: process.env,
63692
64190
  cwd: process.cwd(),
63693
64191
  writeFile: (p, c) => {
63694
- (0, import_node_fs17.mkdirSync)(import_node_path31.default.dirname(p), { recursive: true, mode: 448 });
63695
- (0, import_node_fs17.writeFileSync)(p, c, { mode: 384 });
64192
+ (0, import_node_fs18.mkdirSync)(import_node_path32.default.dirname(p), { recursive: true, mode: 448 });
64193
+ (0, import_node_fs18.writeFileSync)(p, c, { mode: 384 });
63696
64194
  },
63697
64195
  runPm2: pm2Runner,
63698
64196
  which: whichBin,
63699
64197
  hasSecret: () => Boolean(readAgentSecret()),
64198
+ bootHookStatus,
63700
64199
  log: (m) => process.stderr.write(`[mrrlin-mcp install-service] ${m}
63701
64200
  `)
63702
64201
  }).ok;
@@ -63709,14 +64208,14 @@ async function main() {
63709
64208
  runPm2: (a) => ({ code: pm2Runner(a).code }),
63710
64209
  removeFile: (p) => {
63711
64210
  try {
63712
- (0, import_node_fs17.rmSync)(p, { force: true });
64211
+ (0, import_node_fs18.rmSync)(p, { force: true });
63713
64212
  } catch {
63714
64213
  }
63715
64214
  },
63716
64215
  purgeSecret: purge,
63717
64216
  secretPath: agentCredentialPath(),
63718
64217
  tokenPath: operatorTokenPath(),
63719
- ecoPath: import_node_path31.default.join(process.env.HOME ?? (0, import_node_os15.homedir)(), ".mrrlin", "ecosystem.config.cjs"),
64218
+ ecoPath: import_node_path32.default.join(process.env.HOME ?? (0, import_node_os15.homedir)(), ".mrrlin", "ecosystem.config.cjs"),
63720
64219
  log: (m) => process.stderr.write(`[mrrlin-mcp uninstall-service] ${m}
63721
64220
  `)
63722
64221
  });
@@ -63730,14 +64229,14 @@ async function main() {
63730
64229
  runPm2: (a) => ({ code: pm2Runner(a).code }),
63731
64230
  removeFile: (p) => {
63732
64231
  try {
63733
- (0, import_node_fs17.rmSync)(p, { force: true });
64232
+ (0, import_node_fs18.rmSync)(p, { force: true });
63734
64233
  } catch {
63735
64234
  }
63736
64235
  },
63737
64236
  purgeSecret: true,
63738
64237
  secretPath: agentCredentialPath(),
63739
64238
  tokenPath: operatorTokenPath(),
63740
- ecoPath: import_node_path31.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
64239
+ ecoPath: import_node_path32.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
63741
64240
  log
63742
64241
  });
63743
64242
  let codexOk = true;
@@ -63757,7 +64256,7 @@ async function main() {
63757
64256
  log(`codex config NOT modified: ${error51 instanceof Error ? error51.message : String(error51)}`);
63758
64257
  }
63759
64258
  try {
63760
- (0, import_node_fs17.rmSync)(import_node_path31.default.join(home, ".mrrlin"), { recursive: true, force: true });
64259
+ (0, import_node_fs18.rmSync)(import_node_path32.default.join(home, ".mrrlin"), { recursive: true, force: true });
63761
64260
  } catch {
63762
64261
  }
63763
64262
  log("removed ~/.mrrlin");
@@ -63798,7 +64297,7 @@ ${HELP_TEXT}`);
63798
64297
  }
63799
64298
  }
63800
64299
  function resolveSelfBinPath() {
63801
- return import_node_path30.default.resolve(process.argv[1] ?? process.execPath);
64300
+ return import_node_path31.default.resolve(process.argv[1] ?? process.execPath);
63802
64301
  }
63803
64302
  main().catch((error51) => {
63804
64303
  process.stderr.write(`mrrlin-mcp fatal error: ${error51 instanceof Error ? error51.message : String(error51)}