@mrrlin-dev/mcp 0.4.20 → 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 +317 -34
  2. package/package.json +2 -2
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.20";
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,
@@ -46276,10 +46325,18 @@ var BASE_INSTRUCTIONS_PREFIX = [
46276
46325
  " qualifies, produce the file; never merely offer one.",
46277
46326
  "- Mixed output counts as retrieved: if any material part came from a retrieval, treat the whole",
46278
46327
  " result as retrieved rather than splitting it between inline text and a file.",
46279
- "- When your output is a file (markdown report, HTML), call `deliver_file`: pass the body inline",
46328
+ "- When your output is a file, call `deliver_file`: pass the body inline",
46280
46329
  " as `content` and embed the returned markdown link. It needs no writable path, so it is the",
46281
46330
  " default even when this run path cannot write files \u2014 never inline large blobs into chat,",
46282
46331
  " spec, or note text.",
46332
+ "- Choose the format by who reads it next. HTML (`.html`) when a person will LOOK at it and layout",
46333
+ " carries meaning: reports, comparisons, audits, result tables. Markdown (`.md`) when a machine",
46334
+ " will re-read it or a human will edit it further: a spec draft, text destined for another tool,",
46335
+ " content that will back an evidence page. CSV/JSON as before, for data meant to be loaded.",
46336
+ "- An HTML file MUST be self-contained: inline `<style>` only, and `data:` URIs for any image or",
46337
+ " font. No external stylesheets, scripts, fonts, or images \u2014 the renderer sandboxes the file and",
46338
+ " blocks every outbound request, so a linked resource does not load slowly, it does not load at",
46339
+ " all. Scripts never run; do not ship interactivity.",
46283
46340
  "- class=temp for one-off operator hand-offs (48h); class=durable for anything a spec",
46284
46341
  " or evidence page will reference (180 days). Always pass taskId/runId when known.",
46285
46342
  "- Anything user-facing delivered as a file MUST go through `deliver_file`. Files are capped at",
@@ -47345,6 +47402,40 @@ async function writeServerOwnedTaskState(deps, projectSlug, taskId, runId, lease
47345
47402
  }
47346
47403
  var NO_FILE_CHANGES_THIS_RUN = "no file changes recorded in this run";
47347
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
+ }
47348
47439
  function buildDeliverableAuthorPrompt(task, specMarkdown) {
47349
47440
  return [
47350
47441
  `Produce the deliverable for the task below. Write the FULL deliverable content as markdown.`,
@@ -47361,8 +47452,21 @@ function buildDeliverableAuthorPrompt(task, specMarkdown) {
47361
47452
  specMarkdown,
47362
47453
  ``,
47363
47454
  `--- OUTPUT CONTRACT (strict) ---`,
47364
- `Return ONLY the finished deliverable as markdown, no commentary or meta-notes. Satisfy every`,
47365
- `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.`
47366
47470
  ].join("\n");
47367
47471
  }
47368
47472
  async function writeDeliverablePage(client, projectSlug, task, markdown) {
@@ -47399,6 +47503,50 @@ async function writeDeliverablePage(client, projectSlug, task, markdown) {
47399
47503
  }
47400
47504
  throw new Error(`could not allocate a deliverable page id for ${task.id} (last conflict: ${lastConflict})`);
47401
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
+ }
47402
47550
  async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2) {
47403
47551
  const personaName = resolveProducerPersona(domain2);
47404
47552
  if (!personaName) {
@@ -47458,7 +47606,7 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47458
47606
  });
47459
47607
  }, deps.touchIntervalMs);
47460
47608
  touchTimer.unref?.();
47461
- let deliverableMarkdown;
47609
+ let parsedResult;
47462
47610
  try {
47463
47611
  const exec = await runExec({
47464
47612
  prompt: buildDeliverableAuthorPrompt(task, specMarkdown),
@@ -47472,18 +47620,26 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47472
47620
  await failRun(deps, projectSlug, task, runId, leaseId, `deliverable_authoring_failed: ${exec.error}`);
47473
47621
  return;
47474
47622
  }
47475
- if (exec.text.trim().length === 0) {
47476
- 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
+ );
47477
47634
  return;
47478
47635
  }
47479
- deliverableMarkdown = exec.text.trim();
47480
47636
  } finally {
47481
47637
  clearInterval(touchTimer);
47482
47638
  }
47483
47639
  let pageId;
47484
47640
  let contentHash;
47485
47641
  try {
47486
- ({ pageId, contentHash } = await writeDeliverablePage(deps.client, projectSlug, task, deliverableMarkdown));
47642
+ ({ pageId, contentHash } = await writeDeliverablePage(deps.client, projectSlug, task, parsedResult.markdownBody));
47487
47643
  } catch (error51) {
47488
47644
  await failRun(
47489
47645
  deps,
@@ -47514,7 +47670,13 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47514
47670
  }
47515
47671
  let gateResult;
47516
47672
  try {
47517
- 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
+ });
47518
47680
  } catch (error51) {
47519
47681
  await failRun(
47520
47682
  deps,
@@ -47538,14 +47700,52 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47538
47700
  );
47539
47701
  return;
47540
47702
  }
47541
- if (!await runHasLease(deps, projectSlug, runId, leaseId)) return;
47542
- await deps.client.updateTask(projectSlug, task.id, { status: "closed_done" }).catch(() => {
47543
- });
47544
- await deps.client.updateExecutionRun(projectSlug, runId, {
47545
- status: "done",
47546
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
47547
- leaseId
47548
- });
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
+ }
47549
47749
  }
47550
47750
  async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
47551
47751
  if (!interruptFailed) return;
@@ -48057,6 +48257,22 @@ function createBridgeLogger(stateDir, env = process.env) {
48057
48257
  }
48058
48258
  const includePrompts = resolvePromptsEnabled(env);
48059
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
+ };
48060
48276
  const gainDay = (sessionId) => {
48061
48277
  const sessionsPath = (0, import_node_path16.join)(logsDir, ".sessions.json");
48062
48278
  let sessionsObj = {};
@@ -48086,6 +48302,23 @@ function createBridgeLogger(stateDir, env = process.env) {
48086
48302
  projectionFailed = true;
48087
48303
  }
48088
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
+ }
48089
48322
  const { payload: redactedPayload, redactedCount } = redactMessage(rec.payload, { includePrompts });
48090
48323
  const line = JSON.stringify({
48091
48324
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48097,7 +48330,8 @@ function createBridgeLogger(stateDir, env = process.env) {
48097
48330
  payload: redactedPayload,
48098
48331
  redactedCount,
48099
48332
  projection,
48100
- ...projectionFailed ? { projectionFailed: true } : {}
48333
+ ...projectionFailed ? { projectionFailed: true } : {},
48334
+ ...projectScopeMismatch ? { projectScopeMismatch } : {}
48101
48335
  }) + "\n";
48102
48336
  (0, import_node_fs8.appendFileSync)(fileFor(), line);
48103
48337
  if (rec.sessionId !== null) gainDay(rec.sessionId);
@@ -48294,7 +48528,7 @@ async function runAsyncBackstopSweep(deps, drive = (slug, jobId) => driveAsyncJo
48294
48528
  }
48295
48529
 
48296
48530
  // src/consensus/deliverable-gate.ts
48297
- async function runDeliverableGate(_target, deps) {
48531
+ async function runDeliverableGate(target, deps) {
48298
48532
  if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
48299
48533
  const ac = await deps.readAcceptanceCriteria();
48300
48534
  if (!ac || ac.criteria.length === 0) return { outcome: "MISSING_ACCEPTANCE_CRITERIA" };
@@ -48342,6 +48576,7 @@ async function runDeliverableGate(_target, deps) {
48342
48576
  }
48343
48577
  await deps.writeEvidence({
48344
48578
  deliverablePageId: deliverable.pageId,
48579
+ ...target.declaredOutcome ? { declaredOutcome: target.declaredOutcome } : {},
48345
48580
  outcome: result.outcome,
48346
48581
  rounds: result.rounds,
48347
48582
  history: result.history,
@@ -53773,6 +54008,18 @@ function buildThreadStartParams(msg, config2, desiredModel) {
53773
54008
  "order, separated by a blank line. Do NOT paraphrase, summarise, or rewrite any of it, and do",
53774
54009
  "NOT add words of your own.",
53775
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
+ "",
53776
54023
  "If a task has no real operator request behind it at all (pure system bookkeeping nobody asked",
53777
54024
  "for), do NOT invent one to fill this field \u2014 leave operatorSeedRequest unset. Never fabricate",
53778
54025
  "operator wording; an unset seed is handled downstream, a fabricated one is not.",
@@ -53878,6 +54125,8 @@ function buildThreadStartParams(msg, config2, desiredModel) {
53878
54125
  " needs no local file (class=temp for one-off downloads, class=durable when a spec/evidence",
53879
54126
  " page references it; pass taskId when known), and paste the returned markdown link into your",
53880
54127
  " reply. Use `upload_artifact` only for a file that already exists on disk (e.g. a screenshot).",
54128
+ "- An HTML file must be self-contained \u2014 inline `<style>`, `data:` URIs, no external resources and",
54129
+ " no scripts. The viewer sandboxes it and blocks outbound requests, so linked assets never load.",
53881
54130
  "- The chat reply must still stand alone as a complete answer even if the file is never opened:",
53882
54131
  " the headline finding, the few points that matter, then the link. Never reduce it to",
53883
54132
  ' "see attached" or "details in the report".',
@@ -55761,16 +56010,17 @@ async function startDirectorBridge() {
55761
56010
  // Builds the gate deps (resolving domain/heads/criteria from the page's task + spec) and runs the
55762
56011
  // same convergence loop the spec-gate uses. runId is threaded for verdict provenance (decision 10).
55763
56012
  // Authoring uses the defaults (runAgentExec + loadPersona) — no extra wiring needed for those.
55764
- runDeliverableGate: async ({ projectSlug, runId, deliverablePageId }) => {
56013
+ runDeliverableGate: async ({ projectSlug, runId, deliverablePageId, declaredOutcome }) => {
55765
56014
  const gateDeps = await buildDeliverableGateDeps({
55766
56015
  client,
55767
56016
  projectSlug,
55768
56017
  deliverablePageId,
56018
+ declaredOutcome,
55769
56019
  cwd: config2.codexCwd ?? process.cwd(),
55770
56020
  codexExecutable: config2.codexExecutable,
55771
56021
  runId
55772
56022
  });
55773
- return runDeliverableGate({ projectSlug, deliverablePageId }, gateDeps);
56023
+ return runDeliverableGate({ projectSlug, deliverablePageId, declaredOutcome }, gateDeps);
55774
56024
  }
55775
56025
  };
55776
56026
  const sessionSinks = createSessionSinkRegistry();
@@ -56538,12 +56788,16 @@ function renderContextPrefix(context) {
56538
56788
  const urlPath = context?.urlPath?.trim() ? context.urlPath.trim() : void 0;
56539
56789
  const focusTaskId = context?.focusTaskId?.trim() ? context.focusTaskId.trim() : void 0;
56540
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
+ );
56541
56794
  const answeredQuestions = context?.answeredQuestions?.length ? context.answeredQuestions : void 0;
56542
56795
  const payload = {};
56543
56796
  if (projectSlug) payload.projectSlug = projectSlug;
56544
56797
  if (urlPath) payload.urlPath = urlPath;
56545
56798
  if (focusTaskId) payload.focusTaskId = focusTaskId;
56546
56799
  if (directorSessionId) payload.directorSessionId = directorSessionId;
56800
+ if (contextMessages?.length) payload.contextMessages = contextMessages;
56547
56801
  if (answeredQuestions) payload.answeredQuestions = answeredQuestions;
56548
56802
  const keys = Object.keys(payload);
56549
56803
  if (keys.length === 0) return "";
@@ -62861,6 +63115,31 @@ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, proj
62861
63115
  }
62862
63116
  };
62863
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
+ }
62864
63143
  function createMrrlinTools(options) {
62865
63144
  const client = createMrrlinClient(withAuth(options));
62866
63145
  async function uploadArtifactBytes(c, args) {
@@ -63327,13 +63606,17 @@ function createMrrlinTools(options) {
63327
63606
  codexExecutable: process.env.MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN?.trim() || void 0,
63328
63607
  extraHeads
63329
63608
  });
63330
- const activeCodeRunHead = await resolveActiveRunHead({
63331
- heads: ["code-reviewer", ...extraHeads ?? []],
63332
- persistedProvider: getSelectedProvider()
63333
- });
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
+ }
63334
63617
  if (activeCodeRunHead) deps.runHead = activeCodeRunHead;
63335
63618
  const codeGateResult = await runCodeGate({ projectSlug, baseRef }, deps);
63336
- if (process.env[SHADOW_ENV_FLAG]) {
63619
+ if (process.env[SHADOW_ENV_FLAG] && persistedProvider != null) {
63337
63620
  try {
63338
63621
  const diff = await readDiff({ cwd: process.cwd(), baseRef, run: defaultRunCmd });
63339
63622
  await maybeRunShadow({
@@ -63341,7 +63624,7 @@ function createMrrlinTools(options) {
63341
63624
  artifact: diff,
63342
63625
  heads: ["code-reviewer", ...extraHeads ?? []],
63343
63626
  correlationId: `code:${baseRef}`,
63344
- persistedProvider: getSelectedProvider()
63627
+ persistedProvider
63345
63628
  });
63346
63629
  } catch {
63347
63630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -21,9 +21,9 @@
21
21
  "@types/ws": "^8.18.1",
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
+ "@mrrlin/client": "0.0.0",
24
25
  "@mrrlin/agent-client": "0.0.0",
25
26
  "@mrrlin/director-e2e": "0.0.0",
26
- "@mrrlin/client": "0.0.0",
27
27
  "@mrrlin/schemas": "0.0.0",
28
28
  "@mrrlin/tsconfig": "0.0.0",
29
29
  "@mrrlin/wiki": "0.0.0"