@mrrlin-dev/mcp 0.3.60 → 0.3.62

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 +984 -153
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -6543,7 +6543,7 @@ var require_truncate = __commonJS({
6543
6543
  var parse5 = require_parse2();
6544
6544
  var constants = require_constants2();
6545
6545
  var SemVer = require_semver();
6546
- var truncate2 = (version2, truncation, options) => {
6546
+ var truncate3 = (version2, truncation, options) => {
6547
6547
  if (!constants.RELEASE_TYPES.includes(truncation)) {
6548
6548
  return null;
6549
6549
  }
@@ -6573,7 +6573,7 @@ var require_truncate = __commonJS({
6573
6573
  var isPrerelease = (type) => {
6574
6574
  return type.startsWith("pre");
6575
6575
  };
6576
- module2.exports = truncate2;
6576
+ module2.exports = truncate3;
6577
6577
  }
6578
6578
  });
6579
6579
 
@@ -7611,7 +7611,7 @@ var require_semver2 = __commonJS({
7611
7611
  var lte = require_lte();
7612
7612
  var cmp = require_cmp();
7613
7613
  var coerce = require_coerce();
7614
- var truncate2 = require_truncate();
7614
+ var truncate3 = require_truncate();
7615
7615
  var Comparator = require_comparator();
7616
7616
  var Range = require_range();
7617
7617
  var satisfies = require_satisfies();
@@ -7650,7 +7650,7 @@ var require_semver2 = __commonJS({
7650
7650
  lte,
7651
7651
  cmp,
7652
7652
  coerce,
7653
- truncate: truncate2,
7653
+ truncate: truncate3,
7654
7654
  Comparator,
7655
7655
  Range,
7656
7656
  satisfies,
@@ -36432,6 +36432,13 @@ var openApiOperationIds = {
36432
36432
  restartExecutionRun: "restartExecutionRun",
36433
36433
  resolveHandoff: "resolveHandoff",
36434
36434
  touchExecutionRun: "touchExecutionRun",
36435
+ getTaskState: "getTaskState",
36436
+ upsertTaskState: "upsertTaskState",
36437
+ retireThread: "retireThread",
36438
+ // Run-scoped, model-facing half of the resume contract (D8). Distinct from upsertTaskState:
36439
+ // that one is the server's own write (whole record, explicit lastRunSeq), this one is the
36440
+ // executor reporting narrative only, with the ordering value derived server-side from the run.
36441
+ reportTaskState: "reportTaskState",
36435
36442
  issueAgentCredential: "issueAgentCredential",
36436
36443
  listAgentCredentials: "listAgentCredentials",
36437
36444
  revokeAgentCredential: "revokeAgentCredential",
@@ -37463,7 +37470,55 @@ var mrrlinExecutionRunSchema = external_exports.object({
37463
37470
  status: mrrlinExecutionRunStatusSchema,
37464
37471
  taskId: mrrlinTaskIdSchema,
37465
37472
  updatedAt: external_exports.string().datetime(),
37466
- worktreeId: external_exports.string().min(1).nullable()
37473
+ worktreeId: external_exports.string().min(1).nullable(),
37474
+ stateSeq: external_exports.number().int(),
37475
+ promptBytes: external_exports.number().int().nullable(),
37476
+ turnCount: external_exports.number().int().nullable(),
37477
+ inputTokens: external_exports.number().int().nullable(),
37478
+ cachedInputTokens: external_exports.number().int().nullable(),
37479
+ outputTokens: external_exports.number().int().nullable(),
37480
+ effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable(),
37481
+ effortEscalationReason: external_exports.string().min(1).nullable()
37482
+ });
37483
+ var mrrlinTaskStateSchema = external_exports.object({
37484
+ projectSlug: mrrlinProjectSlugSchema,
37485
+ taskId: mrrlinTaskIdSchema,
37486
+ goal: external_exports.string().min(1).nullable(),
37487
+ acceptedRequirements: external_exports.string().min(1).nullable(),
37488
+ decisions: external_exports.string().min(1).nullable(),
37489
+ relevantFiles: external_exports.string().min(1).nullable(),
37490
+ filesChanged: external_exports.string().min(1).nullable(),
37491
+ latestVerification: external_exports.string().min(1).nullable(),
37492
+ openProblems: external_exports.string().min(1).nullable(),
37493
+ nextActions: external_exports.string().min(1).nullable(),
37494
+ lastRunSeq: external_exports.number().int(),
37495
+ threadRunCount: external_exports.number().int(),
37496
+ retiredThreadId: external_exports.string().min(1).nullable(),
37497
+ updatedAt: external_exports.string().datetime()
37498
+ });
37499
+ var mrrlinTaskStateUpsertSchema = external_exports.object({
37500
+ goal: external_exports.string().min(1).nullable(),
37501
+ acceptedRequirements: external_exports.string().min(1).nullable(),
37502
+ decisions: external_exports.string().min(1).nullable(),
37503
+ relevantFiles: external_exports.string().min(1).nullable(),
37504
+ filesChanged: external_exports.string().min(1).nullable(),
37505
+ latestVerification: external_exports.string().min(1).nullable(),
37506
+ openProblems: external_exports.string().min(1).nullable(),
37507
+ nextActions: external_exports.string().min(1).nullable(),
37508
+ lastRunSeq: external_exports.number().int()
37509
+ });
37510
+ var mrrlinTaskStateReportSchema = external_exports.object({
37511
+ goal: external_exports.string().min(1).nullish(),
37512
+ acceptedRequirements: external_exports.string().min(1).nullish(),
37513
+ decisions: external_exports.string().min(1).nullish(),
37514
+ openProblems: external_exports.string().min(1).nullish(),
37515
+ nextActions: external_exports.string().min(1).nullish()
37516
+ });
37517
+ var taskStateResponseSchema = external_exports.object({
37518
+ data: mrrlinTaskStateSchema.nullable()
37519
+ });
37520
+ var retireThreadResponseSchema = external_exports.object({
37521
+ data: external_exports.object({ retiredThreadId: external_exports.string().min(1).nullable() })
37467
37522
  });
37468
37523
  var mrrlinExecutionRunCreateSchema = external_exports.object({
37469
37524
  actor: external_exports.string().min(1),
@@ -37495,7 +37550,18 @@ var mrrlinExecutionRunUpdateSchema = external_exports.object({
37495
37550
  prodWorkflowId: external_exports.number().int().positive().nullable().optional(),
37496
37551
  pullRequestUrl: external_exports.string().min(1).nullable().optional(),
37497
37552
  status: mrrlinExecutionRunStatusSchema.optional(),
37498
- worktreeId: external_exports.string().min(1).nullable().optional()
37553
+ worktreeId: external_exports.string().min(1).nullable().optional(),
37554
+ // Slice 6 (cost telemetry) + Slice 7 (effort). All nullable and optional: per D6, NULL means
37555
+ // "not measured" and must never be confused with a measured zero, so a disabled slice omits its
37556
+ // fields entirely rather than writing 0. `nonnegative` rather than `positive` because a real
37557
+ // measured zero is legitimate here (a turn can genuinely produce 0 output tokens).
37558
+ promptBytes: external_exports.number().int().nonnegative().nullable().optional(),
37559
+ turnCount: external_exports.number().int().nonnegative().nullable().optional(),
37560
+ inputTokens: external_exports.number().int().nonnegative().nullable().optional(),
37561
+ cachedInputTokens: external_exports.number().int().nonnegative().nullable().optional(),
37562
+ outputTokens: external_exports.number().int().nonnegative().nullable().optional(),
37563
+ effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
37564
+ effortEscalationReason: external_exports.string().min(1).nullable().optional()
37499
37565
  }).refine((value) => Object.keys(value).length > 0, {
37500
37566
  message: "ExecutionRunUpdate requires at least one field"
37501
37567
  });
@@ -38274,6 +38340,117 @@ function applyHistoricalBudgetIfNeeded(input) {
38274
38340
  droppedHandles
38275
38341
  };
38276
38342
  }
38343
+ function renderTaskStateSection(state) {
38344
+ const rows = [
38345
+ ["goal", state.goal],
38346
+ ["accepted requirements", state.acceptedRequirements],
38347
+ ["decisions (and why)", state.decisions],
38348
+ ["relevant files", state.relevantFiles],
38349
+ ["files changed", state.filesChanged],
38350
+ ["latest verification", state.latestVerification],
38351
+ ["open problems", state.openProblems],
38352
+ ["next actions", state.nextActions]
38353
+ ];
38354
+ const present = rows.filter(([, value]) => typeof value === "string" && value.trim().length > 0);
38355
+ if (present.length === 0) {
38356
+ return "## Task state (normative)\n(no state recorded yet \u2014 establish it before ending your turn)";
38357
+ }
38358
+ const lines2 = ["## Task state (normative)"];
38359
+ for (const [label, value] of present) {
38360
+ lines2.push(`${label}: ${value}`);
38361
+ }
38362
+ return lines2.join("\n");
38363
+ }
38364
+ function renderRunDelta(input) {
38365
+ const sections = [
38366
+ renderDeltaTaskStatus(input.task),
38367
+ renderDeltaRuns(input.newlyFinishedRuns),
38368
+ renderDeltaArtifacts(input.newArtifacts, input.omittedArtifactCount ?? 0)
38369
+ ];
38370
+ return sections.join("\n\n");
38371
+ }
38372
+ function renderDeltaArtifacts(artifacts, omittedCount) {
38373
+ if (artifacts.length === 0 && omittedCount === 0)
38374
+ return "## New artifacts since your last turn\n(none)";
38375
+ const lines2 = ["## New artifacts since your last turn"];
38376
+ if (omittedCount > 0) {
38377
+ lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
38378
+ }
38379
+ for (const artifact of artifacts) {
38380
+ if (artifact.kind === "file") {
38381
+ const p = artifact.payload;
38382
+ if (p.filename && p.stableUrl) {
38383
+ lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
38384
+ continue;
38385
+ }
38386
+ }
38387
+ lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${JSON.stringify(artifact.payload)}`);
38388
+ }
38389
+ return lines2.join("\n");
38390
+ }
38391
+ function renderDeltaTaskStatus(task) {
38392
+ const lines2 = ["## Since your last turn", `task ${task.id} status: ${task.status}`];
38393
+ if (task.latestJudgement) {
38394
+ lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
38395
+ }
38396
+ return lines2.join("\n");
38397
+ }
38398
+ function renderDeltaRuns(runs) {
38399
+ if (runs.length === 0)
38400
+ return "## Runs completed since your last turn\n(none)";
38401
+ const lines2 = ["## Runs completed since your last turn"];
38402
+ for (const run3 of runs) {
38403
+ lines2.push(`- ${run3.id} [${run3.status}] finished ${run3.finishedAt ?? run3.createdAt}`);
38404
+ }
38405
+ return lines2.join("\n");
38406
+ }
38407
+
38408
+ // ../../packages/codex-client/dist/token-usage.js
38409
+ var TOKEN_USAGE_NOTIFICATION_METHOD = "thread/tokenUsage/updated";
38410
+ function readCount(source, key) {
38411
+ const value = source[key];
38412
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
38413
+ }
38414
+ function extractTurnTokenUsage(method, params) {
38415
+ if (method !== TOKEN_USAGE_NOTIFICATION_METHOD)
38416
+ return null;
38417
+ if (params === null || typeof params !== "object")
38418
+ return null;
38419
+ const usage = params.tokenUsage;
38420
+ if (usage === null || typeof usage !== "object")
38421
+ return null;
38422
+ const last = usage.last;
38423
+ if (last === null || typeof last !== "object")
38424
+ return null;
38425
+ const block = last;
38426
+ const extracted = {
38427
+ inputTokens: readCount(block, "inputTokens"),
38428
+ cachedInputTokens: readCount(block, "cachedInputTokens"),
38429
+ outputTokens: readCount(block, "outputTokens")
38430
+ };
38431
+ if (extracted.inputTokens === null && extracted.cachedInputTokens === null && extracted.outputTokens === null) {
38432
+ return null;
38433
+ }
38434
+ return extracted;
38435
+ }
38436
+ function addTurnTokenUsage(total, turn) {
38437
+ if (turn === null)
38438
+ return total;
38439
+ if (total === null)
38440
+ return { ...turn };
38441
+ const add2 = (a, b) => {
38442
+ if (a === null)
38443
+ return b;
38444
+ if (b === null)
38445
+ return a;
38446
+ return a + b;
38447
+ };
38448
+ return {
38449
+ inputTokens: add2(total.inputTokens, turn.inputTokens),
38450
+ cachedInputTokens: add2(total.cachedInputTokens, turn.cachedInputTokens),
38451
+ outputTokens: add2(total.outputTokens, turn.outputTokens)
38452
+ };
38453
+ }
38277
38454
 
38278
38455
  // ../../packages/client/dist/agent-token-provider.js
38279
38456
  var AgentTokenProviderError = class extends Error {
@@ -38998,6 +39175,34 @@ function createMrrlinClient(config2) {
38998
39175
  throw error51;
38999
39176
  }
39000
39177
  },
39178
+ async getTaskState(projectSlug, taskId) {
39179
+ const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/state`);
39180
+ return taskStateResponseSchema.parse(body).data;
39181
+ },
39182
+ async upsertTaskState(projectSlug, taskId, input) {
39183
+ const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/state`, {
39184
+ body: JSON.stringify(mrrlinTaskStateUpsertSchema.parse(input)),
39185
+ headers: { "content-type": "application/json" },
39186
+ method: "PUT"
39187
+ });
39188
+ return taskStateResponseSchema.parse(body).data;
39189
+ },
39190
+ async retireThread(projectSlug, taskId, newThreadId) {
39191
+ const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/retire-thread`, {
39192
+ body: JSON.stringify({ newThreadId }),
39193
+ headers: { "content-type": "application/json" },
39194
+ method: "POST"
39195
+ });
39196
+ return retireThreadResponseSchema.parse(body).data.retiredThreadId;
39197
+ },
39198
+ async reportTaskState(projectSlug, runId, report) {
39199
+ const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/task-state`, {
39200
+ body: JSON.stringify(mrrlinTaskStateReportSchema.parse(report)),
39201
+ headers: { "content-type": "application/json" },
39202
+ method: "POST"
39203
+ });
39204
+ return taskStateResponseSchema.parse(body).data;
39205
+ },
39001
39206
  async resolveHandoff(projectSlug, runId, input) {
39002
39207
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/resolve-handoff`, {
39003
39208
  body: JSON.stringify(input),
@@ -39201,6 +39406,36 @@ function createMrrlinClient(config2) {
39201
39406
  };
39202
39407
  }
39203
39408
 
39409
+ // src/describe-error.ts
39410
+ var MAX_DETAIL_CHARS = 200;
39411
+ function truncate(value) {
39412
+ return value.length > MAX_DETAIL_CHARS ? value.slice(0, MAX_DETAIL_CHARS) : value;
39413
+ }
39414
+ function codeAndMessage(value) {
39415
+ if (!value || typeof value !== "object") return null;
39416
+ const obj = value;
39417
+ const code = typeof obj.code === "string" ? obj.code : null;
39418
+ const message = typeof obj.message === "string" ? obj.message : null;
39419
+ if (code) return message ? `${code}: ${message}` : code;
39420
+ return message;
39421
+ }
39422
+ function detailFromCause(cause) {
39423
+ if (!cause || typeof cause !== "object") return null;
39424
+ const direct = codeAndMessage(cause);
39425
+ const errors = cause.errors;
39426
+ const hasCode = typeof cause.code === "string";
39427
+ if (!hasCode && Array.isArray(errors) && errors.length > 0) {
39428
+ const fromFirst = codeAndMessage(errors[0]);
39429
+ if (fromFirst) return fromFirst;
39430
+ }
39431
+ return direct;
39432
+ }
39433
+ function describeError(error51) {
39434
+ if (!(error51 instanceof Error)) return String(error51);
39435
+ const detail = detailFromCause(error51.cause);
39436
+ return detail ? `${error51.message} (${truncate(detail)})` : error51.message;
39437
+ }
39438
+
39204
39439
  // src/token-refresher.ts
39205
39440
  function createTokenRefresher(opts) {
39206
39441
  const setIntervalFn = opts.setIntervalFn ?? setInterval;
@@ -39217,7 +39452,7 @@ function createTokenRefresher(opts) {
39217
39452
  opts.onOk?.();
39218
39453
  } catch (error51) {
39219
39454
  process.stderr.write(
39220
- `[mrrlin-mcp director-bridge] token refresh failed (will retry next tick): ${error51 instanceof Error ? error51.message : String(error51)}
39455
+ `[mrrlin-mcp director-bridge] token refresh failed (will retry next tick): ${describeError(error51)}
39221
39456
  `
39222
39457
  );
39223
39458
  opts.onError?.(error51);
@@ -39256,29 +39491,117 @@ function createServiceHealth() {
39256
39491
  }
39257
39492
 
39258
39493
  // src/turn-lock.ts
39494
+ var CHAT_LIMIT = 5;
39495
+ var AUTONOMOUS_LIMIT = 10;
39259
39496
  function createTurnLock() {
39260
- let tail2 = Promise.resolve();
39261
- let active = 0;
39262
- return {
39263
- runExclusive(fn) {
39264
- active += 1;
39265
- const run3 = tail2.then(() => fn());
39266
- tail2 = run3.then(
39267
- () => void 0,
39268
- () => void 0
39269
- );
39270
- run3.then(
39271
- () => {
39272
- active -= 1;
39273
- },
39274
- () => {
39275
- active -= 1;
39497
+ let chatTotalActive = 0;
39498
+ let autonomousTotalActive = 0;
39499
+ let chatRunning = 0;
39500
+ let autonomousRunning = 0;
39501
+ const chatQueue = [];
39502
+ const execQueue = [];
39503
+ const asyncQueue = [];
39504
+ let lastServed = null;
39505
+ let autonomousBound = false;
39506
+ const logBound = (queued) => {
39507
+ console.warn(
39508
+ `[mrrlin-mcp director-bridge] warn autonomous_ceiling_bound {"limit":${AUTONOMOUS_LIMIT},"queued":${queued}}`
39509
+ );
39510
+ };
39511
+ const maybeEnterBound = () => {
39512
+ if (!autonomousBound && autonomousRunning === AUTONOMOUS_LIMIT && execQueue.length + asyncQueue.length > 0) {
39513
+ autonomousBound = true;
39514
+ logBound(execQueue.length + asyncQueue.length);
39515
+ }
39516
+ };
39517
+ const maybeLeaveBound = () => {
39518
+ if (autonomousBound && (autonomousRunning < AUTONOMOUS_LIMIT || execQueue.length + asyncQueue.length === 0)) {
39519
+ autonomousBound = false;
39520
+ logBound(execQueue.length + asyncQueue.length);
39521
+ }
39522
+ };
39523
+ const acquire = (lane, startRunner) => {
39524
+ if (lane === "chat") {
39525
+ if (chatRunning < CHAT_LIMIT) {
39526
+ chatRunning += 1;
39527
+ startRunner();
39528
+ } else {
39529
+ chatQueue.push(startRunner);
39530
+ }
39531
+ } else {
39532
+ if (autonomousRunning < AUTONOMOUS_LIMIT) {
39533
+ autonomousRunning += 1;
39534
+ startRunner();
39535
+ } else {
39536
+ if (lane === "exec") {
39537
+ execQueue.push(startRunner);
39538
+ } else {
39539
+ asyncQueue.push(startRunner);
39276
39540
  }
39277
- );
39278
- return run3;
39279
- },
39280
- activeCount: () => active
39541
+ maybeEnterBound();
39542
+ }
39543
+ }
39544
+ };
39545
+ const release = (lane) => {
39546
+ if (lane === "chat") {
39547
+ chatRunning -= 1;
39548
+ const next = chatQueue.shift();
39549
+ if (next) {
39550
+ chatRunning += 1;
39551
+ next();
39552
+ }
39553
+ } else {
39554
+ autonomousRunning -= 1;
39555
+ const execWait = execQueue.length > 0;
39556
+ const asyncWait = asyncQueue.length > 0;
39557
+ if (execWait || asyncWait) {
39558
+ let serveLane;
39559
+ if (execWait && !asyncWait) {
39560
+ serveLane = "exec";
39561
+ } else if (!execWait && asyncWait) {
39562
+ serveLane = "async";
39563
+ } else {
39564
+ serveLane = lastServed === "exec" ? "async" : "exec";
39565
+ }
39566
+ const queue = serveLane === "exec" ? execQueue : asyncQueue;
39567
+ const next = queue.shift();
39568
+ if (next) {
39569
+ autonomousRunning += 1;
39570
+ lastServed = serveLane;
39571
+ next();
39572
+ }
39573
+ }
39574
+ maybeLeaveBound();
39575
+ }
39281
39576
  };
39577
+ const run3 = async (lane, fn) => {
39578
+ if (lane === "chat") {
39579
+ chatTotalActive += 1;
39580
+ } else {
39581
+ autonomousTotalActive += 1;
39582
+ }
39583
+ return new Promise((resolve, reject) => {
39584
+ const start = () => {
39585
+ let bodyPromise;
39586
+ try {
39587
+ bodyPromise = fn();
39588
+ } catch (e) {
39589
+ bodyPromise = Promise.reject(e);
39590
+ }
39591
+ bodyPromise.then((v) => resolve(v)).catch((e) => reject(e)).finally(() => {
39592
+ if (lane === "chat") {
39593
+ chatTotalActive -= 1;
39594
+ } else {
39595
+ autonomousTotalActive -= 1;
39596
+ }
39597
+ release(lane);
39598
+ });
39599
+ };
39600
+ acquire(lane, start);
39601
+ });
39602
+ };
39603
+ const activeCount = () => chatTotalActive + autonomousTotalActive;
39604
+ return { run: run3, activeCount };
39282
39605
  }
39283
39606
 
39284
39607
  // src/self-update.ts
@@ -39642,7 +39965,7 @@ function startCodexProbe(deps = {}) {
39642
39965
  }
39643
39966
 
39644
39967
  // src/_generated/version.ts
39645
- var PKG_VERSION = "0.3.60";
39968
+ var PKG_VERSION = "0.3.62";
39646
39969
 
39647
39970
  // src/api-base-url-migrate.ts
39648
39971
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -40142,7 +40465,7 @@ async function attemptSchedulerTick(deps) {
40142
40465
  return { nextWakeAt: typeof data.nextWakeAt === "string" ? data.nextWakeAt : null, failed: false };
40143
40466
  } catch (error51) {
40144
40467
  process.stderr.write(
40145
- `[mrrlin-mcp director-bridge] scheduler tick failed: ${error51 instanceof Error ? error51.message : String(error51)}
40468
+ `[mrrlin-mcp director-bridge] scheduler tick failed: ${describeError(error51)}
40146
40469
  `
40147
40470
  );
40148
40471
  bridgeHealth?.recordFailure("schedulerTick", error51);
@@ -41831,6 +42154,11 @@ function renderMilestone(m, ctx) {
41831
42154
  return "Autodeploy is enabled, but this task has nothing deployable \u2014 skipping";
41832
42155
  case "daily_limit_reached":
41833
42156
  return "Daily autonomous operations limit reached \u2014 I will continue tomorrow";
42157
+ case "thread_retired": {
42158
+ if (ctx.reason === "bounded_no_state")
42159
+ return "Started a fresh conversation for this task \u2014 the previous one grew too long and no progress notes were saved, so some earlier reasoning may be lost";
42160
+ return "Started a fresh conversation for this task \u2014 the previous one grew too long, so I carried the saved progress notes over";
42161
+ }
41834
42162
  }
41835
42163
  }
41836
42164
 
@@ -41854,6 +42182,82 @@ async function postRunFeed(client, projectSlug, runId, taskId, milestone, ctx =
41854
42182
  }
41855
42183
  }
41856
42184
 
42185
+ // src/output-projection.ts
42186
+ var FAILURE_PATTERNS = [
42187
+ /^\s*(?:E|FAIL|ERROR|ERR!)\b/i,
42188
+ /\bTraceback \(most recent call last\)/,
42189
+ /^\s*at\s+.+:\d+:\d+/,
42190
+ // stack frames
42191
+ /\bAssertionError\b|\bexpected\b.*\breceived\b|\bexpected:\b/i,
42192
+ /\berror\b[:\s]|\bfailed\b[:\s]|\bfailure\b[:\s]/i,
42193
+ /^\s*not ok\b/,
42194
+ // TAP
42195
+ /\b(?:SyntaxError|TypeError|ReferenceError|RangeError)\b/,
42196
+ /^\s*✕|^\s*✗|^\s*×/,
42197
+ /\bexit(?:ed)? (?:code|status)\s*[:=]?\s*[1-9]/i
42198
+ ];
42199
+ var DEFAULT_OUTPUT_PROJECTION_MAX_CHARS = 2e3;
42200
+ function projectCommandOutput(output3, options = {}) {
42201
+ const maxChars = options.maxChars ?? DEFAULT_OUTPUT_PROJECTION_MAX_CHARS;
42202
+ const exitStatus = options.exitStatus ?? null;
42203
+ const fullOutputRef = options.fullOutputRef ?? null;
42204
+ const label = options.label ?? "command output";
42205
+ const lines2 = output3.split(/\r?\n/);
42206
+ const matched = [];
42207
+ for (let index = 0; index < lines2.length; index += 1) {
42208
+ const line = lines2[index] ?? "";
42209
+ if (FAILURE_PATTERNS.some((pattern) => pattern.test(line))) matched.push(index);
42210
+ }
42211
+ const candidates = matched.length > 0 ? matched : lines2.map((_, index) => index);
42212
+ const kept = [];
42213
+ let used = 0;
42214
+ for (let cursor = candidates.length - 1; cursor >= 0; cursor -= 1) {
42215
+ const index = candidates[cursor];
42216
+ if (index === void 0) continue;
42217
+ let text = lines2[index] ?? "";
42218
+ if (text.trim().length === 0) continue;
42219
+ if (text.length > maxChars) {
42220
+ text = `${text.slice(0, Math.max(0, maxChars - 14))}\u2026[line cut]`;
42221
+ }
42222
+ const cost = text.length + 1;
42223
+ if (used + cost > maxChars) break;
42224
+ kept.push({ index, text });
42225
+ used += cost;
42226
+ }
42227
+ kept.sort((a, b) => a.index - b.index);
42228
+ const failureLines = kept.map((entry) => entry.text);
42229
+ const carried = failureLines.reduce((total, line) => total + line.length + 1, 0);
42230
+ const omittedChars = Math.max(0, output3.length - carried);
42231
+ const parts = [`${label}:`];
42232
+ parts.push(exitStatus === null ? "exit status unknown" : `exit status ${exitStatus}`);
42233
+ parts.push(
42234
+ matched.length > 0 ? `${failureLines.length} of ${matched.length} failure-bearing line(s) shown` : `no failure markers found; showing the tail (${failureLines.length} line(s))`
42235
+ );
42236
+ if (omittedChars > 0) {
42237
+ parts.push(
42238
+ fullOutputRef ? `${omittedChars} chars omitted \u2014 retrieve ${fullOutputRef} for the full output` : `${omittedChars} chars omitted`
42239
+ );
42240
+ }
42241
+ return {
42242
+ exitStatus,
42243
+ failureLines,
42244
+ summary: parts.join(" "),
42245
+ omittedChars,
42246
+ fullOutputRef,
42247
+ usedTailFallback: matched.length === 0
42248
+ };
42249
+ }
42250
+ function renderOutputProjection(projection) {
42251
+ const lines2 = [projection.summary];
42252
+ if (projection.failureLines.length > 0) {
42253
+ lines2.push(...projection.failureLines);
42254
+ }
42255
+ return lines2.join("\n");
42256
+ }
42257
+ function outputProjectionDisabled(env = process.env) {
42258
+ return (env.MRRLIN_EXEC_OUTPUT_PROJECTION_DISABLE ?? "").trim() === "1";
42259
+ }
42260
+
41857
42261
  // src/deploy-gate.ts
41858
42262
  var DEFAULT_DEPLOY_POLL_BUDGET_SECONDS = 1200;
41859
42263
  var DEFAULT_PROD_GRACE_SECONDS = 900;
@@ -42209,10 +42613,48 @@ async function runDeployGateTick(deps, projectSlug, run3, task) {
42209
42613
  task,
42210
42614
  run: run3
42211
42615
  });
42616
+ const verificationAttempt = attemptIndex(artifacts, env, "verification") + 1;
42617
+ let outputRef = null;
42618
+ if (verdict.fullOutput && !outputProjectionDisabled()) {
42619
+ outputRef = `EA-veroutput-${env}-${verificationAttempt}`;
42620
+ const stored = await deps.client.appendExecutionArtifact(projectSlug, run3.id, {
42621
+ id: outputRef,
42622
+ kind: "command_output",
42623
+ payload: { env, source: "verification-turn", output: verdict.fullOutput }
42624
+ }).then(() => true).catch(() => false);
42625
+ if (!stored) outputRef = null;
42626
+ }
42627
+ const projection = verdict.fullOutput && !outputProjectionDisabled() ? projectCommandOutput(verdict.fullOutput, {
42628
+ // A model turn reports no exit code; claiming 0 would assert a success never observed.
42629
+ exitStatus: null,
42630
+ fullOutputRef: outputRef,
42631
+ label: `verification (${env})`
42632
+ }) : null;
42633
+ if (projection?.usedTailFallback) {
42634
+ console.warn(
42635
+ JSON.stringify({
42636
+ event: "output_projection_tail_fallback",
42637
+ projectSlug,
42638
+ runId: run3.id,
42639
+ env,
42640
+ detail: "no failure marker matched; the projection carries the tail instead"
42641
+ })
42642
+ );
42643
+ }
42212
42644
  await deps.client.appendExecutionArtifact(projectSlug, run3.id, {
42213
- id: `EA-verification-${env}-${attemptIndex(artifacts, env, "verification") + 1}`,
42645
+ id: `EA-verification-${env}-${verificationAttempt}`,
42214
42646
  kind: "verification",
42215
- payload: { env, verdict: verdict.verdict, summary: verdict.summary, turnRef: verdict.turnRef }
42647
+ payload: {
42648
+ env,
42649
+ verdict: verdict.verdict,
42650
+ summary: verdict.summary,
42651
+ turnRef: verdict.turnRef,
42652
+ ...projection ? {
42653
+ projection: renderOutputProjection(projection),
42654
+ fullOutputArtifactId: projection.fullOutputRef,
42655
+ omittedChars: projection.omittedChars
42656
+ } : {}
42657
+ }
42216
42658
  });
42217
42659
  if (verdict.verdict === "fail") {
42218
42660
  if (env === "prod" && task.autonomyLevel === "auto") {
@@ -42524,6 +42966,112 @@ function buildPullRequestBody(input) {
42524
42966
  ${lines2.join("\n")}`;
42525
42967
  }
42526
42968
 
42969
+ // src/context-compact/policy.ts
42970
+ var DEFAULT_THRESHOLD_CHARS = 12e3;
42971
+ var DEFAULT_MAX_ITEMS = 25;
42972
+ function readPositiveInt(value, fallback) {
42973
+ const raw = (value ?? "").trim();
42974
+ if (!raw) return fallback;
42975
+ const parsed = Number.parseInt(raw, 10);
42976
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
42977
+ }
42978
+ function readCompactPolicy(env = process.env) {
42979
+ const disabled = (env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() === "1";
42980
+ return {
42981
+ enabled: !disabled,
42982
+ thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
42983
+ maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
42984
+ debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1",
42985
+ retrievalBudgetDisable: (env.MRRLIN_RETRIEVAL_BUDGET_DISABLE ?? "").trim() === "1"
42986
+ };
42987
+ }
42988
+ function warmDeltaDisabled(env = process.env) {
42989
+ return (env.MRRLIN_EXEC_WARM_DELTA_DISABLE ?? "").trim() === "1";
42990
+ }
42991
+ var DEFAULT_THREAD_MAX_RUNS = 12;
42992
+ var THREAD_MAX_RUNS_MIN = 4;
42993
+ var THREAD_MAX_RUNS_MAX = 50;
42994
+ var DEFAULT_THREAD_RETIRE_GRACE_RUNS = 6;
42995
+ var warnedOutOfRange = /* @__PURE__ */ new Set();
42996
+ function readBoundedInt(name, raw, fallback, min, max) {
42997
+ const trimmed = (raw ?? "").trim();
42998
+ if (!trimmed) return fallback;
42999
+ const parsed = Number.parseInt(trimmed, 10);
43000
+ if (Number.isInteger(parsed) && parsed >= min && parsed <= max) return parsed;
43001
+ if (!warnedOutOfRange.has(name)) {
43002
+ warnedOutOfRange.add(name);
43003
+ console.warn(
43004
+ JSON.stringify({
43005
+ event: "thread_retirement_bound_out_of_range",
43006
+ variable: name,
43007
+ provided: trimmed,
43008
+ accepted: `${min}-${max}`,
43009
+ usingDefault: fallback
43010
+ })
43011
+ );
43012
+ }
43013
+ return fallback;
43014
+ }
43015
+ function threadMaxRuns(env = process.env) {
43016
+ return readBoundedInt(
43017
+ "MRRLIN_EXEC_THREAD_MAX_RUNS",
43018
+ env.MRRLIN_EXEC_THREAD_MAX_RUNS,
43019
+ DEFAULT_THREAD_MAX_RUNS,
43020
+ THREAD_MAX_RUNS_MIN,
43021
+ THREAD_MAX_RUNS_MAX
43022
+ );
43023
+ }
43024
+ function threadRetireGraceRuns(env = process.env) {
43025
+ return readBoundedInt(
43026
+ "MRRLIN_EXEC_THREAD_RETIRE_GRACE_RUNS",
43027
+ env.MRRLIN_EXEC_THREAD_RETIRE_GRACE_RUNS,
43028
+ DEFAULT_THREAD_RETIRE_GRACE_RUNS,
43029
+ 0,
43030
+ THREAD_MAX_RUNS_MAX
43031
+ );
43032
+ }
43033
+ function threadRetireDisabled(env = process.env) {
43034
+ return (env.MRRLIN_EXEC_THREAD_RETIRE_DISABLE ?? "").trim() === "1";
43035
+ }
43036
+
43037
+ // src/thread-retirement.ts
43038
+ function decideThreadRetirement(input) {
43039
+ if (input.disabled) return { retire: false };
43040
+ if (input.threadRunCount < input.maxRuns) return { retire: false };
43041
+ if (input.hasNarrative) return { retire: true, reason: "bounded_run_count" };
43042
+ if (input.threadRunCount >= input.maxRuns + input.graceRuns) {
43043
+ return { retire: true, reason: "bounded_no_state" };
43044
+ }
43045
+ return { retire: false };
43046
+ }
43047
+ function hasNarrativeContent(state) {
43048
+ if (!state) return false;
43049
+ return [state.goal, state.acceptedRequirements, state.decisions, state.openProblems, state.nextActions].some(
43050
+ (field) => typeof field === "string" && field.trim().length > 0
43051
+ );
43052
+ }
43053
+
43054
+ // src/effort-tier.ts
43055
+ var DEFAULT_EFFORT_LEVEL = "medium";
43056
+ var MECHANICAL_EFFORT_LEVEL = "low";
43057
+ var ESCALATED_EFFORT_LEVEL = "high";
43058
+ function deriveEffort(input) {
43059
+ if (input.disabled) return null;
43060
+ if (input.impact === "high") {
43061
+ return {
43062
+ level: ESCALATED_EFFORT_LEVEL,
43063
+ escalationReason: "task impact is high (operator-set)"
43064
+ };
43065
+ }
43066
+ if (input.impact === "low") {
43067
+ return { level: MECHANICAL_EFFORT_LEVEL, escalationReason: null };
43068
+ }
43069
+ return { level: DEFAULT_EFFORT_LEVEL, escalationReason: null };
43070
+ }
43071
+ function effortDisabled(env = process.env) {
43072
+ return (env.MRRLIN_EXEC_EFFORT_DISABLE ?? "").trim() === "1";
43073
+ }
43074
+
42527
43075
  // src/execution-consumer.ts
42528
43076
  var DEFAULT_STALE_SECONDS = 300;
42529
43077
  var DEFAULT_MAX_LEASE_RECLAIMS = 3;
@@ -42688,6 +43236,18 @@ var BASE_INSTRUCTIONS_PREFIX = [
42688
43236
  " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
42689
43237
  " Do NOT remove earlier sections \u2014 they remain useful for context.",
42690
43238
  " Adapt depth to the task: a one-line fix needs one sentence, not four headings.",
43239
+ "",
43240
+ // Slice 4 of token-efficient-execution-runs (D8). This is the ONLY way the reasoning behind a
43241
+ // decision survives past this conversation: a thread is retired once it has accumulated too many
43242
+ // runs, and the next run then starts from this record plus the repository. Everything else the
43243
+ // next run receives (spec, run history, artifacts) is reconstructed from the database anyway —
43244
+ // what only the model holds is why it chose what it chose, and what it intended to do next.
43245
+ "BEFORE you end your turn, call `update_task_state` to record where this task now stands: the",
43246
+ "goal as you understand it, decisions you made AND WHY, problems still open, and the next",
43247
+ "concrete actions. A later run may start from a FRESH conversation that has none of this turn's",
43248
+ "context \u2014 that record is what it will read. A decision without its reason is the thing most",
43249
+ "often lost, so state the reason. Omit any field you learned nothing new about; an omitted field",
43250
+ "keeps its previous value. Do not restate the spec or the file list \u2014 the next run gets those.",
42691
43251
  ""
42692
43252
  ];
42693
43253
  var CONSUMER_BASE_INSTRUCTIONS = [
@@ -42923,13 +43483,15 @@ async function runHasLease(deps, projectSlug, runId, leaseId) {
42923
43483
  return false;
42924
43484
  }
42925
43485
  }
42926
- async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inputText, overallMs, phase) {
43486
+ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inputText, overallMs, phase, effort) {
42927
43487
  const mapper = new CodexBridgeEventMapper();
42928
43488
  mapper.reset();
42929
43489
  let turnError = null;
42930
43490
  let interruptFailed = false;
42931
43491
  let touch = () => {
42932
43492
  };
43493
+ let turnUsage = null;
43494
+ const promptBytes = Buffer.byteLength(inputText, "utf8");
42933
43495
  const turnStartedAt = Date.now();
42934
43496
  let lastActivityAt = turnStartedAt;
42935
43497
  const persist = async (event) => {
@@ -42945,7 +43507,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42945
43507
  } catch {
42946
43508
  }
42947
43509
  };
42948
- return deps.runExclusive(async () => {
43510
+ return deps.run("exec", async () => {
42949
43511
  const pending = [];
42950
43512
  let resolveTurnEnded = null;
42951
43513
  const turnEnded = new Promise((resolve) => {
@@ -42958,6 +43520,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42958
43520
  lastActivityAt = Date.now();
42959
43521
  touch();
42960
43522
  }
43523
+ const usage = !notifThreadId || notifThreadId === threadId ? extractTurnTokenUsage(method, params) : null;
43524
+ if (usage) turnUsage = usage;
42961
43525
  const events = mapper.mapNotification(
42962
43526
  { method, params },
42963
43527
  threadId
@@ -42983,7 +43547,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42983
43547
  touch = activity.touch;
42984
43548
  const accepted = await codex.turn.start({
42985
43549
  threadId,
42986
- input: [{ type: "text", text: inputText }]
43550
+ input: [{ type: "text", text: inputText }],
43551
+ ...effort === void 0 ? {} : { effort }
42987
43552
  });
42988
43553
  acceptedTurnId = accepted?.turn?.id ?? null;
42989
43554
  await turnEnded;
@@ -43018,7 +43583,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
43018
43583
  clearInterval(touchTimer);
43019
43584
  unsub();
43020
43585
  }
43021
- return turnError ? { ok: false, error: turnError, interruptFailed } : { ok: true };
43586
+ const telemetry = { promptBytes, usage: turnUsage };
43587
+ return turnError ? { ok: false, error: turnError, interruptFailed, telemetry } : { ok: true, telemetry };
43022
43588
  });
43023
43589
  }
43024
43590
  async function driveRun(deps, projectSlug, runId) {
@@ -43088,6 +43654,47 @@ async function driveRun(deps, projectSlug, runId) {
43088
43654
  } : context
43089
43655
  );
43090
43656
  bundle += await deployWorkflowReverifyNotice(deps.client, projectSlug);
43657
+ const runTelemetry = {
43658
+ promptBytes: 0,
43659
+ turnCount: 0,
43660
+ usage: null
43661
+ };
43662
+ const recordTurn = (outcome) => {
43663
+ runTelemetry.promptBytes += outcome.telemetry.promptBytes;
43664
+ runTelemetry.turnCount += 1;
43665
+ runTelemetry.usage = addTurnTokenUsage(runTelemetry.usage, outcome.telemetry.usage);
43666
+ return outcome;
43667
+ };
43668
+ const effortDecision = deriveEffort({
43669
+ impact: task.impact,
43670
+ type: task.type,
43671
+ disabled: effortDisabled()
43672
+ });
43673
+ const taskState = await deps.client.getTaskState(projectSlug, task.id).catch(() => null);
43674
+ const retirement = decideThreadRetirement({
43675
+ threadRunCount: taskState?.threadRunCount ?? 0,
43676
+ hasNarrative: hasNarrativeContent(taskState),
43677
+ disabled: threadRetireDisabled(),
43678
+ maxRuns: threadMaxRuns(),
43679
+ graceRuns: threadRetireGraceRuns()
43680
+ });
43681
+ const threadIdForAcquire = retirement.retire ? null : task.directorThreadId;
43682
+ if (retirement.retire) {
43683
+ console.warn(
43684
+ JSON.stringify({
43685
+ event: "thread_retirement_triggered",
43686
+ projectSlug,
43687
+ runId,
43688
+ taskId: task.id,
43689
+ reason: retirement.reason,
43690
+ threadRunCount: taskState?.threadRunCount ?? 0,
43691
+ retiringThreadId: task.directorThreadId
43692
+ })
43693
+ );
43694
+ }
43695
+ if (taskState) bundle += `
43696
+
43697
+ ${renderTaskStateSection(taskState)}`;
43091
43698
  const checkout = deps.checkoutRegistry?.get(projectSlug) ?? null;
43092
43699
  let sandbox = "read-only";
43093
43700
  let turnCwd = deps.codexCwd ?? null;
@@ -43163,7 +43770,7 @@ async function driveRun(deps, projectSlug, runId) {
43163
43770
  const pushFn = deps.pushRunBranch ?? pushRunBranch;
43164
43771
  try {
43165
43772
  const { threadId, resumed } = await acquireThread(codex, {
43166
- threadId: task.directorThreadId,
43773
+ threadId: threadIdForAcquire,
43167
43774
  startParams: {
43168
43775
  cwd: turnCwd,
43169
43776
  approvalPolicy: "never",
@@ -43179,9 +43786,36 @@ async function driveRun(deps, projectSlug, runId) {
43179
43786
  },
43180
43787
  onThreadIdChanged: async (newId) => {
43181
43788
  if (!await runHasLease(deps, projectSlug, runId, leaseId)) return;
43789
+ if (retirement.retire) return;
43182
43790
  await deps.client.updateTask(projectSlug, task.id, { directorThreadId: newId });
43183
43791
  }
43184
43792
  });
43793
+ if (retirement.retire && await runHasLease(deps, projectSlug, runId, leaseId)) {
43794
+ let persisted = true;
43795
+ const retiredThreadId = await deps.client.retireThread(projectSlug, task.id, threadId).catch(() => {
43796
+ persisted = false;
43797
+ return null;
43798
+ });
43799
+ if (persisted) {
43800
+ await postRunFeed(deps.client, projectSlug, runId, task.id, "thread_retired", {
43801
+ reason: retirement.reason
43802
+ });
43803
+ }
43804
+ console.warn(
43805
+ JSON.stringify({
43806
+ event: persisted ? "thread_retired" : "thread_retirement_write_failed",
43807
+ projectSlug,
43808
+ runId,
43809
+ taskId: task.id,
43810
+ reason: retirement.reason,
43811
+ retiredThreadId,
43812
+ newThreadId: threadId,
43813
+ // Says out loud what an unpersisted retirement costs, so the log line is actionable
43814
+ // rather than merely present.
43815
+ ...persisted ? {} : { impact: "counter not reset and task still on the old thread; later runs will re-retire until this write succeeds" }
43816
+ })
43817
+ );
43818
+ }
43185
43819
  if (!resumed) {
43186
43820
  const primingBudgetMs = remainingTurnBudgetMs();
43187
43821
  if (primingBudgetMs <= 0) {
@@ -43196,14 +43830,52 @@ async function driveRun(deps, projectSlug, runId) {
43196
43830
  );
43197
43831
  return;
43198
43832
  }
43199
- const primed = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, bundle, primingBudgetMs, "priming");
43833
+ const primed = recordTurn(
43834
+ await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, bundle, primingBudgetMs, "priming", effortDecision?.level)
43835
+ );
43200
43836
  if (!primed.ok) {
43837
+ if (retirement.retire && task.directorThreadId) {
43838
+ await deps.client.updateTask(projectSlug, task.id, { directorThreadId: task.directorThreadId }).catch(() => {
43839
+ });
43840
+ console.warn(
43841
+ JSON.stringify({
43842
+ event: "thread_retirement_reverted",
43843
+ projectSlug,
43844
+ runId,
43845
+ taskId: task.id,
43846
+ reason: retirement.reason,
43847
+ restoredThreadId: task.directorThreadId,
43848
+ detail: "re-prime failed; predecessor thread restored"
43849
+ })
43850
+ );
43851
+ }
43201
43852
  await resetWedgedThread(deps, projectSlug, task.id, primed.interruptFailed);
43202
43853
  await failRun(deps, projectSlug, task, runId, leaseId, primed.error);
43203
43854
  return;
43204
43855
  }
43205
43856
  }
43206
- const executionPrompt = resumed ? `${bundle}
43857
+ const priorRuns = context.runs.filter((r) => r.id !== runId);
43858
+ const priorRun = priorRuns[priorRuns.length - 1] ?? null;
43859
+ const useDelta = resumed && !warmDeltaDisabled() && priorRun !== null;
43860
+ let resumeContent;
43861
+ if (useDelta) {
43862
+ const cutoffTime = priorRun.finishedAt ?? priorRun.createdAt;
43863
+ const newArtifacts = context.artifacts.filter((a) => a.createdAt > cutoffTime);
43864
+ resumeContent = renderRunDelta({
43865
+ task: {
43866
+ id: task.id,
43867
+ status: task.status,
43868
+ latestJudgement: task.latestJudgement,
43869
+ latestJudgementKind: task.latestJudgementKind
43870
+ },
43871
+ newlyFinishedRuns: [priorRun],
43872
+ newArtifacts,
43873
+ omittedArtifactCount: context.omittedArtifactCount
43874
+ });
43875
+ } else {
43876
+ resumeContent = bundle;
43877
+ }
43878
+ const executionPrompt = resumed ? `${resumeContent}
43207
43879
 
43208
43880
  Continue executing task ${task.id}. Make the next concrete increment of progress and record artifacts/judgements via MCP tools.` : `Continue executing task ${task.id}. Make the next concrete increment of progress and record artifacts/judgements via MCP tools.`;
43209
43881
  const workBudgetMs = remainingTurnBudgetMs();
@@ -43219,7 +43891,9 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
43219
43891
  );
43220
43892
  return;
43221
43893
  }
43222
- const outcome = await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, executionPrompt, workBudgetMs, "work");
43894
+ const outcome = recordTurn(
43895
+ await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, executionPrompt, workBudgetMs, "work", effortDecision?.level)
43896
+ );
43223
43897
  if (!outcome.ok) {
43224
43898
  await resetWedgedThread(deps, projectSlug, task.id, outcome.interruptFailed);
43225
43899
  await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
@@ -43243,16 +43917,19 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
43243
43917
  })
43244
43918
  );
43245
43919
  const completionPrompt = buildCompletionTurnPrompt(task.id, readiness.task.status);
43246
- const completionOutcome = await driveTurn(
43247
- deps,
43248
- codex,
43249
- projectSlug,
43250
- runId,
43251
- leaseId,
43252
- threadId,
43253
- completionPrompt,
43254
- completionBudget,
43255
- "completion"
43920
+ const completionOutcome = recordTurn(
43921
+ await driveTurn(
43922
+ deps,
43923
+ codex,
43924
+ projectSlug,
43925
+ runId,
43926
+ leaseId,
43927
+ threadId,
43928
+ completionPrompt,
43929
+ completionBudget,
43930
+ "completion",
43931
+ effortDecision?.level
43932
+ )
43256
43933
  );
43257
43934
  if (!completionOutcome.ok) {
43258
43935
  await resetWedgedThread(deps, projectSlug, task.id, completionOutcome.interruptFailed);
@@ -43431,9 +44108,97 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
43431
44108
  error51 instanceof Error ? error51.message : String(error51)
43432
44109
  );
43433
44110
  } finally {
44111
+ await deps.client.updateExecutionRun(projectSlug, runId, {
44112
+ promptBytes: runTelemetry.promptBytes,
44113
+ turnCount: runTelemetry.turnCount,
44114
+ inputTokens: runTelemetry.usage?.inputTokens ?? null,
44115
+ cachedInputTokens: runTelemetry.usage?.cachedInputTokens ?? null,
44116
+ outputTokens: runTelemetry.usage?.outputTokens ?? null,
44117
+ // Slice 7 (7.4): record the level actually used and, when it was an escalation, what caused
44118
+ // it — so "why did this run cost that much?" is answerable after the fact. NULL when the
44119
+ // slice is disabled, which per D6 reads as "not set" rather than a level we chose.
44120
+ effort: effortDecision?.level ?? null,
44121
+ effortEscalationReason: effortDecision?.escalationReason ?? null
44122
+ }).catch((error51) => {
44123
+ console.warn(
44124
+ JSON.stringify({
44125
+ event: "run_telemetry_write_failed",
44126
+ projectSlug,
44127
+ runId,
44128
+ detail: error51 instanceof Error ? error51.message : String(error51)
44129
+ })
44130
+ );
44131
+ });
44132
+ await writeServerOwnedTaskState(deps, projectSlug, task.id, runId, leaseId).catch((error51) => {
44133
+ console.warn(
44134
+ JSON.stringify({
44135
+ event: "task_state_server_write_failed",
44136
+ projectSlug,
44137
+ runId,
44138
+ taskId: task.id,
44139
+ detail: error51 instanceof Error ? error51.message : String(error51)
44140
+ })
44141
+ );
44142
+ });
43434
44143
  disposeCodex();
43435
44144
  }
43436
44145
  }
44146
+ async function writeServerOwnedTaskState(deps, projectSlug, taskId, runId, leaseId) {
44147
+ const run3 = await deps.client.getExecutionRun(projectSlug, runId).catch(() => null);
44148
+ if (!run3) {
44149
+ console.warn(
44150
+ JSON.stringify({
44151
+ event: "task_state_server_write_skipped",
44152
+ projectSlug,
44153
+ runId,
44154
+ taskId,
44155
+ detail: "could not read the run's state_seq; skipped rather than writing a defaulted ordering value"
44156
+ })
44157
+ );
44158
+ return;
44159
+ }
44160
+ if (run3.leaseId !== null && run3.leaseId !== leaseId) {
44161
+ console.warn(
44162
+ JSON.stringify({
44163
+ event: "task_state_server_write_skipped",
44164
+ projectSlug,
44165
+ runId,
44166
+ taskId,
44167
+ detail: "the run is held by another lease (reclaimed); refusing to write state for a run we no longer own"
44168
+ })
44169
+ );
44170
+ return;
44171
+ }
44172
+ let artifacts = null;
44173
+ try {
44174
+ artifacts = await deps.client.listExecutionArtifacts(projectSlug, runId);
44175
+ } catch {
44176
+ artifacts = null;
44177
+ }
44178
+ let changed = null;
44179
+ if (artifacts !== null) {
44180
+ const summaries = artifacts.filter((artifact) => artifact.kind === "diff_summary").map((artifact) => JSON.stringify(artifact.payload));
44181
+ changed = summaries.length > 0 ? summaries.join("\n") : NO_FILE_CHANGES_THIS_RUN;
44182
+ }
44183
+ let latestVerification = null;
44184
+ if (artifacts !== null) {
44185
+ const verifications = artifacts.filter((artifact) => artifact.kind === "verification").map((artifact) => JSON.stringify(artifact.payload));
44186
+ latestVerification = verifications[verifications.length - 1] ?? NO_VERIFICATION_THIS_RUN;
44187
+ }
44188
+ await deps.client.upsertTaskState(projectSlug, taskId, {
44189
+ goal: null,
44190
+ acceptedRequirements: null,
44191
+ decisions: null,
44192
+ openProblems: null,
44193
+ nextActions: null,
44194
+ relevantFiles: null,
44195
+ filesChanged: changed,
44196
+ latestVerification,
44197
+ lastRunSeq: run3.stateSeq
44198
+ });
44199
+ }
44200
+ var NO_FILE_CHANGES_THIS_RUN = "no file changes recorded in this run";
44201
+ var NO_VERIFICATION_THIS_RUN = "not run in this run";
43437
44202
  function buildDeliverableAuthorPrompt(task, specMarkdown) {
43438
44203
  return [
43439
44204
  `Produce the deliverable for the task below. Write the FULL deliverable content as markdown.`,
@@ -43963,7 +44728,8 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
43963
44728
  break;
43964
44729
  }
43965
44730
  try {
43966
- outcome = await deps.runExclusive(
44731
+ outcome = await deps.run(
44732
+ "async",
43967
44733
  () => descriptor2.run(
43968
44734
  { client: deps.client, cwd: deps.cwd, codexExecutable: deps.codexExecutable },
43969
44735
  input
@@ -44038,7 +44804,7 @@ async function runAsyncBackstopSweep(deps, drive = (slug, jobId) => driveAsyncJo
44038
44804
  } catch {
44039
44805
  queued = [];
44040
44806
  }
44041
- for (const job of queued) await drive(slug, job.id);
44807
+ await Promise.all(queued.map((job) => drive(slug, job.id)));
44042
44808
  }
44043
44809
  }
44044
44810
 
@@ -44506,13 +45272,16 @@ function createInactivityWatchdog(opts) {
44506
45272
  const clearT = opts.clearTimeoutFn ?? clearTimeout;
44507
45273
  const now = opts.now ?? Date.now;
44508
45274
  const suspended = opts.suspendedMs ?? (() => 0);
45275
+ const excluded = opts.idleExcludedMs ?? (() => 0);
44509
45276
  let idleTimer = null;
44510
45277
  let maxTimer = null;
44511
45278
  let done = false;
44512
45279
  const startAt = now();
44513
45280
  const suspendedAtStart = suspended();
45281
+ const excludedAtStart = excluded();
44514
45282
  let lastPokeAt = startAt;
44515
45283
  let suspendedAtLastPoke = suspendedAtStart;
45284
+ let excludedAtLastPoke = excludedAtStart;
44516
45285
  const expire = (reason) => {
44517
45286
  if (done) return;
44518
45287
  done = true;
@@ -44530,7 +45299,7 @@ function createInactivityWatchdog(opts) {
44530
45299
  };
44531
45300
  const onIdleDeadline = () => {
44532
45301
  if (done) return;
44533
- const awakeSilence = now() - lastPokeAt - (suspended() - suspendedAtLastPoke);
45302
+ const awakeSilence = now() - lastPokeAt - (suspended() - suspendedAtLastPoke) - (excluded() - excludedAtLastPoke);
44534
45303
  if (awakeSilence < opts.idleMs) {
44535
45304
  armIdle(opts.idleMs - awakeSilence);
44536
45305
  return;
@@ -44554,6 +45323,7 @@ function createInactivityWatchdog(opts) {
44554
45323
  if (idleTimer) clearT(idleTimer);
44555
45324
  lastPokeAt = now();
44556
45325
  suspendedAtLastPoke = suspended();
45326
+ excludedAtLastPoke = excluded();
44557
45327
  armIdle(opts.idleMs);
44558
45328
  },
44559
45329
  cancel: () => {
@@ -44563,8 +45333,8 @@ function createInactivityWatchdog(opts) {
44563
45333
  }
44564
45334
  };
44565
45335
  }
44566
- var DEFAULT_CHAT_TURN_IDLE_SECONDS = 180;
44567
- var DEFAULT_CHAT_TURN_MAX_SECONDS = 1800;
45336
+ var DEFAULT_CHAT_TURN_IDLE_SECONDS = 300;
45337
+ var DEFAULT_CHAT_TURN_MAX_SECONDS = 2700;
44568
45338
  function readPositiveSeconds(name, fallback) {
44569
45339
  const raw = (process.env[name] ?? "").trim();
44570
45340
  const parsed = Number.parseInt(raw, 10);
@@ -44577,6 +45347,64 @@ function chatTurnMaxMs() {
44577
45347
  return readPositiveSeconds("MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_MAX_SECONDS", DEFAULT_CHAT_TURN_MAX_SECONDS) * 1e3;
44578
45348
  }
44579
45349
 
45350
+ // src/tool-busy.ts
45351
+ function createToolBusyClock(opts) {
45352
+ const now = opts?.now ?? Date.now;
45353
+ const open = /* @__PURE__ */ new Set();
45354
+ let total = 0;
45355
+ let segmentStart = null;
45356
+ function start(itemId) {
45357
+ if (!open.has(itemId)) {
45358
+ open.add(itemId);
45359
+ if (open.size === 1) {
45360
+ segmentStart = now();
45361
+ }
45362
+ }
45363
+ }
45364
+ function end(itemId) {
45365
+ if (open.has(itemId)) {
45366
+ open.delete(itemId);
45367
+ if (open.size === 0) {
45368
+ const nowVal = now();
45369
+ if (segmentStart !== null) {
45370
+ total += nowVal - segmentStart;
45371
+ segmentStart = null;
45372
+ }
45373
+ }
45374
+ }
45375
+ }
45376
+ function endAll() {
45377
+ if (open.size > 0) {
45378
+ const nowVal = now();
45379
+ if (segmentStart !== null) {
45380
+ total += nowVal - segmentStart;
45381
+ segmentStart = null;
45382
+ }
45383
+ open.clear();
45384
+ }
45385
+ }
45386
+ function ms() {
45387
+ const nowVal = now();
45388
+ if (open.size > 0 && segmentStart !== null) {
45389
+ return total + (nowVal - segmentStart);
45390
+ }
45391
+ return total;
45392
+ }
45393
+ return { start, end, endAll, ms };
45394
+ }
45395
+ var TOOL_ITEM_TYPES = /* @__PURE__ */ new Set(["commandExecution", "mcpToolCall", "fileChange"]);
45396
+ function applyToolBusyNotification(clock, method, params) {
45397
+ if (method !== "item/started" && method !== "item/completed") return;
45398
+ if (typeof params !== "object" || params === null) return;
45399
+ const p = params;
45400
+ const item = typeof p.item === "object" && p.item !== null ? p.item : void 0;
45401
+ if (typeof item?.type !== "string" || !TOOL_ITEM_TYPES.has(item.type)) return;
45402
+ const id = typeof item.id === "string" ? item.id : typeof p.itemId === "string" ? p.itemId : void 0;
45403
+ if (id === void 0) return;
45404
+ if (method === "item/started") clock.start(id);
45405
+ else clock.end(id);
45406
+ }
45407
+
44580
45408
  // src/worker-token.ts
44581
45409
  var import_node_crypto7 = require("node:crypto");
44582
45410
  var import_node_fs9 = require("node:fs");
@@ -48581,11 +49409,16 @@ function parseVerdict2(text) {
48581
49409
  let last = null;
48582
49410
  while ((match = re.exec(text)) !== null) last = match;
48583
49411
  if (!last) {
48584
- return { verdict: "fail", summary: "no parseable VERDICT line in verification output", turnRef: null };
49412
+ return {
49413
+ verdict: "fail",
49414
+ summary: "no parseable VERDICT line in verification output",
49415
+ turnRef: null,
49416
+ fullOutput: text
49417
+ };
48585
49418
  }
48586
49419
  const verdict = (last[1] ?? "").toLowerCase() === "pass" ? "pass" : "fail";
48587
49420
  const summary = (last[2] ?? "").trim() || `verification ${verdict}`;
48588
- return { verdict, summary, turnRef: null };
49421
+ return { verdict, summary, turnRef: null, fullOutput: text };
48589
49422
  }
48590
49423
  function isBlockedVerificationHost(hostname3) {
48591
49424
  const h = hostname3.toLowerCase().replace(/^\[|\]$/g, "");
@@ -50685,7 +51518,7 @@ function createBridgeMessageHandler(deps) {
50685
51518
  }
50686
51519
  };
50687
51520
  try {
50688
- await deps.runExclusive(async () => {
51521
+ await deps.run("chat", async () => {
50689
51522
  if (currentReplayBuf.state === "interrupted" || currentReplayBuf.state === "completed") {
50690
51523
  logCtx?.logger.logOut({
50691
51524
  spanId,
@@ -50714,7 +51547,9 @@ function createBridgeMessageHandler(deps) {
50714
51547
  outputEvents += 1;
50715
51548
  sendInTurn({ type: "event", directorSessionId, event });
50716
51549
  };
51550
+ const toolBusy = createToolBusyClock();
50717
51551
  const unsub = codexClient.onNotification((method, params) => {
51552
+ applyToolBusyNotification(toolBusy, method, params);
50718
51553
  pokeActivity();
50719
51554
  if (method === "account/rateLimits/updated") {
50720
51555
  const snapshot = params?.rateLimits ?? null;
@@ -50742,6 +51577,7 @@ function createBridgeMessageHandler(deps) {
50742
51577
  idleMs,
50743
51578
  maxMs,
50744
51579
  ...deps.suspendedMs ? { suspendedMs: deps.suspendedMs } : {},
51580
+ idleExcludedMs: () => toolBusy.ms(),
50745
51581
  onExpire: (reason) => {
50746
51582
  rejectWatchdog?.(
50747
51583
  new Error(
@@ -50845,6 +51681,7 @@ ${effectiveText}` : effectiveText;
50845
51681
  }
50846
51682
  watchdog.cancel();
50847
51683
  unsub();
51684
+ toolBusy.endAll();
50848
51685
  thread.cancel = null;
50849
51686
  }
50850
51687
  });
@@ -50964,8 +51801,8 @@ async function startDirectorBridge() {
50964
51801
  const turnLock = createTurnLock();
50965
51802
  let lastTurnEndedAt = Date.now();
50966
51803
  const wrappedTurnLock = {
50967
- runExclusive(fn) {
50968
- return turnLock.runExclusive(async () => {
51804
+ run(lane, fn) {
51805
+ return turnLock.run(lane, async () => {
50969
51806
  try {
50970
51807
  return await fn();
50971
51808
  } finally {
@@ -51008,7 +51845,11 @@ async function startDirectorBridge() {
51008
51845
  }
51009
51846
  });
51010
51847
  const codexProbeHandle = startCodexProbe();
51011
- const consumerState = { inFlightRunId: null, lastClaimAt: null };
51848
+ const consumerState = {
51849
+ inFlightRunIds: /* @__PURE__ */ new Set(),
51850
+ lastClaimedRunId: null,
51851
+ lastClaimAt: null
51852
+ };
51012
51853
  const health = createServiceHealth();
51013
51854
  try {
51014
51855
  migrateRetiredApiBaseUrls();
@@ -51087,7 +51928,7 @@ async function startDirectorBridge() {
51087
51928
  // handle returned by acquireExecutorCodex (Fix A + G), so there is no shared
51088
51929
  // mutable `activeExecutorClient` slot to race a concurrent chat/bootstrap turn.
51089
51930
  releaseCodex: () => codexKeepalive.release(),
51090
- runExclusive: wrappedTurnLock.runExclusive,
51931
+ run: wrappedTurnLock.run,
51091
51932
  staleSeconds,
51092
51933
  touchIntervalMs: Math.min(DEFAULT_TOUCH_INTERVAL_MS, Math.floor(staleSeconds * 1e3 / 2)),
51093
51934
  codexCwd: config2.codexCwd,
@@ -51114,7 +51955,7 @@ async function startDirectorBridge() {
51114
51955
  // ADR 0007 self-review: critique the committed diff on the executor's own client before
51115
51956
  // push/PR. Reuses the verification turn-runner (acquireThread + collect assistant text).
51116
51957
  selfReviewTurn: createSelfReviewTurn({
51117
- runTurn: (codexClient, cwd, prompt) => runCollectedCodexTurn(codexClient, prompt, cwd, SELF_REVIEW_BASE_INSTRUCTIONS)
51958
+ runTurn: (codexClient, cwd, prompt) => wrappedTurnLock.run("chat", () => runCollectedCodexTurn(codexClient, prompt, cwd, SELF_REVIEW_BASE_INSTRUCTIONS))
51118
51959
  }),
51119
51960
  // ADR 0015 deploy gate: after a pushed run reaches dev_deploy/prod_deploy, the backstop sweep
51120
51961
  // dispatches the deploy + polls + runs a verification turn against the deployed env URL in a
@@ -51153,7 +51994,7 @@ async function startDirectorBridge() {
51153
51994
  }
51154
51995
  };
51155
51996
  },
51156
- runTurn: (codexClient, prompt, cwd) => runCollectedCodexTurn(codexClient, prompt, cwd, VERIFICATION_BASE_INSTRUCTIONS)
51997
+ runTurn: (codexClient, prompt, cwd) => wrappedTurnLock.run("chat", () => runCollectedCodexTurn(codexClient, prompt, cwd, VERIFICATION_BASE_INSTRUCTIONS))
51157
51998
  }),
51158
51999
  budgetSeconds: deployPollBudgetSeconds()
51159
52000
  },
@@ -51178,7 +52019,7 @@ async function startDirectorBridge() {
51178
52019
  const bridgeInstanceId = (0, import_node_crypto8.randomUUID)();
51179
52020
  const asyncConsumerDeps = {
51180
52021
  client,
51181
- runExclusive: wrappedTurnLock.runExclusive,
52022
+ run: wrappedTurnLock.run,
51182
52023
  cwd: config2.codexCwd ?? process.cwd(),
51183
52024
  codexExecutable: config2.codexExecutable,
51184
52025
  staleSeconds,
@@ -51194,40 +52035,34 @@ async function startDirectorBridge() {
51194
52035
  sink({ type: "async-result", directorSessionId, projectSlug: slug, deliveryKind, body });
51195
52036
  }
51196
52037
  };
51197
- let execChain = Promise.resolve();
51198
- const enqueueRun = (job) => {
51199
- execChain = execChain.then(job, job);
51200
- };
51201
- const driveTracked = (slug, runId) => enqueueRun(async () => {
51202
- consumerState.inFlightRunId = runId;
52038
+ const driveTracked = (slug, runId) => {
52039
+ consumerState.inFlightRunIds.add(runId);
52040
+ consumerState.lastClaimedRunId = runId;
51203
52041
  consumerState.lastClaimAt = (/* @__PURE__ */ new Date()).toISOString();
51204
- try {
51205
- await driveRun(consumerDeps, slug, runId);
51206
- } catch (error51) {
52042
+ void driveRun(consumerDeps, slug, runId).catch((error51) => {
51207
52043
  process.stderr.write(`[mrrlin-mcp director-bridge] drive failed for ${slug}/${runId}: ${error51 instanceof Error ? error51.message : String(error51)}
51208
52044
  `);
51209
- } finally {
51210
- consumerState.inFlightRunId = null;
51211
- }
51212
- });
52045
+ }).finally(() => {
52046
+ consumerState.inFlightRunIds.delete(runId);
52047
+ });
52048
+ };
51213
52049
  const driveRunNow = (slug, runId) => {
51214
- consumerState.inFlightRunId = runId;
52050
+ consumerState.inFlightRunIds.add(runId);
52051
+ consumerState.lastClaimedRunId = runId;
51215
52052
  consumerState.lastClaimAt = (/* @__PURE__ */ new Date()).toISOString();
51216
52053
  void driveRun(consumerDeps, slug, runId).catch((error51) => {
51217
52054
  process.stderr.write(`[mrrlin-mcp director-bridge] drive failed for ${slug}/${runId}: ${error51 instanceof Error ? error51.message : String(error51)}
51218
52055
  `);
51219
52056
  }).finally(() => {
51220
- consumerState.inFlightRunId = null;
52057
+ consumerState.inFlightRunIds.delete(runId);
51221
52058
  });
51222
52059
  };
51223
- const driveAsyncTracked = (slug, jobId) => enqueueRun(async () => {
51224
- try {
51225
- await driveAsyncJob(asyncConsumerDeps, slug, jobId);
51226
- } catch (error51) {
52060
+ const driveAsyncTracked = (slug, jobId) => {
52061
+ void driveAsyncJob(asyncConsumerDeps, slug, jobId).catch((error51) => {
51227
52062
  process.stderr.write(`[mrrlin-mcp director-bridge] async drive failed for ${slug}/${jobId}: ${error51 instanceof Error ? error51.message : String(error51)}
51228
52063
  `);
51229
- }
51230
- });
52064
+ });
52065
+ };
51231
52066
  const driveAsyncNow = (slug, jobId) => {
51232
52067
  void driveAsyncJob(asyncConsumerDeps, slug, jobId).catch((error51) => {
51233
52068
  process.stderr.write(`[mrrlin-mcp director-bridge] async drive failed for ${slug}/${jobId}: ${error51 instanceof Error ? error51.message : String(error51)}
@@ -51240,7 +52075,15 @@ async function startDirectorBridge() {
51240
52075
  port: config2.port,
51241
52076
  token: config2.token,
51242
52077
  allowedOrigins: config2.allowedOrigins,
51243
- getHealth: () => ({ ok: true, inFlightRunId: consumerState.inFlightRunId, lastClaimAt: consumerState.lastClaimAt, service: health.snapshot() })
52078
+ getHealth: () => ({
52079
+ ok: true,
52080
+ // Most recently claimed run, or null once nothing is in flight — kept for existing consumers.
52081
+ inFlightRunId: consumerState.inFlightRunIds.size > 0 ? consumerState.lastClaimedRunId : null,
52082
+ inFlightRunIds: Array.from(consumerState.inFlightRunIds),
52083
+ inFlightCount: consumerState.inFlightRunIds.size,
52084
+ lastClaimAt: consumerState.lastClaimAt,
52085
+ service: health.snapshot()
52086
+ })
51244
52087
  })
51245
52088
  );
51246
52089
  const threads = /* @__PURE__ */ new Map();
@@ -51303,7 +52146,7 @@ async function startDirectorBridge() {
51303
52146
  const sharedHandlerDeps = {
51304
52147
  acquireCodex: () => codexKeepalive.acquire(),
51305
52148
  releaseCodex: () => codexKeepalive.release(),
51306
- runExclusive: wrappedTurnLock.runExclusive,
52149
+ run: wrappedTurnLock.run,
51307
52150
  suspendedMs: () => suspensionDetector.suspendedMs(),
51308
52151
  buildStartParams: (msg, desiredModel) => buildThreadStartParams(msg, config2, desiredModel),
51309
52152
  turnInactivity: () => ({ idleMs: chatTurnIdleMs(), maxMs: chatTurnMaxMs() }),
@@ -51449,7 +52292,7 @@ async function startDirectorBridge() {
51449
52292
  await bridgeHealth.recordSuccess();
51450
52293
  } catch (error51) {
51451
52294
  process.stderr.write(
51452
- `[mrrlin-mcp director-bridge] ${label} failed: ${error51 instanceof Error ? error51.message : String(error51)}
52295
+ `[mrrlin-mcp director-bridge] ${label} failed: ${describeError(error51)}
51453
52296
  `
51454
52297
  );
51455
52298
  bridgeHealth.recordFailure(id, error51);
@@ -51461,34 +52304,28 @@ async function startDirectorBridge() {
51461
52304
  const scheduleClaimLoop = () => {
51462
52305
  if (claimLoopStopped) return;
51463
52306
  claimTimer = setTimeout(() => {
51464
- enqueueRun(
51465
- () => runTrackedSweep(
51466
- "claimSweep",
51467
- "claim sweep",
51468
- () => runBackstopSweep(consumerDeps, (slug, runId) => {
51469
- driveTracked(slug, runId);
51470
- return Promise.resolve();
51471
- })
51472
- )
51473
- );
51474
- enqueueRun(
51475
- () => runTrackedSweep(
51476
- "asyncSweep",
51477
- "async sweep",
51478
- () => runAsyncBackstopSweep(asyncConsumerDeps, (slug, jobId) => {
51479
- driveAsyncTracked(slug, jobId);
51480
- return Promise.resolve();
51481
- })
51482
- )
52307
+ void runTrackedSweep(
52308
+ "claimSweep",
52309
+ "claim sweep",
52310
+ () => runBackstopSweep(consumerDeps, (slug, runId) => {
52311
+ driveTracked(slug, runId);
52312
+ return Promise.resolve();
52313
+ })
51483
52314
  );
51484
- enqueueRun(
51485
- () => runOrphanWorktreeSweep(client, {
51486
- checkoutRegistry,
51487
- worktreeRoot: import_node_path18.default.join(config2.stateDir, "worktrees"),
51488
- locksDir: import_node_path18.default.join(config2.stateDir, "locks")
52315
+ void runTrackedSweep(
52316
+ "asyncSweep",
52317
+ "async sweep",
52318
+ () => runAsyncBackstopSweep(asyncConsumerDeps, (slug, jobId) => {
52319
+ driveAsyncTracked(slug, jobId);
52320
+ return Promise.resolve();
51489
52321
  })
51490
52322
  );
51491
- enqueueRun(async () => {
52323
+ void runOrphanWorktreeSweep(client, {
52324
+ checkoutRegistry,
52325
+ worktreeRoot: import_node_path18.default.join(config2.stateDir, "worktrees"),
52326
+ locksDir: import_node_path18.default.join(config2.stateDir, "locks")
52327
+ });
52328
+ {
51492
52329
  const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
51493
52330
  now: Date.now(),
51494
52331
  suspendedMs: () => suspensionDetector.suspendedMs(),
@@ -51526,7 +52363,7 @@ async function startDirectorBridge() {
51526
52363
  payload: { removed: tombstonesRemoved }
51527
52364
  });
51528
52365
  }
51529
- });
52366
+ }
51530
52367
  scheduleClaimLoop();
51531
52368
  }, claimIntervalMs());
51532
52369
  claimTimer.unref?.();
@@ -51564,7 +52401,7 @@ async function startDirectorBridge() {
51564
52401
  agentPoolLoopHandle = startAgentPoolLoop({
51565
52402
  push: (snapshot) => client.putOperatorAgentPool(snapshot),
51566
52403
  onError: (err) => process.stderr.write(
51567
- `[mrrlin-mcp director-bridge] agent-pool snapshot push failed: ${err instanceof Error ? err.message : String(err)}
52404
+ `[mrrlin-mcp director-bridge] agent-pool snapshot push failed: ${describeError(err)}
51568
52405
  `
51569
52406
  )
51570
52407
  });
@@ -56500,31 +57337,12 @@ var mcpToolNames = {
56500
57337
  restartExecutionRun: "restart_execution_run",
56501
57338
  resolveHandoff: "resolve_handoff",
56502
57339
  recordProducedUrl: "record_produced_url",
57340
+ updateTaskState: "update_task_state",
56503
57341
  registerLocalCheckout: "register_local_checkout",
56504
57342
  /** Compact-context retrieval: rehydrate a mrrlin:// handle into the canonical full record. */
56505
57343
  retrieveContext: "retrieve_context"
56506
57344
  };
56507
57345
 
56508
- // src/context-compact/policy.ts
56509
- var DEFAULT_THRESHOLD_CHARS = 12e3;
56510
- var DEFAULT_MAX_ITEMS = 25;
56511
- function readPositiveInt(value, fallback) {
56512
- const raw = (value ?? "").trim();
56513
- if (!raw) return fallback;
56514
- const parsed = Number.parseInt(raw, 10);
56515
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
56516
- }
56517
- function readCompactPolicy(env = process.env) {
56518
- const disabled = (env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() === "1";
56519
- return {
56520
- enabled: !disabled,
56521
- thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
56522
- maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
56523
- debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1",
56524
- retrievalBudgetDisable: (env.MRRLIN_RETRIEVAL_BUDGET_DISABLE ?? "").trim() === "1"
56525
- };
56526
- }
56527
-
56528
57346
  // src/context-compact/handles.ts
56529
57347
  var MRRLIN_HANDLE_SCHEME = "mrrlin://";
56530
57348
  var SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
@@ -56599,7 +57417,7 @@ function parseMrrlinHandle(value) {
56599
57417
  var NOTES_EXCERPT_CHARS = 200;
56600
57418
  var TITLE_TRUNCATE_CHARS = 160;
56601
57419
  var EXCERPT_TRUNCATE_CHARS = 240;
56602
- function truncate(value, maxChars) {
57420
+ function truncate2(value, maxChars) {
56603
57421
  if (value.length <= maxChars) return value;
56604
57422
  return `${value.slice(0, Math.max(0, maxChars - 1))}\u2026`;
56605
57423
  }
@@ -56615,7 +57433,7 @@ function compactTaskList(projectSlug, tasks, maxItems) {
56615
57433
  const slice = tasks.slice(0, included);
56616
57434
  const items = slice.map((t) => ({
56617
57435
  id: t.id,
56618
- title: truncate(t.title, TITLE_TRUNCATE_CHARS),
57436
+ title: truncate2(t.title, TITLE_TRUNCATE_CHARS),
56619
57437
  status: t.status,
56620
57438
  type: t.type,
56621
57439
  category: t.category,
@@ -56626,8 +57444,8 @@ function compactTaskList(projectSlug, tasks, maxItems) {
56626
57444
  assignee: t.assignee,
56627
57445
  planId: t.planId,
56628
57446
  specWikiPageId: t.specWikiPageId,
56629
- notesExcerpt: t.notes ? truncate(t.notes, NOTES_EXCERPT_CHARS) : null,
56630
- latestJudgementExcerpt: t.latestJudgement ? truncate(t.latestJudgement, NOTES_EXCERPT_CHARS) : null,
57447
+ notesExcerpt: t.notes ? truncate2(t.notes, NOTES_EXCERPT_CHARS) : null,
57448
+ latestJudgementExcerpt: t.latestJudgement ? truncate2(t.latestJudgement, NOTES_EXCERPT_CHARS) : null,
56631
57449
  handle: formatTaskHandle(projectSlug, t.id)
56632
57450
  }));
56633
57451
  const handles = slice.map((t) => ({
@@ -56648,7 +57466,7 @@ function compactWikiPageList(projectSlug, pages, maxItems) {
56648
57466
  const slice = pages.slice(0, included);
56649
57467
  const items = slice.map((p) => ({
56650
57468
  id: p.id,
56651
- title: truncate(p.title, TITLE_TRUNCATE_CHARS),
57469
+ title: truncate2(p.title, TITLE_TRUNCATE_CHARS),
56652
57470
  folder: p.folder,
56653
57471
  path: p.path,
56654
57472
  updatedAt: p.updatedAt,
@@ -56675,7 +57493,7 @@ function compactWikiSearch(projectSlug, results, maxItems) {
56675
57493
  const slice = results.slice(0, included);
56676
57494
  const items = slice.map((r) => ({
56677
57495
  pageId: r.id,
56678
- title: truncate(r.title, TITLE_TRUNCATE_CHARS),
57496
+ title: truncate2(r.title, TITLE_TRUNCATE_CHARS),
56679
57497
  folder: r.folder,
56680
57498
  path: r.path,
56681
57499
  rank: r.rank,
@@ -56683,7 +57501,7 @@ function compactWikiSearch(projectSlug, results, maxItems) {
56683
57501
  ageDays: r.ageDays,
56684
57502
  memoryClass: r.memoryClass,
56685
57503
  precedence: r.precedence,
56686
- snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
57504
+ snippet: truncate2(r.snippet, EXCERPT_TRUNCATE_CHARS),
56687
57505
  handle: formatWikiHandle(projectSlug, r.id)
56688
57506
  }));
56689
57507
  const handles = slice.map((r) => ({
@@ -56712,7 +57530,7 @@ function compactProjectSnapshot(projectSlug, snapshot, maxItems) {
56712
57530
  }
56713
57531
  const compactPlans = snapshot.plans.map((p) => ({
56714
57532
  id: p.id,
56715
- name: truncate(p.name, TITLE_TRUNCATE_CHARS),
57533
+ name: truncate2(p.name, TITLE_TRUNCATE_CHARS),
56716
57534
  status: p.status,
56717
57535
  taskCount: p.taskCount,
56718
57536
  closedTaskCount: p.closedTaskCount,
@@ -56784,7 +57602,7 @@ function compactArtifactPayload(artifact) {
56784
57602
  cwd,
56785
57603
  exitCode,
56786
57604
  firstError,
56787
- tail: truncate(tail2, ARTIFACT_PAYLOAD_BUDGET),
57605
+ tail: truncate2(tail2, ARTIFACT_PAYLOAD_BUDGET),
56788
57606
  totalLines: lines2.length
56789
57607
  };
56790
57608
  }
@@ -56792,7 +57610,7 @@ function compactArtifactPayload(artifact) {
56792
57610
  return {
56793
57611
  status: payload.status ?? null,
56794
57612
  command: payload.command ?? null,
56795
- summary: typeof payload.summary === "string" ? truncate(payload.summary, ARTIFACT_PAYLOAD_BUDGET) : null,
57613
+ summary: typeof payload.summary === "string" ? truncate2(payload.summary, ARTIFACT_PAYLOAD_BUDGET) : null,
56796
57614
  failed: Array.isArray(payload.failedTests) ? payload.failedTests.slice(0, 25) : null
56797
57615
  };
56798
57616
  }
@@ -56810,7 +57628,7 @@ function compactArtifactPayload(artifact) {
56810
57628
  case "checkpoint":
56811
57629
  default: {
56812
57630
  const flattened = JSON.stringify(payload);
56813
- return { excerpt: truncate(flattened, ARTIFACT_PAYLOAD_BUDGET) };
57631
+ return { excerpt: truncate2(flattened, ARTIFACT_PAYLOAD_BUDGET) };
56814
57632
  }
56815
57633
  }
56816
57634
  }
@@ -56849,7 +57667,7 @@ function compactTaskEvents(projectSlug, taskId, events, maxItems) {
56849
57667
  createdAt: e.createdAt,
56850
57668
  autonomyLevel: e.autonomyLevel,
56851
57669
  autonomyReason: e.autonomyReason,
56852
- bodyExcerpt: e.body ? truncate(e.body, NOTES_EXCERPT_CHARS) : null
57670
+ bodyExcerpt: e.body ? truncate2(e.body, NOTES_EXCERPT_CHARS) : null
56853
57671
  }));
56854
57672
  const handles = [
56855
57673
  {
@@ -56871,7 +57689,7 @@ function compactGeneric(value, maxItems, projectSlug) {
56871
57689
  const included = Math.min(total, maxItems);
56872
57690
  const slice = value.slice(0, included).map((entry) => {
56873
57691
  const text2 = typeof entry === "string" ? entry : JSON.stringify(entry);
56874
- return truncate(text2, ARTIFACT_PAYLOAD_BUDGET);
57692
+ return truncate2(text2, ARTIFACT_PAYLOAD_BUDGET);
56875
57693
  });
56876
57694
  return {
56877
57695
  summary: `Compact array view: ${total} entries, ${included} included.`,
@@ -56902,7 +57720,7 @@ function compactGeneric(value, maxItems, projectSlug) {
56902
57720
  const text = typeof value === "string" ? value : JSON.stringify(value);
56903
57721
  return {
56904
57722
  summary: "Compact scalar view.",
56905
- items: [truncate(text, ARTIFACT_PAYLOAD_BUDGET)],
57723
+ items: [truncate2(text, ARTIFACT_PAYLOAD_BUDGET)],
56906
57724
  handles: [],
56907
57725
  counts: { total: 1, included: 1, omitted: 0 }
56908
57726
  };
@@ -57072,6 +57890,7 @@ var updateExecutionRunInputSchema = runScopedSchema.merge(mrrlinExecutionRunUpda
57072
57890
  var listExecutionArtifactsInputSchema = runScopedSchema;
57073
57891
  var appendExecutionArtifactInputSchema = runScopedSchema.merge(mrrlinExecutionArtifactCreateSchema);
57074
57892
  var recordProducedUrlInputSchema = runScopedSchema.merge(mrrlinProducedUrlPayloadSchema);
57893
+ var updateTaskStateInputSchema = runScopedSchema.merge(mrrlinTaskStateReportSchema);
57075
57894
  var getProjectSchedulerPolicyInputSchema = projectScopedSchema;
57076
57895
  var upsertProjectSchedulerPolicyInputSchema = projectScopedSchema.merge(mrrlinProjectSchedulerPolicyUpsertSchema);
57077
57896
  var recordDeployWorkflowInputSchema = projectScopedSchema.extend({
@@ -57184,6 +58003,7 @@ var mcpToolInputSchemas = {
57184
58003
  [mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
57185
58004
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
57186
58005
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
58006
+ [mcpToolNames.updateTaskState]: updateTaskStateInputSchema,
57187
58007
  [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
57188
58008
  [mcpToolNames.retrieveContext]: retrieveContextInputSchema
57189
58009
  };
@@ -57235,6 +58055,7 @@ var mcpToolOperationIds = {
57235
58055
  [mcpToolNames.recordDeployWorkflow]: openApiOperationIds.upsertProjectDeployWorkflow,
57236
58056
  [mcpToolNames.updatePlan]: openApiOperationIds.updatePlan,
57237
58057
  [mcpToolNames.updateTask]: openApiOperationIds.updateTask,
58058
+ [mcpToolNames.updateTaskState]: openApiOperationIds.reportTaskState,
57238
58059
  [mcpToolNames.resetDirectorThread]: openApiOperationIds.resetDirectorThread,
57239
58060
  [mcpToolNames.archiveTask]: openApiOperationIds.archiveTask,
57240
58061
  [mcpToolNames.unarchiveTask]: openApiOperationIds.unarchiveTask,
@@ -57295,6 +58116,7 @@ var mcpToolDescriptions = {
57295
58116
  [mcpToolNames.runCodeReviewerGate]: `Review the current branch's code diff (vs baseRef) with a Code Reviewer gate \u2014 independent reviewers iterate review\u2192fix\u2192re-check (checkpoint, scope-limited reviser edits, typecheck/build fast-gate, rollback) until the diff converges or is blocked. Run before opening a PR. Pass extraHeads: ["security-analyst"] for sensitive diffs (auth, secrets, input handling, new endpoints).`,
57296
58117
  [mcpToolNames.appendExecutionArtifact]: "Append a durable artifact (plan, checkpoint, command output, test result, diff summary, self-review, error, deploy attempt, or handoff judgement) to an execution run.",
57297
58118
  [mcpToolNames.recordProducedUrl]: 'Call this after publishing each web page produced by the task, so the UI link opens the page itself instead of the deployed site root. Pass the page\'s full https URL (for external pages, e.g. "https://dev.to/<slug>") OR a path starting with "/" for the deploy environment (e.g. "/blog/<slug>") \u2014 paths are resolved against the run\'s deployed env URL at render time. Optional `label` (\u226440 chars) is shown next to the link; omit and the UI defaults to the page title. Call once per page; multiple calls dedupe by resolved URL in the UI.',
58119
+ [mcpToolNames.updateTaskState]: "Record where this task actually stands, in your own words, BEFORE you end your turn. This is the handoff the next run reads: it survives even when this conversation does not, because a thread is retired once it has accumulated too many runs and the next run then starts from this record plus the repository. Report what only you know \u2014 the goal as you now understand it, decisions you made AND WHY (a decision without its reason is the thing most often lost), problems still open, and the next concrete actions. Omit any field you learned nothing new about; an omitted field keeps its previous value rather than being cleared. Use one entry per line for decisions/next actions. Do not restate the spec or the file list \u2014 the next run gets those anyway.",
57298
58120
  [mcpToolNames.createExecutionRun]: 'Create a durable Director execution run for a task. Persists status, branch/SHA, workflow ids, and checkpoint cursor outside the chat transcript. For tasks that require a browser (login flows, UI verification, scraping), set prerequisites:[{mcp:"mrrlin-browser",reason:"<why a browser is needed>"}] so any resulting handoff offers a one-click "Launch browser & resume". When actually driving the browser inside the run, invoke tools from the `mrrlin-browser` MCP server ONLY (browser_navigate/browser_click/browser_snapshot/browser_type/etc.) \u2014 never from `playwright`, `chrome-devtools`, `puppeteer`, or any other browser MCP that may also be connected on the operator\'s box. Only `mrrlin-browser` uses the dedicated Chromium + persistent operator profile.',
57299
58121
  [mcpToolNames.getExecutionRun]: "Fetch a single execution run by id.",
57300
58122
  [mcpToolNames.listExecutionArtifacts]: "List artifacts captured during an execution run, in chronological order.",
@@ -57892,6 +58714,13 @@ function createMrrlinTools(options) {
57892
58714
  "Unable to append execution artifact.",
57893
58715
  async (c, { projectSlug, runId, ...input }) => await c.appendExecutionArtifact(projectSlug, runId, input)
57894
58716
  ),
58717
+ [mcpToolNames.updateTaskState]: makeTool(
58718
+ client,
58719
+ mcpToolNames.updateTaskState,
58720
+ "TASK_STATE_REPORT_FAILED",
58721
+ "Unable to record task state.",
58722
+ async (c, { projectSlug, runId, ...report }) => await c.reportTaskState(projectSlug, runId, report)
58723
+ ),
57895
58724
  [mcpToolNames.recordProducedUrl]: makeTool(
57896
58725
  client,
57897
58726
  mcpToolNames.recordProducedUrl,
@@ -58553,11 +59382,13 @@ Usage:
58553
59382
  - MRRLIN_WORKER_PORT (dispatch listener, default 8792)
58554
59383
  - MRRLIN_DIRECTOR_BRIDGE_CLAIM_INTERVAL_SECONDS (default 30)
58555
59384
  - MRRLIN_DIRECTOR_BRIDGE_STALE_SECONDS (default 300)
58556
- - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_IDLE_SECONDS (default 180;
59385
+ - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_IDLE_SECONDS (default 300;
58557
59386
  a chat turn is cut only after this long with NO codex
58558
- signal at all \u2014 long-but-active turns keep running)
58559
- - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_MAX_SECONDS (default 1800;
58560
- absolute ceiling for one chat turn regardless of activity)
59387
+ signal at all AND no tool call in flight \u2014 long-but-active
59388
+ turns, and long single tool calls, keep running)
59389
+ - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_MAX_SECONDS (default 2700;
59390
+ absolute ceiling for one chat turn regardless of activity;
59391
+ the only guard against a genuinely hung tool call)
58561
59392
  - MRRLIN_DIRECTOR_BRIDGE_CODEX_IDLE_SECONDS (default 300;
58562
59393
  how long the warm codex app-server is kept alive
58563
59394
  between turns before it is released)