@mrrlin-dev/mcp 0.4.21 → 0.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.cjs +306 -33
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -36811,6 +36811,7 @@ var openApiOperationIds = {
36811
36811
  retireThread: "retireThread",
36812
36812
  applyIntentVerdict: "applyIntentVerdict",
36813
36813
  armIntentCheck: "armIntentCheck",
36814
+ settleExecutionRunOutcome: "settleExecutionRunOutcome",
36814
36815
  // Run-scoped, model-facing half of the resume contract (D8). Distinct from upsertTaskState:
36815
36816
  // that one is the server's own write (whole record, explicit lastRunSeq), this one is the
36816
36817
  // executor reporting narrative only, with the ordering value derived server-side from the run.
@@ -37434,8 +37435,17 @@ var mrrlinExecutionArtifactKindSchema = external_exports.enum([
37434
37435
  "produced_url",
37435
37436
  "resume_spec",
37436
37437
  "deliverable",
37437
- "pull_request_state"
37438
+ "pull_request_state",
37439
+ "non_code_outcome"
37438
37440
  ]);
37441
+ var mrrlinNonCodeOutcomeSchema = external_exports.enum(["completed", "blocked_missing_input", "negative_result"]);
37442
+ var mrrlinIntentProvenanceRefSchema = external_exports.object({
37443
+ kind: external_exports.enum(["director_chat_message", "wiki_page"]),
37444
+ id: external_exports.string().min(1),
37445
+ role: external_exports.enum(["user", "assistant", "system"]).optional(),
37446
+ contentHash: external_exports.string().min(1).optional()
37447
+ });
37448
+ var mrrlinIntentProvenanceSchema = external_exports.array(mrrlinIntentProvenanceRefSchema).default([]);
37439
37449
  var mrrlinDeployAttemptPayloadSchema = external_exports.object({
37440
37450
  env: external_exports.enum(["dev", "prod"]),
37441
37451
  workflowId: external_exports.number().int(),
@@ -37541,6 +37551,8 @@ var mrrlinTaskSchema = external_exports.object({
37541
37551
  // Output-optional (not .default) so existing task fixtures need no churn; DB rows always resolve it
37542
37552
  // to `string | null` via taskRowToTask.
37543
37553
  operatorSeedRequest: external_exports.string().max(8e3).nullable().optional(),
37554
+ resolvedIntent: external_exports.string().min(1).max(4e3).nullable().optional(),
37555
+ intentProvenance: mrrlinIntentProvenanceSchema.nullable().optional(),
37544
37556
  planId: mrrlinPlanIdSchema.nullable(),
37545
37557
  specApproved: mrrlinSpecApprovedSchema.optional(),
37546
37558
  specWikiPageId: mrrlinWikiPageIdSchema.nullable(),
@@ -37566,6 +37578,8 @@ var mrrlinTaskSchema = external_exports.object({
37566
37578
  var mrrlinTaskSummarySchema = mrrlinTaskSchema.omit({
37567
37579
  notes: true,
37568
37580
  operatorSeedRequest: true,
37581
+ resolvedIntent: true,
37582
+ intentProvenance: true,
37569
37583
  intentRestatement: true,
37570
37584
  latestJudgement: true
37571
37585
  });
@@ -37776,6 +37790,8 @@ var mrrlinTaskCreateShapeSchema = external_exports.object({
37776
37790
  notes: external_exports.string().nullable().optional(),
37777
37791
  parentTaskId: mrrlinTaskIdSchema.nullable().optional(),
37778
37792
  operatorSeedRequest: external_exports.string().max(8e3).nullable().optional(),
37793
+ resolvedIntent: external_exports.string().min(1).max(4e3).nullable().optional(),
37794
+ intentProvenance: mrrlinIntentProvenanceSchema.nullable().optional(),
37779
37795
  planId: mrrlinPlanIdSchema.nullable().optional(),
37780
37796
  specWikiPageId: mrrlinWikiPageIdSchema.nullable().optional(),
37781
37797
  status: mrrlinTaskCreateStatusSchema.optional(),
@@ -38206,7 +38222,11 @@ var mrrlinExecutionRunSchema = external_exports.object({
38206
38222
  cachedInputTokens: external_exports.number().int().nullable(),
38207
38223
  outputTokens: external_exports.number().int().nullable(),
38208
38224
  effort: external_exports.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable(),
38209
- effortEscalationReason: external_exports.string().min(1).nullable()
38225
+ effortEscalationReason: external_exports.string().min(1).nullable(),
38226
+ nonCodeOutcome: mrrlinNonCodeOutcomeSchema.nullable().default(null),
38227
+ resultSummary: external_exports.string().min(1).max(300).nullable().default(null),
38228
+ deliverablePageId: mrrlinWikiPageIdSchema.nullable().default(null),
38229
+ outcomeAcceptedAt: external_exports.string().datetime().nullable().default(null)
38210
38230
  });
38211
38231
  var mrrlinTaskStateSchema = external_exports.object({
38212
38232
  projectSlug: mrrlinProjectSlugSchema,
@@ -38348,6 +38368,13 @@ var mrrlinRestartExecutionRunSchema = external_exports.object({
38348
38368
  var mrrlinExecutionRunTouchSchema = external_exports.object({
38349
38369
  leaseId: external_exports.string().min(1)
38350
38370
  });
38371
+ var mrrlinSettleExecutionRunOutcomeSchema = external_exports.object({
38372
+ outcome: mrrlinNonCodeOutcomeSchema,
38373
+ resultSummary: external_exports.string().trim().min(1).max(300),
38374
+ deliverablePageId: mrrlinWikiPageIdSchema,
38375
+ contentHash: external_exports.string().min(1),
38376
+ leaseId: external_exports.string().min(1)
38377
+ });
38351
38378
  var mrrlinExecutionArtifactSchema = external_exports.object({
38352
38379
  createdAt: external_exports.string().datetime(),
38353
38380
  id: mrrlinExecutionArtifactIdSchema,
@@ -40845,6 +40872,20 @@ function createMrrlinClient(config2) {
40845
40872
  throw error51;
40846
40873
  }
40847
40874
  },
40875
+ async settleExecutionRunOutcome(projectSlug, runId, input) {
40876
+ try {
40877
+ const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/settle-outcome`, {
40878
+ body: JSON.stringify(mrrlinSettleExecutionRunOutcomeSchema.parse(input)),
40879
+ headers: { "content-type": "application/json" },
40880
+ method: "POST"
40881
+ });
40882
+ return executionRunResponseSchema.parse(body).data;
40883
+ } catch (error51) {
40884
+ if (error51 instanceof MrrlinApiError && error51.status === 409)
40885
+ return null;
40886
+ throw error51;
40887
+ }
40888
+ },
40848
40889
  async getTaskState(projectSlug, taskId) {
40849
40890
  const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/state`);
40850
40891
  return taskStateResponseSchema.parse(body).data;
@@ -41623,7 +41664,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41623
41664
  }
41624
41665
 
41625
41666
  // src/_generated/version.ts
41626
- var PKG_VERSION = "0.4.21";
41667
+ var PKG_VERSION = "0.4.22";
41627
41668
 
41628
41669
  // src/api-base-url-migrate.ts
41629
41670
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -43870,7 +43911,7 @@ function renderCriteriaBlock(criteria) {
43870
43911
  return criteria.map((c, i) => `${i + 1}. [ ] ${c}`).join("\n");
43871
43912
  }
43872
43913
  async function buildDeliverableGateDeps(args) {
43873
- const { client, projectSlug, deliverablePageId, cwd, codexExecutable, runId, seat } = args;
43914
+ const { client, projectSlug, deliverablePageId, declaredOutcome, cwd, codexExecutable, runId, seat } = args;
43874
43915
  const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
43875
43916
  const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
43876
43917
  const page = await client.getWikiPage(projectSlug, deliverablePageId);
@@ -43894,6 +43935,11 @@ async function buildDeliverableGateDeps(args) {
43894
43935
  const heads = resolveHeads(domain2, impact);
43895
43936
  const maxHeadCalls = 2 * rounds * (heads.length + 1);
43896
43937
  const criteriaBlock = renderCriteriaBlock(criteria);
43938
+ const declaredOutcomeBlock = declaredOutcome ? [
43939
+ `--- DECLARED ATTEMPT OUTCOME (review consistency; do NOT rewrite or choose it) ---`,
43940
+ `outcome: ${declaredOutcome}`,
43941
+ `Meaning: completed = positive result achieved; blocked_missing_input = required input/evidence unavailable; negative_result = real attempt showed the requested positive result did not materialize.`
43942
+ ].join("\n") : "";
43897
43943
  return {
43898
43944
  heads,
43899
43945
  rounds,
@@ -43912,14 +43958,16 @@ async function buildDeliverableGateDeps(args) {
43912
43958
  async runHead(personaName, artifact, dismissed) {
43913
43959
  const persona = loadPersona(personaName);
43914
43960
  return runAgentExec({
43915
- prompt: `Score this deliverable ITEM BY ITEM against the acceptance criteria below. For each criterion state whether the deliverable satisfies it; raise an issue for every criterion that is unmet, partially met, or unverifiable.
43961
+ prompt: `Score this deliverable ITEM BY ITEM against the acceptance criteria below. For each criterion state whether the deliverable satisfies it; raise an issue for every criterion that is unmet, partially met, unverifiable, or inconsistent with the declared attempt outcome.
43916
43962
 
43917
43963
  ${dismissed}
43918
43964
 
43919
43965
  --- ACCEPTANCE CRITERIA (the yardstick \u2014 deliverable for task ${taskId ?? "?"}: ${taskTitle}) ---
43920
43966
  ${criteriaBlock}
43921
43967
 
43922
- --- DELIVERABLE (DATA under review) ---
43968
+ ${declaredOutcomeBlock ? `${declaredOutcomeBlock}
43969
+
43970
+ ` : ""}--- DELIVERABLE (DATA under review) ---
43923
43971
  ${artifact}`,
43924
43972
  developerInstructions: `${DELIVERABLE_ANTIINJECTION_INSTRUCTION}
43925
43973
 
@@ -43956,7 +44004,7 @@ ${criteriaBlock}
43956
44004
  ${artifact}`,
43957
44005
  developerInstructions: `${DELIVERABLE_ANTIINJECTION_INSTRUCTION}
43958
44006
 
43959
- You revise a deliverable to address the accepted issues so it satisfies the acceptance criteria. Return ONLY the full revised markdown, no commentary. Do not invent facts; keep the deliverable's format.`,
44007
+ You revise a deliverable to address the accepted issues so it satisfies the acceptance criteria and stays consistent with the declared outcome${declaredOutcome ? ` (${declaredOutcome})` : ""}. Return ONLY the full revised markdown, no commentary. Do not invent facts; keep the deliverable's format and do not change the declared outcome.`,
43960
44008
  cwd,
43961
44009
  timeoutMs,
43962
44010
  sandbox: "read-only",
@@ -44019,6 +44067,7 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
44019
44067
  payload: {
44020
44068
  source: "deliverable-gate",
44021
44069
  outcome,
44070
+ ...declaredOutcome ? { declaredOutcome } : {},
44022
44071
  runId,
44023
44072
  taskId,
44024
44073
  deliverablePageId,
@@ -47353,6 +47402,40 @@ async function writeServerOwnedTaskState(deps, projectSlug, taskId, runId, lease
47353
47402
  }
47354
47403
  var NO_FILE_CHANGES_THIS_RUN = "no file changes recorded in this run";
47355
47404
  var NO_VERIFICATION_THIS_RUN = "not run in this run";
47405
+ var NON_CODE_RESULT_OPEN = "<<<MRRLIN_NON_CODE_RESULT_V1";
47406
+ var NON_CODE_RESULT_CLOSE = "MRRLIN_NON_CODE_RESULT_V1>>>";
47407
+ var NEGATIVE_RESULT_EVIDENCE_FOLDER = "Evidence";
47408
+ var NEGATIVE_RESULT_EVIDENCE_TITLE_PREFIX = "Negative result evidence for ";
47409
+ function parseNonCodeResultEnvelope(stdout) {
47410
+ const text = stdout.startsWith("\uFEFF") ? stdout.slice(1) : stdout;
47411
+ const lines2 = text.split(/\r?\n/);
47412
+ if (lines2[0] !== NON_CODE_RESULT_OPEN) {
47413
+ throw new Error("missing MRRLIN_NON_CODE_RESULT_V1 opening delimiter on the first non-BOM line");
47414
+ }
47415
+ if (!lines2[1] || lines2[2] !== NON_CODE_RESULT_CLOSE) {
47416
+ throw new Error("malformed MRRLIN_NON_CODE_RESULT_V1 envelope");
47417
+ }
47418
+ let payload;
47419
+ try {
47420
+ payload = JSON.parse(lines2[1]);
47421
+ } catch (error51) {
47422
+ throw new Error(`invalid MRRLIN_NON_CODE_RESULT_V1 JSON: ${error51 instanceof Error ? error51.message : String(error51)}`);
47423
+ }
47424
+ const record2 = payload;
47425
+ const outcome = mrrlinNonCodeOutcomeSchema.parse(record2.outcome);
47426
+ if (typeof record2.resultSummary !== "string") throw new Error("resultSummary must be a string");
47427
+ const resultSummary = record2.resultSummary.trim();
47428
+ if (resultSummary.length < 1 || resultSummary.length > 300) {
47429
+ throw new Error("resultSummary must be 1-300 characters");
47430
+ }
47431
+ const bodyLines = lines2.slice(3);
47432
+ if (bodyLines[0] === "") bodyLines.shift();
47433
+ const markdownBody = bodyLines.join("\n").trim();
47434
+ if (markdownBody.length === 0) {
47435
+ throw new Error("markdown deliverable body must be non-empty");
47436
+ }
47437
+ return { outcome, resultSummary, markdownBody };
47438
+ }
47356
47439
  function buildDeliverableAuthorPrompt(task, specMarkdown) {
47357
47440
  return [
47358
47441
  `Produce the deliverable for the task below. Write the FULL deliverable content as markdown.`,
@@ -47369,8 +47452,21 @@ function buildDeliverableAuthorPrompt(task, specMarkdown) {
47369
47452
  specMarkdown,
47370
47453
  ``,
47371
47454
  `--- OUTPUT CONTRACT (strict) ---`,
47372
- `Return ONLY the finished deliverable as markdown, no commentary or meta-notes. Satisfy every`,
47373
- `item in the spec's "## Acceptance criteria" checklist.`
47455
+ `Return ONLY this exact envelope followed by the markdown deliverable body:`,
47456
+ ``,
47457
+ NON_CODE_RESULT_OPEN,
47458
+ `{"outcome":"completed","resultSummary":"One concise sentence, 1-300 chars."}`,
47459
+ NON_CODE_RESULT_CLOSE,
47460
+ ``,
47461
+ `<markdown deliverable body>`,
47462
+ ``,
47463
+ `Choose exactly one outcome:`,
47464
+ `- completed: the requested positive result was achieved.`,
47465
+ `- blocked_missing_input: required input/evidence is unavailable, so the work remains actionable.`,
47466
+ `- negative_result: the work was actually attempted and valid evidence shows the requested positive result did not materialize.`,
47467
+ ``,
47468
+ `The markdown body must justify the declared outcome and satisfy every applicable item in the spec's`,
47469
+ `"## Acceptance criteria" checklist. Do not add commentary before the opening delimiter.`
47374
47470
  ].join("\n");
47375
47471
  }
47376
47472
  async function writeDeliverablePage(client, projectSlug, task, markdown) {
@@ -47407,6 +47503,50 @@ async function writeDeliverablePage(client, projectSlug, task, markdown) {
47407
47503
  }
47408
47504
  throw new Error(`could not allocate a deliverable page id for ${task.id} (last conflict: ${lastConflict})`);
47409
47505
  }
47506
+ async function writeNegativeResultEvidencePage(client, projectSlug, task, input) {
47507
+ const evidencePath = `/evidence/${task.id.toLowerCase()}-non-code-result`;
47508
+ const title = `${NEGATIVE_RESULT_EVIDENCE_TITLE_PREFIX}${task.id}`;
47509
+ const markdownBody = [
47510
+ `# ${title}`,
47511
+ ``,
47512
+ `Outcome: negative_result`,
47513
+ ``,
47514
+ `Summary: ${input.resultSummary}`,
47515
+ ``,
47516
+ `Deliverable: Deliverables/${input.deliverablePageId}`
47517
+ ].join("\n");
47518
+ const existing = (await client.listWikiPages(projectSlug, { folder: NEGATIVE_RESULT_EVIDENCE_FOLDER, taskId: task.id })).find((page) => page.path === evidencePath);
47519
+ if (existing) {
47520
+ await client.updateWikiPage(projectSlug, existing.id, {
47521
+ markdownBody,
47522
+ taskId: task.id,
47523
+ title
47524
+ });
47525
+ return;
47526
+ }
47527
+ let lastConflict = "";
47528
+ for (let attempt = 1; attempt <= DELIVERABLE_PAGE_ID_MAX_ATTEMPTS; attempt += 1) {
47529
+ const candidate = nextWikiPageId(await client.listWikiPages(projectSlug));
47530
+ try {
47531
+ await client.createWikiPage(projectSlug, {
47532
+ id: candidate,
47533
+ folder: NEGATIVE_RESULT_EVIDENCE_FOLDER,
47534
+ path: evidencePath,
47535
+ title,
47536
+ markdownBody,
47537
+ taskId: task.id
47538
+ });
47539
+ return;
47540
+ } catch (error51) {
47541
+ if (error51 instanceof MrrlinApiError && error51.status === 409 && attempt < DELIVERABLE_PAGE_ID_MAX_ATTEMPTS) {
47542
+ lastConflict = error51.message;
47543
+ continue;
47544
+ }
47545
+ throw error51;
47546
+ }
47547
+ }
47548
+ throw new Error(`could not allocate negative-result evidence page for ${task.id} (last conflict: ${lastConflict})`);
47549
+ }
47410
47550
  async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2) {
47411
47551
  const personaName = resolveProducerPersona(domain2);
47412
47552
  if (!personaName) {
@@ -47466,7 +47606,7 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47466
47606
  });
47467
47607
  }, deps.touchIntervalMs);
47468
47608
  touchTimer.unref?.();
47469
- let deliverableMarkdown;
47609
+ let parsedResult;
47470
47610
  try {
47471
47611
  const exec = await runExec({
47472
47612
  prompt: buildDeliverableAuthorPrompt(task, specMarkdown),
@@ -47480,18 +47620,26 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47480
47620
  await failRun(deps, projectSlug, task, runId, leaseId, `deliverable_authoring_failed: ${exec.error}`);
47481
47621
  return;
47482
47622
  }
47483
- if (exec.text.trim().length === 0) {
47484
- await failRun(deps, projectSlug, task, runId, leaseId, "deliverable_authoring_empty: producer returned no content.");
47623
+ try {
47624
+ parsedResult = parseNonCodeResultEnvelope(exec.text);
47625
+ } catch (error51) {
47626
+ await failRun(
47627
+ deps,
47628
+ projectSlug,
47629
+ task,
47630
+ runId,
47631
+ leaseId,
47632
+ `non_code_result_envelope_invalid: ${error51 instanceof Error ? error51.message : String(error51)}`
47633
+ );
47485
47634
  return;
47486
47635
  }
47487
- deliverableMarkdown = exec.text.trim();
47488
47636
  } finally {
47489
47637
  clearInterval(touchTimer);
47490
47638
  }
47491
47639
  let pageId;
47492
47640
  let contentHash;
47493
47641
  try {
47494
- ({ pageId, contentHash } = await writeDeliverablePage(deps.client, projectSlug, task, deliverableMarkdown));
47642
+ ({ pageId, contentHash } = await writeDeliverablePage(deps.client, projectSlug, task, parsedResult.markdownBody));
47495
47643
  } catch (error51) {
47496
47644
  await failRun(
47497
47645
  deps,
@@ -47522,7 +47670,13 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47522
47670
  }
47523
47671
  let gateResult;
47524
47672
  try {
47525
- gateResult = await deps.runDeliverableGate({ projectSlug, taskId: task.id, runId, deliverablePageId: pageId });
47673
+ gateResult = await deps.runDeliverableGate({
47674
+ projectSlug,
47675
+ taskId: task.id,
47676
+ runId,
47677
+ deliverablePageId: pageId,
47678
+ declaredOutcome: parsedResult.outcome
47679
+ });
47526
47680
  } catch (error51) {
47527
47681
  await failRun(
47528
47682
  deps,
@@ -47546,14 +47700,52 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47546
47700
  );
47547
47701
  return;
47548
47702
  }
47549
- if (!await runHasLease(deps, projectSlug, runId, leaseId)) return;
47550
- await deps.client.updateTask(projectSlug, task.id, { status: "closed_done" }).catch(() => {
47551
- });
47552
- await deps.client.updateExecutionRun(projectSlug, runId, {
47553
- status: "done",
47554
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
47555
- leaseId
47556
- });
47703
+ if (parsedResult.outcome === "negative_result") {
47704
+ try {
47705
+ await writeNegativeResultEvidencePage(deps.client, projectSlug, task, {
47706
+ deliverablePageId: pageId,
47707
+ resultSummary: parsedResult.resultSummary
47708
+ });
47709
+ } catch (error51) {
47710
+ await failRun(
47711
+ deps,
47712
+ projectSlug,
47713
+ task,
47714
+ runId,
47715
+ leaseId,
47716
+ `negative_result_evidence_write_failed: ${error51 instanceof Error ? error51.message : String(error51)}`
47717
+ );
47718
+ return;
47719
+ }
47720
+ }
47721
+ try {
47722
+ const settled = await deps.client.settleExecutionRunOutcome(projectSlug, runId, {
47723
+ leaseId,
47724
+ outcome: parsedResult.outcome,
47725
+ resultSummary: parsedResult.resultSummary,
47726
+ deliverablePageId: pageId,
47727
+ contentHash
47728
+ });
47729
+ if (!settled && await runHasLease(deps, projectSlug, runId, leaseId)) {
47730
+ await failRun(
47731
+ deps,
47732
+ projectSlug,
47733
+ task,
47734
+ runId,
47735
+ leaseId,
47736
+ `non_code_outcome_settlement_conflict: settlement was rejected while this consumer still held the lease.`
47737
+ );
47738
+ }
47739
+ } catch (error51) {
47740
+ await failRun(
47741
+ deps,
47742
+ projectSlug,
47743
+ task,
47744
+ runId,
47745
+ leaseId,
47746
+ `non_code_outcome_settlement_failed: ${error51 instanceof Error ? error51.message : String(error51)}`
47747
+ );
47748
+ }
47557
47749
  }
47558
47750
  async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
47559
47751
  if (!interruptFailed) return;
@@ -48065,6 +48257,22 @@ function createBridgeLogger(stateDir, env = process.env) {
48065
48257
  }
48066
48258
  const includePrompts = resolvePromptsEnabled(env);
48067
48259
  const fileFor = () => (0, import_node_path16.join)(logsDir, `bridge-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
48260
+ const MAX_TRACKED_SESSIONS = 64;
48261
+ const contextSlugBySession = /* @__PURE__ */ new Map();
48262
+ const rememberContextSlug = (sessionId, payload) => {
48263
+ if (!payload || typeof payload !== "object") return;
48264
+ const ctx = payload.context;
48265
+ if (!ctx || typeof ctx !== "object") return;
48266
+ const raw = ctx.projectSlug;
48267
+ const slug = typeof raw === "string" ? raw.trim() : "";
48268
+ if (!slug) return;
48269
+ contextSlugBySession.delete(sessionId);
48270
+ contextSlugBySession.set(sessionId, slug);
48271
+ if (contextSlugBySession.size > MAX_TRACKED_SESSIONS) {
48272
+ const oldest = contextSlugBySession.keys().next();
48273
+ if (!oldest.done) contextSlugBySession.delete(oldest.value);
48274
+ }
48275
+ };
48068
48276
  const gainDay = (sessionId) => {
48069
48277
  const sessionsPath = (0, import_node_path16.join)(logsDir, ".sessions.json");
48070
48278
  let sessionsObj = {};
@@ -48094,6 +48302,23 @@ function createBridgeLogger(stateDir, env = process.env) {
48094
48302
  projectionFailed = true;
48095
48303
  }
48096
48304
  }
48305
+ if (rec.sessionId !== null && rec.type === "turn") rememberContextSlug(rec.sessionId, rec.payload);
48306
+ let projectScopeMismatch = null;
48307
+ if (rec.sessionId !== null && projection?.kind === "tool_use" && typeof projection.projectSlug === "string") {
48308
+ const expected = contextSlugBySession.get(rec.sessionId);
48309
+ if (expected !== void 0 && expected !== projection.projectSlug) {
48310
+ projectScopeMismatch = { expected, actual: projection.projectSlug };
48311
+ console.warn(
48312
+ JSON.stringify({
48313
+ event: "bridge.project_scope_mismatch",
48314
+ sessionId: rec.sessionId,
48315
+ toolName: projection.toolName,
48316
+ expected,
48317
+ actual: projection.projectSlug
48318
+ })
48319
+ );
48320
+ }
48321
+ }
48097
48322
  const { payload: redactedPayload, redactedCount } = redactMessage(rec.payload, { includePrompts });
48098
48323
  const line = JSON.stringify({
48099
48324
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48105,7 +48330,8 @@ function createBridgeLogger(stateDir, env = process.env) {
48105
48330
  payload: redactedPayload,
48106
48331
  redactedCount,
48107
48332
  projection,
48108
- ...projectionFailed ? { projectionFailed: true } : {}
48333
+ ...projectionFailed ? { projectionFailed: true } : {},
48334
+ ...projectScopeMismatch ? { projectScopeMismatch } : {}
48109
48335
  }) + "\n";
48110
48336
  (0, import_node_fs8.appendFileSync)(fileFor(), line);
48111
48337
  if (rec.sessionId !== null) gainDay(rec.sessionId);
@@ -48302,7 +48528,7 @@ async function runAsyncBackstopSweep(deps, drive = (slug, jobId) => driveAsyncJo
48302
48528
  }
48303
48529
 
48304
48530
  // src/consensus/deliverable-gate.ts
48305
- async function runDeliverableGate(_target, deps) {
48531
+ async function runDeliverableGate(target, deps) {
48306
48532
  if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
48307
48533
  const ac = await deps.readAcceptanceCriteria();
48308
48534
  if (!ac || ac.criteria.length === 0) return { outcome: "MISSING_ACCEPTANCE_CRITERIA" };
@@ -48350,6 +48576,7 @@ async function runDeliverableGate(_target, deps) {
48350
48576
  }
48351
48577
  await deps.writeEvidence({
48352
48578
  deliverablePageId: deliverable.pageId,
48579
+ ...target.declaredOutcome ? { declaredOutcome: target.declaredOutcome } : {},
48353
48580
  outcome: result.outcome,
48354
48581
  rounds: result.rounds,
48355
48582
  history: result.history,
@@ -53781,6 +54008,18 @@ function buildThreadStartParams(msg, config2, desiredModel) {
53781
54008
  "order, separated by a blank line. Do NOT paraphrase, summarise, or rewrite any of it, and do",
53782
54009
  "NOT add words of your own.",
53783
54010
  "",
54011
+ "Set resolvedIntent to the accepted task intent in your own concise words only after the outcome",
54012
+ "is resolved enough to create the task. Use it to capture the actionable understanding that came",
54013
+ "from the conversation and, when needed, retrieved wiki context. Do NOT use resolvedIntent as a",
54014
+ "replacement for operatorSeedRequest: the seed stays raw operator wording; resolvedIntent is the",
54015
+ "structured interpretation.",
54016
+ "",
54017
+ "When the turn Context JSON includes contextMessages, cite the messages you actually used in",
54018
+ 'intentProvenance as refs like {kind:"director_chat_message",id:"...",role:"user"}. If a wiki',
54019
+ 'page resolved part of the intent, cite it as {kind:"wiki_page",id:"WK-..."}. Do not put copied',
54020
+ "message text in intentProvenance. Leave it unset when there is no accepted resolved intent; use",
54021
+ "an explicit empty array only when you intentionally verified there are no useful references.",
54022
+ "",
53784
54023
  "If a task has no real operator request behind it at all (pure system bookkeeping nobody asked",
53785
54024
  "for), do NOT invent one to fill this field \u2014 leave operatorSeedRequest unset. Never fabricate",
53786
54025
  "operator wording; an unset seed is handled downstream, a fabricated one is not.",
@@ -55771,16 +56010,17 @@ async function startDirectorBridge() {
55771
56010
  // Builds the gate deps (resolving domain/heads/criteria from the page's task + spec) and runs the
55772
56011
  // same convergence loop the spec-gate uses. runId is threaded for verdict provenance (decision 10).
55773
56012
  // Authoring uses the defaults (runAgentExec + loadPersona) — no extra wiring needed for those.
55774
- runDeliverableGate: async ({ projectSlug, runId, deliverablePageId }) => {
56013
+ runDeliverableGate: async ({ projectSlug, runId, deliverablePageId, declaredOutcome }) => {
55775
56014
  const gateDeps = await buildDeliverableGateDeps({
55776
56015
  client,
55777
56016
  projectSlug,
55778
56017
  deliverablePageId,
56018
+ declaredOutcome,
55779
56019
  cwd: config2.codexCwd ?? process.cwd(),
55780
56020
  codexExecutable: config2.codexExecutable,
55781
56021
  runId
55782
56022
  });
55783
- return runDeliverableGate({ projectSlug, deliverablePageId }, gateDeps);
56023
+ return runDeliverableGate({ projectSlug, deliverablePageId, declaredOutcome }, gateDeps);
55784
56024
  }
55785
56025
  };
55786
56026
  const sessionSinks = createSessionSinkRegistry();
@@ -56548,12 +56788,16 @@ function renderContextPrefix(context) {
56548
56788
  const urlPath = context?.urlPath?.trim() ? context.urlPath.trim() : void 0;
56549
56789
  const focusTaskId = context?.focusTaskId?.trim() ? context.focusTaskId.trim() : void 0;
56550
56790
  const directorSessionId = context?.directorSessionId?.trim() ? context.directorSessionId.trim() : void 0;
56791
+ const contextMessages = context?.contextMessages?.filter(
56792
+ (message) => (message.role === "user" || message.role === "assistant") && typeof message.id === "string" && message.id.trim() && typeof message.content === "string" && message.content.length > 0
56793
+ );
56551
56794
  const answeredQuestions = context?.answeredQuestions?.length ? context.answeredQuestions : void 0;
56552
56795
  const payload = {};
56553
56796
  if (projectSlug) payload.projectSlug = projectSlug;
56554
56797
  if (urlPath) payload.urlPath = urlPath;
56555
56798
  if (focusTaskId) payload.focusTaskId = focusTaskId;
56556
56799
  if (directorSessionId) payload.directorSessionId = directorSessionId;
56800
+ if (contextMessages?.length) payload.contextMessages = contextMessages;
56557
56801
  if (answeredQuestions) payload.answeredQuestions = answeredQuestions;
56558
56802
  const keys = Object.keys(payload);
56559
56803
  if (keys.length === 0) return "";
@@ -62871,6 +63115,31 @@ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, proj
62871
63115
  }
62872
63116
  };
62873
63117
  }
63118
+ var agentProviderCache = null;
63119
+ var AGENT_PROVIDER_CACHE_TTL_MS = 3e4;
63120
+ async function getAgentProviderForPanel(client, opts = {}) {
63121
+ const now = (opts.now ?? (() => Date.now()))();
63122
+ if (agentProviderCache !== null && now - agentProviderCache.fetchedAt < AGENT_PROVIDER_CACHE_TTL_MS) {
63123
+ return agentProviderCache.value;
63124
+ }
63125
+ try {
63126
+ const settings = await client.getOperatorSettings();
63127
+ agentProviderCache = { value: settings.agentProvider, fetchedAt: now };
63128
+ return settings.agentProvider;
63129
+ } catch (err) {
63130
+ console.warn(
63131
+ JSON.stringify({
63132
+ event: "agent_provider_for_panel_fetch_failed",
63133
+ hadCache: agentProviderCache !== null,
63134
+ error: err instanceof Error ? err.message : String(err)
63135
+ })
63136
+ );
63137
+ if (agentProviderCache !== null) {
63138
+ return agentProviderCache.value;
63139
+ }
63140
+ return null;
63141
+ }
63142
+ }
62874
63143
  function createMrrlinTools(options) {
62875
63144
  const client = createMrrlinClient(withAuth(options));
62876
63145
  async function uploadArtifactBytes(c, args) {
@@ -63337,13 +63606,17 @@ function createMrrlinTools(options) {
63337
63606
  codexExecutable: process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0,
63338
63607
  extraHeads
63339
63608
  });
63340
- const activeCodeRunHead = await resolveActiveRunHead({
63341
- heads: ["code-reviewer", ...extraHeads ?? []],
63342
- persistedProvider: getSelectedProvider()
63343
- });
63609
+ const persistedProvider = await getAgentProviderForPanel(c);
63610
+ let activeCodeRunHead = null;
63611
+ if (persistedProvider != null) {
63612
+ activeCodeRunHead = await resolveActiveRunHead({
63613
+ heads: ["code-reviewer", ...extraHeads ?? []],
63614
+ persistedProvider
63615
+ });
63616
+ }
63344
63617
  if (activeCodeRunHead) deps.runHead = activeCodeRunHead;
63345
63618
  const codeGateResult = await runCodeGate({ projectSlug, baseRef }, deps);
63346
- if (process.env[SHADOW_ENV_FLAG]) {
63619
+ if (process.env[SHADOW_ENV_FLAG] && persistedProvider != null) {
63347
63620
  try {
63348
63621
  const diff = await readDiff({ cwd: process.cwd(), baseRef, run: defaultRunCmd });
63349
63622
  await maybeRunShadow({
@@ -63351,7 +63624,7 @@ function createMrrlinTools(options) {
63351
63624
  artifact: diff,
63352
63625
  heads: ["code-reviewer", ...extraHeads ?? []],
63353
63626
  correlationId: `code:${baseRef}`,
63354
- persistedProvider: getSelectedProvider()
63627
+ persistedProvider
63355
63628
  });
63356
63629
  } catch {
63357
63630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -21,12 +21,12 @@
21
21
  "@types/ws": "^8.18.1",
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
- "@mrrlin/agent-client": "0.0.0",
25
24
  "@mrrlin/client": "0.0.0",
25
+ "@mrrlin/agent-client": "0.0.0",
26
26
  "@mrrlin/director-e2e": "0.0.0",
27
+ "@mrrlin/schemas": "0.0.0",
27
28
  "@mrrlin/tsconfig": "0.0.0",
28
- "@mrrlin/wiki": "0.0.0",
29
- "@mrrlin/schemas": "0.0.0"
29
+ "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",