@mrrlin-dev/mcp 0.3.54 → 0.3.55

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 +226 -107
  2. package/package.json +5 -5
package/dist/bin.cjs CHANGED
@@ -36506,6 +36506,8 @@ var openApiOperationIds = {
36506
36506
  getProject: "getProject",
36507
36507
  getProjectSnapshot: "getProjectSnapshot",
36508
36508
  getTask: "getTask",
36509
+ archiveTask: "archiveTask",
36510
+ unarchiveTask: "unarchiveTask",
36509
36511
  getWikiPage: "getWikiPage",
36510
36512
  handleGithubInstallCallback: "handleGithubInstallCallback",
36511
36513
  listExecutionArtifacts: "listExecutionArtifacts",
@@ -36556,6 +36558,7 @@ var openApiOperationIds = {
36556
36558
  listArtifactFiles: "listArtifactFiles",
36557
36559
  getOperatorSettings: "getOperatorSettings",
36558
36560
  updateOperatorSettings: "updateOperatorSettings",
36561
+ recordOperatorPresence: "recordOperatorPresence",
36559
36562
  getAgentPool: "getAgentPool",
36560
36563
  putAgentPool: "putAgentPool",
36561
36564
  getConsensusConfig: "getConsensusConfig",
@@ -36600,6 +36603,8 @@ var mrrlinOperatorSettingsSchema = external_exports.object({
36600
36603
  // to it (the web active-project selection is otherwise only a browser cookie). Null = not
36601
36604
  // selected yet.
36602
36605
  activeProjectSlug: external_exports.string().min(1).nullable(),
36606
+ // The last time an authenticated BROWSER operator session was observed. Null means never seen.
36607
+ lastPresenceAt: external_exports.string().datetime().nullable(),
36603
36608
  createdAt: external_exports.string().datetime(),
36604
36609
  updatedAt: external_exports.string().datetime()
36605
36610
  });
@@ -36611,6 +36616,7 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
36611
36616
  message: "OperatorSettingsUpdate requires at least one field"
36612
36617
  });
36613
36618
  var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
36619
+ var operatorPresenceResponseSchema = external_exports.object({ data: external_exports.object({ lastPresenceAt: external_exports.string().datetime() }) });
36614
36620
  var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
36615
36621
  var consensusConfigResponseSchema = external_exports.object({ data: mrrlinConsensusConfigSchema });
36616
36622
  var agentPoolSnapshotSchema = external_exports.object({
@@ -36748,7 +36754,13 @@ var mrrlinTaskDomainSchema = external_exports.enum(["code", "seo", "marketing",
36748
36754
  var mrrlinSpecApprovedSchema = external_exports.enum(["pending", "approved", "skipped", "skipped_legacy"]);
36749
36755
  var mrrlinTaskSourceSchema = external_exports.enum(["fixture-import", "api"]);
36750
36756
  var mrrlinPlanStatusSchema = external_exports.enum(["active", "paused", "done", "archived"]);
36751
- var mrrlinTaskEventTypeSchema = external_exports.enum(["autonomy_set", "director_thread_reset", "note"]);
36757
+ var mrrlinTaskEventTypeSchema = external_exports.enum([
36758
+ "autonomy_set",
36759
+ "director_thread_reset",
36760
+ "note",
36761
+ "archived",
36762
+ "restored"
36763
+ ]);
36752
36764
  var mrrlinTaskEventCreateKindSchema = external_exports.enum(["note"]);
36753
36765
  var githubRepositorySelectionSchema = external_exports.enum(["all", "selected"]);
36754
36766
  var mrrlinTaskIdSchema = external_exports.string().regex(/^GT-\d+$/);
@@ -36862,6 +36874,9 @@ var taxonomyCategorySchema = external_exports.string().min(1).regex(/^[a-z][a-z0
36862
36874
  var taxonomySubcategorySchema = external_exports.string().min(1).regex(/^[a-z][a-z0-9-]*$/);
36863
36875
  var mrrlinTaskAssigneeSchema = external_exports.union([external_exports.string().regex(/^@/), external_exports.string().email()]).nullable();
36864
36876
  var mrrlinTaskSchema = external_exports.object({
36877
+ // Output-optional (not defaulted-required) so existing task fixtures/mocks across the
36878
+ // codebase need no churn; DB rows always resolve it to `string | null` via taskRowToTask.
36879
+ archivedAt: external_exports.string().datetime().nullable().optional(),
36865
36880
  assignee: mrrlinTaskAssigneeSchema,
36866
36881
  autoDeploy: mrrlinAutoDeploySchema,
36867
36882
  // ADR 0020. OUTPUT-optional (not defaulted-required) so hardcoded task fixtures/mocks across the
@@ -37109,6 +37124,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
37109
37124
  }
37110
37125
  });
37111
37126
  var mrrlinTaskListFiltersSchema = external_exports.object({
37127
+ archived: external_exports.enum(["true", "false", "all"]).optional(),
37112
37128
  assignee: external_exports.string().startsWith("@").optional(),
37113
37129
  autoDeploy: mrrlinAutoDeploySchema.optional(),
37114
37130
  autonomyLevel: mrrlinAutonomyLevelSchema.optional(),
@@ -37829,7 +37845,11 @@ var schedulerPendingTasksResponseSchema = external_exports.object({
37829
37845
  // disclosure that some exist, so a misconfigured `github.operator` (e.g. it doesn't match the
37830
37846
  // person actually running the project) is visible as a large number instead of the autopilot
37831
37847
  // silently doing nothing.
37832
- excludedByOwnershipCount: external_exports.number().int().min(0)
37848
+ excludedByOwnershipCount: external_exports.number().int().min(0),
37849
+ // Additive: true when the operator-web-presence gate is the reason nothing new is starting
37850
+ // (autonomyPaused takes precedence when both apply). Computed by the same shared helper the
37851
+ // tick uses, so this page can never claim a task is starting when the tick would decline it.
37852
+ presenceGated: external_exports.boolean()
37833
37853
  })
37834
37854
  });
37835
37855
  var wakeDirectorRequestSchema = external_exports.object({
@@ -38386,6 +38406,7 @@ function createMrrlinClient(config2) {
38386
38406
  },
38387
38407
  async listTasks(projectSlug, filters = {}) {
38388
38408
  const path19 = appendQuery(`${projectPath(projectSlug)}/tasks`, {
38409
+ archived: filters.archived,
38389
38410
  assignee: filters.assignee,
38390
38411
  autonomyLevel: filters.autonomyLevel,
38391
38412
  blockedBy: filters.blockedBy,
@@ -38443,6 +38464,18 @@ function createMrrlinClient(config2) {
38443
38464
  const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/director-thread/reset`, { method: "POST" });
38444
38465
  return taskResponseSchema.parse(body).data;
38445
38466
  },
38467
+ async archiveTask(projectSlug, taskId) {
38468
+ const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/archive`, {
38469
+ method: "POST"
38470
+ });
38471
+ return taskResponseSchema.parse(body).data;
38472
+ },
38473
+ async unarchiveTask(projectSlug, taskId) {
38474
+ const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/unarchive`, {
38475
+ method: "POST"
38476
+ });
38477
+ return taskResponseSchema.parse(body).data;
38478
+ },
38446
38479
  async updateWikiPage(projectSlug, pageId, patch) {
38447
38480
  const body = await request(`${projectPath(projectSlug)}/wiki/${encodeURIComponent(pageId)}`, {
38448
38481
  body: JSON.stringify(patch),
@@ -38808,6 +38841,10 @@ function createMrrlinClient(config2) {
38808
38841
  });
38809
38842
  return operatorSettingsResponseSchema.parse(body).data;
38810
38843
  },
38844
+ async recordOperatorPresence() {
38845
+ const body = await request(`/operator/presence`, { method: "POST" });
38846
+ return operatorPresenceResponseSchema.parse(body).data;
38847
+ },
38811
38848
  async getOperatorAgentPool() {
38812
38849
  const body = await request(`/operator/agent-pool`);
38813
38850
  return agentPoolResponseSchema.parse(body).data;
@@ -39322,7 +39359,7 @@ function startCodexProbe(deps = {}) {
39322
39359
  }
39323
39360
 
39324
39361
  // src/_generated/version.ts
39325
- var PKG_VERSION = "0.3.54";
39362
+ var PKG_VERSION = "0.3.55";
39326
39363
 
39327
39364
  // src/api-base-url-migrate.ts
39328
39365
  var import_node_fs2 = __toESM(require("node:fs"), 1);
@@ -48772,6 +48809,21 @@ function buildThreadStartParams(msg, config2, desiredModel) {
48772
48809
  "transcript. Just call the tools and reply with the outcome \u2014 start your reply with the result.",
48773
48810
  "When creating or updating tasks, always provide `autonomyLevel` and `autonomyReason`.",
48774
48811
  "",
48812
+ "Tool-use contract \u2014 typed MCP tools are the ONLY channel for Mrrlin data:",
48813
+ "- Every read or write of Mrrlin data \u2014 tasks, plans, wiki pages, inbox items, execution runs,",
48814
+ " projects \u2014 must go through the corresponding typed MCP tool. Never use `commandExecution`,",
48815
+ " `curl`, `python`, or any direct HTTP call to the Mrrlin API.",
48816
+ "- A single `create_task` call creates a task completely; it accepts `title`, `notes`, `category`,",
48817
+ " `subcategory`, `impact`, `autonomyLevel`, `planId`, `parentTaskId`, and more in one call.",
48818
+ " Do not create then patch, and do not re-read the task afterwards unless the operator asked",
48819
+ " for confirmation.",
48820
+ "- `commandExecution` is for the operator's own machine and repositories \u2014 running builds, tests,",
48821
+ " `git`, inspecting local files \u2014 and for public third-party web checks. It is not a transport",
48822
+ " for this product's API.",
48823
+ "- If no typed tool exists for a needed operation, state that to the operator instead of reaching",
48824
+ " for the API by hand.",
48825
+ "- Do not spend turns re-reading state you just wrote.",
48826
+ "",
48775
48827
  "Turn lifecycle \u2014 what you can and cannot do between turns:",
48776
48828
  "- Your turn ends the moment you stop producing output. You CANNOT start a new turn yourself:",
48777
48829
  " no timer and no scheduler wakes you up. Only a new operator message starts the next turn.",
@@ -48845,9 +48897,10 @@ function buildThreadStartParams(msg, config2, desiredModel) {
48845
48897
  ' Never answer "nothing found" from plans alone.',
48846
48898
  '- Only report "nothing found" after the q-variants AND a full-list scan are empty, and then',
48847
48899
  " state exactly what you searched.",
48848
- "- For public HTTP/HTML verification through `commandExecution`, prefer built-in tooling such",
48849
- " as `curl`, `python3` stdlib `urllib.request`, and `html.parser`; do not assume third-party",
48850
- " Python packages such as `requests` or `bs4` are installed.",
48900
+ "- For public/third-party HTTP/HTML verification through `commandExecution`, prefer built-in",
48901
+ " tooling such as `curl`, `python3` stdlib `urllib.request`, and `html.parser`; do not assume",
48902
+ " third-party Python packages such as `requests` or `bs4` are installed. This permission covers",
48903
+ " third-party sites ONLY \u2014 never call the Mrrlin API from `commandExecution`.",
48851
48904
  "",
48852
48905
  "Handoff contract \u2014 durable operator action MUST NOT live only in chat:",
48853
48906
  "- If you conclude that a specific task needs human input, account access, manual evidence,",
@@ -54649,15 +54702,35 @@ function firstTaskIdToken(text) {
54649
54702
  }
54650
54703
 
54651
54704
  // src/consensus/spec-gate.ts
54705
+ function mapAcceptedIssueToClarification(issue2) {
54706
+ const text = issue2.text.toLowerCase();
54707
+ let kind;
54708
+ if (issue2.category === "scope") {
54709
+ const directionCues = ["only", "or also", "narrower", "broader", "include", "exclude", "versus", "vs"];
54710
+ kind = directionCues.some((cue) => text.includes(cue)) ? "scope_direction" : "missing_requirement";
54711
+ } else {
54712
+ const acceptanceCues = ["acceptance", "done", "success", "criteria", "satisfy"];
54713
+ kind = acceptanceCues.some((cue) => text.includes(cue)) ? "acceptance_criteria_detail" : "default_choice";
54714
+ }
54715
+ return {
54716
+ kind,
54717
+ diagnostic: false,
54718
+ rawQuestion: issue2.text,
54719
+ whyBlocking: `reviewer raised a ${issue2.category} issue`,
54720
+ category: issue2.category
54721
+ };
54722
+ }
54652
54723
  async function runSpecGate(_target, deps, opts) {
54653
54724
  if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
54654
54725
  const intent = await deps.readIntent();
54655
54726
  if (intent.taskId !== null && (intent.seed === null || intent.seed.trim() === "")) {
54656
54727
  return {
54657
54728
  outcome: "CONSENSUS_BLOCKED",
54658
- clarifications: [
54729
+ operatorClarifications: [
54659
54730
  {
54660
- question: "What are the requirements for this task? There is no operator request on record, so the spec cannot be validated against your intent \u2014 please state what this task must do.",
54731
+ kind: "missing_requirement",
54732
+ diagnostic: false,
54733
+ rawQuestion: "What are the requirements for this task? There is no operator request on record, so the spec cannot be validated against your intent \u2014 please state what this task must do.",
54661
54734
  whyBlocking: "operator_seed_request is empty",
54662
54735
  category: "scope"
54663
54736
  }
@@ -54671,9 +54744,11 @@ async function runSpecGate(_target, deps, opts) {
54671
54744
  if (normalizeDomain(intent.domain) !== "code" && parseAcceptanceCriteria(spec.markdownBody) === null) {
54672
54745
  return {
54673
54746
  outcome: "CONSENSUS_BLOCKED",
54674
- clarifications: [
54747
+ operatorClarifications: [
54675
54748
  {
54676
- question: "This spec has no machine-usable acceptance criteria. Add an `## Acceptance criteria` section written as a markdown checklist (`- [ ] \u2026` items) describing exactly what the finished deliverable must satisfy \u2014 the review of the produced work is scored against it.",
54749
+ kind: "acceptance_criteria_detail",
54750
+ diagnostic: false,
54751
+ rawQuestion: "This spec has no machine-usable acceptance criteria. Add an `## Acceptance criteria` section written as a markdown checklist (`- [ ] \u2026` items) describing exactly what the finished deliverable must satisfy \u2014 the review of the produced work is scored against it.",
54677
54752
  whyBlocking: "non-code spec is missing a parseable `## Acceptance criteria` checklist",
54678
54753
  category: "scope"
54679
54754
  }
@@ -54723,9 +54798,11 @@ async function runSpecGate(_target, deps, opts) {
54723
54798
  );
54724
54799
  return {
54725
54800
  outcome: "CONSENSUS_BLOCKED",
54726
- clarifications: [
54801
+ diagnostics: [
54727
54802
  {
54728
- question: `SPEC_IDENTITY_MISMATCH: this spec page gates ${intent.taskId}, but its heading identifies a different task \u2014 rewrite the spec heading and content for ${intent.taskId}, or re-link the task.`,
54803
+ kind: "spec_identity_mismatch",
54804
+ diagnostic: true,
54805
+ rawQuestion: `SPEC_IDENTITY_MISMATCH: this spec page gates ${intent.taskId}, but its heading identifies a different task \u2014 rewrite the spec heading and content for ${intent.taskId}, or re-link the task.`,
54729
54806
  whyBlocking: "converged spec content belongs to a different task than the one it gates",
54730
54807
  category: "scope"
54731
54808
  }
@@ -54745,12 +54822,8 @@ async function runSpecGate(_target, deps, opts) {
54745
54822
  }
54746
54823
  if (result.outcome === "CONSENSUS_BLOCKED") {
54747
54824
  const accepted = result.history.at(-1)?.accepted ?? [];
54748
- const clarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => ({
54749
- question: issue2.text,
54750
- whyBlocking: `reviewer raised a ${issue2.category} issue`,
54751
- category: issue2.category
54752
- }));
54753
- return clarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", clarifications } : { outcome: "CONSENSUS_BLOCKED" };
54825
+ const operatorClarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => mapAcceptedIssueToClarification(issue2));
54826
+ return operatorClarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", operatorClarifications } : { outcome: "CONSENSUS_BLOCKED" };
54754
54827
  }
54755
54828
  return { outcome: result.outcome };
54756
54829
  }
@@ -55175,6 +55248,22 @@ function buildGateQuestionOptions(question, category) {
55175
55248
  }
55176
55249
  ];
55177
55250
  }
55251
+ var HUMANIZE_TEMPLATE_BY_KIND = {
55252
+ scope_direction: "The spec can be read in two different ways. Which direction should it follow?",
55253
+ default_choice: "One important detail is still ambiguous. Which interpretation should the spec use?",
55254
+ missing_requirement: "A required part of the task is still missing from the spec. What should it say?",
55255
+ acceptance_criteria_detail: "The direction is clear, but the spec still needs a sharper success check. What should count as done?"
55256
+ };
55257
+ var HUMANIZE_CONTEXT_CAP = 200;
55258
+ function humanizeClarificationBody(clarification) {
55259
+ const prefix = HUMANIZE_TEMPLATE_BY_KIND[clarification.kind];
55260
+ let context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
55261
+ if (context.length > HUMANIZE_CONTEXT_CAP) {
55262
+ const cutIdx = context.lastIndexOf(" ", HUMANIZE_CONTEXT_CAP);
55263
+ context = context.slice(0, cutIdx > 0 ? cutIdx : HUMANIZE_CONTEXT_CAP).trim() + "\u2026";
55264
+ }
55265
+ return context ? `${prefix} ${context}` : prefix;
55266
+ }
55178
55267
  var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
55179
55268
  async function runNativeSpecGate(ctx, input, builders = defaultNativeSpecGateBuilders) {
55180
55269
  const deps = builders.buildSpecGateDeps({
@@ -55215,6 +55304,100 @@ async function cachedResult(client, input) {
55215
55304
  }
55216
55305
  return null;
55217
55306
  }
55307
+ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
55308
+ const operatorClarifications = result.operatorClarifications ?? [];
55309
+ const diagnostics = result.diagnostics ?? [];
55310
+ if (operatorClarifications.length > 0) {
55311
+ try {
55312
+ const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55313
+ if (page) {
55314
+ const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
55315
+ id: page.id,
55316
+ taskId: page.taskId ?? null
55317
+ });
55318
+ await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
55319
+ contentHash: domainScopedReviewHash(page.contentHash, domain2),
55320
+ outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
55321
+ rounds: 0
55322
+ });
55323
+ }
55324
+ } catch (error52) {
55325
+ process.stderr.write(
55326
+ `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
55327
+ `
55328
+ );
55329
+ }
55330
+ }
55331
+ let filedQuestionCount = 0;
55332
+ if (operatorClarifications.length > 0) {
55333
+ const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55334
+ const taskId = page?.taskId ?? null;
55335
+ if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
55336
+ let planId = null;
55337
+ if (!taskId) {
55338
+ const plans = await ctx.client.listPlans(input.projectSlug);
55339
+ planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
55340
+ }
55341
+ if (taskId) {
55342
+ const scopeId = taskId;
55343
+ const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55344
+ for (const clarification of operatorClarifications) {
55345
+ const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55346
+ try {
55347
+ const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
55348
+ await ctx.client.createInboxItem(input.projectSlug, {
55349
+ id,
55350
+ kind: "question",
55351
+ taskId: scopeId,
55352
+ body: humanizeClarificationBody(clarification),
55353
+ origin: "gate",
55354
+ questionOptions,
55355
+ ...groupId ? { groupId } : {}
55356
+ });
55357
+ } catch {
55358
+ }
55359
+ }
55360
+ filedQuestionCount = operatorClarifications.length;
55361
+ } else if (planId) {
55362
+ const scopeId = planId;
55363
+ const groupId = operatorClarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(operatorClarifications.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55364
+ for (const clarification of operatorClarifications) {
55365
+ const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.rawQuestion).digest("hex").slice(0, 10)}`;
55366
+ try {
55367
+ const questionOptions = buildGateQuestionOptions(clarification.rawQuestion, clarification.category);
55368
+ await ctx.client.createInboxItem(input.projectSlug, {
55369
+ id,
55370
+ kind: "question",
55371
+ planId: scopeId,
55372
+ body: humanizeClarificationBody(clarification),
55373
+ origin: "gate",
55374
+ questionOptions,
55375
+ ...groupId ? { groupId } : {}
55376
+ });
55377
+ } catch {
55378
+ }
55379
+ }
55380
+ filedQuestionCount = operatorClarifications.length;
55381
+ }
55382
+ }
55383
+ let error51;
55384
+ if (filedQuestionCount > 0) {
55385
+ error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
55386
+ } else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
55387
+ error51 = "Consensus blocked: the spec content does not match its linked task. This requires fixing the spec/task relationship directly, not an inbox answer.";
55388
+ } else if (operatorClarifications.length > 0) {
55389
+ error51 = `Consensus blocked on ${operatorClarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
55390
+ } else {
55391
+ error51 = "Consensus blocked: reviewers did not converge within budget.";
55392
+ }
55393
+ return {
55394
+ kind: "failed",
55395
+ failureReason: "consensus_blocked",
55396
+ error: error51,
55397
+ filedQuestionCount,
55398
+ ...resolvedTaskId ? { taskId: resolvedTaskId } : {}
55399
+ };
55400
+ }
55218
55401
  async function run(ctx, input) {
55219
55402
  const result = await runNativeSpecGate(ctx, input);
55220
55403
  if (process.env[SHADOW_ENV_FLAG]) {
@@ -55243,95 +55426,7 @@ async function run(ctx, input) {
55243
55426
  } catch {
55244
55427
  }
55245
55428
  if (result.outcome === "CONSENSUS_BLOCKED") {
55246
- const clarifications = result.clarifications ?? [];
55247
- if (clarifications.length > 0) {
55248
- try {
55249
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55250
- if (page) {
55251
- const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
55252
- id: page.id,
55253
- taskId: page.taskId ?? null
55254
- });
55255
- await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
55256
- contentHash: domainScopedReviewHash(page.contentHash, domain2),
55257
- outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
55258
- rounds: 0
55259
- });
55260
- }
55261
- } catch (error52) {
55262
- process.stderr.write(
55263
- `[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
55264
- `
55265
- );
55266
- }
55267
- }
55268
- let filedQuestionCount = 0;
55269
- if (clarifications.length > 0) {
55270
- const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
55271
- const taskId = page?.taskId ?? null;
55272
- if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
55273
- let planId = null;
55274
- if (!taskId) {
55275
- const plans = await ctx.client.listPlans(input.projectSlug);
55276
- planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
55277
- }
55278
- if (taskId) {
55279
- const scopeId = taskId;
55280
- const groupId = clarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(clarifications.map((c) => c.question).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55281
- for (const clarification of clarifications) {
55282
- const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
55283
- try {
55284
- const questionOptions = buildGateQuestionOptions(clarification.question, clarification.category);
55285
- await ctx.client.createInboxItem(input.projectSlug, {
55286
- id,
55287
- kind: "question",
55288
- taskId: scopeId,
55289
- body: clarification.question,
55290
- origin: "gate",
55291
- questionOptions,
55292
- ...groupId ? { groupId } : {}
55293
- });
55294
- } catch {
55295
- }
55296
- }
55297
- filedQuestionCount = clarifications.length;
55298
- } else if (planId) {
55299
- const scopeId = planId;
55300
- const groupId = clarifications.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto9.createHash)("sha256").update(clarifications.map((c) => c.question).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
55301
- for (const clarification of clarifications) {
55302
- const id = `II-${scopeId}-gate-${(0, import_node_crypto9.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
55303
- try {
55304
- const questionOptions = buildGateQuestionOptions(clarification.question, clarification.category);
55305
- await ctx.client.createInboxItem(input.projectSlug, {
55306
- id,
55307
- kind: "question",
55308
- planId: scopeId,
55309
- body: clarification.question,
55310
- origin: "gate",
55311
- questionOptions,
55312
- ...groupId ? { groupId } : {}
55313
- });
55314
- } catch {
55315
- }
55316
- }
55317
- filedQuestionCount = clarifications.length;
55318
- }
55319
- }
55320
- let error51;
55321
- if (filedQuestionCount > 0) {
55322
- error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
55323
- } else if (clarifications.length > 0) {
55324
- error51 = `Consensus blocked on ${clarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
55325
- } else {
55326
- error51 = "Consensus blocked: reviewers did not converge within budget.";
55327
- }
55328
- return {
55329
- kind: "failed",
55330
- failureReason: "consensus_blocked",
55331
- error: error51,
55332
- filedQuestionCount,
55333
- ...resolvedTaskId ? { taskId: resolvedTaskId } : {}
55334
- };
55429
+ return handleConsensusBlocked(result, ctx, input, resolvedTaskId);
55335
55430
  }
55336
55431
  switch (result.outcome) {
55337
55432
  case "CONVERGED":
@@ -55601,6 +55696,8 @@ var mcpToolNames = {
55601
55696
  updatePlan: "update_plan",
55602
55697
  updateTask: "update_task",
55603
55698
  resetDirectorThread: "reset_director_thread",
55699
+ archiveTask: "archive_task",
55700
+ unarchiveTask: "unarchive_task",
55604
55701
  updateWikiPage: "update_wiki_page",
55605
55702
  markSpecReady: "mark_spec_ready",
55606
55703
  runSpecConsensus: "run_spec_consensus",
@@ -56116,6 +56213,8 @@ var getTaskInputSchema = taskScopedSchema;
56116
56213
  var createTaskInputSchema = projectScopedSchema.merge(mrrlinTaskCreateSchema);
56117
56214
  var updateTaskInputSchema = taskScopedSchema.merge(mrrlinTaskUpdateSchema);
56118
56215
  var resetDirectorThreadInputSchema = taskScopedSchema;
56216
+ var archiveTaskInputSchema = taskScopedSchema;
56217
+ var unarchiveTaskInputSchema = taskScopedSchema;
56119
56218
  var listPlansInputSchema = projectScopedSchema;
56120
56219
  var createPlanInputSchema = projectScopedSchema.merge(mrrlinPlanCreateSchema);
56121
56220
  var updatePlanInputSchema = planScopedSchema.merge(mrrlinPlanUpdateSchema);
@@ -56268,6 +56367,8 @@ var mcpToolInputSchemas = {
56268
56367
  [mcpToolNames.updatePlan]: updatePlanInputSchema,
56269
56368
  [mcpToolNames.updateTask]: updateTaskInputSchema,
56270
56369
  [mcpToolNames.resetDirectorThread]: resetDirectorThreadInputSchema,
56370
+ [mcpToolNames.archiveTask]: archiveTaskInputSchema,
56371
+ [mcpToolNames.unarchiveTask]: unarchiveTaskInputSchema,
56271
56372
  [mcpToolNames.updateWikiPage]: updateWikiPageInputSchema,
56272
56373
  [mcpToolNames.markSpecReady]: markSpecReadyInputSchema,
56273
56374
  [mcpToolNames.runSpecConsensus]: runSpecConsensusInputSchema,
@@ -56331,6 +56432,8 @@ var mcpToolOperationIds = {
56331
56432
  [mcpToolNames.updatePlan]: openApiOperationIds.updatePlan,
56332
56433
  [mcpToolNames.updateTask]: openApiOperationIds.updateTask,
56333
56434
  [mcpToolNames.resetDirectorThread]: openApiOperationIds.resetDirectorThread,
56435
+ [mcpToolNames.archiveTask]: openApiOperationIds.archiveTask,
56436
+ [mcpToolNames.unarchiveTask]: openApiOperationIds.unarchiveTask,
56334
56437
  [mcpToolNames.updateWikiPage]: openApiOperationIds.updateWikiPage,
56335
56438
  [mcpToolNames.markSpecReady]: openApiOperationIds.markSpecReady,
56336
56439
  [mcpToolNames.listInboxItems]: openApiOperationIds.listInboxItems,
@@ -56378,6 +56481,8 @@ var mcpToolDescriptions = {
56378
56481
  [mcpToolNames.updatePlan]: "Update a plan/epic.",
56379
56482
  [mcpToolNames.updateTask]: "Update a task. Honors DB invariants: closed_no_result requires linked Evidence wiki pages. Set directorThreadId here to persist the worker's resumable provider thread handle.",
56380
56483
  [mcpToolNames.resetDirectorThread]: "Clear a task's persisted Director execution thread handle so the next execution run starts a fresh provider thread. Records a director_thread_reset task event.",
56484
+ [mcpToolNames.archiveTask]: "Archive a task: hides it from default reads, removes it from plan progress and scheduler consideration, cancels its non-terminal execution runs, and closes its open inbox items. Reversible \u2014 the project owner can restore it with unarchive_task. Records an 'archived' task event.",
56485
+ [mcpToolNames.unarchiveTask]: "Restore an archived task to its original status and visibility. Owner-only \u2014 refused with NOT_PROJECT_OWNER for any other caller. Does not resurrect the execution runs or inbox items that were cancelled/closed when the task was archived. Records a 'restored' task event.",
56381
56486
  [mcpToolNames.updateWikiPage]: "Update a markdown Wiki page.",
56382
56487
  [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.",
56383
56488
  [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. Report the spec\'s review outcome to the operator.',
@@ -56808,6 +56913,20 @@ function createMrrlinTools(options) {
56808
56913
  "Unable to reset director thread.",
56809
56914
  async (c, { projectSlug, taskId }) => await c.resetDirectorThread(projectSlug, taskId)
56810
56915
  ),
56916
+ [mcpToolNames.archiveTask]: makeTool(
56917
+ client,
56918
+ mcpToolNames.archiveTask,
56919
+ "TASK_ARCHIVE_FAILED",
56920
+ "Unable to archive task.",
56921
+ async (c, { projectSlug, taskId }) => await c.archiveTask(projectSlug, taskId)
56922
+ ),
56923
+ [mcpToolNames.unarchiveTask]: makeTool(
56924
+ client,
56925
+ mcpToolNames.unarchiveTask,
56926
+ "TASK_UNARCHIVE_FAILED",
56927
+ "Unable to unarchive task.",
56928
+ async (c, { projectSlug, taskId }) => await c.unarchiveTask(projectSlug, taskId)
56929
+ ),
56811
56930
  [mcpToolNames.updateWikiPage]: makeTool(
56812
56931
  client,
56813
56932
  mcpToolNames.updateWikiPage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.3.54",
3
+ "version": "0.3.55",
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/director-e2e": "0.0.0",
25
- "@mrrlin/schemas": "0.0.0",
26
24
  "@mrrlin/client": "0.0.0",
27
- "@mrrlin/wiki": "0.0.0",
25
+ "@mrrlin/codex-client": "0.0.0",
28
26
  "@mrrlin/tsconfig": "0.0.0",
29
- "@mrrlin/codex-client": "0.0.0"
27
+ "@mrrlin/director-e2e": "0.0.0",
28
+ "@mrrlin/schemas": "0.0.0",
29
+ "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",