@mrrlin-dev/mcp 0.4.17 → 0.4.19

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 +230 -3
  2. package/package.json +5 -5
package/dist/bin.cjs CHANGED
@@ -41623,7 +41623,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41623
41623
  }
41624
41624
 
41625
41625
  // src/_generated/version.ts
41626
- var PKG_VERSION = "0.4.17";
41626
+ var PKG_VERSION = "0.4.19";
41627
41627
 
41628
41628
  // src/api-base-url-migrate.ts
41629
41629
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -53688,6 +53688,29 @@ function buildThreadStartParams(msg, config2, desiredModel) {
53688
53688
  " for the API by hand.",
53689
53689
  "- Do not spend turns re-reading state you just wrote.",
53690
53690
  "",
53691
+ "Checking a decision you are unsure about:",
53692
+ "- `run_decision_consensus` reviews a DECISION before you act on it, not a document. You pass the",
53693
+ " question and the position you lean toward, and it tries to break that position. Calling it is",
53694
+ " your judgement: nothing requires it, and no action of yours is blocked for lacking it.",
53695
+ "- It is blind on purpose. Reviewers see only what you put in `question`, `position` and `context`",
53696
+ " \u2014 never this conversation and never your reasoning \u2014 so anything you leave out is invisible to",
53697
+ " them. Put the facts they need into `context` in your own words.",
53698
+ "- Reviewers are EXPECTED, not exotic, whenever any of these holds:",
53699
+ " (a) the decision is hard to reverse \u2014 a DB schema or migration, a published interface, a merge,",
53700
+ " a deploy, deleting data;",
53701
+ " (b) two readings of the facts both fit and you are choosing one;",
53702
+ " (c) the blind check objects and you still believe your position \u2014 disagreeing with your own",
53703
+ " check is exactly the moment an outside voice is worth having;",
53704
+ " (d) the operator says they are unsure, or asks you to check with someone else.",
53705
+ " In those cases pass `reviewers` between 1 and 3. Quota and latency are NOT a reason to skip",
53706
+ " reviewers on a decision that meets one of those conditions.",
53707
+ "- Read what came back instead of assuming it. The result reports how many of the requested",
53708
+ " reviewers actually answered, and two reviewer ids that differ only by a trailing digit, or that",
53709
+ " name the same model, are ONE opinion \u2014 not two that agree.",
53710
+ "- An objection is something you report, never something you route around. State the objection in",
53711
+ " plain language and let the operator decide. Do not quietly adopt a different answer, and do not",
53712
+ " drop the objection because you disagree with it.",
53713
+ "",
53691
53714
  "Turn lifecycle \u2014 what you can and cannot do between turns:",
53692
53715
  "- Your turn ends the moment you stop producing output. You CANNOT start a new turn yourself:",
53693
53716
  " no timer and no scheduler wakes you up. Only a new operator message starts the next turn.",
@@ -53883,6 +53906,35 @@ function buildThreadStartParams(msg, config2, desiredModel) {
53883
53906
  " still end at a clean boundary with an explicit request to continue. Ask when the work is",
53884
53907
  " genuinely big, needs an operator decision, or would change scope \u2014 never to dodge cheap work.",
53885
53908
  "",
53909
+ "Immediate-execution contract - close tiny same-session loops without creating task churn:",
53910
+ "- This is a runtime behaviour inside the normal Codex tool loop, not a new bridge protocol. The",
53911
+ " bridge only starts/resumes turns and exposes typed MCP tools; it must not hold active-action",
53912
+ " state, parse operator intent semantically, or invoke browser/Mrrlin tools directly on its own.",
53913
+ "- Low-effect observation is proactive: if the operator asks you to verify, inspect logs, read a",
53914
+ " page, check a status, or collect facts and the work is small enough for this turn, do it now",
53915
+ " using the cheapest tool that reaches ground truth. Do not ask for permission just to observe.",
53916
+ "- For any mutation, submission, browser form, external account change, purchase, publish, deploy,",
53917
+ " message, or durable repo/project write, offer the immediate action before doing it. Name the",
53918
+ " target and effect in plain language, ask for a short go-ahead, then act autonomously only inside",
53919
+ " that same-session envelope.",
53920
+ "- Same-session approval expires at a turn boundary, disconnect, resume, or materially changed",
53921
+ " target. Re-validate the visible/current target before acting after any resume, redirect, or",
53922
+ " changed page state. Never stretch one approval to a different target, broader batch, or hidden",
53923
+ " external effect.",
53924
+ "- Sensitive boundaries stay outside this lane: credentials, secrets, billing, legal/compliance,",
53925
+ " destructive deletes, production release/merge, broad scraping, and customer/user messaging need",
53926
+ " the normal task/approval path unless the operator explicitly scopes that exact action.",
53927
+ "- Keep retries bounded: one cheap retry is fine for flaky observation or validation. Repeated",
53928
+ " failure, CAPTCHA/login walls you cannot resolve, ambiguous identity, or missing authority becomes",
53929
+ " a blocker/question, not a completed receipt.",
53930
+ "- Always leave a durable receipt for material immediate work. If a parent task exists, append a",
53931
+ " task event summarizing scope, outcome/no-result, evidence or artifacts, blockers, and the next",
53932
+ ' action. If no parent task exists, create a taskless inbox item with kind="completed" and omit',
53933
+ " taskId. Do not record blockers as completed receipts.",
53934
+ "- Promote to a normal task when the action changes code, product behaviour, durable architecture,",
53935
+ " policy, or needs consensus/review; when it spans more than a small batch (roughly eight similar",
53936
+ " items); or when the result cannot be verified in the current turn.",
53937
+ "",
53886
53938
  "Context-handle contract \u2014 a `mrrlin://` handle is a pointer, not an answer:",
53887
53939
  "- When tool output carries a `mrrlin://` handle and your answer depends on detail the compact",
53888
53940
  " envelope does not contain, call `retrieve_context` on that handle BEFORE answering.",
@@ -60546,6 +60598,146 @@ async function resolveActiveRunHead(input, deps) {
60546
60598
  }
60547
60599
  }
60548
60600
 
60601
+ // src/decision-consensus.ts
60602
+ var ANCHOR_TIMEOUT_MS = Number(process.env["MRRLIN_DECISION_TIMEOUT_MS"] ?? 9e4);
60603
+ var PICK_TIMEOUT_MS3 = 15e3;
60604
+ var DISPATCH_TIMEOUT_MS3 = 6e4;
60605
+ function buildReviewArtifact(input) {
60606
+ const blocks = [
60607
+ `--- QUESTION ---
60608
+ ${input.question}`,
60609
+ `--- POSITION UNDER REVIEW ---
60610
+ ${input.position}`
60611
+ ];
60612
+ if (input.context !== void 0 && input.context.trim() !== "") {
60613
+ blocks.push(`--- CONTEXT ---
60614
+ ${input.context}`);
60615
+ }
60616
+ return blocks.join("\n\n");
60617
+ }
60618
+ var LADDER = ["architect", "plan-reviewer", "scope-analyst"];
60619
+ var MAX_ISSUES = 3;
60620
+ var MAX_ISSUE_TEXT = 200;
60621
+ function boundIssues(issues) {
60622
+ return issues.slice(0, MAX_ISSUES).map(
60623
+ (issue2) => issue2.text.length > MAX_ISSUE_TEXT ? { category: issue2.category, text: `${issue2.text.slice(0, MAX_ISSUE_TEXT)}\u2026` } : issue2
60624
+ );
60625
+ }
60626
+ async function runDecisionConsensus(input, deps) {
60627
+ const runExec = deps.runExec ?? runAgentExec;
60628
+ const buildRunner = deps.buildRunner ?? buildMultiHeadRunner;
60629
+ const persona = deps.loadPersona ?? loadPersona;
60630
+ const chosen = input.heads && input.heads.length > 0 ? input.heads : LADDER;
60631
+ const firstChosen = chosen[0];
60632
+ const anchorPersona = firstChosen === void 0 ? LADDER[0] : firstChosen;
60633
+ const seatedPersonas = input.reviewers === 0 ? [] : Array.from({ length: input.reviewers }, (_, i) => chosen[i % chosen.length] ?? anchorPersona);
60634
+ const artifact = buildReviewArtifact(input);
60635
+ const anchorLeg = async () => {
60636
+ const result = await runExec(
60637
+ {
60638
+ prompt: artifact,
60639
+ developerInstructions: persona(anchorPersona),
60640
+ cwd: input.cwd,
60641
+ timeoutMs: ANCHOR_TIMEOUT_MS,
60642
+ sandbox: "read-only",
60643
+ codexExecutable: process.env["MRRLIN_DIRECTOR_BRIDGE_CODEX_BIN"]?.trim() || void 0
60644
+ },
60645
+ void 0,
60646
+ deps.persistedProvider
60647
+ );
60648
+ if (!result.ok) return { unavailable: result.error };
60649
+ const parsed = parseVerdict(result.text);
60650
+ return { persona: anchorPersona, verdict: parsed.verdict, issues: boundIssues(parsed.issues) };
60651
+ };
60652
+ const panelLeg = async () => {
60653
+ if (input.reviewers === 0) return null;
60654
+ const seat = await deps.mintSeat();
60655
+ const personaInstructions = {};
60656
+ for (const name of seatedPersonas) {
60657
+ try {
60658
+ personaInstructions[name] = persona(name);
60659
+ } catch {
60660
+ personaInstructions[name] = "";
60661
+ }
60662
+ }
60663
+ const built = await buildRunner({
60664
+ personas: seatedPersonas,
60665
+ personaInstructions,
60666
+ excludeProvider: seat.capabilities.poolProviderLabels.join(","),
60667
+ tierPrefer: "strong",
60668
+ pickTimeoutMs: PICK_TIMEOUT_MS3,
60669
+ dispatchTimeoutMs: DISPATCH_TIMEOUT_MS3,
60670
+ seat
60671
+ });
60672
+ if (!built.ok) return { ok: false, error: built.error };
60673
+ if (built.runner.agents.length === 0) {
60674
+ return { ok: false, error: "the reviewer pool returned an empty panel" };
60675
+ }
60676
+ const responses = await Promise.all(
60677
+ seatedPersonas.map(async (personaName) => {
60678
+ const agentId = built.runner.assignment[personaName] ?? "";
60679
+ const result = await built.runner.runHead(personaName, artifact, "");
60680
+ return { personaName, agentId, result };
60681
+ })
60682
+ );
60683
+ const seats = [];
60684
+ for (const response of responses) {
60685
+ if (!response.result.ok) continue;
60686
+ const parsed = parseVerdict(response.result.text);
60687
+ seats.push({
60688
+ persona: response.personaName,
60689
+ agentId: response.agentId,
60690
+ verdict: parsed.verdict,
60691
+ issues: boundIssues(parsed.issues)
60692
+ });
60693
+ }
60694
+ return { ok: true, agents: built.runner.agents, seats };
60695
+ };
60696
+ const [anchor, panel] = await Promise.all([anchorLeg(), panelLeg()]);
60697
+ if (panel === null) return { anchor, panel: null, degraded: null };
60698
+ if (!panel.ok) {
60699
+ return {
60700
+ anchor,
60701
+ panel: {
60702
+ requested: input.reviewers,
60703
+ filled: 0,
60704
+ agentIds: [],
60705
+ underpowered: true,
60706
+ responded: 0,
60707
+ seats: []
60708
+ },
60709
+ degraded: `the reviewer pool could not be seated (${panel.error}); this review is the blind check alone`
60710
+ };
60711
+ }
60712
+ const filled = panel.agents.length;
60713
+ const responded = panel.seats.length;
60714
+ return {
60715
+ anchor,
60716
+ // D6: the seated identities are reported verbatim and no "distinct voices" number is computed.
60717
+ // Deriving one from id strings alone is a heuristic that will be wrong — `groq-gpt-oss-120b`,
60718
+ // `groq-gpt-oss-120b-2` and `ollama-gpt-oss-120b` are three ids and one model — so the reading
60719
+ // rule lives in the Director's instruction instead, where it can be stated honestly.
60720
+ panel: {
60721
+ requested: input.reviewers,
60722
+ filled,
60723
+ agentIds: panel.agents,
60724
+ underpowered: filled < input.reviewers,
60725
+ responded,
60726
+ seats: panel.seats
60727
+ },
60728
+ // `degraded` fires whenever FEWER voices answered than were asked for, not only at zero: asking
60729
+ // for three and hearing one is a thinner review than the Director requested, and it has to say
60730
+ // so rather than present one answer as the panel's. `underpowered` above stays the mechanical
60731
+ // seat count; this is the sentence the Director reads out.
60732
+ // Two halves, both load-bearing. `filled` is the honest measure of INDEPENDENCE: two personas
60733
+ // answering from one seated model are one voice wearing two hats, and reporting that as a full
60734
+ // panel is exactly the trap D6 exists to avoid. `responded` catches the other shape — seats
60735
+ // filled that then stayed silent. `underpowered` above is the mechanical flag; this is the
60736
+ // sentence the Director reads out.
60737
+ degraded: filled < input.reviewers || responded < input.reviewers ? `${filled} of ${input.reviewers} requested reviewers seated, ${responded} answered` + (responded === 0 ? "; this review is the blind check alone" : "") : null
60738
+ };
60739
+ }
60740
+
60549
60741
  // src/async-tools/consensus.ts
60550
60742
  var import_node_crypto11 = require("node:crypto");
60551
60743
 
@@ -61683,6 +61875,7 @@ var mcpToolNames = {
61683
61875
  markSpecReady: "mark_spec_ready",
61684
61876
  runSpecConsensus: "run_spec_consensus",
61685
61877
  runCodeReviewerGate: "run_code_reviewer_gate",
61878
+ runDecisionConsensus: "run_decision_consensus",
61686
61879
  uploadArtifact: "upload_artifact",
61687
61880
  deliverFile: "deliver_file",
61688
61881
  listArtifactFiles: "list_artifact_files",
@@ -62226,6 +62419,20 @@ var runCodeReviewerGateInputSchema = external_exports.object({
62226
62419
  'Opt-in reviewer personas to add on top of the fixed Code Reviewer head. "security-analyst" \u2014 for sensitive diffs (auth, secrets, input handling, new endpoints); adds a threat-modeling reviewer. OFF by default.'
62227
62420
  )
62228
62421
  });
62422
+ var runDecisionConsensusInputSchema = external_exports.object({
62423
+ projectSlug: external_exports.string().min(1),
62424
+ question: external_exports.string().min(1).describe("The decision to be reviewed, as a question."),
62425
+ position: external_exports.string().min(1).describe("The answer you are leaning toward. Reviewers try to break THIS, so state it plainly."),
62426
+ context: external_exports.string().optional().describe(
62427
+ "Only the facts a reviewer needs, in your own words. Reviewers see nothing else \u2014 not this chat, not your reasoning \u2014 so anything you omit here is invisible to them. Keep it an excerpt, not a file dump."
62428
+ ),
62429
+ reviewers: external_exports.number().int().min(0).max(3).default(0).describe(
62430
+ "How many independent reviewers to seat from the pool, on top of the blind check that always runs. 0 (default) contacts nobody outside this machine's own model."
62431
+ ),
62432
+ heads: external_exports.array(external_exports.enum(["architect", "plan-reviewer", "scope-analyst", "security-analyst", "debugger", "researcher"])).min(1).max(3).optional().describe(
62433
+ 'Which reviewer lenses to use. Omitted, the blind check uses "architect" and seats fill from architect / plan-reviewer / scope-analyst. Name one explicitly when the doubt has a shape \u2014 "security-analyst" for auth or secrets, "debugger" for a root-cause claim, "researcher" for a dependency or API behaviour claim.'
62434
+ )
62435
+ });
62229
62436
  var searchKnowledgeInputSchema = wikiKnowledgeSearchInputSchema;
62230
62437
  var createGithubBranchInputSchema = projectScopedSchema.merge(githubCreateBranchRequestSchema);
62231
62438
  var createGithubPullRequestInputSchema = projectScopedSchema.merge(githubCreatePullRequestRequestSchema);
@@ -62367,6 +62574,7 @@ var mcpToolInputSchemas = {
62367
62574
  [mcpToolNames.markSpecReady]: markSpecReadyInputSchema,
62368
62575
  [mcpToolNames.runSpecConsensus]: runSpecConsensusInputSchema,
62369
62576
  [mcpToolNames.runCodeReviewerGate]: runCodeReviewerGateInputSchema,
62577
+ [mcpToolNames.runDecisionConsensus]: runDecisionConsensusInputSchema,
62370
62578
  [mcpToolNames.listInboxItems]: listInboxItemsInputSchema,
62371
62579
  [mcpToolNames.createInboxItem]: createInboxItemInputSchema,
62372
62580
  [mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
@@ -62459,7 +62667,7 @@ var mcpToolDescriptions = {
62459
62667
  [mcpToolNames.listProjectMembers]: "List a project's teammates (the owner is derived from github.operator, not a member row).",
62460
62668
  [mcpToolNames.addProjectMember]: "Add a teammate to a project by email (owner-only).",
62461
62669
  [mcpToolNames.removeProjectMember]: "Remove a teammate from a project by email (owner-only); returns the updated list.",
62462
- [mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline.",
62670
+ [mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline. Also use it as the task-linked receipt path for material immediate work that has a parent task; summarize scope, outcome/no-result, evidence or artifacts, blockers, and next action in the note body.",
62463
62671
  [mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
62464
62672
  [mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
62465
62673
  [mcpToolNames.createTask]: "Create a new task in a project. Defaults to status='backlog' with a blank assignee; pass status='todo' explicitly to authorize immediate autopilot work. 'backlog' and 'todo' are the only creatable statuses. 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.",
@@ -62492,6 +62700,7 @@ var mcpToolDescriptions = {
62492
62700
  [mcpToolNames.markSpecReady]: "Mark a wiki page (folder=Specs) as ready (or not) for execution-run gating. The Director sets this true after validating the spec; any edit to the page's markdown auto-resets it to false.",
62493
62701
  [mcpToolNames.runSpecConsensus]: 'Enqueue the consensus review gate over a Specs/{id} page. Returns IMMEDIATELY with one of: already_converged (a CONVERGED review already exists for the current content \u2014 proceed straight to create_execution_run, do NOT call mark_spec_ready, it is already set), already_reviewed (a terminal CONSENSUS_BLOCKED for this content), already_running (a consensus job is in flight), or queued (a new job was launched). The review runs in the background; its result/blocker arrives in the inbox (info on CONVERGED, handoff on failure) and is surfaced into the open chat. Do NOT call again for the same spec and do NOT poll. On queued/already_running, tell the user it was launched and end the turn. Pass extraHeads: ["researcher"] for specs referencing external libraries/APIs, or ["debugger"] for root-cause/investigation specs to add opt-in specialist reviewers on top of the fixed four. 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, using the same flow as code tasks. When the review agrees, tell the operator "the reviewers agreed on the spec" and proceed to create_execution_run. When it does not, tell them the reviewers could not agree and that there is a decision question in the inbox. Never say "consensus", "converged", or "CONVERGED" to the operator; those are internal words.',
62494
62702
  [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).`,
62703
+ [mcpToolNames.runDecisionConsensus]: 'Check a DECISION you are about to act on, before you act on it \u2014 not a document. Where run_spec_consensus reviews a spec page and run_code_reviewer_gate reviews a branch diff, this one reviews a judgement: "new API route or widen the existing accessor", "does this need a DB change", "is this task ready to start". Pass the `question` and the `position` you are leaning toward; the review tries to break that position. It is BLIND: reviewers see only `question`, `position` and `context` \u2014 never this conversation and never your reasoning \u2014 so put the facts they need into `context` in your own words, and expect anything you leave out to be invisible to them. With `reviewers` omitted (the default) the check runs entirely on this machine\'s own model: nothing is dispatched, no external provider is contacted, no quota is spent. Set `reviewers` to 1-3 to also seat that many independent reviewers from the pool for the same question; they run once, in parallel, and are never re-asked. A seated reviewer can time out or be unavailable, in which case the result reports how many of the requested reviewers actually answered and the check falls back to the blind pass alone \u2014 read that number rather than assuming a full panel, and treat two reviewer ids that differ only by a trailing digit, or that name the same model, as ONE opinion rather than two agreeing ones. When the result objects to your position, do not quietly adopt a different answer and do not drop the objection: tell the operator what the objection is, in plain language, and let them decide. Never say "consensus", "converged", "anchor", or a verdict code like APPROVE or REQUEST_CHANGES to the operator; those are internal words.',
62495
62704
  [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.",
62496
62705
  [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.',
62497
62706
  [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.",
@@ -62506,7 +62715,7 @@ var mcpToolDescriptions = {
62506
62715
  [mcpToolNames.getSchedulerPendingTasks]: "Return tasks the scheduler should wake now: ones with follow_up_at or verification_due_at passed, or the policy wake_floor reached, filtered by eligible autonomy levels.",
62507
62716
  [mcpToolNames.wakeDirector]: "Wake Director once for a project: optionally dry-run scheduler evaluation or enqueue execution runs best-effort.",
62508
62717
  [mcpToolNames.listInboxItems]: "List operator-inbox items for a project. Optional filters: kind, status.",
62509
- [mcpToolNames.createInboxItem]: "Create an operator-inbox item (proposal, check_in, question, handoff, or info) for a project.",
62718
+ [mcpToolNames.createInboxItem]: 'Create an operator-inbox item (proposal, check_in, question, handoff, info, or completed) for a project. Use kind="completed" for material taskless completion receipts after same-session immediate work with no parent task; omit taskId for taskless receipts and summarize scope, outcome/no-result, evidence or artifacts, blocker if any, and next action in body. Do not file blockers as completed receipts.',
62510
62719
  [mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
62511
62720
  [mcpToolNames.uploadArtifact]: "Upload a local file (md/txt/html/csv/json/pdf/png/jpg/webp/gif, <=5MB) as a project artifact. Returns a stable markdown link to embed in chat/spec/handoff text. Use class=temp for one-off hand-offs, class=durable for anything referenced by specs or evidence. Pass runId to record the file in that run's artifact history; pass taskId to surface it on the task's Artifacts list.",
62512
62721
  [mcpToolNames.deliverFile]: "This is the DEFAULT way to deliver a file the agent just composed; it takes the body inline as UTF-8 `content` in one call and needs no local file or writable path; `upload_artifact` is for a file that already exists on disk; use class=temp / class=durable and taskId/runId semantics with a 5MB cap.",
@@ -63088,6 +63297,24 @@ function createMrrlinTools(options) {
63088
63297
  return result;
63089
63298
  }
63090
63299
  ),
63300
+ [mcpToolNames.runDecisionConsensus]: makeTool(
63301
+ client,
63302
+ mcpToolNames.runDecisionConsensus,
63303
+ "DECISION_CONSENSUS_FAILED",
63304
+ "Unable to run the decision review.",
63305
+ // No client call: this review reads and writes no Mrrlin state. `projectSlug` is accepted for
63306
+ // signature consistency with every other Director tool (see design.md D4a), not consumed here.
63307
+ // tools.ts is an allowed provider mint site (eslint.config.mjs); decision-consensus.ts is
63308
+ // deliberately not, so the seat and the persisted provider are minted HERE and threaded down.
63309
+ // mintSeat stays a thunk so the anchor-only path never mints a seat it has no use for.
63310
+ async (_c, input) => runDecisionConsensus(
63311
+ { ...input, cwd: process.cwd() },
63312
+ {
63313
+ persistedProvider: getSelectedProvider(),
63314
+ mintSeat: () => mintResolvedSeat(resolveProvider(process.env, getSelectedProvider()))
63315
+ }
63316
+ )
63317
+ ),
63091
63318
  [mcpToolNames.runCodeReviewerGate]: makeTool(
63092
63319
  client,
63093
63320
  mcpToolNames.runCodeReviewerGate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
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/client": "0.0.0",
25
- "@mrrlin/agent-client": "0.0.0",
26
- "@mrrlin/director-e2e": "0.0.0",
27
24
  "@mrrlin/schemas": "0.0.0",
25
+ "@mrrlin/director-e2e": "0.0.0",
26
+ "@mrrlin/agent-client": "0.0.0",
28
27
  "@mrrlin/tsconfig": "0.0.0",
29
- "@mrrlin/wiki": "0.0.0"
28
+ "@mrrlin/wiki": "0.0.0",
29
+ "@mrrlin/client": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",