@mrrlin-dev/mcp 0.3.59 → 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.
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,
@@ -21833,136 +21833,6 @@ async function acquireThread(codex, opts) {
21833
21833
  return { threadId: newId, resumed: false };
21834
21834
  }
21835
21835
 
21836
- // ../../packages/codex-client/dist/render-context-bundle.js
21837
- var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
21838
- var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
21839
- function renderContextBundle(input) {
21840
- const sections = [];
21841
- sections.push(renderTaskSummary(input.task));
21842
- sections.push(renderSpec(input.specMarkdown, input.compact));
21843
- sections.push(renderRunHistory(input.runs, input.openRunId));
21844
- sections.push(renderOpenRun(input.openRunId));
21845
- sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0, input.compact));
21846
- sections.push(renderAutonomyEvents(input.autonomyEvents));
21847
- return sections.join("\n\n");
21848
- }
21849
- function renderTaskSummary(task) {
21850
- const lines2 = [
21851
- "## Task",
21852
- `id: ${task.id}`,
21853
- `title: ${task.title}`,
21854
- `autonomy: ${task.autonomyLevel} (${task.autonomyReason})`
21855
- ];
21856
- if (task.notes)
21857
- lines2.push(`description: ${task.notes}`);
21858
- if (task.latestJudgement) {
21859
- lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
21860
- }
21861
- return lines2.join("\n");
21862
- }
21863
- function renderSpec(specMarkdown, compact) {
21864
- if (!specMarkdown)
21865
- return "## Spec\n(none)";
21866
- if (!compact)
21867
- return `## Spec
21868
- ${specMarkdown}`;
21869
- const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
21870
- if (specMarkdown.length <= budget)
21871
- return `## Spec
21872
- ${specMarkdown}`;
21873
- return `## Spec
21874
- ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
21875
- }
21876
- function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
21877
- const lines2 = markdown.split(/\r?\n/);
21878
- const sections = [{ heading: "", body: [] }];
21879
- for (const line of lines2) {
21880
- if (/^#{1,6}\s/.test(line)) {
21881
- sections.push({ heading: line, body: [] });
21882
- } else {
21883
- sections[sections.length - 1]?.body.push(line);
21884
- }
21885
- }
21886
- const sectionCount = Math.max(1, sections.length);
21887
- const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
21888
- const out = [];
21889
- for (const section of sections) {
21890
- const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
21891
- if (section.heading)
21892
- out.push(section.heading);
21893
- if (!bodyText)
21894
- continue;
21895
- if (bodyText.length <= perSectionBudget) {
21896
- out.push(bodyText);
21897
- } else {
21898
- const kept = bodyText.slice(0, perSectionBudget);
21899
- const omitted = bodyText.length - perSectionBudget;
21900
- out.push(`${kept}
21901
- \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
21902
- }
21903
- }
21904
- if (projectSlug && specWikiPageId) {
21905
- out.push("");
21906
- out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
21907
- } else {
21908
- out.push("");
21909
- out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
21910
- }
21911
- return out.join("\n");
21912
- }
21913
- function renderRunHistory(runs, openRunId) {
21914
- if (runs.length === 0)
21915
- return "## Execution-run history\n(none)";
21916
- const lines2 = ["## Execution-run history"];
21917
- for (const run3 of runs) {
21918
- const marker = run3.id === openRunId ? " (open)" : "";
21919
- lines2.push(`- ${run3.id} [${run3.status}] created ${run3.createdAt}${marker}`);
21920
- }
21921
- return lines2.join("\n");
21922
- }
21923
- function renderOpenRun(openRunId) {
21924
- return `## Open run
21925
- ${openRunId ?? "(none)"}`;
21926
- }
21927
- function renderArtifacts(artifacts, omittedCount, compact) {
21928
- if (artifacts.length === 0 && omittedCount === 0)
21929
- return "## Artifacts\n(none)";
21930
- const lines2 = ["## Artifacts"];
21931
- if (omittedCount > 0) {
21932
- lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
21933
- }
21934
- let truncatedAny = false;
21935
- for (const artifact of artifacts) {
21936
- if (artifact.kind === "file") {
21937
- const p = artifact.payload;
21938
- if (p.filename && p.stableUrl) {
21939
- lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
21940
- continue;
21941
- }
21942
- }
21943
- const payloadText = JSON.stringify(artifact.payload);
21944
- const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
21945
- const willTruncate = !!compact && payloadText.length > budget;
21946
- const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
21947
- if (willTruncate)
21948
- truncatedAny = true;
21949
- lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
21950
- }
21951
- if (compact && (omittedCount > 0 || truncatedAny)) {
21952
- lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
21953
- }
21954
- return lines2.join("\n");
21955
- }
21956
- function renderAutonomyEvents(events) {
21957
- if (events.length === 0)
21958
- return "## Autonomy history\n(none)";
21959
- const lines2 = ["## Autonomy history"];
21960
- for (const event of events) {
21961
- lines2.push(`- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`);
21962
- }
21963
- return lines2.join("\n");
21964
- }
21965
-
21966
21836
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
21967
21837
  var external_exports = {};
21968
21838
  __export(external_exports, {
@@ -36562,6 +36432,13 @@ var openApiOperationIds = {
36562
36432
  restartExecutionRun: "restartExecutionRun",
36563
36433
  resolveHandoff: "resolveHandoff",
36564
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",
36565
36442
  issueAgentCredential: "issueAgentCredential",
36566
36443
  listAgentCredentials: "listAgentCredentials",
36567
36444
  revokeAgentCredential: "revokeAgentCredential",
@@ -36662,19 +36539,22 @@ var agentPoolResponseSchema = external_exports.object({
36662
36539
  });
36663
36540
 
36664
36541
  // ../../packages/schemas/dist/specConsensusReview.js
36542
+ var mrrlinSpecConsensusSuppressionEvidenceSchema = external_exports.record(external_exports.string(), external_exports.unknown()).nullable();
36665
36543
  var mrrlinSpecConsensusReviewSchema = external_exports.object({
36666
36544
  projectSlug: external_exports.string().min(1),
36667
36545
  specPageId: external_exports.string().min(1),
36668
36546
  contentHash: external_exports.string().min(1),
36669
36547
  outcome: external_exports.string().min(1),
36670
36548
  rounds: external_exports.number().int().nonnegative(),
36549
+ suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.default(null),
36671
36550
  createdAt: external_exports.string().datetime()
36672
36551
  });
36673
36552
  var mrrlinSpecConsensusReviewCreateSchema = external_exports.object({
36674
36553
  specPageId: external_exports.string().min(1),
36675
36554
  contentHash: external_exports.string().min(1),
36676
36555
  outcome: external_exports.string().min(1),
36677
- rounds: external_exports.number().int().nonnegative()
36556
+ rounds: external_exports.number().int().nonnegative(),
36557
+ suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.optional()
36678
36558
  }).strict();
36679
36559
  var specConsensusReviewResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema });
36680
36560
  var specConsensusReviewMaybeResponseSchema = external_exports.object({ data: mrrlinSpecConsensusReviewSchema.nullable() });
@@ -36750,6 +36630,97 @@ var mrrlinAsyncJobRequeueSchema = external_exports.object({
36750
36630
  staleSeconds: external_exports.number().int().min(60).max(86400).default(300)
36751
36631
  });
36752
36632
 
36633
+ // ../../packages/schemas/dist/memory-budget.js
36634
+ function evictionKey(item, idx) {
36635
+ const memClass = item.memoryClass === "historical" ? 0 : 1;
36636
+ const prec = item.precedence === null ? -4 : -item.precedence;
36637
+ const score = item.score ?? 0;
36638
+ return { memClass, prec, score, idx };
36639
+ }
36640
+ function compareEvictionKeys(a, b) {
36641
+ if (a.memClass !== b.memClass)
36642
+ return a.memClass - b.memClass;
36643
+ if (a.prec !== b.prec)
36644
+ return a.prec - b.prec;
36645
+ if (a.score !== b.score)
36646
+ return a.score - b.score;
36647
+ return a.idx - b.idx;
36648
+ }
36649
+ function sortByEvictionOrder(items) {
36650
+ const indexed = items.map((item, idx) => ({ item, key: evictionKey(item, idx) }));
36651
+ indexed.sort((a, b) => compareEvictionKeys(a.key, b.key));
36652
+ return indexed.map((x) => x.item);
36653
+ }
36654
+ function keepUnderCap(sorted, cap) {
36655
+ const kept = [];
36656
+ let used = 0;
36657
+ let i = sorted.length - 1;
36658
+ for (; i >= 0; i--) {
36659
+ const item = sorted[i];
36660
+ if (item === void 0)
36661
+ break;
36662
+ if (used + item.size <= cap) {
36663
+ kept.unshift(item);
36664
+ used += item.size;
36665
+ } else {
36666
+ break;
36667
+ }
36668
+ }
36669
+ const dropped = sorted.slice(0, i + 1);
36670
+ return { kept, dropped };
36671
+ }
36672
+ function applyBudget(items, spec) {
36673
+ const prec0Items = [];
36674
+ const restItems = [];
36675
+ for (const item of items) {
36676
+ if (item.precedence === 0) {
36677
+ prec0Items.push(item);
36678
+ } else {
36679
+ restItems.push(item);
36680
+ }
36681
+ }
36682
+ const sourceCaps = spec.sourceCaps ?? {};
36683
+ const sourceMap = /* @__PURE__ */ new Map();
36684
+ for (const item of restItems) {
36685
+ const list = sourceMap.get(item.source);
36686
+ if (list)
36687
+ list.push(item);
36688
+ else
36689
+ sourceMap.set(item.source, [item]);
36690
+ }
36691
+ const keptAfterSourceCap = [];
36692
+ const droppedAfterSourceCap = [];
36693
+ for (const [source, sourceItems] of sourceMap) {
36694
+ const cap = sourceCaps[source];
36695
+ if (cap === void 0) {
36696
+ keptAfterSourceCap.push(...sourceItems);
36697
+ } else {
36698
+ const sorted = sortByEvictionOrder(sourceItems);
36699
+ const { kept, dropped } = keepUnderCap(sorted, cap);
36700
+ keptAfterSourceCap.push(...kept);
36701
+ droppedAfterSourceCap.push(...dropped);
36702
+ }
36703
+ }
36704
+ const allEligible = keptAfterSourceCap;
36705
+ const sortedGlobal = sortByEvictionOrder(allEligible);
36706
+ const { kept: keptGlobal, dropped: droppedGlobal } = keepUnderCap(sortedGlobal, spec.totalBytes - totalSize(prec0Items));
36707
+ const finalKept = [...prec0Items, ...keptGlobal];
36708
+ const finalDropped = [...droppedAfterSourceCap, ...droppedGlobal];
36709
+ const trimmed = finalDropped.length > 0;
36710
+ const keptSize = totalSize(finalKept);
36711
+ const overflowBy = Math.max(0, keptSize - spec.totalBytes);
36712
+ const handles = [];
36713
+ for (const item of finalDropped) {
36714
+ if (item.handle && item.handle.trim() !== "") {
36715
+ handles.push(item.handle);
36716
+ }
36717
+ }
36718
+ return { kept: finalKept, dropped: finalDropped, handles, trimmed, overflowBy };
36719
+ }
36720
+ function totalSize(items) {
36721
+ return items.reduce((sum, i) => sum + i.size, 0);
36722
+ }
36723
+
36753
36724
  // ../../packages/schemas/dist/index.js
36754
36725
  var mrrlinTaskStatusSchema = external_exports.enum([
36755
36726
  "todo",
@@ -36981,6 +36952,7 @@ var mrrlinTaskEventSchema = external_exports.object({
36981
36952
  });
36982
36953
  var mrrlinWikiPageSchema = external_exports.object({
36983
36954
  contentHash: external_exports.string().default(""),
36955
+ createdAt: external_exports.string().datetime().nullable().default(null),
36984
36956
  folder: external_exports.string().min(1),
36985
36957
  id: mrrlinWikiPageIdSchema,
36986
36958
  markdownBody: external_exports.string().min(1),
@@ -36988,7 +36960,8 @@ var mrrlinWikiPageSchema = external_exports.object({
36988
36960
  preview: external_exports.string(),
36989
36961
  specReady: external_exports.boolean().default(false),
36990
36962
  taskId: mrrlinTaskIdSchema.nullable().default(null),
36991
- title: external_exports.string().min(1)
36963
+ title: external_exports.string().min(1),
36964
+ updatedAt: external_exports.string().datetime().nullable().default(null)
36992
36965
  });
36993
36966
  var mrrlinMarkSpecReadyRequestSchema = external_exports.object({
36994
36967
  ready: external_exports.boolean()
@@ -37112,14 +37085,12 @@ var mrrlinProjectSnapshotSchema = external_exports.object({
37112
37085
  taxonomy: external_exports.array(mrrlinTaxonomyEntrySchema).default([]),
37113
37086
  wikiPages: external_exports.array(mrrlinWikiPageSchema).default([])
37114
37087
  });
37115
- var mrrlinTaskCreateSchema = external_exports.object({
37088
+ var mrrlinTaskCreateShapeSchema = external_exports.object({
37116
37089
  // Auto-deploy design Part B: input-optional (NOT .default("off")) so createTask can tell
37117
37090
  // "caller omitted this" apart from "caller explicitly chose off" -- omitted falls through to
37118
37091
  // the project's default_auto_deploy; the tasks column itself still defaults to 'off' at the
37119
37092
  // SQL level for any direct-insert path that bypasses createTask entirely.
37120
37093
  autoDeploy: mrrlinAutoDeploySchema.optional(),
37121
- // Input-optional so existing createTask call sites need no churn; createTask fills 'code' for the
37122
- // INSERT (the pg column is NOT NULL, so a concrete value is required, not undefined→NULL).
37123
37094
  domain: mrrlinTaskDomainSchema.optional(),
37124
37095
  autonomyLevel: mrrlinAutonomyLevelSchema.default("human_review"),
37125
37096
  autonomyReason: external_exports.string().min(1).max(240).default("Needs human review before automation."),
@@ -37141,6 +37112,17 @@ var mrrlinTaskCreateSchema = external_exports.object({
37141
37112
  type: mrrlinTaskTypeSchema.default("task"),
37142
37113
  verificationDueAt: external_exports.string().datetime().nullable().optional()
37143
37114
  });
37115
+ function refineTaskCreateDomain(data, ctx) {
37116
+ if (data.domain === void 0 && data.parentTaskId == null) {
37117
+ ctx.addIssue({
37118
+ code: external_exports.ZodIssueCode.custom,
37119
+ path: ["domain"],
37120
+ message: "domain is required unless parentTaskId is set (a follow-up inherits its parent's domain)"
37121
+ });
37122
+ }
37123
+ }
37124
+ var mrrlinTaskCreateSchema = mrrlinTaskCreateShapeSchema.superRefine(refineTaskCreateDomain);
37125
+ var mrrlinTaskCreateWithoutIdSchema = mrrlinTaskCreateShapeSchema.omit({ id: true }).superRefine(refineTaskCreateDomain);
37144
37126
  var mrrlinTaskUpdateSchema = external_exports.object({
37145
37127
  actor: external_exports.string().min(1).optional(),
37146
37128
  assignee: mrrlinTaskAssigneeSchema.optional(),
@@ -37258,13 +37240,19 @@ var mrrlinWikiPageSearchQuerySchema = external_exports.object({
37258
37240
  q: external_exports.string().min(2)
37259
37241
  });
37260
37242
  var mrrlinWikiPageSearchResultSchema = external_exports.object({
37243
+ ageDays: external_exports.number().nullable(),
37261
37244
  folder: external_exports.string().min(1),
37262
37245
  id: mrrlinWikiPageIdSchema,
37246
+ // Task-relative; always null from a generic (non-task-scoped) search — resolved only where
37247
+ // a caller has task context (director-loop retrieve()), never at this layer or in the API (D14).
37248
+ memoryClass: external_exports.enum(["normative", "historical"]),
37263
37249
  path: external_exports.string().min(1),
37250
+ precedence: external_exports.union([external_exports.literal(0), external_exports.literal(1), external_exports.literal(2), external_exports.literal(3)]).nullable(),
37264
37251
  preview: external_exports.string(),
37265
37252
  rank: external_exports.number(),
37266
37253
  snippet: external_exports.string(),
37267
- title: external_exports.string().min(1)
37254
+ title: external_exports.string().min(1),
37255
+ updatedAt: external_exports.string().datetime().nullable()
37268
37256
  });
37269
37257
  var wikiKnowledgeChunkSchema = external_exports.object({
37270
37258
  chunk_text: external_exports.string().min(1),
@@ -37482,7 +37470,55 @@ var mrrlinExecutionRunSchema = external_exports.object({
37482
37470
  status: mrrlinExecutionRunStatusSchema,
37483
37471
  taskId: mrrlinTaskIdSchema,
37484
37472
  updatedAt: external_exports.string().datetime(),
37485
- 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() })
37486
37522
  });
37487
37523
  var mrrlinExecutionRunCreateSchema = external_exports.object({
37488
37524
  actor: external_exports.string().min(1),
@@ -37514,7 +37550,18 @@ var mrrlinExecutionRunUpdateSchema = external_exports.object({
37514
37550
  prodWorkflowId: external_exports.number().int().positive().nullable().optional(),
37515
37551
  pullRequestUrl: external_exports.string().min(1).nullable().optional(),
37516
37552
  status: mrrlinExecutionRunStatusSchema.optional(),
37517
- 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()
37518
37565
  }).refine((value) => Object.keys(value).length > 0, {
37519
37566
  message: "ExecutionRunUpdate requires at least one field"
37520
37567
  });
@@ -38090,6 +38137,321 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
38090
38137
  var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
38091
38138
  var inboxItemListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSchema) });
38092
38139
 
38140
+ // ../../packages/codex-client/dist/render-context-bundle.js
38141
+ var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
38142
+ var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
38143
+ function renderContextBundle(input) {
38144
+ const sections = [];
38145
+ sections.push(renderTaskSummary(input.task));
38146
+ sections.push(renderSpec(input.specMarkdown, input.compact));
38147
+ const { keptRuns, keptArtifacts, keptAutonomy, anyHistoricalDropped, droppedHandles } = applyHistoricalBudgetIfNeeded(input);
38148
+ sections.push(renderRunHistory(keptRuns, input.openRunId, input.compact, anyHistoricalDropped));
38149
+ sections.push(renderOpenRun(input.openRunId));
38150
+ sections.push(renderArtifacts(keptArtifacts, input.omittedArtifactCount ?? 0, input.compact, anyHistoricalDropped, droppedHandles));
38151
+ sections.push(renderAutonomyEvents(keptAutonomy, input.compact, anyHistoricalDropped));
38152
+ return sections.join("\n\n");
38153
+ }
38154
+ function renderTaskSummary(task) {
38155
+ const lines2 = [
38156
+ "## Task (normative)",
38157
+ `id: ${task.id}`,
38158
+ `title: ${task.title}`,
38159
+ `autonomy: ${task.autonomyLevel} (${task.autonomyReason})`
38160
+ ];
38161
+ if (task.notes)
38162
+ lines2.push(`description: ${task.notes}`);
38163
+ if (task.latestJudgement) {
38164
+ lines2.push(`latest judgement (${task.latestJudgementKind ?? "unknown"}): ${task.latestJudgement}`);
38165
+ }
38166
+ return lines2.join("\n");
38167
+ }
38168
+ function renderSpec(specMarkdown, compact) {
38169
+ if (!specMarkdown)
38170
+ return "## Spec (normative)\n(none)";
38171
+ if (!compact)
38172
+ return `## Spec (normative)
38173
+ ${specMarkdown}`;
38174
+ const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
38175
+ if (specMarkdown.length <= budget)
38176
+ return `## Spec (normative)
38177
+ ${specMarkdown}`;
38178
+ return `## Spec (normative)
38179
+ ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
38180
+ }
38181
+ function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
38182
+ const lines2 = markdown.split(/\r?\n/);
38183
+ const sections = [{ heading: "", body: [] }];
38184
+ for (const line of lines2) {
38185
+ if (/^#{1,6}\s/.test(line)) {
38186
+ sections.push({ heading: line, body: [] });
38187
+ } else {
38188
+ sections[sections.length - 1]?.body.push(line);
38189
+ }
38190
+ }
38191
+ const sectionCount = Math.max(1, sections.length);
38192
+ const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
38193
+ const out = [];
38194
+ for (const section of sections) {
38195
+ const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
38196
+ if (section.heading)
38197
+ out.push(section.heading);
38198
+ if (!bodyText)
38199
+ continue;
38200
+ if (bodyText.length <= perSectionBudget) {
38201
+ out.push(bodyText);
38202
+ } else {
38203
+ const kept = bodyText.slice(0, perSectionBudget);
38204
+ const omitted = bodyText.length - perSectionBudget;
38205
+ out.push(`${kept}
38206
+ \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
38207
+ }
38208
+ }
38209
+ if (projectSlug && specWikiPageId) {
38210
+ out.push("");
38211
+ out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
38212
+ } else {
38213
+ out.push("");
38214
+ out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
38215
+ }
38216
+ return out.join("\n");
38217
+ }
38218
+ function renderRunHistory(runs, openRunId, compact, anyHistoricalDropped) {
38219
+ if (runs.length === 0)
38220
+ return "## Execution-run history (historical)\n(none)";
38221
+ const lines2 = ["## Execution-run history (historical)"];
38222
+ for (const run3 of runs) {
38223
+ const marker = run3.id === openRunId ? " (open)" : "";
38224
+ lines2.push(`- ${run3.id} [${run3.status}] created ${run3.createdAt}${marker}`);
38225
+ }
38226
+ if (compact && anyHistoricalDropped) {
38227
+ lines2.push("> Compact execution-run history. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38228
+ }
38229
+ return lines2.join("\n");
38230
+ }
38231
+ function renderOpenRun(openRunId) {
38232
+ return `## Open run (normative)
38233
+ ${openRunId ?? "(none)"}`;
38234
+ }
38235
+ function renderArtifacts(artifacts, omittedCount, compact, anyHistoricalDropped, droppedHandles) {
38236
+ if (artifacts.length === 0 && omittedCount === 0)
38237
+ return "## Artifacts (historical)\n(none)";
38238
+ const lines2 = ["## Artifacts (historical)"];
38239
+ if (omittedCount > 0) {
38240
+ lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
38241
+ }
38242
+ let truncatedAny = false;
38243
+ for (const artifact of artifacts) {
38244
+ if (artifact.kind === "file") {
38245
+ const p = artifact.payload;
38246
+ if (p.filename && p.stableUrl) {
38247
+ lines2.push(`- ${artifact.createdAt} [file] ${p.filename} (${p.class ?? "?"}, ${p.sizeBytes ?? "?"} bytes) ${p.stableUrl}`);
38248
+ continue;
38249
+ }
38250
+ }
38251
+ const payloadText = JSON.stringify(artifact.payload);
38252
+ const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
38253
+ const willTruncate = !!compact && payloadText.length > budget;
38254
+ const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
38255
+ if (willTruncate)
38256
+ truncatedAny = true;
38257
+ lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
38258
+ }
38259
+ if (compact && (omittedCount > 0 || truncatedAny || anyHistoricalDropped)) {
38260
+ lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38261
+ }
38262
+ if (compact && droppedHandles.length > 0) {
38263
+ lines2.push(`> Dropped by budget, retrievable via: ${droppedHandles.map((h) => `\`${h}\``).join(", ")}`);
38264
+ }
38265
+ return lines2.join("\n");
38266
+ }
38267
+ function renderAutonomyEvents(events, compact, anyHistoricalDropped) {
38268
+ if (events.length === 0)
38269
+ return "## Autonomy history (historical)\n(none)";
38270
+ const lines2 = ["## Autonomy history (historical)"];
38271
+ for (const event of events) {
38272
+ lines2.push(`- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`);
38273
+ }
38274
+ if (compact && anyHistoricalDropped) {
38275
+ lines2.push("> Compact autonomy history. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
38276
+ }
38277
+ return lines2.join("\n");
38278
+ }
38279
+ function applyHistoricalBudgetIfNeeded(input) {
38280
+ if (!input.compact?.budget) {
38281
+ return {
38282
+ keptRuns: input.runs,
38283
+ keptArtifacts: input.artifacts,
38284
+ keptAutonomy: input.autonomyEvents,
38285
+ anyHistoricalDropped: false,
38286
+ droppedHandles: []
38287
+ };
38288
+ }
38289
+ const candidates = [];
38290
+ for (const run3 of input.runs) {
38291
+ const line = `- ${run3.id} [${run3.status}] created ${run3.createdAt}`;
38292
+ candidates.push({
38293
+ kind: "run",
38294
+ runId: run3.id,
38295
+ size: line.length,
38296
+ memoryClass: "historical",
38297
+ precedence: 3,
38298
+ handle: input.compact.projectSlug ? `mrrlin://run/${input.compact.projectSlug}/${run3.id}/artifacts` : null,
38299
+ source: "run_history"
38300
+ });
38301
+ }
38302
+ for (const artifact of input.artifacts) {
38303
+ const line = `- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${JSON.stringify(artifact.payload)}`;
38304
+ candidates.push({
38305
+ kind: "artifact",
38306
+ artifactId: artifact.id,
38307
+ size: line.length,
38308
+ memoryClass: "historical",
38309
+ precedence: 3,
38310
+ handle: input.compact.projectSlug ? `mrrlin://run/${input.compact.projectSlug}/${artifact.runId}/artifacts` : null,
38311
+ source: "artifacts"
38312
+ });
38313
+ }
38314
+ for (const event of input.autonomyEvents) {
38315
+ const line = `- ${event.createdAt} ${event.autonomyLevel ?? "?"} (${event.autonomyReason ?? ""})`;
38316
+ candidates.push({
38317
+ kind: "autonomy",
38318
+ autonomyEventId: event.id,
38319
+ size: line.length,
38320
+ memoryClass: "historical",
38321
+ precedence: 3,
38322
+ handle: null,
38323
+ source: "autonomy"
38324
+ });
38325
+ }
38326
+ const { kept, dropped } = applyBudget(candidates, input.compact.budget);
38327
+ const keptRunIds = new Set(kept.filter((c) => c.kind === "run").map((c) => c.runId));
38328
+ const keptArtifactIds = new Set(kept.filter((c) => c.kind === "artifact").map((c) => c.artifactId));
38329
+ const keptAutonomyIds = new Set(kept.filter((c) => c.kind === "autonomy").map((c) => c.autonomyEventId));
38330
+ const droppedHandles = [];
38331
+ for (const item of dropped) {
38332
+ if (item.handle)
38333
+ droppedHandles.push(item.handle);
38334
+ }
38335
+ return {
38336
+ keptRuns: input.runs.filter((r) => keptRunIds.has(r.id)),
38337
+ keptArtifacts: input.artifacts.filter((a) => keptArtifactIds.has(a.id)),
38338
+ keptAutonomy: input.autonomyEvents.filter((e) => keptAutonomyIds.has(e.id)),
38339
+ anyHistoricalDropped: dropped.length > 0,
38340
+ droppedHandles
38341
+ };
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
+ }
38454
+
38093
38455
  // ../../packages/client/dist/agent-token-provider.js
38094
38456
  var AgentTokenProviderError = class extends Error {
38095
38457
  code;
@@ -38813,6 +39175,34 @@ function createMrrlinClient(config2) {
38813
39175
  throw error51;
38814
39176
  }
38815
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
+ },
38816
39206
  async resolveHandoff(projectSlug, runId, input) {
38817
39207
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/resolve-handoff`, {
38818
39208
  body: JSON.stringify(input),
@@ -39016,6 +39406,36 @@ function createMrrlinClient(config2) {
39016
39406
  };
39017
39407
  }
39018
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
+
39019
39439
  // src/token-refresher.ts
39020
39440
  function createTokenRefresher(opts) {
39021
39441
  const setIntervalFn = opts.setIntervalFn ?? setInterval;
@@ -39032,7 +39452,7 @@ function createTokenRefresher(opts) {
39032
39452
  opts.onOk?.();
39033
39453
  } catch (error51) {
39034
39454
  process.stderr.write(
39035
- `[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)}
39036
39456
  `
39037
39457
  );
39038
39458
  opts.onError?.(error51);
@@ -39071,29 +39491,117 @@ function createServiceHealth() {
39071
39491
  }
39072
39492
 
39073
39493
  // src/turn-lock.ts
39494
+ var CHAT_LIMIT = 5;
39495
+ var AUTONOMOUS_LIMIT = 10;
39074
39496
  function createTurnLock() {
39075
- let tail2 = Promise.resolve();
39076
- let active = 0;
39077
- return {
39078
- runExclusive(fn) {
39079
- active += 1;
39080
- const run3 = tail2.then(() => fn());
39081
- tail2 = run3.then(
39082
- () => void 0,
39083
- () => void 0
39084
- );
39085
- run3.then(
39086
- () => {
39087
- active -= 1;
39088
- },
39089
- () => {
39090
- 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);
39091
39540
  }
39092
- );
39093
- return run3;
39094
- },
39095
- 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
+ }
39096
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 };
39097
39605
  }
39098
39606
 
39099
39607
  // src/self-update.ts
@@ -39457,7 +39965,7 @@ function startCodexProbe(deps = {}) {
39457
39965
  }
39458
39966
 
39459
39967
  // src/_generated/version.ts
39460
- var PKG_VERSION = "0.3.59";
39968
+ var PKG_VERSION = "0.3.62";
39461
39969
 
39462
39970
  // src/api-base-url-migrate.ts
39463
39971
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -39957,7 +40465,7 @@ async function attemptSchedulerTick(deps) {
39957
40465
  return { nextWakeAt: typeof data.nextWakeAt === "string" ? data.nextWakeAt : null, failed: false };
39958
40466
  } catch (error51) {
39959
40467
  process.stderr.write(
39960
- `[mrrlin-mcp director-bridge] scheduler tick failed: ${error51 instanceof Error ? error51.message : String(error51)}
40468
+ `[mrrlin-mcp director-bridge] scheduler tick failed: ${describeError(error51)}
39961
40469
  `
39962
40470
  );
39963
40471
  bridgeHealth?.recordFailure("schedulerTick", error51);
@@ -40273,13 +40781,26 @@ var HEADS_BY_DOMAIN = {
40273
40781
  content: ["plan-reviewer", "scope-analyst", "content-editor", "compliance-reviewer"]
40274
40782
  };
40275
40783
  var SPEC_HEADS = HEADS_BY_DOMAIN.code;
40784
+ var CODE_HEADS_BY_IMPACT = {
40785
+ high: HEADS_BY_DOMAIN.code,
40786
+ medium: ["plan-reviewer", "security-analyst", "scope-analyst"],
40787
+ low: ["plan-reviewer", "scope-analyst"]
40788
+ };
40276
40789
  function normalizeDomain(value) {
40277
40790
  if (typeof value !== "string") return DEFAULT_DOMAIN;
40278
40791
  const v = value.trim().toLowerCase();
40279
40792
  return DOMAINS.includes(v) ? v : DEFAULT_DOMAIN;
40280
40793
  }
40281
- function resolveHeads(domain2) {
40282
- return [...HEADS_BY_DOMAIN[normalizeDomain(domain2)]];
40794
+ function normalizeImpact(value) {
40795
+ if (typeof value !== "string") return null;
40796
+ const v = value.trim().toLowerCase();
40797
+ return v === "low" || v === "medium" || v === "high" ? v : null;
40798
+ }
40799
+ function resolveHeads(domain2, impact) {
40800
+ const d = normalizeDomain(domain2);
40801
+ if (d !== "code") return [...HEADS_BY_DOMAIN[d]];
40802
+ const i = normalizeImpact(impact) ?? "high";
40803
+ return [...CODE_HEADS_BY_IMPACT[i]];
40283
40804
  }
40284
40805
  var PERSONAS_BY_DOMAIN = {
40285
40806
  seo: "seo-writer",
@@ -40726,7 +41247,7 @@ function loadPersona(name) {
40726
41247
  personaCache.set(name, content);
40727
41248
  return content;
40728
41249
  }
40729
- function specHeadIntentBlock(taskId, title, seed) {
41250
+ function specHeadIntentBlock(taskId, title, seed, impact, adjudicated) {
40730
41251
  const identity = taskId ? `--- TASK IDENTITY (the spec MUST be about THIS task; if its scope or direction contradicts this identity, raise a \`scope\` issue instead of steering the spec elsewhere) ---
40731
41252
  TASK ${taskId}: ${title ?? ""}
40732
41253
 
@@ -40735,7 +41256,15 @@ TASK ${taskId}: ${title ?? ""}
40735
41256
  ${seed}
40736
41257
 
40737
41258
  ` : "";
40738
- return `${identity}${intent}`;
41259
+ const impactLine = impact ? `--- IMPACT: ${impact} ---
41260
+
41261
+ ` : "";
41262
+ const adjudicatedBlock = adjudicated && adjudicated.length > 0 ? `--- ADJUDICATED \u2014 the operator already decided these; do not re-raise without new information ---
41263
+ ${adjudicated.map((q) => `- Q: ${q.body}
41264
+ ${q.status === "rejected" ? "(rejected" + (q.answer ? `: ${q.answer}` : "") + ")" : `A: ${q.answer ?? "(acknowledged, no answer recorded)"}`}`).join("\n")}
41265
+
41266
+ ` : "";
41267
+ return `${identity}${intent}${impactLine}${adjudicatedBlock}`;
40739
41268
  }
40740
41269
  function reviserIdentityInstructions(taskId, title) {
40741
41270
  const lock = taskId ? `This spec gates task ${taskId}${title ? ` ("${title}")` : ""}. You MUST NOT change the spec's task identity or heading, retarget it to a different task id, or invert its direction (e.g. live-submission vs preparation-only). Preserve the leading \`task: ${taskId}\` frontmatter EXACTLY; never change it to another task id. Address ONLY the accepted issues.
@@ -40774,7 +41303,21 @@ function buildSpecGateDeps(args) {
40774
41303
  resolvedSeed = "(No operator statement on record for this task. Derived from the task's own notes \u2014 the following acceptance criteria were extracted verbatim:)\n\n" + parsed.items.map((c) => `- ${c}`).join("\n");
40775
41304
  }
40776
41305
  }
40777
- return { taskId, seed: resolvedSeed, title: task.title ?? "", domain: task.domain, notes };
41306
+ const taskInboxItems = await client.listInboxItems(projectSlug, { taskId });
41307
+ const adjudicated = taskInboxItems.filter((item) => item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")).map((item) => ({
41308
+ body: item.body,
41309
+ answer: item.answer,
41310
+ status: item.status
41311
+ }));
41312
+ return {
41313
+ taskId,
41314
+ seed: resolvedSeed,
41315
+ title: task.title ?? "",
41316
+ domain: task.domain,
41317
+ notes,
41318
+ impact: task.impact,
41319
+ adjudicated
41320
+ };
40778
41321
  })();
40779
41322
  }
40780
41323
  return intentPromise;
@@ -40799,8 +41342,8 @@ function buildSpecGateDeps(args) {
40799
41342
  },
40800
41343
  async runHead(personaName, artifact, dismissed) {
40801
41344
  const persona = loadPersona(personaName);
40802
- const { taskId, title, seed } = await loadIntent();
40803
- const intentBlock = specHeadIntentBlock(taskId, title, seed);
41345
+ const { taskId, title, seed, impact, adjudicated } = await loadIntent();
41346
+ const intentBlock = specHeadIntentBlock(taskId, title, seed, impact, adjudicated);
40804
41347
  return runCodexExec(
40805
41348
  {
40806
41349
  prompt: `Review this spec against the operator's intent.
@@ -40994,12 +41537,14 @@ async function buildDeliverableGateDeps(args) {
40994
41537
  const page = await client.getWikiPage(projectSlug, deliverablePageId);
40995
41538
  const taskId = page.taskId ?? null;
40996
41539
  let domain2 = "code";
41540
+ let impact;
40997
41541
  let taskTitle = "";
40998
41542
  let specPageId = null;
40999
41543
  let criteria = [];
41000
41544
  if (taskId) {
41001
41545
  const task = await client.getTask(projectSlug, taskId);
41002
41546
  domain2 = normalizeDomain(task?.domain);
41547
+ impact = task?.impact;
41003
41548
  taskTitle = task?.title ?? "";
41004
41549
  if (task?.specWikiPageId) {
41005
41550
  specPageId = task.specWikiPageId;
@@ -41007,7 +41552,7 @@ async function buildDeliverableGateDeps(args) {
41007
41552
  criteria = parseAcceptanceCriteria(specPage.markdownBody)?.items ?? [];
41008
41553
  }
41009
41554
  }
41010
- const heads = resolveHeads(domain2);
41555
+ const heads = resolveHeads(domain2, impact);
41011
41556
  const maxHeadCalls = 2 * rounds * (heads.length + 1);
41012
41557
  const criteriaBlock = renderCriteriaBlock(criteria);
41013
41558
  return {
@@ -41250,6 +41795,17 @@ var SPECS_FOLDER = "Specs";
41250
41795
  var DRAFT_SPEC_TLDR_MAX_CHARS = 300;
41251
41796
  var DRAFT_SPEC_PERSONA = "spec-author";
41252
41797
  var PAGE_ID_MAX_ATTEMPTS = 5;
41798
+ function resolveSpecBudget(impact) {
41799
+ const tier = impact?.toLowerCase();
41800
+ switch (tier) {
41801
+ case "high":
41802
+ return { maxRequirements: 12, maxChars: 25e3 };
41803
+ case "medium":
41804
+ return { maxRequirements: 6, maxChars: 12e3 };
41805
+ default:
41806
+ return { maxRequirements: 3, maxChars: 6e3 };
41807
+ }
41808
+ }
41253
41809
  function defaultSleep(ms) {
41254
41810
  return new Promise((resolve) => setTimeout(resolve, ms));
41255
41811
  }
@@ -41262,7 +41818,7 @@ function enforceSpecOutputContract(markdown, taskId, title) {
41262
41818
  ${body}`;
41263
41819
  }
41264
41820
  let tldrChars = 0;
41265
- const tldrMatch = body.match(/(^## Summary\s*\n)([\s\S]*?)(?=\n## |$)/im);
41821
+ const tldrMatch = body.match(/(^## TL;DR\s*\n)([\s\S]*?)(?=\n## |$)/im);
41266
41822
  if (tldrMatch && tldrMatch.index !== void 0) {
41267
41823
  const heading = tldrMatch[1] ?? "";
41268
41824
  const section = (tldrMatch[2] ?? "").trim();
@@ -41286,6 +41842,7 @@ function nextWikiPageId(pages) {
41286
41842
  return `WK-${max + 1}`;
41287
41843
  }
41288
41844
  function buildDraftSpecPrompt(task, constitution) {
41845
+ const budget = resolveSpecBudget(task.impact);
41289
41846
  const lines2 = [
41290
41847
  `Write a complete implementation spec for the task below.`,
41291
41848
  ``,
@@ -41307,11 +41864,19 @@ function buildDraftSpecPrompt(task, constitution) {
41307
41864
  }
41308
41865
  lines2.push(
41309
41866
  `--- OUTPUT CONTRACT (strict) ---`,
41310
- `Return ONLY the full spec markdown, no commentary. Required structure:`,
41867
+ `Return ONLY the full spec markdown, no commentary. Required structure, in this order:`,
41311
41868
  `- First line heading: \`# ${task.id} \u2014 ${task.title}\``,
41312
- `- Sections: Problem, Scope, Non-goals, Acceptance criteria, Verification.`,
41313
- `- Final section \`## Summary\`: a plain-English TL;DR of what will be done and how it`,
41314
- ` will be verified, at most ${DRAFT_SPEC_TLDR_MAX_CHARS} characters.`
41869
+ `- \`## TL;DR\``,
41870
+ `- One or more \`### Requirement: <name>\` sections, each stating what the change SHALL do.`,
41871
+ ` Each requirement has one or more \`#### Scenario: <name>\` sub-sections using WHEN/THEN`,
41872
+ ` (optionally AND) to describe a concrete situation and its expected outcome.`,
41873
+ `- \`## Acceptance criteria\`: a markdown checklist (\`- [ ] ...\`) of concrete, verifiable`,
41874
+ ` conditions \u2014 this is the machine-usable yardstick the review scores against.`,
41875
+ `- \`## Verification\`: the exact tests, commands, or manual checks that prove each`,
41876
+ ` acceptance criterion holds.`,
41877
+ ``,
41878
+ `The \`## TL;DR\` section MUST be at most ${DRAFT_SPEC_TLDR_MAX_CHARS} characters.`,
41879
+ `The spec MUST contain at most ${budget.maxRequirements} acceptance-criteria items and no more than ${budget.maxChars} characters total.`
41315
41880
  );
41316
41881
  return lines2.join("\n");
41317
41882
  }
@@ -41589,6 +42154,11 @@ function renderMilestone(m, ctx) {
41589
42154
  return "Autodeploy is enabled, but this task has nothing deployable \u2014 skipping";
41590
42155
  case "daily_limit_reached":
41591
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
+ }
41592
42162
  }
41593
42163
  }
41594
42164
 
@@ -41612,6 +42182,82 @@ async function postRunFeed(client, projectSlug, runId, taskId, milestone, ctx =
41612
42182
  }
41613
42183
  }
41614
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
+
41615
42261
  // src/deploy-gate.ts
41616
42262
  var DEFAULT_DEPLOY_POLL_BUDGET_SECONDS = 1200;
41617
42263
  var DEFAULT_PROD_GRACE_SECONDS = 900;
@@ -41967,10 +42613,48 @@ async function runDeployGateTick(deps, projectSlug, run3, task) {
41967
42613
  task,
41968
42614
  run: run3
41969
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
+ }
41970
42644
  await deps.client.appendExecutionArtifact(projectSlug, run3.id, {
41971
- id: `EA-verification-${env}-${attemptIndex(artifacts, env, "verification") + 1}`,
42645
+ id: `EA-verification-${env}-${verificationAttempt}`,
41972
42646
  kind: "verification",
41973
- 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
+ }
41974
42658
  });
41975
42659
  if (verdict.verdict === "fail") {
41976
42660
  if (env === "prod" && task.autonomyLevel === "auto") {
@@ -42282,6 +42966,112 @@ function buildPullRequestBody(input) {
42282
42966
  ${lines2.join("\n")}`;
42283
42967
  }
42284
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
+
42285
43075
  // src/execution-consumer.ts
42286
43076
  var DEFAULT_STALE_SECONDS = 300;
42287
43077
  var DEFAULT_MAX_LEASE_RECLAIMS = 3;
@@ -42313,6 +43103,13 @@ function readPositiveIntEnv(name, fallback) {
42313
43103
  function delayMs(ms) {
42314
43104
  return new Promise((resolve) => setTimeout(resolve, ms));
42315
43105
  }
43106
+ function executionBundleBudget() {
43107
+ if ((process.env.MRRLIN_RETRIEVAL_BUDGET_DISABLE ?? "").trim() === "1") return void 0;
43108
+ return {
43109
+ totalBytes: readPositiveIntEnv("MRRLIN_RETRIEVAL_BUDGET_BUNDLE_CHARS", 15500),
43110
+ roundTripCap: 0
43111
+ };
43112
+ }
42316
43113
  function execTurnIdleTimeoutMs() {
42317
43114
  return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_EXEC_TURN_IDLE_TIMEOUT_SECONDS", DEFAULT_EXEC_TURN_IDLE_TIMEOUT_SECONDS) * 1e3;
42318
43115
  }
@@ -42439,6 +43236,18 @@ var BASE_INSTRUCTIONS_PREFIX = [
42439
43236
  " ## Result \u2014 what was accomplished, key findings or metrics, follow-up tasks created.",
42440
43237
  " Do NOT remove earlier sections \u2014 they remain useful for context.",
42441
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.",
42442
43251
  ""
42443
43252
  ];
42444
43253
  var CONSUMER_BASE_INSTRUCTIONS = [
@@ -42674,13 +43483,15 @@ async function runHasLease(deps, projectSlug, runId, leaseId) {
42674
43483
  return false;
42675
43484
  }
42676
43485
  }
42677
- 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) {
42678
43487
  const mapper = new CodexBridgeEventMapper();
42679
43488
  mapper.reset();
42680
43489
  let turnError = null;
42681
43490
  let interruptFailed = false;
42682
43491
  let touch = () => {
42683
43492
  };
43493
+ let turnUsage = null;
43494
+ const promptBytes = Buffer.byteLength(inputText, "utf8");
42684
43495
  const turnStartedAt = Date.now();
42685
43496
  let lastActivityAt = turnStartedAt;
42686
43497
  const persist = async (event) => {
@@ -42696,7 +43507,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42696
43507
  } catch {
42697
43508
  }
42698
43509
  };
42699
- return deps.runExclusive(async () => {
43510
+ return deps.run("exec", async () => {
42700
43511
  const pending = [];
42701
43512
  let resolveTurnEnded = null;
42702
43513
  const turnEnded = new Promise((resolve) => {
@@ -42709,6 +43520,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42709
43520
  lastActivityAt = Date.now();
42710
43521
  touch();
42711
43522
  }
43523
+ const usage = !notifThreadId || notifThreadId === threadId ? extractTurnTokenUsage(method, params) : null;
43524
+ if (usage) turnUsage = usage;
42712
43525
  const events = mapper.mapNotification(
42713
43526
  { method, params },
42714
43527
  threadId
@@ -42734,7 +43547,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42734
43547
  touch = activity.touch;
42735
43548
  const accepted = await codex.turn.start({
42736
43549
  threadId,
42737
- input: [{ type: "text", text: inputText }]
43550
+ input: [{ type: "text", text: inputText }],
43551
+ ...effort === void 0 ? {} : { effort }
42738
43552
  });
42739
43553
  acceptedTurnId = accepted?.turn?.id ?? null;
42740
43554
  await turnEnded;
@@ -42769,7 +43583,8 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
42769
43583
  clearInterval(touchTimer);
42770
43584
  unsub();
42771
43585
  }
42772
- 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 };
42773
43588
  });
42774
43589
  }
42775
43590
  async function driveRun(deps, projectSlug, runId) {
@@ -42833,11 +43648,53 @@ async function driveRun(deps, projectSlug, runId) {
42833
43648
  ...context,
42834
43649
  compact: {
42835
43650
  projectSlug,
42836
- specWikiPageId: task.specWikiPageId ?? null
43651
+ specWikiPageId: task.specWikiPageId ?? null,
43652
+ budget: executionBundleBudget()
42837
43653
  }
42838
43654
  } : context
42839
43655
  );
42840
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)}`;
42841
43698
  const checkout = deps.checkoutRegistry?.get(projectSlug) ?? null;
42842
43699
  let sandbox = "read-only";
42843
43700
  let turnCwd = deps.codexCwd ?? null;
@@ -42913,7 +43770,7 @@ async function driveRun(deps, projectSlug, runId) {
42913
43770
  const pushFn = deps.pushRunBranch ?? pushRunBranch;
42914
43771
  try {
42915
43772
  const { threadId, resumed } = await acquireThread(codex, {
42916
- threadId: task.directorThreadId,
43773
+ threadId: threadIdForAcquire,
42917
43774
  startParams: {
42918
43775
  cwd: turnCwd,
42919
43776
  approvalPolicy: "never",
@@ -42929,9 +43786,36 @@ async function driveRun(deps, projectSlug, runId) {
42929
43786
  },
42930
43787
  onThreadIdChanged: async (newId) => {
42931
43788
  if (!await runHasLease(deps, projectSlug, runId, leaseId)) return;
43789
+ if (retirement.retire) return;
42932
43790
  await deps.client.updateTask(projectSlug, task.id, { directorThreadId: newId });
42933
43791
  }
42934
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
+ }
42935
43819
  if (!resumed) {
42936
43820
  const primingBudgetMs = remainingTurnBudgetMs();
42937
43821
  if (primingBudgetMs <= 0) {
@@ -42946,14 +43830,52 @@ async function driveRun(deps, projectSlug, runId) {
42946
43830
  );
42947
43831
  return;
42948
43832
  }
42949
- 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
+ );
42950
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
+ }
42951
43852
  await resetWedgedThread(deps, projectSlug, task.id, primed.interruptFailed);
42952
43853
  await failRun(deps, projectSlug, task, runId, leaseId, primed.error);
42953
43854
  return;
42954
43855
  }
42955
43856
  }
42956
- 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}
42957
43879
 
42958
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.`;
42959
43881
  const workBudgetMs = remainingTurnBudgetMs();
@@ -42969,7 +43891,9 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
42969
43891
  );
42970
43892
  return;
42971
43893
  }
42972
- 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
+ );
42973
43897
  if (!outcome.ok) {
42974
43898
  await resetWedgedThread(deps, projectSlug, task.id, outcome.interruptFailed);
42975
43899
  await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
@@ -42993,16 +43917,19 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
42993
43917
  })
42994
43918
  );
42995
43919
  const completionPrompt = buildCompletionTurnPrompt(task.id, readiness.task.status);
42996
- const completionOutcome = await driveTurn(
42997
- deps,
42998
- codex,
42999
- projectSlug,
43000
- runId,
43001
- leaseId,
43002
- threadId,
43003
- completionPrompt,
43004
- completionBudget,
43005
- "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
+ )
43006
43933
  );
43007
43934
  if (!completionOutcome.ok) {
43008
43935
  await resetWedgedThread(deps, projectSlug, task.id, completionOutcome.interruptFailed);
@@ -43181,9 +44108,97 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
43181
44108
  error51 instanceof Error ? error51.message : String(error51)
43182
44109
  );
43183
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
+ });
43184
44143
  disposeCodex();
43185
44144
  }
43186
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";
43187
44202
  function buildDeliverableAuthorPrompt(task, specMarkdown) {
43188
44203
  return [
43189
44204
  `Produce the deliverable for the task below. Write the FULL deliverable content as markdown.`,
@@ -43713,7 +44728,8 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
43713
44728
  break;
43714
44729
  }
43715
44730
  try {
43716
- outcome = await deps.runExclusive(
44731
+ outcome = await deps.run(
44732
+ "async",
43717
44733
  () => descriptor2.run(
43718
44734
  { client: deps.client, cwd: deps.cwd, codexExecutable: deps.codexExecutable },
43719
44735
  input
@@ -43788,7 +44804,7 @@ async function runAsyncBackstopSweep(deps, drive = (slug, jobId) => driveAsyncJo
43788
44804
  } catch {
43789
44805
  queued = [];
43790
44806
  }
43791
- for (const job of queued) await drive(slug, job.id);
44807
+ await Promise.all(queued.map((job) => drive(slug, job.id)));
43792
44808
  }
43793
44809
  }
43794
44810
 
@@ -44256,13 +45272,16 @@ function createInactivityWatchdog(opts) {
44256
45272
  const clearT = opts.clearTimeoutFn ?? clearTimeout;
44257
45273
  const now = opts.now ?? Date.now;
44258
45274
  const suspended = opts.suspendedMs ?? (() => 0);
45275
+ const excluded = opts.idleExcludedMs ?? (() => 0);
44259
45276
  let idleTimer = null;
44260
45277
  let maxTimer = null;
44261
45278
  let done = false;
44262
45279
  const startAt = now();
44263
45280
  const suspendedAtStart = suspended();
45281
+ const excludedAtStart = excluded();
44264
45282
  let lastPokeAt = startAt;
44265
45283
  let suspendedAtLastPoke = suspendedAtStart;
45284
+ let excludedAtLastPoke = excludedAtStart;
44266
45285
  const expire = (reason) => {
44267
45286
  if (done) return;
44268
45287
  done = true;
@@ -44280,7 +45299,7 @@ function createInactivityWatchdog(opts) {
44280
45299
  };
44281
45300
  const onIdleDeadline = () => {
44282
45301
  if (done) return;
44283
- const awakeSilence = now() - lastPokeAt - (suspended() - suspendedAtLastPoke);
45302
+ const awakeSilence = now() - lastPokeAt - (suspended() - suspendedAtLastPoke) - (excluded() - excludedAtLastPoke);
44284
45303
  if (awakeSilence < opts.idleMs) {
44285
45304
  armIdle(opts.idleMs - awakeSilence);
44286
45305
  return;
@@ -44304,6 +45323,7 @@ function createInactivityWatchdog(opts) {
44304
45323
  if (idleTimer) clearT(idleTimer);
44305
45324
  lastPokeAt = now();
44306
45325
  suspendedAtLastPoke = suspended();
45326
+ excludedAtLastPoke = excluded();
44307
45327
  armIdle(opts.idleMs);
44308
45328
  },
44309
45329
  cancel: () => {
@@ -44313,8 +45333,8 @@ function createInactivityWatchdog(opts) {
44313
45333
  }
44314
45334
  };
44315
45335
  }
44316
- var DEFAULT_CHAT_TURN_IDLE_SECONDS = 180;
44317
- var DEFAULT_CHAT_TURN_MAX_SECONDS = 1800;
45336
+ var DEFAULT_CHAT_TURN_IDLE_SECONDS = 300;
45337
+ var DEFAULT_CHAT_TURN_MAX_SECONDS = 2700;
44318
45338
  function readPositiveSeconds(name, fallback) {
44319
45339
  const raw = (process.env[name] ?? "").trim();
44320
45340
  const parsed = Number.parseInt(raw, 10);
@@ -44327,6 +45347,64 @@ function chatTurnMaxMs() {
44327
45347
  return readPositiveSeconds("MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_MAX_SECONDS", DEFAULT_CHAT_TURN_MAX_SECONDS) * 1e3;
44328
45348
  }
44329
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
+
44330
45408
  // src/worker-token.ts
44331
45409
  var import_node_crypto7 = require("node:crypto");
44332
45410
  var import_node_fs9 = require("node:fs");
@@ -48331,11 +49409,16 @@ function parseVerdict2(text) {
48331
49409
  let last = null;
48332
49410
  while ((match = re.exec(text)) !== null) last = match;
48333
49411
  if (!last) {
48334
- 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
+ };
48335
49418
  }
48336
49419
  const verdict = (last[1] ?? "").toLowerCase() === "pass" ? "pass" : "fail";
48337
49420
  const summary = (last[2] ?? "").trim() || `verification ${verdict}`;
48338
- return { verdict, summary, turnRef: null };
49421
+ return { verdict, summary, turnRef: null, fullOutput: text };
48339
49422
  }
48340
49423
  function isBlockedVerificationHost(hostname3) {
48341
49424
  const h = hostname3.toLowerCase().replace(/^\[|\]$/g, "");
@@ -49208,6 +50291,7 @@ function buildThreadStartParams(msg, config2, desiredModel) {
49208
50291
  "- Keep behavior contracts in the spec; implementation details belong in `## Implementation Plan`.",
49209
50292
  "- Apply this format ONLY to Specs/{id} pages; do not retro-fit it onto other wiki page types.",
49210
50293
  "- Spec consensus runs ASYNCHRONOUSLY. Call `run_spec_consensus` for a `Specs/{id}` page; it returns IMMEDIATELY. On `already_converged`, the spec is already marked ready \u2014 proceed straight to `create_execution_run`; do NOT call `mark_spec_ready` and do NOT re-run consensus. On `queued` or `already_running`, tell the user it was launched and END your turn \u2014 do NOT poll or call it again; the result arrives in the inbox (an `info` item on CONVERGED, a `handoff` item if it was blocked/failed). When a consensus `info` item says CONVERGED, proceed to `create_execution_run`. When a `handoff` says it was blocked, address the recorded issues in the spec (any edit re-arms the gate) and call `run_spec_consensus` once more. For a NON-code task (domain seo/marketing/content, ADR 0020): the low-risk lanes `seo`, `marketing`, and `content` proceed to `create_execution_run` after consensus converges, using the same flow as code tasks. Report the spec's review outcome to the operator.",
50294
+ "- When creating a task, classify and supply the `domain` field explicitly based on the nature of the work: pass `domain:'code'` for software engineering work, `domain:'seo'` for SEO/optimization, `domain:'marketing'` for marketing/growth, or `domain:'content'` for content creation/writing \u2014 the same way you select category/subcategory. A follow-up task spawned with `parentTaskId` inherits its parent's domain automatically and requires no explicit domain.",
49211
50295
  '- When a task needs a browser (login flows, UI verification/clicks, scraping), pass `prerequisites:[{mcp:"mrrlin-browser",reason:"..."}]` to `create_execution_run`. If that run later fails into a handoff, the operator gets a one-click "Launch browser & resume" that brings up the dedicated browser as the turn starts.',
49212
50296
  "- When you actually drive a browser (calling `browser_navigate`, `browser_click`, `browser_snapshot`, `browser_type`, etc.), invoke those tools from the **`mrrlin-browser`** MCP server ONLY. Do NOT use tools from any other browser MCP (`playwright`, `chrome-devtools`, `puppeteer`, etc.) even if they are also connected on this operator's box \u2014 those use a shared/default Playwright profile that is fragile across runs. Only `mrrlin-browser` uses this project's dedicated Chromium + persistent operator profile (`~/.mrrlin/browser/profile`). If your host exposes multiple browser MCPs with overlapping tool names, prefer the `mrrlin-browser` server unconditionally.",
49213
50297
  "",
@@ -50434,7 +51518,7 @@ function createBridgeMessageHandler(deps) {
50434
51518
  }
50435
51519
  };
50436
51520
  try {
50437
- await deps.runExclusive(async () => {
51521
+ await deps.run("chat", async () => {
50438
51522
  if (currentReplayBuf.state === "interrupted" || currentReplayBuf.state === "completed") {
50439
51523
  logCtx?.logger.logOut({
50440
51524
  spanId,
@@ -50463,7 +51547,9 @@ function createBridgeMessageHandler(deps) {
50463
51547
  outputEvents += 1;
50464
51548
  sendInTurn({ type: "event", directorSessionId, event });
50465
51549
  };
51550
+ const toolBusy = createToolBusyClock();
50466
51551
  const unsub = codexClient.onNotification((method, params) => {
51552
+ applyToolBusyNotification(toolBusy, method, params);
50467
51553
  pokeActivity();
50468
51554
  if (method === "account/rateLimits/updated") {
50469
51555
  const snapshot = params?.rateLimits ?? null;
@@ -50491,6 +51577,7 @@ function createBridgeMessageHandler(deps) {
50491
51577
  idleMs,
50492
51578
  maxMs,
50493
51579
  ...deps.suspendedMs ? { suspendedMs: deps.suspendedMs } : {},
51580
+ idleExcludedMs: () => toolBusy.ms(),
50494
51581
  onExpire: (reason) => {
50495
51582
  rejectWatchdog?.(
50496
51583
  new Error(
@@ -50594,6 +51681,7 @@ ${effectiveText}` : effectiveText;
50594
51681
  }
50595
51682
  watchdog.cancel();
50596
51683
  unsub();
51684
+ toolBusy.endAll();
50597
51685
  thread.cancel = null;
50598
51686
  }
50599
51687
  });
@@ -50713,8 +51801,8 @@ async function startDirectorBridge() {
50713
51801
  const turnLock = createTurnLock();
50714
51802
  let lastTurnEndedAt = Date.now();
50715
51803
  const wrappedTurnLock = {
50716
- runExclusive(fn) {
50717
- return turnLock.runExclusive(async () => {
51804
+ run(lane, fn) {
51805
+ return turnLock.run(lane, async () => {
50718
51806
  try {
50719
51807
  return await fn();
50720
51808
  } finally {
@@ -50757,7 +51845,11 @@ async function startDirectorBridge() {
50757
51845
  }
50758
51846
  });
50759
51847
  const codexProbeHandle = startCodexProbe();
50760
- const consumerState = { inFlightRunId: null, lastClaimAt: null };
51848
+ const consumerState = {
51849
+ inFlightRunIds: /* @__PURE__ */ new Set(),
51850
+ lastClaimedRunId: null,
51851
+ lastClaimAt: null
51852
+ };
50761
51853
  const health = createServiceHealth();
50762
51854
  try {
50763
51855
  migrateRetiredApiBaseUrls();
@@ -50836,7 +51928,7 @@ async function startDirectorBridge() {
50836
51928
  // handle returned by acquireExecutorCodex (Fix A + G), so there is no shared
50837
51929
  // mutable `activeExecutorClient` slot to race a concurrent chat/bootstrap turn.
50838
51930
  releaseCodex: () => codexKeepalive.release(),
50839
- runExclusive: wrappedTurnLock.runExclusive,
51931
+ run: wrappedTurnLock.run,
50840
51932
  staleSeconds,
50841
51933
  touchIntervalMs: Math.min(DEFAULT_TOUCH_INTERVAL_MS, Math.floor(staleSeconds * 1e3 / 2)),
50842
51934
  codexCwd: config2.codexCwd,
@@ -50863,7 +51955,7 @@ async function startDirectorBridge() {
50863
51955
  // ADR 0007 self-review: critique the committed diff on the executor's own client before
50864
51956
  // push/PR. Reuses the verification turn-runner (acquireThread + collect assistant text).
50865
51957
  selfReviewTurn: createSelfReviewTurn({
50866
- 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))
50867
51959
  }),
50868
51960
  // ADR 0015 deploy gate: after a pushed run reaches dev_deploy/prod_deploy, the backstop sweep
50869
51961
  // dispatches the deploy + polls + runs a verification turn against the deployed env URL in a
@@ -50902,7 +51994,7 @@ async function startDirectorBridge() {
50902
51994
  }
50903
51995
  };
50904
51996
  },
50905
- 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))
50906
51998
  }),
50907
51999
  budgetSeconds: deployPollBudgetSeconds()
50908
52000
  },
@@ -50927,7 +52019,7 @@ async function startDirectorBridge() {
50927
52019
  const bridgeInstanceId = (0, import_node_crypto8.randomUUID)();
50928
52020
  const asyncConsumerDeps = {
50929
52021
  client,
50930
- runExclusive: wrappedTurnLock.runExclusive,
52022
+ run: wrappedTurnLock.run,
50931
52023
  cwd: config2.codexCwd ?? process.cwd(),
50932
52024
  codexExecutable: config2.codexExecutable,
50933
52025
  staleSeconds,
@@ -50943,40 +52035,34 @@ async function startDirectorBridge() {
50943
52035
  sink({ type: "async-result", directorSessionId, projectSlug: slug, deliveryKind, body });
50944
52036
  }
50945
52037
  };
50946
- let execChain = Promise.resolve();
50947
- const enqueueRun = (job) => {
50948
- execChain = execChain.then(job, job);
50949
- };
50950
- const driveTracked = (slug, runId) => enqueueRun(async () => {
50951
- consumerState.inFlightRunId = runId;
52038
+ const driveTracked = (slug, runId) => {
52039
+ consumerState.inFlightRunIds.add(runId);
52040
+ consumerState.lastClaimedRunId = runId;
50952
52041
  consumerState.lastClaimAt = (/* @__PURE__ */ new Date()).toISOString();
50953
- try {
50954
- await driveRun(consumerDeps, slug, runId);
50955
- } catch (error51) {
52042
+ void driveRun(consumerDeps, slug, runId).catch((error51) => {
50956
52043
  process.stderr.write(`[mrrlin-mcp director-bridge] drive failed for ${slug}/${runId}: ${error51 instanceof Error ? error51.message : String(error51)}
50957
52044
  `);
50958
- } finally {
50959
- consumerState.inFlightRunId = null;
50960
- }
50961
- });
52045
+ }).finally(() => {
52046
+ consumerState.inFlightRunIds.delete(runId);
52047
+ });
52048
+ };
50962
52049
  const driveRunNow = (slug, runId) => {
50963
- consumerState.inFlightRunId = runId;
52050
+ consumerState.inFlightRunIds.add(runId);
52051
+ consumerState.lastClaimedRunId = runId;
50964
52052
  consumerState.lastClaimAt = (/* @__PURE__ */ new Date()).toISOString();
50965
52053
  void driveRun(consumerDeps, slug, runId).catch((error51) => {
50966
52054
  process.stderr.write(`[mrrlin-mcp director-bridge] drive failed for ${slug}/${runId}: ${error51 instanceof Error ? error51.message : String(error51)}
50967
52055
  `);
50968
52056
  }).finally(() => {
50969
- consumerState.inFlightRunId = null;
52057
+ consumerState.inFlightRunIds.delete(runId);
50970
52058
  });
50971
52059
  };
50972
- const driveAsyncTracked = (slug, jobId) => enqueueRun(async () => {
50973
- try {
50974
- await driveAsyncJob(asyncConsumerDeps, slug, jobId);
50975
- } catch (error51) {
52060
+ const driveAsyncTracked = (slug, jobId) => {
52061
+ void driveAsyncJob(asyncConsumerDeps, slug, jobId).catch((error51) => {
50976
52062
  process.stderr.write(`[mrrlin-mcp director-bridge] async drive failed for ${slug}/${jobId}: ${error51 instanceof Error ? error51.message : String(error51)}
50977
52063
  `);
50978
- }
50979
- });
52064
+ });
52065
+ };
50980
52066
  const driveAsyncNow = (slug, jobId) => {
50981
52067
  void driveAsyncJob(asyncConsumerDeps, slug, jobId).catch((error51) => {
50982
52068
  process.stderr.write(`[mrrlin-mcp director-bridge] async drive failed for ${slug}/${jobId}: ${error51 instanceof Error ? error51.message : String(error51)}
@@ -50989,7 +52075,15 @@ async function startDirectorBridge() {
50989
52075
  port: config2.port,
50990
52076
  token: config2.token,
50991
52077
  allowedOrigins: config2.allowedOrigins,
50992
- 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
+ })
50993
52087
  })
50994
52088
  );
50995
52089
  const threads = /* @__PURE__ */ new Map();
@@ -51052,7 +52146,7 @@ async function startDirectorBridge() {
51052
52146
  const sharedHandlerDeps = {
51053
52147
  acquireCodex: () => codexKeepalive.acquire(),
51054
52148
  releaseCodex: () => codexKeepalive.release(),
51055
- runExclusive: wrappedTurnLock.runExclusive,
52149
+ run: wrappedTurnLock.run,
51056
52150
  suspendedMs: () => suspensionDetector.suspendedMs(),
51057
52151
  buildStartParams: (msg, desiredModel) => buildThreadStartParams(msg, config2, desiredModel),
51058
52152
  turnInactivity: () => ({ idleMs: chatTurnIdleMs(), maxMs: chatTurnMaxMs() }),
@@ -51198,7 +52292,7 @@ async function startDirectorBridge() {
51198
52292
  await bridgeHealth.recordSuccess();
51199
52293
  } catch (error51) {
51200
52294
  process.stderr.write(
51201
- `[mrrlin-mcp director-bridge] ${label} failed: ${error51 instanceof Error ? error51.message : String(error51)}
52295
+ `[mrrlin-mcp director-bridge] ${label} failed: ${describeError(error51)}
51202
52296
  `
51203
52297
  );
51204
52298
  bridgeHealth.recordFailure(id, error51);
@@ -51210,34 +52304,28 @@ async function startDirectorBridge() {
51210
52304
  const scheduleClaimLoop = () => {
51211
52305
  if (claimLoopStopped) return;
51212
52306
  claimTimer = setTimeout(() => {
51213
- enqueueRun(
51214
- () => runTrackedSweep(
51215
- "claimSweep",
51216
- "claim sweep",
51217
- () => runBackstopSweep(consumerDeps, (slug, runId) => {
51218
- driveTracked(slug, runId);
51219
- return Promise.resolve();
51220
- })
51221
- )
51222
- );
51223
- enqueueRun(
51224
- () => runTrackedSweep(
51225
- "asyncSweep",
51226
- "async sweep",
51227
- () => runAsyncBackstopSweep(asyncConsumerDeps, (slug, jobId) => {
51228
- driveAsyncTracked(slug, jobId);
51229
- return Promise.resolve();
51230
- })
51231
- )
52307
+ void runTrackedSweep(
52308
+ "claimSweep",
52309
+ "claim sweep",
52310
+ () => runBackstopSweep(consumerDeps, (slug, runId) => {
52311
+ driveTracked(slug, runId);
52312
+ return Promise.resolve();
52313
+ })
51232
52314
  );
51233
- enqueueRun(
51234
- () => runOrphanWorktreeSweep(client, {
51235
- checkoutRegistry,
51236
- worktreeRoot: import_node_path18.default.join(config2.stateDir, "worktrees"),
51237
- 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();
51238
52321
  })
51239
52322
  );
51240
- 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
+ {
51241
52329
  const { retainedBuffers, retainedBytes, tombstonesRemoved } = sweepReplayBuffers(threads, tombstones, {
51242
52330
  now: Date.now(),
51243
52331
  suspendedMs: () => suspensionDetector.suspendedMs(),
@@ -51275,7 +52363,7 @@ async function startDirectorBridge() {
51275
52363
  payload: { removed: tombstonesRemoved }
51276
52364
  });
51277
52365
  }
51278
- });
52366
+ }
51279
52367
  scheduleClaimLoop();
51280
52368
  }, claimIntervalMs());
51281
52369
  claimTimer.unref?.();
@@ -51313,7 +52401,7 @@ async function startDirectorBridge() {
51313
52401
  agentPoolLoopHandle = startAgentPoolLoop({
51314
52402
  push: (snapshot) => client.putOperatorAgentPool(snapshot),
51315
52403
  onError: (err) => process.stderr.write(
51316
- `[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)}
51317
52405
  `
51318
52406
  )
51319
52407
  });
@@ -55164,6 +56252,44 @@ function mapAcceptedIssueToClarification(issue2) {
55164
56252
  category: issue2.category
55165
56253
  };
55166
56254
  }
56255
+ var CLARIFICATION_CAP_BY_IMPACT = { low: 2, medium: 3, high: 5 };
56256
+ function resolveClarificationCap(impact) {
56257
+ const tier = impact?.trim().toLowerCase();
56258
+ if (tier === "medium") return CLARIFICATION_CAP_BY_IMPACT.medium;
56259
+ if (tier === "high") return CLARIFICATION_CAP_BY_IMPACT.high;
56260
+ return CLARIFICATION_CAP_BY_IMPACT.low;
56261
+ }
56262
+ function headIndexOf(id) {
56263
+ const match = /^r\d+-h(\d+)-\d+$/.exec(id);
56264
+ return match?.[1] !== void 0 ? Number(match[1]) : null;
56265
+ }
56266
+ function orderClarificationIssues(issues) {
56267
+ const distinctHeadsByKey = /* @__PURE__ */ new Map();
56268
+ for (const issue2 of issues) {
56269
+ const key = `${issue2.category} ${issue2.text}`;
56270
+ const heads = distinctHeadsByKey.get(key) ?? /* @__PURE__ */ new Set();
56271
+ const headIdx = headIndexOf(issue2.id);
56272
+ if (headIdx !== null) heads.add(headIdx);
56273
+ distinctHeadsByKey.set(key, heads);
56274
+ }
56275
+ const categoryRank = (category) => category === "scope" ? 0 : category === "ambiguity" ? 1 : 2;
56276
+ return issues.map((issue2, index) => ({ issue: issue2, index })).sort((a, b) => {
56277
+ const rankDiff = categoryRank(a.issue.category) - categoryRank(b.issue.category);
56278
+ if (rankDiff !== 0) return rankDiff;
56279
+ const headCountA = distinctHeadsByKey.get(`${a.issue.category} ${a.issue.text}`)?.size ?? 0;
56280
+ const headCountB = distinctHeadsByKey.get(`${b.issue.category} ${b.issue.text}`)?.size ?? 0;
56281
+ if (headCountA !== headCountB) return headCountB - headCountA;
56282
+ return a.index - b.index;
56283
+ }).map(({ issue: issue2 }) => issue2);
56284
+ }
56285
+ function applyClarificationCap(issues, impact) {
56286
+ const ordered = orderClarificationIssues(issues);
56287
+ const cap = resolveClarificationCap(impact);
56288
+ return {
56289
+ kept: ordered.slice(0, cap).map(mapAcceptedIssueToClarification),
56290
+ dropped: ordered.slice(cap).map(mapAcceptedIssueToClarification)
56291
+ };
56292
+ }
55167
56293
  async function runSpecGate(_target, deps, opts) {
55168
56294
  if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
55169
56295
  const intent = await deps.readIntent();
@@ -55181,11 +56307,11 @@ async function runSpecGate(_target, deps, opts) {
55181
56307
  ]
55182
56308
  };
55183
56309
  }
55184
- const baseHeads = resolveHeads(intent.domain);
56310
+ const baseHeads = resolveHeads(intent.domain, intent.impact);
55185
56311
  const extraHeads = opts?.extraHeads ?? [];
55186
56312
  const heads = [...baseHeads, ...extraHeads.filter((h) => !baseHeads.includes(h))];
55187
56313
  const spec = await deps.readSpec();
55188
- if (normalizeDomain(intent.domain) !== "code" && parseAcceptanceCriteria(spec.markdownBody) === null) {
56314
+ if (parseAcceptanceCriteria(spec.markdownBody) === null) {
55189
56315
  return {
55190
56316
  outcome: "CONSENSUS_BLOCKED",
55191
56317
  operatorClarifications: [
@@ -55253,23 +56379,32 @@ async function runSpecGate(_target, deps, opts) {
55253
56379
  ]
55254
56380
  };
55255
56381
  }
56382
+ let keptClarifications = [];
56383
+ let droppedByCap = [];
56384
+ if (result.outcome === "CONSENSUS_BLOCKED") {
56385
+ const accepted = result.history.at(-1)?.accepted ?? [];
56386
+ const materialIssues = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope");
56387
+ const capped = applyClarificationCap(materialIssues, intent.impact);
56388
+ keptClarifications = capped.kept;
56389
+ droppedByCap = capped.dropped;
56390
+ }
55256
56391
  await deps.writeEvidence({
55257
56392
  specPageId: spec.pageId,
55258
56393
  outcome: result.outcome,
55259
56394
  rounds: result.rounds,
55260
56395
  history: result.history,
55261
- contentHash: currentHash
56396
+ contentHash: currentHash,
56397
+ ...droppedByCap.length > 0 ? { droppedByCap } : {}
55262
56398
  });
55263
56399
  if (result.outcome === "CONVERGED") {
55264
56400
  await deps.promoteSpecReady(currentHash);
55265
56401
  return { outcome: "CONVERGED" };
55266
56402
  }
55267
56403
  if (result.outcome === "CONSENSUS_BLOCKED") {
55268
- const accepted = result.history.at(-1)?.accepted ?? [];
55269
- const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
55270
56404
  return {
55271
56405
  outcome: "CONSENSUS_BLOCKED",
55272
- ...operatorClarifications.length > 0 ? { operatorClarifications } : {},
56406
+ ...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
56407
+ ...droppedByCap.length > 0 ? { droppedByCap } : {},
55273
56408
  ...result.escalation ? { escalation: result.escalation } : {}
55274
56409
  };
55275
56410
  }
@@ -55550,7 +56685,7 @@ async function maybeRunShadow(input, deps, logger) {
55550
56685
  const productionVerdict = productionVerdictFromOutcome(input.outcome);
55551
56686
  if (productionVerdict === null) return false;
55552
56687
  try {
55553
- const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
56688
+ const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
55554
56689
  const personaInstructions = {};
55555
56690
  for (const name of heads) {
55556
56691
  try {
@@ -55592,7 +56727,7 @@ function multiLlmActive(env = process.env) {
55592
56727
  async function resolveActiveRunHead(input, deps) {
55593
56728
  if (!multiLlmActive(input.env ?? process.env)) return null;
55594
56729
  try {
55595
- const heads = input.heads ?? [...resolveHeads(input.domain), ...input.extraHeads ?? []];
56730
+ const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
55596
56731
  if (heads.length === 0) return null;
55597
56732
  const personaInstructions = {};
55598
56733
  for (const name of heads) {
@@ -55622,6 +56757,35 @@ async function resolveActiveRunHead(input, deps) {
55622
56757
 
55623
56758
  // src/async-tools/consensus.ts
55624
56759
  var import_node_crypto9 = require("node:crypto");
56760
+
56761
+ // src/consensus/question-similarity.ts
56762
+ var SUPPRESSION_SIMILARITY_THRESHOLD = 0.6;
56763
+ function tokenSet(text) {
56764
+ const normalized = text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").trim();
56765
+ if (!normalized) return /* @__PURE__ */ new Set();
56766
+ return new Set(normalized.split(/\s+/));
56767
+ }
56768
+ function jaccardSimilarity(a, b) {
56769
+ const setA = tokenSet(a);
56770
+ const setB = tokenSet(b);
56771
+ if (setA.size === 0 || setB.size === 0) return 0;
56772
+ let intersection2 = 0;
56773
+ for (const token of setA) {
56774
+ if (setB.has(token)) intersection2 += 1;
56775
+ }
56776
+ const union2 = setA.size + setB.size - intersection2;
56777
+ return union2 === 0 ? 0 : intersection2 / union2;
56778
+ }
56779
+ function isCoveredByAdjudicated(rawQuestion, adjudicated) {
56780
+ for (const item of adjudicated) {
56781
+ if (jaccardSimilarity(rawQuestion, item.body) >= SUPPRESSION_SIMILARITY_THRESHOLD) {
56782
+ return item;
56783
+ }
56784
+ }
56785
+ return null;
56786
+ }
56787
+
56788
+ // src/async-tools/consensus.ts
55625
56789
  function normalizeClarificationQuestion(question) {
55626
56790
  return question.replace(/\s+/g, " ").trim().replace(/[?.!]+$/, "").trim();
55627
56791
  }
@@ -55752,35 +56916,43 @@ async function cachedResult(client, input) {
55752
56916
  }
55753
56917
  return null;
55754
56918
  }
56919
+ function stripHumanizeOpener(body) {
56920
+ for (const prefix of Object.values(HUMANIZE_TEMPLATE_BY_KIND)) {
56921
+ if (body.startsWith(prefix)) return body.slice(prefix.length).trim();
56922
+ }
56923
+ return body;
56924
+ }
55755
56925
  async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55756
56926
  const operatorClarifications = result.operatorClarifications ?? [];
55757
56927
  const diagnostics = result.diagnostics ?? [];
55758
- if (operatorClarifications.length > 0) {
56928
+ const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
56929
+ const taskId = page?.taskId ?? null;
56930
+ if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
56931
+ const adjudicated = [];
56932
+ if (taskId) {
55759
56933
  try {
55760
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55761
- if (page) {
55762
- const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
55763
- id: page.id,
55764
- taskId: page.taskId ?? null
55765
- });
55766
- await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
55767
- contentHash: domainScopedReviewHash(page.contentHash, domain2),
55768
- outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
55769
- rounds: 0
55770
- });
56934
+ const taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
56935
+ for (const item of taskInboxItems) {
56936
+ if (item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")) {
56937
+ adjudicated.push({ body: stripHumanizeOpener(item.body), answer: item.answer, status: item.status });
56938
+ }
55771
56939
  }
55772
56940
  } catch (error52) {
55773
56941
  process.stderr.write(
55774
- `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
56942
+ `[mrrlin-mcp consensus] adjudicated-questions read failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
55775
56943
  `
55776
56944
  );
55777
56945
  }
55778
56946
  }
56947
+ const suppressedAsAdjudicated = [];
56948
+ const toFile = operatorClarifications.filter((clarification) => {
56949
+ const covering = isCoveredByAdjudicated(clarification.rawQuestion, adjudicated);
56950
+ if (!covering) return true;
56951
+ suppressedAsAdjudicated.push({ rawQuestion: clarification.rawQuestion, coveredByAnswer: covering.answer });
56952
+ return false;
56953
+ });
55779
56954
  let filedQuestionCount = 0;
55780
- if (operatorClarifications.length > 0) {
55781
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55782
- const taskId = page?.taskId ?? null;
55783
- if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
56955
+ if (toFile.length > 0) {
55784
56956
  let planId = null;
55785
56957
  if (!taskId) {
55786
56958
  const plans = await ctx.client.listPlans(input.projectSlug);
@@ -55788,8 +56960,8 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55788
56960
  }
55789
56961
  if (taskId) {
55790
56962
  const scopeId = taskId;
55791
- const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55792
- for (const clarification of operatorClarifications) {
56963
+ const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
56964
+ for (const clarification of toFile) {
55793
56965
  const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55794
56966
  try {
55795
56967
  const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
@@ -55805,11 +56977,11 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55805
56977
  } catch {
55806
56978
  }
55807
56979
  }
55808
- filedQuestionCount = operatorClarifications.length;
56980
+ filedQuestionCount = toFile.length;
55809
56981
  } else if (planId) {
55810
56982
  const scopeId = planId;
55811
- const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55812
- for (const clarification of operatorClarifications) {
56983
+ const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
56984
+ for (const clarification of toFile) {
55813
56985
  const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55814
56986
  try {
55815
56987
  const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
@@ -55825,7 +56997,32 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55825
56997
  } catch {
55826
56998
  }
55827
56999
  }
55828
- filedQuestionCount = operatorClarifications.length;
57000
+ filedQuestionCount = toFile.length;
57001
+ }
57002
+ }
57003
+ if (operatorClarifications.length > 0) {
57004
+ try {
57005
+ if (page) {
57006
+ const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
57007
+ id: page.id,
57008
+ taskId: page.taskId ?? null
57009
+ });
57010
+ const suppressionEvidence = (result.droppedByCap?.length ?? 0) > 0 || suppressedAsAdjudicated.length > 0 ? {
57011
+ ...result.droppedByCap?.length ? { droppedByCap: result.droppedByCap } : {},
57012
+ ...suppressedAsAdjudicated.length ? { suppressedAsAdjudicated } : {}
57013
+ } : void 0;
57014
+ await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
57015
+ contentHash: domainScopedReviewHash(page.contentHash, domain2),
57016
+ outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
57017
+ rounds: 0,
57018
+ ...suppressionEvidence ? { suppressionEvidence } : {}
57019
+ });
57020
+ }
57021
+ } catch (error52) {
57022
+ process.stderr.write(
57023
+ `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
57024
+ `
57025
+ );
55829
57026
  }
55830
57027
  }
55831
57028
  let error51;
@@ -55833,6 +57030,8 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55833
57030
  error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
55834
57031
  } else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
55835
57032
  error51 = "Consensus blocked: the spec content does not match its linked task. This requires fixing the spec/task relationship directly, not an inbox answer.";
57033
+ } else if (operatorClarifications.length > 0 && toFile.length === 0 && suppressedAsAdjudicated.length > 0) {
57034
+ error51 = `Consensus blocked, but all ${suppressedAsAdjudicated.length} clarification(s) were reworded repeats of already-adjudicated questions \u2014 nothing new filed.`;
55836
57035
  } else if (operatorClarifications.length > 0) {
55837
57036
  error51 = `Consensus blocked on ${operatorClarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
55838
57037
  } else {
@@ -56138,30 +57337,12 @@ var mcpToolNames = {
56138
57337
  restartExecutionRun: "restart_execution_run",
56139
57338
  resolveHandoff: "resolve_handoff",
56140
57339
  recordProducedUrl: "record_produced_url",
57340
+ updateTaskState: "update_task_state",
56141
57341
  registerLocalCheckout: "register_local_checkout",
56142
57342
  /** Compact-context retrieval: rehydrate a mrrlin:// handle into the canonical full record. */
56143
57343
  retrieveContext: "retrieve_context"
56144
57344
  };
56145
57345
 
56146
- // src/context-compact/policy.ts
56147
- var DEFAULT_THRESHOLD_CHARS = 12e3;
56148
- var DEFAULT_MAX_ITEMS = 25;
56149
- function readPositiveInt(value, fallback) {
56150
- const raw = (value ?? "").trim();
56151
- if (!raw) return fallback;
56152
- const parsed = Number.parseInt(raw, 10);
56153
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
56154
- }
56155
- function readCompactPolicy(env = process.env) {
56156
- const disabled = (env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() === "1";
56157
- return {
56158
- enabled: !disabled,
56159
- thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
56160
- maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
56161
- debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1"
56162
- };
56163
- }
56164
-
56165
57346
  // src/context-compact/handles.ts
56166
57347
  var MRRLIN_HANDLE_SCHEME = "mrrlin://";
56167
57348
  var SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
@@ -56236,7 +57417,7 @@ function parseMrrlinHandle(value) {
56236
57417
  var NOTES_EXCERPT_CHARS = 200;
56237
57418
  var TITLE_TRUNCATE_CHARS = 160;
56238
57419
  var EXCERPT_TRUNCATE_CHARS = 240;
56239
- function truncate(value, maxChars) {
57420
+ function truncate2(value, maxChars) {
56240
57421
  if (value.length <= maxChars) return value;
56241
57422
  return `${value.slice(0, Math.max(0, maxChars - 1))}\u2026`;
56242
57423
  }
@@ -56252,7 +57433,7 @@ function compactTaskList(projectSlug, tasks, maxItems) {
56252
57433
  const slice = tasks.slice(0, included);
56253
57434
  const items = slice.map((t) => ({
56254
57435
  id: t.id,
56255
- title: truncate(t.title, TITLE_TRUNCATE_CHARS),
57436
+ title: truncate2(t.title, TITLE_TRUNCATE_CHARS),
56256
57437
  status: t.status,
56257
57438
  type: t.type,
56258
57439
  category: t.category,
@@ -56263,8 +57444,8 @@ function compactTaskList(projectSlug, tasks, maxItems) {
56263
57444
  assignee: t.assignee,
56264
57445
  planId: t.planId,
56265
57446
  specWikiPageId: t.specWikiPageId,
56266
- notesExcerpt: t.notes ? truncate(t.notes, NOTES_EXCERPT_CHARS) : null,
56267
- 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,
56268
57449
  handle: formatTaskHandle(projectSlug, t.id)
56269
57450
  }));
56270
57451
  const handles = slice.map((t) => ({
@@ -56285,9 +57466,10 @@ function compactWikiPageList(projectSlug, pages, maxItems) {
56285
57466
  const slice = pages.slice(0, included);
56286
57467
  const items = slice.map((p) => ({
56287
57468
  id: p.id,
56288
- title: truncate(p.title, TITLE_TRUNCATE_CHARS),
57469
+ title: truncate2(p.title, TITLE_TRUNCATE_CHARS),
56289
57470
  folder: p.folder,
56290
57471
  path: p.path,
57472
+ updatedAt: p.updatedAt,
56291
57473
  taskId: p.taskId,
56292
57474
  specReady: p.specReady,
56293
57475
  firstHeading: firstHeadingLine(p.markdownBody),
@@ -56311,11 +57493,15 @@ function compactWikiSearch(projectSlug, results, maxItems) {
56311
57493
  const slice = results.slice(0, included);
56312
57494
  const items = slice.map((r) => ({
56313
57495
  pageId: r.id,
56314
- title: truncate(r.title, TITLE_TRUNCATE_CHARS),
57496
+ title: truncate2(r.title, TITLE_TRUNCATE_CHARS),
56315
57497
  folder: r.folder,
56316
57498
  path: r.path,
56317
57499
  rank: r.rank,
56318
- snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
57500
+ updatedAt: r.updatedAt,
57501
+ ageDays: r.ageDays,
57502
+ memoryClass: r.memoryClass,
57503
+ precedence: r.precedence,
57504
+ snippet: truncate2(r.snippet, EXCERPT_TRUNCATE_CHARS),
56319
57505
  handle: formatWikiHandle(projectSlug, r.id)
56320
57506
  }));
56321
57507
  const handles = slice.map((r) => ({
@@ -56344,7 +57530,7 @@ function compactProjectSnapshot(projectSlug, snapshot, maxItems) {
56344
57530
  }
56345
57531
  const compactPlans = snapshot.plans.map((p) => ({
56346
57532
  id: p.id,
56347
- name: truncate(p.name, TITLE_TRUNCATE_CHARS),
57533
+ name: truncate2(p.name, TITLE_TRUNCATE_CHARS),
56348
57534
  status: p.status,
56349
57535
  taskCount: p.taskCount,
56350
57536
  closedTaskCount: p.closedTaskCount,
@@ -56416,7 +57602,7 @@ function compactArtifactPayload(artifact) {
56416
57602
  cwd,
56417
57603
  exitCode,
56418
57604
  firstError,
56419
- tail: truncate(tail2, ARTIFACT_PAYLOAD_BUDGET),
57605
+ tail: truncate2(tail2, ARTIFACT_PAYLOAD_BUDGET),
56420
57606
  totalLines: lines2.length
56421
57607
  };
56422
57608
  }
@@ -56424,7 +57610,7 @@ function compactArtifactPayload(artifact) {
56424
57610
  return {
56425
57611
  status: payload.status ?? null,
56426
57612
  command: payload.command ?? null,
56427
- 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,
56428
57614
  failed: Array.isArray(payload.failedTests) ? payload.failedTests.slice(0, 25) : null
56429
57615
  };
56430
57616
  }
@@ -56442,7 +57628,7 @@ function compactArtifactPayload(artifact) {
56442
57628
  case "checkpoint":
56443
57629
  default: {
56444
57630
  const flattened = JSON.stringify(payload);
56445
- return { excerpt: truncate(flattened, ARTIFACT_PAYLOAD_BUDGET) };
57631
+ return { excerpt: truncate2(flattened, ARTIFACT_PAYLOAD_BUDGET) };
56446
57632
  }
56447
57633
  }
56448
57634
  }
@@ -56481,7 +57667,7 @@ function compactTaskEvents(projectSlug, taskId, events, maxItems) {
56481
57667
  createdAt: e.createdAt,
56482
57668
  autonomyLevel: e.autonomyLevel,
56483
57669
  autonomyReason: e.autonomyReason,
56484
- bodyExcerpt: e.body ? truncate(e.body, NOTES_EXCERPT_CHARS) : null
57670
+ bodyExcerpt: e.body ? truncate2(e.body, NOTES_EXCERPT_CHARS) : null
56485
57671
  }));
56486
57672
  const handles = [
56487
57673
  {
@@ -56503,7 +57689,7 @@ function compactGeneric(value, maxItems, projectSlug) {
56503
57689
  const included = Math.min(total, maxItems);
56504
57690
  const slice = value.slice(0, included).map((entry) => {
56505
57691
  const text2 = typeof entry === "string" ? entry : JSON.stringify(entry);
56506
- return truncate(text2, ARTIFACT_PAYLOAD_BUDGET);
57692
+ return truncate2(text2, ARTIFACT_PAYLOAD_BUDGET);
56507
57693
  });
56508
57694
  return {
56509
57695
  summary: `Compact array view: ${total} entries, ${included} included.`,
@@ -56534,7 +57720,7 @@ function compactGeneric(value, maxItems, projectSlug) {
56534
57720
  const text = typeof value === "string" ? value : JSON.stringify(value);
56535
57721
  return {
56536
57722
  summary: "Compact scalar view.",
56537
- items: [truncate(text, ARTIFACT_PAYLOAD_BUDGET)],
57723
+ items: [truncate2(text, ARTIFACT_PAYLOAD_BUDGET)],
56538
57724
  handles: [],
56539
57725
  counts: { total: 1, included: 1, omitted: 0 }
56540
57726
  };
@@ -56704,6 +57890,7 @@ var updateExecutionRunInputSchema = runScopedSchema.merge(mrrlinExecutionRunUpda
56704
57890
  var listExecutionArtifactsInputSchema = runScopedSchema;
56705
57891
  var appendExecutionArtifactInputSchema = runScopedSchema.merge(mrrlinExecutionArtifactCreateSchema);
56706
57892
  var recordProducedUrlInputSchema = runScopedSchema.merge(mrrlinProducedUrlPayloadSchema);
57893
+ var updateTaskStateInputSchema = runScopedSchema.merge(mrrlinTaskStateReportSchema);
56707
57894
  var getProjectSchedulerPolicyInputSchema = projectScopedSchema;
56708
57895
  var upsertProjectSchedulerPolicyInputSchema = projectScopedSchema.merge(mrrlinProjectSchedulerPolicyUpsertSchema);
56709
57896
  var recordDeployWorkflowInputSchema = projectScopedSchema.extend({
@@ -56816,6 +58003,7 @@ var mcpToolInputSchemas = {
56816
58003
  [mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
56817
58004
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
56818
58005
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
58006
+ [mcpToolNames.updateTaskState]: updateTaskStateInputSchema,
56819
58007
  [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
56820
58008
  [mcpToolNames.retrieveContext]: retrieveContextInputSchema
56821
58009
  };
@@ -56867,6 +58055,7 @@ var mcpToolOperationIds = {
56867
58055
  [mcpToolNames.recordDeployWorkflow]: openApiOperationIds.upsertProjectDeployWorkflow,
56868
58056
  [mcpToolNames.updatePlan]: openApiOperationIds.updatePlan,
56869
58057
  [mcpToolNames.updateTask]: openApiOperationIds.updateTask,
58058
+ [mcpToolNames.updateTaskState]: openApiOperationIds.reportTaskState,
56870
58059
  [mcpToolNames.resetDirectorThread]: openApiOperationIds.resetDirectorThread,
56871
58060
  [mcpToolNames.archiveTask]: openApiOperationIds.archiveTask,
56872
58061
  [mcpToolNames.unarchiveTask]: openApiOperationIds.unarchiveTask,
@@ -56897,7 +58086,7 @@ var mcpToolDescriptions = {
56897
58086
  [mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline.",
56898
58087
  [mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
56899
58088
  [mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
56900
- [mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own.",
58089
+ [mcpToolNames.createTask]: "Create a new task in a project. Always starts as status='todo' with a blank assignee. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own. Domain is required UNLESS parentTaskId is supplied (a follow-up inherits its parent's domain automatically). Domain selects which reviewer roster judges the task's spec. Valid values: code, seo, marketing, content.",
56901
58090
  [mcpToolNames.getProject]: "Fetch a single project summary by slug.",
56902
58091
  [mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
56903
58092
  [mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",
@@ -56927,6 +58116,7 @@ var mcpToolDescriptions = {
56927
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).`,
56928
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.",
56929
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.",
56930
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.',
56931
58121
  [mcpToolNames.getExecutionRun]: "Fetch a single execution run by id.",
56932
58122
  [mcpToolNames.listExecutionArtifacts]: "List artifacts captured during an execution run, in chronological order.",
@@ -57524,6 +58714,13 @@ function createMrrlinTools(options) {
57524
58714
  "Unable to append execution artifact.",
57525
58715
  async (c, { projectSlug, runId, ...input }) => await c.appendExecutionArtifact(projectSlug, runId, input)
57526
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
+ ),
57527
58724
  [mcpToolNames.recordProducedUrl]: makeTool(
57528
58725
  client,
57529
58726
  mcpToolNames.recordProducedUrl,
@@ -58185,11 +59382,13 @@ Usage:
58185
59382
  - MRRLIN_WORKER_PORT (dispatch listener, default 8792)
58186
59383
  - MRRLIN_DIRECTOR_BRIDGE_CLAIM_INTERVAL_SECONDS (default 30)
58187
59384
  - MRRLIN_DIRECTOR_BRIDGE_STALE_SECONDS (default 300)
58188
- - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_IDLE_SECONDS (default 180;
59385
+ - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_IDLE_SECONDS (default 300;
58189
59386
  a chat turn is cut only after this long with NO codex
58190
- signal at all \u2014 long-but-active turns keep running)
58191
- - MRRLIN_DIRECTOR_BRIDGE_CHAT_TURN_MAX_SECONDS (default 1800;
58192
- 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)
58193
59392
  - MRRLIN_DIRECTOR_BRIDGE_CODEX_IDLE_SECONDS (default 300;
58194
59393
  how long the warm codex app-server is kept alive
58195
59394
  between turns before it is released)