@mrrlin-dev/mcp 0.4.139 → 0.4.142

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 +132 -40
  2. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -43704,7 +43704,8 @@ var CodexServerManager = class {
43704
43704
  thread: {
43705
43705
  start: (params) => rpcClient.request("thread/start", params),
43706
43706
  resume: (params) => rpcClient.request("thread/resume", params),
43707
- read: (params) => rpcClient.request("thread/read", params)
43707
+ read: (params) => rpcClient.request("thread/read", params),
43708
+ loadedList: (params) => rpcClient.request("thread/loaded/list", params)
43708
43709
  },
43709
43710
  turn: {
43710
43711
  start: (params) => rpcClient.request("turn/start", params),
@@ -43771,6 +43772,28 @@ function addTurnTokenUsage(total, turn) {
43771
43772
  outputTokens: add2(total.outputTokens, turn.outputTokens)
43772
43773
  };
43773
43774
  }
43775
+ var CONTEXT_WINDOW_TOKENS = 2e5;
43776
+ function extractContextWindow(method, params) {
43777
+ if (method !== TOKEN_USAGE_NOTIFICATION_METHOD)
43778
+ return null;
43779
+ if (params === null || typeof params !== "object")
43780
+ return null;
43781
+ const usage = params.tokenUsage;
43782
+ if (usage === null || typeof usage !== "object")
43783
+ return null;
43784
+ const window = readCount(usage, "modelContextWindow");
43785
+ return window !== null && window > 0 ? window : null;
43786
+ }
43787
+ function isNearContextLimit(usage, contextWindow) {
43788
+ if (!usage)
43789
+ return false;
43790
+ const contextTokens = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
43791
+ if (contextTokens <= 0)
43792
+ return false;
43793
+ const window = contextWindow && contextWindow > 0 ? contextWindow : CONTEXT_WINDOW_TOKENS;
43794
+ const remainingTokens = window - contextTokens;
43795
+ return remainingTokens / window < 0.03 || remainingTokens < 4e3;
43796
+ }
43774
43797
 
43775
43798
  // ../../packages/agent-client/dist/providers/codex/event-mapper.js
43776
43799
  var CodexBridgeEventMapper = class {
@@ -43937,7 +43960,8 @@ var CodexBridgeEventMapper = class {
43937
43960
  const usage = extractTurnTokenUsage(TOKEN_USAGE_NOTIFICATION_METHOD, notification.params);
43938
43961
  if (!usage)
43939
43962
  return [];
43940
- return [{ type: "token_usage", usage }];
43963
+ const contextWindow = extractContextWindow(TOKEN_USAGE_NOTIFICATION_METHOD, notification.params);
43964
+ return [contextWindow === null ? { type: "token_usage", usage } : { type: "token_usage", usage, contextWindow }];
43941
43965
  }
43942
43966
  /** Bare `error` notifications carry model-stream failures ({ error: { message }, willRetry }).
43943
43967
  * Retryable ones (willRetry=true) are transport noise — codex is still driving the turn — so
@@ -44339,12 +44363,57 @@ var CodexProviderAdapter = class {
44339
44363
  function createCodexAgentHandle(client) {
44340
44364
  return new CodexAgentHandle(client);
44341
44365
  }
44366
+ var MRRLIN_MCP_SERVER = "mrrlin";
44367
+ function codexThreadConfig(env) {
44368
+ if (!env)
44369
+ return null;
44370
+ const config2 = {};
44371
+ for (const [key, value] of Object.entries(env)) {
44372
+ if (!ENV_NAME.test(key))
44373
+ continue;
44374
+ config2[`mcp_servers.${MRRLIN_MCP_SERVER}.env.${key}`] = value ?? "";
44375
+ }
44376
+ return Object.keys(config2).length > 0 ? config2 : null;
44377
+ }
44378
+ var ENV_NAME = /^[A-Z_][A-Z0-9_]*$/;
44379
+ var loadedThreadConfig = /* @__PURE__ */ new WeakMap();
44380
+ function threadConfigsOf(client) {
44381
+ let configs = loadedThreadConfig.get(client);
44382
+ if (!configs) {
44383
+ configs = /* @__PURE__ */ new Map();
44384
+ loadedThreadConfig.set(client, configs);
44385
+ }
44386
+ return configs;
44387
+ }
44388
+ function configSignature(config2) {
44389
+ return JSON.stringify(Object.entries(config2 ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
44390
+ }
44391
+ var resumeQueues = /* @__PURE__ */ new WeakMap();
44392
+ function serializePerThread(client, threadId, work) {
44393
+ let queues = resumeQueues.get(client);
44394
+ if (!queues) {
44395
+ queues = /* @__PURE__ */ new Map();
44396
+ resumeQueues.set(client, queues);
44397
+ }
44398
+ const previous = queues.get(threadId) ?? Promise.resolve();
44399
+ const run3 = previous.then(work, work);
44400
+ const settled = run3.then(() => {
44401
+ }, () => {
44402
+ });
44403
+ queues.set(threadId, settled);
44404
+ void settled.then(() => {
44405
+ if (queues.get(threadId) === settled)
44406
+ queues.delete(threadId);
44407
+ });
44408
+ return run3;
44409
+ }
44342
44410
  var CodexAgentHandle = class {
44343
44411
  client;
44344
44412
  constructor(client) {
44345
44413
  this.client = client;
44346
44414
  }
44347
44415
  async startThread(params) {
44416
+ const config2 = codexThreadConfig(params.env);
44348
44417
  const started = await this.client.thread.start({
44349
44418
  model: params.model ?? null,
44350
44419
  cwd: params.cwd ?? null,
@@ -44353,21 +44422,59 @@ var CodexAgentHandle = class {
44353
44422
  baseInstructions: params.baseInstructions ?? null,
44354
44423
  developerInstructions: params.developerInstructions ?? null,
44355
44424
  ephemeral: params.ephemeral ?? null,
44356
- mcpConfig: params.mcpConfig ?? null
44425
+ mcpConfig: params.mcpConfig ?? null,
44426
+ config: config2
44357
44427
  });
44428
+ threadConfigsOf(this.client).set(started.thread.id, configSignature(config2));
44358
44429
  return { threadId: started.thread.id };
44359
44430
  }
44360
- async resumeThread(threadId, params) {
44431
+ resumeThread(threadId, params) {
44432
+ return serializePerThread(this.client, threadId, () => this.resumeNow(threadId, params));
44433
+ }
44434
+ async resumeNow(threadId, params) {
44435
+ const config2 = codexThreadConfig(params.env);
44436
+ const signature = configSignature(config2);
44437
+ const configs = threadConfigsOf(this.client);
44438
+ const loadedWith = configs.get(threadId);
44439
+ if (loadedWith !== void 0 && loadedWith !== signature) {
44440
+ configs.delete(threadId);
44441
+ throw new Error("codex: thread binding changed; start a fresh thread");
44442
+ }
44443
+ if (loadedWith === void 0 && await this.isLoaded(threadId)) {
44444
+ throw new Error("codex: thread is loaded with an unknown binding; start a fresh thread");
44445
+ }
44361
44446
  const resumed = await this.client.thread.resume({
44362
44447
  threadId,
44363
44448
  model: params.model ?? null,
44364
44449
  cwd: params.cwd ?? null,
44365
44450
  approvalPolicy: params.approvalPolicy ?? null,
44366
44451
  sandbox: params.sandbox ?? null,
44367
- mcpConfig: params.mcpConfig ?? null
44452
+ mcpConfig: params.mcpConfig ?? null,
44453
+ config: config2
44368
44454
  });
44455
+ configs.set(resumed.thread.id, signature);
44369
44456
  return { threadId: resumed.thread.id };
44370
44457
  }
44458
+ /** Whether this app-server currently holds the thread. A query that fails counts as loaded, so
44459
+ * the caller gets a fresh thread rather than a possibly stale one. A client without the query
44460
+ * (a wrapper or test double; the app-server client always has it) can only hold the threads this
44461
+ * adapter recorded, so for it an unrecorded thread is not loaded. */
44462
+ async isLoaded(threadId) {
44463
+ if (!this.client.thread.loadedList)
44464
+ return false;
44465
+ try {
44466
+ let cursor = null;
44467
+ do {
44468
+ const page = await this.client.thread.loadedList(cursor ? { cursor } : {});
44469
+ if (page.data?.includes(threadId))
44470
+ return true;
44471
+ cursor = page.nextCursor ?? null;
44472
+ } while (cursor);
44473
+ return false;
44474
+ } catch {
44475
+ return true;
44476
+ }
44477
+ }
44371
44478
  async startTurn(threadId, input, effort) {
44372
44479
  const accepted = await this.client.turn.start({ threadId, input, ...effort ? { effort } : {} });
44373
44480
  return { turnId: accepted?.turn?.id };
@@ -59211,22 +59318,22 @@ function isBridgeRunWorktree(worktreePath, checkout, worktreeRoot) {
59211
59318
  return false;
59212
59319
  }
59213
59320
  }
59214
- var short = (sha) => (sha ?? "").slice(0, 7);
59215
59321
  function localLandingNote(outcome, ctx) {
59216
59322
  const { taskId, title, defaultBranch: d, detail } = ctx;
59323
+ const prefix = `${taskId} (${title})`;
59217
59324
  const bodies = {
59218
- applied: `${taskId} (${title}) is now in your project's ${d} branch (was ${short(ctx.previousSha)}, now ${short(ctx.headSha)}). If the app is running, restart it to pick up the change.`,
59219
- already_applied: `${taskId} is already in your ${d} branch.`,
59220
- uncommitted_changes: `${taskId} is finished and tested, but it could not be placed into your ${d} branch because the project folder has unsaved (uncommitted) changes. Commit or stash them, then ask the Director to apply ${taskId} again.`,
59221
- would_overwrite_files: `${taskId} is finished and tested, but it could not be placed into your ${d} branch because files in the project folder would be overwritten: ${detail}. Move them aside, then ask the Director to apply ${taskId} again.`,
59222
- default_branch_moved: `${taskId} is finished and tested, but your ${d} branch received new commits while it was being built. Ask the Director to apply ${taskId} again; it will be merged with the newest version and re-tested first.`,
59223
- conflict: `${taskId} could not be merged with the newest ${d}: conflicts in ${detail}. Ask the Director to create a follow-up task that reconciles ${taskId} with the current ${d}.`,
59224
- build_failed: `${taskId} merged with the newest ${d}, but the build or tests failed afterwards (${detail}). Ask the Director to create a follow-up task to fix it.`,
59225
- ff_failed: `${taskId} could not be placed into your ${d} branch: git refused the update (${detail}). Ask the Director to apply ${taskId} again.`,
59226
- run_reclaimed: `${taskId} is finished, but it was not placed into your ${d} branch automatically because another Mrrlin bridge took over run ${ctx.runId}. Ask the Director to apply ${taskId} again.`,
59227
- landing_partial: `${taskId} was only partly placed into your ${d} branch, and Mrrlin could not undo it cleanly (${detail}). Your work is not lost, but please do not change the project folder yet: ask the Director to help you check it.`,
59228
- run_branch_changed: `${taskId} was not placed into your ${d} branch: its branch mrrlin/run-${ctx.runId} got new commits after the task was finished and checked, so nothing was changed. Ask the Director to restart the run if those commits belong in it.`,
59229
- delivery_ref_mismatch: `${taskId} was not placed into your ${d} branch: the run's recorded result (${detail}) does not belong to this project and run ${ctx.runId}, so nothing was changed. Ask the Director to look into run ${ctx.runId}.`
59325
+ applied: `${prefix} \u2014 Applied and merged to ${d}. Restart your dev server if running to pick up changes.`,
59326
+ already_applied: `${prefix} \u2014 Already applied to ${d}. No changes needed.`,
59327
+ uncommitted_changes: `${prefix} \u2014 Can't apply: you have uncommitted changes in your project folder. Commit or stash them, then ask the Director to apply ${taskId} again.`,
59328
+ would_overwrite_files: `${prefix} \u2014 Can't apply: local files (${detail}) would be overwritten. Move them aside, then ask the Director to apply ${taskId} again.`,
59329
+ default_branch_moved: `${prefix} \u2014 Can't apply: ${d} received new commits while building. Ask the Director to apply ${taskId} again; it will be merged with the newest version and re-tested first.`,
59330
+ conflict: `${prefix} \u2014 Merge conflicts in ${detail}. Ask the Director to create a follow-up task that reconciles ${taskId} with ${d}.`,
59331
+ build_failed: `${prefix} \u2014 Merged with ${d}, but the build or tests failed (${detail}). Ask the Director to create a follow-up task to fix it.`,
59332
+ ff_failed: `${prefix} \u2014 Could not update ${d} branch: git refused the update (${detail}). Ask the Director to apply ${taskId} again.`,
59333
+ run_reclaimed: `${prefix} \u2014 Run ${ctx.runId} was taken over by another bridge instance. Ask the Director to apply ${taskId} again.`,
59334
+ landing_partial: `${prefix} \u2014 Partially applied (${detail}). Your work is saved. Please do not change the project folder yet: ask the Director to inspect.`,
59335
+ run_branch_changed: `${prefix} \u2014 Not applied: run branch mrrlin/run-${ctx.runId} received new commits. Ask the Director to restart the run if those commits belong in it.`,
59336
+ delivery_ref_mismatch: `${prefix} \u2014 Delivery ref mismatch (${detail}). Nothing was changed. Ask the Director to look into run ${ctx.runId}.`
59230
59337
  };
59231
59338
  return { id: `II-local-apply-${ctx.runId}-${outcome}`, body: bodies[outcome] };
59232
59339
  }
@@ -63710,7 +63817,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
63710
63817
  }
63711
63818
 
63712
63819
  // src/_generated/version.ts
63713
- var PKG_VERSION = "0.4.139";
63820
+ var PKG_VERSION = "0.4.142";
63714
63821
 
63715
63822
  // src/bridge-allowed-origins.ts
63716
63823
  var DEFAULT_DIRECTOR_BRIDGE_ALLOWED_ORIGINS = [
@@ -67799,6 +67906,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
67799
67906
  let touch = () => {
67800
67907
  };
67801
67908
  let turnUsage = null;
67909
+ let turnContextWindow = null;
67802
67910
  const promptBytes = Buffer.byteLength(inputText, "utf8");
67803
67911
  const turnStartedAt = Date.now();
67804
67912
  let lastActivityAt = turnStartedAt;
@@ -67830,6 +67938,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
67830
67938
  pending.push(persist(event));
67831
67939
  if (event.type === "token_usage") {
67832
67940
  turnUsage = event.usage;
67941
+ turnContextWindow = event.contextWindow ?? turnContextWindow;
67833
67942
  } else if (event.type === "rate_limits") {
67834
67943
  const signal = detectExhaustedProviderLimit(event.snapshot, Date.now());
67835
67944
  if (signal) {
@@ -67891,7 +68000,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
67891
68000
  clearInterval(touchTimer);
67892
68001
  unsub();
67893
68002
  }
67894
- const telemetry = { promptBytes, usage: turnUsage };
68003
+ const telemetry = { promptBytes, usage: turnUsage, contextWindow: turnContextWindow };
67895
68004
  return turnError ? { ok: false, error: turnError, interruptFailed, telemetry } : { ok: true, telemetry };
67896
68005
  });
67897
68006
  }
@@ -68154,14 +68263,6 @@ async function driveRun(deps, projectSlug, runId) {
68154
68263
  runTelemetry.usage = addTurnTokenUsage(runTelemetry.usage, outcome.telemetry.usage);
68155
68264
  return outcome;
68156
68265
  };
68157
- const isContextBudgetExhausted = (usage) => {
68158
- if (!usage) return false;
68159
- const contextTokens = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
68160
- if (contextTokens <= 0) return false;
68161
- const maxTokens = 2e5;
68162
- const remainingTokens = maxTokens - contextTokens;
68163
- return remainingTokens / maxTokens < 0.03 || remainingTokens < 4e3;
68164
- };
68165
68266
  const effortDecision = deriveEffort({
68166
68267
  impact: task.impact,
68167
68268
  type: task.type,
@@ -68505,7 +68606,7 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
68505
68606
  await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
68506
68607
  return;
68507
68608
  }
68508
- if (isContextBudgetExhausted(outcome.telemetry.usage)) {
68609
+ if (isNearContextLimit(outcome.telemetry.usage, outcome.telemetry.contextWindow)) {
68509
68610
  console.warn(
68510
68611
  JSON.stringify({
68511
68612
  event: "context_budget_gate_triggered",
@@ -78719,18 +78820,9 @@ function createBridgeMessageHandler(deps) {
78719
78820
  return;
78720
78821
  }
78721
78822
  if (event.type === "token_usage") {
78722
- const usage = event.usage;
78823
+ const { usage, contextWindow } = event;
78723
78824
  turnTokenUsage = addTurnTokenUsage(turnTokenUsage, usage);
78724
- if (turnTokenUsage) {
78725
- const totalTokens = (turnTokenUsage.inputTokens ?? 0) + (turnTokenUsage.outputTokens ?? 0);
78726
- if (totalTokens > 0) {
78727
- const maxTokens = 2e5;
78728
- const remainingTokens = maxTokens - totalTokens;
78729
- if (remainingTokens / maxTokens < 0.03 || remainingTokens < 4e3) {
78730
- contextBudgetGateTriggered = true;
78731
- }
78732
- }
78733
- }
78825
+ if (isNearContextLimit(usage, contextWindow)) contextBudgetGateTriggered = true;
78734
78826
  return;
78735
78827
  }
78736
78828
  if (event.type === "turn_completed" || event.type === "rate_limits") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.139",
3
+ "version": "0.4.142",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"