@mrrlin-dev/mcp 0.4.9 → 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 +671 -157
  2. package/package.json +5 -5
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.9";
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
  }
@@ -48339,6 +48471,16 @@ function createAgentKeepalive(opts) {
48339
48471
  }, opts.idleMs(providerId));
48340
48472
  slot.idleTimer.unref?.();
48341
48473
  },
48474
+ refreshNow: (providerId) => {
48475
+ const slot = getSlot(providerId);
48476
+ if (slot.holdCount > 0) return false;
48477
+ cancelTimer(slot);
48478
+ if (slot.managerHeld) {
48479
+ slot.managerHeld = false;
48480
+ opts.release(providerId);
48481
+ }
48482
+ return true;
48483
+ },
48342
48484
  dropNow: () => {
48343
48485
  for (const [providerId, slot] of slots) {
48344
48486
  cancelTimer(slot);
@@ -52282,7 +52424,8 @@ function boundInboxItems(items) {
52282
52424
  kind: it.kind,
52283
52425
  status: it.status,
52284
52426
  body,
52285
- ...it.taskId ? { taskId: it.taskId } : {}
52427
+ ...it.taskId ? { taskId: it.taskId } : {},
52428
+ ...it.kind === "proposal" ? { runnable: isRunnableProposal(it) } : {}
52286
52429
  };
52287
52430
  const size = enc2.encode(JSON.stringify(projected)).length + 1;
52288
52431
  if (bytes3 + size > MAX_INBOX_PAYLOAD_BYTES && out.length > 0) break;
@@ -52770,19 +52913,179 @@ function extractVerificationSection(markdown) {
52770
52913
  return body.length > 0 ? body : null;
52771
52914
  }
52772
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
+
52773
53076
  // src/external-agents-scrub.ts
52774
- var import_node_fs15 = __toESM(require("node:fs"), 1);
53077
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
52775
53078
  var import_node_os10 = __toESM(require("node:os"), 1);
52776
- var import_node_path22 = __toESM(require("node:path"), 1);
53079
+ var import_node_path23 = __toESM(require("node:path"), 1);
52777
53080
  var REDACTION = "[REDACTED]";
52778
53081
  var SHORT_VALUE_THRESHOLD = 8;
52779
- 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");
52780
53083
  var keysEnvCache = null;
52781
53084
  function readKeysEnv() {
52782
53085
  const filePath = KEYS_ENV_PATH();
52783
53086
  let mtimeMs = 0;
52784
53087
  try {
52785
- mtimeMs = import_node_fs15.default.statSync(filePath).mtimeMs;
53088
+ mtimeMs = import_node_fs16.default.statSync(filePath).mtimeMs;
52786
53089
  } catch {
52787
53090
  mtimeMs = 0;
52788
53091
  }
@@ -52791,7 +53094,7 @@ function readKeysEnv() {
52791
53094
  }
52792
53095
  const map2 = /* @__PURE__ */ new Map();
52793
53096
  try {
52794
- const content = import_node_fs15.default.readFileSync(filePath, "utf8");
53097
+ const content = import_node_fs16.default.readFileSync(filePath, "utf8");
52795
53098
  for (const rawLine of content.split(/\r?\n/)) {
52796
53099
  const line = rawLine.trim();
52797
53100
  if (line === "" || line.startsWith("#")) continue;
@@ -53209,7 +53512,7 @@ function addBridgeModel(target, model) {
53209
53512
  }
53210
53513
  function readTextIfExists(filePath) {
53211
53514
  try {
53212
- return import_node_fs16.default.readFileSync(filePath, "utf8");
53515
+ return import_node_fs17.default.readFileSync(filePath, "utf8");
53213
53516
  } catch {
53214
53517
  return null;
53215
53518
  }
@@ -53220,9 +53523,9 @@ function readKnownGptSlugsFromTomlFile(filePath) {
53220
53523
  return Array.from(raw.matchAll(/^\s*model\s*=\s*"([^"]+)"/gm)).map((match) => match[1]?.trim() ?? "").filter(isSelectableModelSlug);
53221
53524
  }
53222
53525
  function readCodexFallbackModels() {
53223
- 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");
53224
53527
  const models = /* @__PURE__ */ new Map();
53225
- const cachePath = import_node_path23.default.join(codexHome, "models_cache.json");
53528
+ const cachePath = import_node_path24.default.join(codexHome, "models_cache.json");
53226
53529
  const cacheRaw = readTextIfExists(cachePath);
53227
53530
  if (cacheRaw) {
53228
53531
  try {
@@ -53272,7 +53575,7 @@ function readCodexFallbackModels() {
53272
53575
  );
53273
53576
  }
53274
53577
  }
53275
- 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"))) {
53276
53579
  addBridgeModel(
53277
53580
  models,
53278
53581
  mapBridgeModel({
@@ -53286,11 +53589,11 @@ function readCodexFallbackModels() {
53286
53589
  })
53287
53590
  );
53288
53591
  }
53289
- const automationsDir = import_node_path23.default.join(codexHome, "automations");
53592
+ const automationsDir = import_node_path24.default.join(codexHome, "automations");
53290
53593
  try {
53291
- 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 })) {
53292
53595
  if (!entry.isDirectory()) continue;
53293
- 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"))) {
53294
53597
  addBridgeModel(
53295
53598
  models,
53296
53599
  mapBridgeModel({
@@ -53309,10 +53612,16 @@ function readCodexFallbackModels() {
53309
53612
  }
53310
53613
  return [...models.values()];
53311
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
+ }
53312
53621
  function neutralCheckoutCwd(stateDir) {
53313
- const dir = import_node_path23.default.join(stateDir, "no-checkout");
53622
+ const dir = import_node_path24.default.join(stateDir, "no-checkout");
53314
53623
  try {
53315
- import_node_fs16.default.mkdirSync(dir, { recursive: true });
53624
+ import_node_fs17.default.mkdirSync(dir, { recursive: true });
53316
53625
  } catch {
53317
53626
  }
53318
53627
  return dir;
@@ -53742,7 +54051,7 @@ async function runOrphanWorktreeSweep(client, opts) {
53742
54051
  const cutoff = Date.now() - worktreeRetentionMs();
53743
54052
  const isReapable = (worktreePath) => {
53744
54053
  try {
53745
- return import_node_fs16.default.statSync(worktreePath).mtimeMs < cutoff;
54054
+ return import_node_fs17.default.statSync(worktreePath).mtimeMs < cutoff;
53746
54055
  } catch {
53747
54056
  return false;
53748
54057
  }
@@ -53780,8 +54089,8 @@ async function runOrphanWorktreeSweep(client, opts) {
53780
54089
  }
53781
54090
  }
53782
54091
  function readStateDir() {
53783
- if (process.env.CODEX_HOME) return import_node_path23.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge");
53784
- 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");
53785
54094
  }
53786
54095
  function readAllowedOrigins() {
53787
54096
  const raw = (process.env.MRRLIN_DIRECTOR_BRIDGE_ALLOWED_ORIGINS ?? "").trim();
@@ -53991,7 +54300,7 @@ function describeZeroOutputLimit(signal) {
53991
54300
  }
53992
54301
  function createBridgeMessageHandler(deps) {
53993
54302
  const tombstones = deps.tombstones ?? /* @__PURE__ */ new Map();
53994
- const pendingTurnSessions = /* @__PURE__ */ new Set();
54303
+ const pendingTurnSessions = deps.pendingTurnSessions ?? /* @__PURE__ */ new Set();
53995
54304
  return (socket, _request, send, logCtx) => {
53996
54305
  const boundSessions = /* @__PURE__ */ new Set();
53997
54306
  socket.on("close", () => {
@@ -54503,6 +54812,10 @@ function createBridgeMessageHandler(deps) {
54503
54812
  sendForSpan({ type: "bootstrap", models, rateLimits, ...deps.bridgeInstanceId ? { bridgeInstanceId: deps.bridgeInstanceId } : {}, ...bootstrapError ? { error: bootstrapError } : {} });
54504
54813
  return;
54505
54814
  }
54815
+ if (msg.type === "refresh-codex") {
54816
+ sendForSpan({ type: "codex-refreshed", status: deps.refreshCodex?.() === false ? "busy" : "ok" });
54817
+ return;
54818
+ }
54506
54819
  if (msg.type === "resume") {
54507
54820
  handleResumeRequest(msg, sendForSpan);
54508
54821
  return;
@@ -54661,6 +54974,7 @@ function createBridgeMessageHandler(deps) {
54661
54974
  const desiredModel = normalizeModel(msg.model);
54662
54975
  const desiredEffort = normalizeReasoningEffort(msg.reasoningEffort);
54663
54976
  const resolvedProviderId = turnSeat.id;
54977
+ let startedFreshThread = false;
54664
54978
  try {
54665
54979
  const startParams = deps.buildStartParams(msg, desiredModel);
54666
54980
  if (!thread) {
@@ -54680,6 +54994,7 @@ function createBridgeMessageHandler(deps) {
54680
54994
  thread = newThread;
54681
54995
  deps.threads.set(directorSessionId, thread);
54682
54996
  deps.persistThreads();
54997
+ startedFreshThread = true;
54683
54998
  } else {
54684
54999
  const existingThread = thread;
54685
55000
  const providerMismatch = existingThread.provider !== null && existingThread.provider !== resolvedProviderId;
@@ -54698,16 +55013,7 @@ function createBridgeMessageHandler(deps) {
54698
55013
  }
54699
55014
  existingThread.threadId = result.threadId;
54700
55015
  existingThread.provider = resolvedProviderId;
54701
- if (!resumed) {
54702
- sendForSpan({
54703
- type: "event",
54704
- directorSessionId,
54705
- event: {
54706
- type: "error",
54707
- content: "Previous Director thread expired; started a fresh local thread for this chat."
54708
- }
54709
- });
54710
- }
55016
+ if (!resumed) startedFreshThread = true;
54711
55017
  existingThread.model = desiredModel;
54712
55018
  existingThread.reasoningEffort = desiredEffort;
54713
55019
  }
@@ -54750,6 +55056,16 @@ function createBridgeMessageHandler(deps) {
54750
55056
  directorSessionId,
54751
55057
  turnId: thread.replay.turnId
54752
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
+ }
54753
55069
  const contextFocusTaskId = msg.context?.focusTaskId?.trim();
54754
55070
  const contextProjectSlug = msg.context?.projectSlug?.trim();
54755
55071
  let answeredQuestions;
@@ -54922,9 +55238,22 @@ function createBridgeMessageHandler(deps) {
54922
55238
  if (updateOffer !== null && updateOfferProjectSlug && deps.fileUpdateInboxItem) {
54923
55239
  void deps.fileUpdateInboxItem(updateOfferProjectSlug);
54924
55240
  }
54925
- const composedPrefix = updateOffer ? contextPrefix ? `${updateOffer}
54926
-
54927
- ${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");
54928
55257
  const composed = composedPrefix ? `${composedPrefix}
54929
55258
 
54930
55259
  ${effectiveText}` : effectiveText;
@@ -55025,6 +55354,7 @@ function sweepIdleThreads(threads, opts) {
55025
55354
  const reaped = [];
55026
55355
  for (const [directorSessionId, thread] of threads.entries()) {
55027
55356
  if (thread.inFlight) continue;
55357
+ if (opts.isPending?.(directorSessionId)) continue;
55028
55358
  if (thread.replay && !shouldGc(thread.replay, opts.now, suspendedNow)) continue;
55029
55359
  if (thread.supersededReplay && !shouldGc(thread.supersededReplay, opts.now, suspendedNow)) continue;
55030
55360
  if (!shouldReapIdleThread(thread, opts.now, suspendedNow, opts.idleThreadTtlMs)) continue;
@@ -55033,6 +55363,25 @@ function sweepIdleThreads(threads, opts) {
55033
55363
  }
55034
55364
  return reaped;
55035
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
+ }
55036
55385
  function sweepReplayBuffers(threads, tombstones, opts) {
55037
55386
  const suspendedNow = opts.suspendedMs();
55038
55387
  let retainedBuffers = 0;
@@ -55074,8 +55423,25 @@ function parseConfig() {
55074
55423
  const token = readOrCreateBridgeToken(stateDir);
55075
55424
  const codexCwd = process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_CWD?.trim() || void 0;
55076
55425
  const codexExecutable = process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0;
55077
- const idleThreadTtlMs = Math.max(6e4, readEnvInt("MRRLIN_DIRECTOR_BRIDGE_IDLE_TTL_SECONDS", 1800) * 1e3);
55078
- 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
+ };
55079
55445
  }
55080
55446
  function scanForOtherBridges() {
55081
55447
  try {
@@ -55088,11 +55454,15 @@ function scanForOtherBridges() {
55088
55454
  return [];
55089
55455
  }
55090
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
+ }
55091
55463
  async function startDirectorBridge() {
55092
55464
  const config2 = parseConfig();
55093
- if (!process.env.MRRLIN_TELEMETRY_DEFAULT_PROJECT?.trim()) {
55094
- throw new Error("MRRLIN_TELEMETRY_DEFAULT_PROJECT must be set (non-empty) at bridge startup.");
55095
- }
55465
+ checkTelemetryDefaultProject(process.env, (m) => console.error(m));
55096
55466
  const turnLock = createTurnLock();
55097
55467
  let lastTurnEndedAt = Date.now();
55098
55468
  const wrappedTurnLock = {
@@ -55229,8 +55599,8 @@ async function startDirectorBridge() {
55229
55599
  codexCwd: config2.codexCwd,
55230
55600
  codexExecutable: config2.codexExecutable,
55231
55601
  checkoutRegistry,
55232
- worktreeRoot: import_node_path23.default.join(config2.stateDir, "worktrees"),
55233
- 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"),
55234
55604
  // Wire the executor server for code-execution runs. Each run gets its own provider process
55235
55605
  // (app-server for codex, a resident CLI child for claude) at the run worktree, running
55236
55606
  // UNRESTRICTED from the real operator config (owner no-sandbox decision — auth + mrrlin MCP
@@ -55274,13 +55644,13 @@ async function startDirectorBridge() {
55274
55644
  // egressDomains is retained in the seam signature (verification-turn.ts still computes the
55275
55645
  // env host) but no longer constrains anything — the executor is unrestricted.
55276
55646
  startSandbox: async (_egressDomains, providerId) => {
55277
- 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-"));
55278
55648
  let executor;
55279
55649
  try {
55280
55650
  executor = await startExecutorServer({ worktree: cwd, provider: providerId });
55281
55651
  } catch (error51) {
55282
55652
  try {
55283
- import_node_fs16.default.rmSync(cwd, { recursive: true, force: true });
55653
+ import_node_fs17.default.rmSync(cwd, { recursive: true, force: true });
55284
55654
  } catch {
55285
55655
  }
55286
55656
  throw error51;
@@ -55291,7 +55661,7 @@ async function startDirectorBridge() {
55291
55661
  dispose: () => {
55292
55662
  executor.dispose();
55293
55663
  try {
55294
- import_node_fs16.default.rmSync(cwd, { recursive: true, force: true });
55664
+ import_node_fs17.default.rmSync(cwd, { recursive: true, force: true });
55295
55665
  } catch {
55296
55666
  }
55297
55667
  }
@@ -55436,21 +55806,98 @@ async function startDirectorBridge() {
55436
55806
  );
55437
55807
  }
55438
55808
  };
55809
+ const pendingTurnSessions = /* @__PURE__ */ new Set();
55439
55810
  const cleanupTimer = setInterval(() => {
55811
+ const now = Date.now();
55812
+ const isPending = (directorSessionId) => pendingTurnSessions.has(directorSessionId);
55440
55813
  const reaped = sweepIdleThreads(threads, {
55441
- now: Date.now(),
55814
+ now,
55442
55815
  suspendedMs: () => suspensionDetector.suspendedMs(),
55443
- idleThreadTtlMs: config2.idleThreadTtlMs
55816
+ idleThreadTtlMs: config2.idleThreadTtlMs,
55817
+ isPending
55444
55818
  });
55445
- if (reaped.length > 0) persistThreads();
55819
+ const evicted = evictOverflowThreads(threads, {
55820
+ now,
55821
+ suspendedMs: () => suspensionDetector.suspendedMs(),
55822
+ maxThreads: config2.maxThreads,
55823
+ isPending
55824
+ });
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();
55446
55837
  }, 6e4);
55447
55838
  cleanupTimer.unref?.();
55448
55839
  let remoteRelay = null;
55449
55840
  const relayUrl = process.env.MRRLIN_RELAY_URL?.trim();
55450
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
+ };
55451
55897
  const sharedHandlerDeps = {
55452
55898
  acquireCodex: (providerId) => agentKeepalive.acquire(providerId),
55453
55899
  releaseCodex: (providerId) => agentKeepalive.release(providerId),
55900
+ refreshCodex: () => agentKeepalive.refreshNow(resolveProvider(process.env, getSelectedProvider()).id),
55454
55901
  run: wrappedTurnLock.run,
55455
55902
  suspendedMs: () => suspensionDetector.suspendedMs(),
55456
55903
  buildStartParams: (msg, desiredModel) => buildThreadStartParams(msg, config2, desiredModel),
@@ -55475,7 +55922,7 @@ async function startDirectorBridge() {
55475
55922
  fileUpdateInboxItem: async (projectSlug) => {
55476
55923
  const body = selfUpdateHandle.updateInboxBody();
55477
55924
  if (!body) return;
55478
- const id = `II-MCPUPD-${(0, import_node_crypto9.randomUUID)()}`;
55925
+ const id = selfUpdateInboxItemId(selfUpdateHandle.getLatestVersion());
55479
55926
  try {
55480
55927
  const row = await client.createInboxItem(projectSlug, { id, kind: "info", body });
55481
55928
  process.stderr.write(
@@ -55517,13 +55964,21 @@ async function startDirectorBridge() {
55517
55964
  const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
55518
55965
  let bridgeBinShaShort = "unknown00000";
55519
55966
  try {
55520
- 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);
55521
55968
  } catch {
55522
55969
  }
55523
55970
  attachBridgeWss(server, {
55524
55971
  token: config2.token,
55525
55972
  allowedOrigins: config2.allowedOrigins,
55526
- 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
+ }),
55527
55982
  logger: bridgeLogger,
55528
55983
  onPaired: () => health.setPaired(true),
55529
55984
  bridgeInstanceId,
@@ -55644,8 +56099,8 @@ async function startDirectorBridge() {
55644
56099
  );
55645
56100
  void runOrphanWorktreeSweep(client, {
55646
56101
  checkoutRegistry,
55647
- worktreeRoot: import_node_path23.default.join(config2.stateDir, "worktrees"),
55648
- 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")
55649
56104
  });
55650
56105
  {
55651
56106
  const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
@@ -55695,10 +56150,24 @@ async function startDirectorBridge() {
55695
56150
  codexExecutable: config2.codexExecutable,
55696
56151
  timeoutMs: readEnvInt("MRRLIN_TELEMETRY_GATE_TIMEOUT_SECONDS", 60) * 1e3
55697
56152
  });
55698
- await prune(config2.stateDir, {
56153
+ const pruneResult = await prune(config2.stateDir, {
55699
56154
  ageDays: readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30),
55700
- 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
55701
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
+ }
55702
56171
  } catch (error51) {
55703
56172
  bridgeLogger.logOut({
55704
56173
  spanId: "gc",
@@ -55950,15 +56419,15 @@ function readDispatchBody(req) {
55950
56419
  function readOrCreateBridgeToken(stateDir) {
55951
56420
  const explicit = (process.env.MRRLIN_DIRECTOR_BRIDGE_TOKEN ?? "").trim();
55952
56421
  if (explicit) return explicit;
55953
- const tokenPath = import_node_path23.default.join(stateDir, "token.txt");
56422
+ const tokenPath = import_node_path24.default.join(stateDir, "token.txt");
55954
56423
  try {
55955
- const existing = import_node_fs16.default.readFileSync(tokenPath, "utf8").trim();
56424
+ const existing = import_node_fs17.default.readFileSync(tokenPath, "utf8").trim();
55956
56425
  if (existing) return existing;
55957
56426
  } catch {
55958
56427
  }
55959
- import_node_fs16.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
56428
+ import_node_fs17.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
55960
56429
  const token = import_node_crypto9.default.randomBytes(32).toString("base64url");
55961
- import_node_fs16.default.writeFileSync(tokenPath, `${token}
56430
+ import_node_fs17.default.writeFileSync(tokenPath, `${token}
55962
56431
  `, { mode: 384 });
55963
56432
  return token;
55964
56433
  }
@@ -59344,15 +59813,15 @@ var StdioServerTransport = class {
59344
59813
  // src/tools.ts
59345
59814
  var import_node_crypto12 = require("node:crypto");
59346
59815
  var import_promises10 = require("node:fs/promises");
59347
- var import_node_path26 = __toESM(require("node:path"), 1);
59816
+ var import_node_path27 = __toESM(require("node:path"), 1);
59348
59817
  var import_node_os12 = __toESM(require("node:os"), 1);
59349
59818
 
59350
59819
  // ../../packages/wiki/dist/index.js
59351
59820
  var import_promises8 = __toESM(require("node:fs/promises"), 1);
59352
- var import_node_path24 = __toESM(require("node:path"), 1);
59821
+ var import_node_path25 = __toESM(require("node:path"), 1);
59353
59822
  var INCLUDED_DOC_FOLDERS = /* @__PURE__ */ new Set(["_meta", "explanation", "how-to", "reference", "sdd", "tutorials"]);
59354
59823
  function toPosixPath(input) {
59355
- return input.split(import_node_path24.default.sep).join("/");
59824
+ return input.split(import_node_path25.default.sep).join("/");
59356
59825
  }
59357
59826
  function slugifyWikiSegment(input) {
59358
59827
  return input.trim().toLowerCase().replace(/`/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -59366,12 +59835,12 @@ async function pathExists3(input) {
59366
59835
  }
59367
59836
  }
59368
59837
  async function findWorkspaceRoot(start = process.cwd()) {
59369
- let current2 = import_node_path24.default.resolve(start);
59838
+ let current2 = import_node_path25.default.resolve(start);
59370
59839
  while (true) {
59371
- 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"))) {
59372
59841
  return current2;
59373
59842
  }
59374
- const parent = import_node_path24.default.dirname(current2);
59843
+ const parent = import_node_path25.default.dirname(current2);
59375
59844
  if (parent === current2) {
59376
59845
  throw new Error(`Unable to find Mrrlin workspace root from ${start}`);
59377
59846
  }
@@ -59380,28 +59849,28 @@ async function findWorkspaceRoot(start = process.cwd()) {
59380
59849
  }
59381
59850
  async function resolveDocsRoot(input) {
59382
59851
  if (input) {
59383
- return import_node_path24.default.resolve(input);
59852
+ return import_node_path25.default.resolve(input);
59384
59853
  }
59385
59854
  if (process.env.MRRLIN_DOCS_ROOT) {
59386
- return import_node_path24.default.resolve(process.env.MRRLIN_DOCS_ROOT);
59855
+ return import_node_path25.default.resolve(process.env.MRRLIN_DOCS_ROOT);
59387
59856
  }
59388
- return import_node_path24.default.join(await findWorkspaceRoot(), "docs");
59857
+ return import_node_path25.default.join(await findWorkspaceRoot(), "docs");
59389
59858
  }
59390
59859
  async function resolveIndexPath(input) {
59391
59860
  if (input) {
59392
- return import_node_path24.default.resolve(input);
59861
+ return import_node_path25.default.resolve(input);
59393
59862
  }
59394
59863
  if (process.env.MRRLIN_WIKI_INDEX_PATH) {
59395
- 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);
59396
59865
  }
59397
59866
  const workspaceRoot = await findWorkspaceRoot();
59398
- 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");
59399
59868
  }
59400
59869
  async function walkMarkdownFiles(root, dir = root) {
59401
59870
  const entries = await import_promises8.default.readdir(dir, { withFileTypes: true });
59402
59871
  const files = [];
59403
59872
  for (const entry of entries) {
59404
- const absolute = import_node_path24.default.join(dir, entry.name);
59873
+ const absolute = import_node_path25.default.join(dir, entry.name);
59405
59874
  if (entry.isDirectory()) {
59406
59875
  files.push(...await walkMarkdownFiles(root, absolute));
59407
59876
  continue;
@@ -59420,7 +59889,7 @@ function parseTitle(markdown, sourcePath) {
59420
59889
  return title;
59421
59890
  }
59422
59891
  function documentFromFile(docsRoot, absolutePath, markdown) {
59423
- const relativePath = toPosixPath(import_node_path24.default.relative(docsRoot, absolutePath));
59892
+ const relativePath = toPosixPath(import_node_path25.default.relative(docsRoot, absolutePath));
59424
59893
  const [section, fileName] = relativePath.split("/");
59425
59894
  if (!section || !fileName || !INCLUDED_DOC_FOLDERS.has(section)) {
59426
59895
  return null;
@@ -60926,17 +61395,17 @@ var intentCheckDescriptor = createIntentCheckDescriptor();
60926
61395
 
60927
61396
  // src/evidence/assembler.ts
60928
61397
  var import_promises9 = require("node:fs/promises");
60929
- var import_node_path25 = require("node:path");
61398
+ var import_node_path26 = require("node:path");
60930
61399
  function evidenceDedupKey(sessionId, reportId) {
60931
61400
  return `${sessionId}:${reportId}`;
60932
61401
  }
60933
- function isRecord3(v) {
61402
+ function isRecord4(v) {
60934
61403
  return v !== null && typeof v === "object";
60935
61404
  }
60936
61405
  async function readDayLines(stateDir, day, sessionId) {
60937
61406
  let raw;
60938
61407
  try {
60939
- 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");
60940
61409
  } catch {
60941
61410
  return [];
60942
61411
  }
@@ -60949,7 +61418,7 @@ async function readDayLines(stateDir, day, sessionId) {
60949
61418
  } catch {
60950
61419
  continue;
60951
61420
  }
60952
- if (!isRecord3(rec) || !isRecord3(rec.payload)) continue;
61421
+ if (!isRecord4(rec) || !isRecord4(rec.payload)) continue;
60953
61422
  if (rec.payload.directorSessionId !== sessionId) continue;
60954
61423
  kept.push(rec);
60955
61424
  }
@@ -61010,7 +61479,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
61010
61479
  }
61011
61480
  let windowPruned = false;
61012
61481
  try {
61013
- 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");
61014
61483
  outer: for (const line of raw.split("\n")) {
61015
61484
  if (!line.trim()) continue;
61016
61485
  let rec;
@@ -61019,7 +61488,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
61019
61488
  } catch {
61020
61489
  continue;
61021
61490
  }
61022
- if (!isRecord3(rec) || !Array.isArray(rec.daysRemoved)) continue;
61491
+ if (!isRecord4(rec) || !Array.isArray(rec.daysRemoved)) continue;
61023
61492
  for (const removedDay of rec.daysRemoved) {
61024
61493
  if (daysRead.includes(removedDay)) {
61025
61494
  windowPruned = true;
@@ -62743,7 +63212,7 @@ function createMrrlinTools(options) {
62743
63212
  "ARTIFACT_FILE_UPLOAD_FAILED",
62744
63213
  "Unable to upload artifact file.",
62745
63214
  async (c, { projectSlug, path: filePath, class: artifactClass, taskId, runId, description }) => {
62746
- const extension2 = import_node_path26.default.extname(filePath).toLowerCase();
63215
+ const extension2 = import_node_path27.default.extname(filePath).toLowerCase();
62747
63216
  const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
62748
63217
  if (!contentType) {
62749
63218
  throw new McpToolCodedError(
@@ -62771,7 +63240,7 @@ function createMrrlinTools(options) {
62771
63240
  "Artifact exceeds 5MB; split or summarize instead."
62772
63241
  );
62773
63242
  }
62774
- const filename = import_node_path26.default.basename(filePath);
63243
+ const filename = import_node_path27.default.basename(filePath);
62775
63244
  return uploadArtifactBytes(c, {
62776
63245
  bytes: bytes3,
62777
63246
  class: artifactClass,
@@ -62796,7 +63265,7 @@ function createMrrlinTools(options) {
62796
63265
  `Invalid filename '${filename}'; provide a bare filename with no path separators or '..'.`
62797
63266
  );
62798
63267
  }
62799
- const extension2 = import_node_path26.default.extname(filename).toLowerCase();
63268
+ const extension2 = import_node_path27.default.extname(filename).toLowerCase();
62800
63269
  const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
62801
63270
  if (!contentType) {
62802
63271
  throw new McpToolCodedError(
@@ -62863,7 +63332,7 @@ function createMrrlinTools(options) {
62863
63332
  "LOCAL_CHECKOUT_REGISTER_FAILED",
62864
63333
  "Unable to register local checkout.",
62865
63334
  async (_c, { projectSlug, path: checkoutPath, repo }) => {
62866
- 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");
62867
63336
  const registry3 = new CheckoutRegistry(stateDir);
62868
63337
  const result = registerCheckout(registry3, { projectSlug, path: checkoutPath, repo }, import_node_os12.default.hostname());
62869
63338
  if (!result.ok) {
@@ -63103,7 +63572,7 @@ function runSetCredential(args) {
63103
63572
 
63104
63573
  // src/install-service.ts
63105
63574
  var import_node_os13 = __toESM(require("node:os"), 1);
63106
- var import_node_path27 = __toESM(require("node:path"), 1);
63575
+ var import_node_path28 = __toESM(require("node:path"), 1);
63107
63576
  function installService(deps) {
63108
63577
  if (!deps.hasSecret()) {
63109
63578
  deps.log("No agent credential found. Issue one in Settings, then run: mrrlin-mcp set-credential '<secret>'.");
@@ -63125,7 +63594,7 @@ function installService(deps) {
63125
63594
  return { ok: false };
63126
63595
  }
63127
63596
  const resolvedBins = bins.filter((b) => b !== null);
63128
- 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(":");
63129
63598
  const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim() || void 0;
63130
63599
  const staging = deps.env.MRRLIN_STAGING === "1" || !apiBaseUrl && bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL);
63131
63600
  const text = renderEcosystemConfig({
@@ -63136,7 +63605,7 @@ function installService(deps) {
63136
63605
  staging,
63137
63606
  apiBaseUrl
63138
63607
  });
63139
- 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");
63140
63609
  deps.writeFile(ecoPath, text);
63141
63610
  const start = deps.runPm2(["startOrReload", ecoPath]);
63142
63611
  if (start.code !== 0) {
@@ -63148,8 +63617,29 @@ function installService(deps) {
63148
63617
  deps.log(`pm2 save failed (exit ${save.code}).`);
63149
63618
  return { ok: false };
63150
63619
  }
63151
- 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}).`);
63152
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
+ }
63153
63643
  return { ok: true };
63154
63644
  }
63155
63645
  function uninstallService(deps) {
@@ -63168,12 +63658,12 @@ function uninstallService(deps) {
63168
63658
  // src/uninstall-codex.ts
63169
63659
  var import_promises11 = __toESM(require("node:fs/promises"), 1);
63170
63660
  var import_node_os14 = __toESM(require("node:os"), 1);
63171
- var import_node_path28 = __toESM(require("node:path"), 1);
63661
+ var import_node_path29 = __toESM(require("node:path"), 1);
63172
63662
  var toml3 = __toESM(require_toml(), 1);
63173
63663
  function resolveCodexHome3(options) {
63174
63664
  if (options.codexHome) return options.codexHome;
63175
63665
  if (process.env.CODEX_HOME) return process.env.CODEX_HOME;
63176
- 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");
63177
63667
  }
63178
63668
  async function pathExists4(target) {
63179
63669
  try {
@@ -63186,7 +63676,7 @@ async function pathExists4(target) {
63186
63676
  var MRRLIN_BLOCK_RE = /(^|\n)\[mcp_servers\.(?:mrrlin(?:-browser)?|external-agents)(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
63187
63677
  async function uninstallCodex(options = {}) {
63188
63678
  const codexHome = resolveCodexHome3(options);
63189
- const configPath = import_node_path28.default.join(codexHome, "config.toml");
63679
+ const configPath = import_node_path29.default.join(codexHome, "config.toml");
63190
63680
  const removePrompts = async () => uninstallPrompts(codexHome, options.promptNames ?? []);
63191
63681
  if (!await pathExists4(configPath)) {
63192
63682
  return { action: "missing", configPath, prompts: await removePrompts() };
@@ -63214,10 +63704,10 @@ async function uninstallCodex(options = {}) {
63214
63704
  }
63215
63705
  async function uninstallPrompts(codexHome, names) {
63216
63706
  if (names.length === 0) return [];
63217
- const promptsDir = import_node_path28.default.join(codexHome, "prompts");
63707
+ const promptsDir = import_node_path29.default.join(codexHome, "prompts");
63218
63708
  const out = [];
63219
63709
  for (const name of names) {
63220
- const promptPath = import_node_path28.default.join(promptsDir, `${name}.md`);
63710
+ const promptPath = import_node_path29.default.join(promptsDir, `${name}.md`);
63221
63711
  try {
63222
63712
  await import_promises11.default.unlink(promptPath);
63223
63713
  out.push({ name, action: "removed", promptPath });
@@ -63235,7 +63725,7 @@ async function uninstallPrompts(codexHome, names) {
63235
63725
 
63236
63726
  // src/extract/command.ts
63237
63727
  var import_promises12 = require("node:fs/promises");
63238
- var import_node_path29 = require("node:path");
63728
+ var import_node_path30 = require("node:path");
63239
63729
  function parseArgs(args) {
63240
63730
  const map2 = /* @__PURE__ */ new Map();
63241
63731
  for (let i = 0; i < args.length; i++) {
@@ -63251,7 +63741,7 @@ function parseArgs(args) {
63251
63741
  }
63252
63742
  return map2;
63253
63743
  }
63254
- function isRecord4(v) {
63744
+ function isRecord5(v) {
63255
63745
  return v !== null && typeof v === "object";
63256
63746
  }
63257
63747
  async function runExtract(args, deps) {
@@ -63272,7 +63762,7 @@ Missing required flag(s): ${missing.join(
63272
63762
  const days = await findSessionDays(deps.stateDir, sessionId);
63273
63763
  const matchingLines = [];
63274
63764
  for (const day of days) {
63275
- 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`);
63276
63766
  let raw;
63277
63767
  try {
63278
63768
  raw = await (0, import_promises12.readFile)(logPath, "utf8");
@@ -63288,8 +63778,8 @@ Missing required flag(s): ${missing.join(
63288
63778
  } catch {
63289
63779
  continue;
63290
63780
  }
63291
- const payload = isRecord4(parsed) ? parsed.payload : void 0;
63292
- if (isRecord4(payload) && payload.directorSessionId === sessionId) {
63781
+ const payload = isRecord5(parsed) ? parsed.payload : void 0;
63782
+ if (isRecord5(payload) && payload.directorSessionId === sessionId) {
63293
63783
  matchingLines.push(line);
63294
63784
  }
63295
63785
  }
@@ -63368,6 +63858,29 @@ function whichBin(bin) {
63368
63858
  const out = (r.stdout ?? "").trim();
63369
63859
  return r.status === 0 && out ? out.split("\n")[0] ?? null : null;
63370
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
+ }
63371
63884
  var HELP_TEXT = `mrrlin-mcp - Mrrlin Phase 1 MCP server CLI
63372
63885
 
63373
63886
  Usage:
@@ -63614,8 +64127,8 @@ async function main() {
63614
64127
  }
63615
64128
  }
63616
64129
  try {
63617
- const codexHome = process.env.CODEX_HOME ?? import_node_path31.default.join(process.env.HOME ?? (0, import_node_os15.homedir)(), ".codex");
63618
- (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 });
63619
64132
  } catch {
63620
64133
  }
63621
64134
  const claudeResult = await installClaude({ binPath, force, serveEnv });
@@ -63676,12 +64189,13 @@ async function main() {
63676
64189
  env: process.env,
63677
64190
  cwd: process.cwd(),
63678
64191
  writeFile: (p, c) => {
63679
- (0, import_node_fs17.mkdirSync)(import_node_path31.default.dirname(p), { recursive: true, mode: 448 });
63680
- (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 });
63681
64194
  },
63682
64195
  runPm2: pm2Runner,
63683
64196
  which: whichBin,
63684
64197
  hasSecret: () => Boolean(readAgentSecret()),
64198
+ bootHookStatus,
63685
64199
  log: (m) => process.stderr.write(`[mrrlin-mcp install-service] ${m}
63686
64200
  `)
63687
64201
  }).ok;
@@ -63694,14 +64208,14 @@ async function main() {
63694
64208
  runPm2: (a) => ({ code: pm2Runner(a).code }),
63695
64209
  removeFile: (p) => {
63696
64210
  try {
63697
- (0, import_node_fs17.rmSync)(p, { force: true });
64211
+ (0, import_node_fs18.rmSync)(p, { force: true });
63698
64212
  } catch {
63699
64213
  }
63700
64214
  },
63701
64215
  purgeSecret: purge,
63702
64216
  secretPath: agentCredentialPath(),
63703
64217
  tokenPath: operatorTokenPath(),
63704
- 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"),
63705
64219
  log: (m) => process.stderr.write(`[mrrlin-mcp uninstall-service] ${m}
63706
64220
  `)
63707
64221
  });
@@ -63715,14 +64229,14 @@ async function main() {
63715
64229
  runPm2: (a) => ({ code: pm2Runner(a).code }),
63716
64230
  removeFile: (p) => {
63717
64231
  try {
63718
- (0, import_node_fs17.rmSync)(p, { force: true });
64232
+ (0, import_node_fs18.rmSync)(p, { force: true });
63719
64233
  } catch {
63720
64234
  }
63721
64235
  },
63722
64236
  purgeSecret: true,
63723
64237
  secretPath: agentCredentialPath(),
63724
64238
  tokenPath: operatorTokenPath(),
63725
- ecoPath: import_node_path31.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
64239
+ ecoPath: import_node_path32.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
63726
64240
  log
63727
64241
  });
63728
64242
  let codexOk = true;
@@ -63742,7 +64256,7 @@ async function main() {
63742
64256
  log(`codex config NOT modified: ${error51 instanceof Error ? error51.message : String(error51)}`);
63743
64257
  }
63744
64258
  try {
63745
- (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 });
63746
64260
  } catch {
63747
64261
  }
63748
64262
  log("removed ~/.mrrlin");
@@ -63783,7 +64297,7 @@ ${HELP_TEXT}`);
63783
64297
  }
63784
64298
  }
63785
64299
  function resolveSelfBinPath() {
63786
- return import_node_path30.default.resolve(process.argv[1] ?? process.execPath);
64300
+ return import_node_path31.default.resolve(process.argv[1] ?? process.execPath);
63787
64301
  }
63788
64302
  main().catch((error51) => {
63789
64303
  process.stderr.write(`mrrlin-mcp fatal error: ${error51 instanceof Error ? error51.message : String(error51)}