@mrrlin-dev/mcp 0.4.33 → 0.4.37

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 +430 -5
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -36799,6 +36799,24 @@ function date4(params) {
36799
36799
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
36800
36800
  config(en_default());
36801
36801
 
36802
+ // ../../packages/schemas/dist/artifact-checks.js
36803
+ var artifactKindSchema = external_exports.enum([
36804
+ "document",
36805
+ "page",
36806
+ "file_set",
36807
+ "code_change",
36808
+ "structured_data",
36809
+ "message"
36810
+ ]);
36811
+ var ARTIFACT_CHECKS = {
36812
+ document: null,
36813
+ page: { verifier: "url" },
36814
+ file_set: null,
36815
+ code_change: { verifier: "build" },
36816
+ structured_data: null,
36817
+ message: { verifier: "status" }
36818
+ };
36819
+
36802
36820
  // ../../packages/schemas/dist/operationIds.js
36803
36821
  var openApiOperationIds = {
36804
36822
  appendExecutionArtifact: "appendExecutionArtifact",
@@ -36889,6 +36907,7 @@ var openApiOperationIds = {
36889
36907
  restartExecutionRun: "restartExecutionRun",
36890
36908
  resolveHandoff: "resolveHandoff",
36891
36909
  touchExecutionRun: "touchExecutionRun",
36910
+ stopExecutionRun: "stopExecutionRun",
36892
36911
  getTaskState: "getTaskState",
36893
36912
  upsertTaskState: "upsertTaskState",
36894
36913
  retireThread: "retireThread",
@@ -36929,7 +36948,15 @@ var openApiOperationIds = {
36929
36948
  getSpecConsensusReview: "getSpecConsensusReview",
36930
36949
  onboardingKickoffTransition: "onboardingKickoffTransition",
36931
36950
  ingestTelemetry: "ingestTelemetry",
36932
- getTelemetry: "getTelemetry"
36951
+ getTelemetry: "getTelemetry",
36952
+ listCapabilityGrants: "listCapabilityGrants",
36953
+ createCapabilityGrant: "createCapabilityGrant",
36954
+ revokeCapabilityGrant: "revokeCapabilityGrant",
36955
+ listCapabilityRoutes: "listCapabilityRoutes",
36956
+ upsertCapabilityRoute: "upsertCapabilityRoute",
36957
+ resolveCapabilityRoute: "resolveCapabilityRoute",
36958
+ overrideCapabilityToolClass: "overrideCapabilityToolClass",
36959
+ listCapabilityUses: "listCapabilityUses"
36933
36960
  };
36934
36961
 
36935
36962
  // ../../packages/schemas/dist/session.js
@@ -37437,6 +37464,58 @@ function domainScopedReviewHash(rawHash, domain2) {
37437
37464
  const d = normalizeTaskDomain(domain2);
37438
37465
  return d === "code" ? rawHash : `${rawHash}@${d}`;
37439
37466
  }
37467
+ var mrrlinDeclaredOutcomeShapeSchema = external_exports.object({
37468
+ sentence: external_exports.string().trim().min(1),
37469
+ requires: external_exports.array(artifactKindSchema).min(1),
37470
+ verification: external_exports.enum(["machine", "human_tap"])
37471
+ });
37472
+ var mrrlinDeclaredOutcomeSchema = mrrlinDeclaredOutcomeShapeSchema.superRefine((value, ctx) => {
37473
+ const expectedVerifiers = new Set(value.requires.map((kind) => ARTIFACT_CHECKS[kind]?.verifier ?? "human_tap"));
37474
+ if (expectedVerifiers.size > 1) {
37475
+ const spread = value.requires.map((kind) => {
37476
+ const check2 = ARTIFACT_CHECKS[kind];
37477
+ return check2 === null ? `${kind} has no machine check` : `${kind} needs ${check2.verifier}`;
37478
+ }).join(", ");
37479
+ ctx.addIssue({
37480
+ code: external_exports.ZodIssueCode.custom,
37481
+ path: ["requires"],
37482
+ message: `requires spans more than one verifier, so no receipt can ever satisfy it (${spread}). Declare the outcome rather than the steps that produced it; if two things must genuinely exist and neither is a means to the other, split them into linked tasks.`
37483
+ });
37484
+ }
37485
+ const hasVerifiableKind = value.requires.some((kind) => ARTIFACT_CHECKS[kind] !== null);
37486
+ const hasUnverifiableKind = value.requires.some((kind) => ARTIFACT_CHECKS[kind] === null);
37487
+ if (value.verification === "human_tap" && hasVerifiableKind) {
37488
+ ctx.addIssue({
37489
+ code: external_exports.ZodIssueCode.custom,
37490
+ path: ["verification"],
37491
+ message: "human_tap is not permitted: a required kind has a machine verifier"
37492
+ });
37493
+ }
37494
+ if (value.verification === "machine" && hasUnverifiableKind) {
37495
+ ctx.addIssue({
37496
+ code: external_exports.ZodIssueCode.custom,
37497
+ path: ["verification"],
37498
+ message: "machine is not permitted: a required kind has no machine verifier"
37499
+ });
37500
+ }
37501
+ });
37502
+ var mrrlinReceiptSchema = external_exports.object({
37503
+ kind: external_exports.enum(["outcome", "attempt"]),
37504
+ evidence: external_exports.object({
37505
+ type: external_exports.enum(["url", "build", "status", "human_tap"]),
37506
+ value: external_exports.string().trim().min(1)
37507
+ }),
37508
+ observedAt: external_exports.string().datetime(),
37509
+ requiresCovered: external_exports.array(artifactKindSchema)
37510
+ });
37511
+ function buildReceipt(input) {
37512
+ return {
37513
+ kind: input.coveredKinds.length === 0 ? "attempt" : "outcome",
37514
+ evidence: { type: input.evidenceType, value: input.evidenceValue },
37515
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
37516
+ requiresCovered: input.coveredKinds
37517
+ };
37518
+ }
37440
37519
  var mrrlinSpecApprovedSchema = external_exports.enum(["pending", "approved", "skipped", "skipped_legacy"]);
37441
37520
  var mrrlinTaskSourceSchema = external_exports.enum(["fixture-import", "api"]);
37442
37521
  var mrrlinPlanStatusSchema = external_exports.enum(["active", "paused", "done", "archived"]);
@@ -37520,6 +37599,10 @@ var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
37520
37599
  // start (verifyProviderReadiness returned ready:false) — an environment/infrastructure problem,
37521
37600
  // not a task-content one, so it gets its own code rather than reusing task_blocked.
37522
37601
  "provider_not_ready",
37602
+ // agnostic-delivery: an operator-initiated stop. Parks the run at handoff_requested rather
37603
+ // than cancelling it, so restartParkedExecutionRun can resume it — a zod-only enum extension,
37604
+ // since blocking_reason is a plain text column with no DB CHECK.
37605
+ "operator_stopped",
37523
37606
  // The agent provider refused work MID-RUN because its usage quota is exhausted — distinct from
37524
37607
  // `provider_not_ready`, which is the pre-flight readiness gate refusing to start. A quota is
37525
37608
  // transient and the run is retriable once the limit resets, so this code must never be conflated
@@ -37638,6 +37721,14 @@ var mrrlinTaskSchema = external_exports.object({
37638
37721
  // resolve it via taskRowToTask. Null reads as the pre-existing default provider and is never
37639
37722
  // backfilled -- it is NOT a mismatch against whatever provider is currently selected.
37640
37723
  directorThreadProvider: external_exports.enum(["codex", "claude"]).nullable().optional(),
37724
+ // agnostic-delivery: asserted before the work, not the deliverable-gate's differently-meaning
37725
+ // `attemptOutcome` asserted after it (design.md). Required at spec approval (task 3.2's API
37726
+ // gate), never at creation -- null on every task until then, forever on legacy tasks. Shape-only
37727
+ // here deliberately (not the cross-check-enforcing mrrlinDeclaredOutcomeSchema): a read must
37728
+ // never crash on a persisted value that fails the manifest cross-check (a raw SQL row, or one
37729
+ // written before task 1.5's superRefine existed) -- only the write path (mrrlinTaskUpdateSchema
37730
+ // below, and task 3.2's approval-time re-check) enforces that stronger rule.
37731
+ declaredOutcome: mrrlinDeclaredOutcomeShapeSchema.nullable().optional(),
37641
37732
  dueDate: external_exports.string().date().nullable(),
37642
37733
  followUpAt: external_exports.string().datetime().nullable(),
37643
37734
  id: mrrlinTaskIdSchema,
@@ -37974,6 +38065,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
37974
38065
  directorSessionId: external_exports.string().min(1).optional(),
37975
38066
  directorThreadId: external_exports.string().min(1).nullable().optional(),
37976
38067
  directorThreadProvider: external_exports.enum(["codex", "claude"]).nullable().optional(),
38068
+ declaredOutcome: mrrlinDeclaredOutcomeSchema.nullable().optional(),
37977
38069
  dueDate: external_exports.string().date().nullable().optional(),
37978
38070
  impact: mrrlinTaskImpactSchema.optional(),
37979
38071
  notes: external_exports.string().nullable().optional(),
@@ -38481,7 +38573,11 @@ var mrrlinSettleExecutionRunOutcomeSchema = external_exports.object({
38481
38573
  resultSummary: external_exports.string().trim().min(1).max(300),
38482
38574
  deliverablePageId: mrrlinWikiPageIdSchema,
38483
38575
  contentHash: external_exports.string().min(1),
38484
- leaseId: external_exports.string().min(1)
38576
+ leaseId: external_exports.string().min(1),
38577
+ // Optional: a legacy caller sends none. Absent counts as unsatisfied whenever the task has a
38578
+ // non-null declared_outcome (task 2.7b) — losing dedup must not lose the write, but losing a
38579
+ // receipt must not silently close a task that declared one.
38580
+ receipt: mrrlinReceiptSchema.optional()
38485
38581
  });
38486
38582
  var mrrlinExecutionArtifactSchema = external_exports.object({
38487
38583
  createdAt: external_exports.string().datetime(),
@@ -39047,6 +39143,74 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
39047
39143
  });
39048
39144
  var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
39049
39145
  var inboxItemListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSchema) });
39146
+ var capabilityServerIdSchema = external_exports.string().trim().min(1).max(200);
39147
+ var capabilityNameSchema = external_exports.string().trim().min(1).max(200);
39148
+ var capabilityToolNameSchema = external_exports.string().trim().min(1).max(200);
39149
+ var CapabilityReversibilitySchema = external_exports.enum(["reversible", "irreversible"]);
39150
+ var CapabilityGateVerdictSchema = external_exports.enum([
39151
+ "silent",
39152
+ "ask_once",
39153
+ "window",
39154
+ "approval",
39155
+ "refused"
39156
+ ]);
39157
+ var capabilityToolClassSourceSchema = external_exports.enum(["annotation", "operator"]);
39158
+ var capabilityUseOutcomeSchema = external_exports.enum(["ok", "error", "refused"]);
39159
+ var CapabilityGrantSchema = external_exports.object({
39160
+ projectSlug: mrrlinProjectSlugSchema,
39161
+ serverId: capabilityServerIdSchema,
39162
+ grantedAt: external_exports.string().datetime(),
39163
+ // A revoke is a soft revoke: the row stays so `resolveCapabilityRoute` can answer `no_grant`
39164
+ // rather than the indistinguishable `no_route`. A live grant is `revokedAt === null`.
39165
+ revokedAt: external_exports.string().datetime().nullable(),
39166
+ // "First write asks once" is bounded by (project, server) -- exactly this row's key -- so the
39167
+ // confirmation lives here rather than in a second table. `revokeCapability` clears it, which is
39168
+ // what makes a re-grant ask again.
39169
+ firstWriteConfirmedAt: external_exports.string().datetime().nullable()
39170
+ });
39171
+ var CapabilityRouteSchema = external_exports.object({
39172
+ projectSlug: mrrlinProjectSlugSchema,
39173
+ capability: capabilityNameSchema,
39174
+ // The artifact kind this route delivers. NOT the key -- a project can have two sites, and a key
39175
+ // of `page` would let the second overwrite the first. It is here so a declared outcome, which
39176
+ // carries only artifact kinds, can find its route: without it there is no chain from what a task
39177
+ // promises to where the delivery goes.
39178
+ artifactKind: artifactKindSchema,
39179
+ serverId: capabilityServerIdSchema,
39180
+ toolName: capabilityToolNameSchema,
39181
+ createdAt: external_exports.string().datetime(),
39182
+ updatedAt: external_exports.string().datetime()
39183
+ });
39184
+ var CapabilityToolClassSchema = external_exports.object({
39185
+ projectSlug: mrrlinProjectSlugSchema,
39186
+ serverId: capabilityServerIdSchema,
39187
+ toolName: capabilityToolNameSchema,
39188
+ toolClass: CapabilityReversibilitySchema,
39189
+ source: capabilityToolClassSourceSchema,
39190
+ setAt: external_exports.string().datetime()
39191
+ });
39192
+ var CapabilityUseSchema = external_exports.object({
39193
+ id: external_exports.number().int().nonnegative(),
39194
+ projectSlug: mrrlinProjectSlugSchema,
39195
+ serverId: capabilityServerIdSchema,
39196
+ toolName: capabilityToolNameSchema,
39197
+ taskId: mrrlinTaskIdSchema.nullable(),
39198
+ outcome: capabilityUseOutcomeSchema,
39199
+ at: external_exports.string().datetime()
39200
+ });
39201
+ var capabilityGrantCreateSchema = external_exports.object({ serverId: capabilityServerIdSchema });
39202
+ var capabilityRouteUpsertSchema = external_exports.object({
39203
+ artifactKind: artifactKindSchema,
39204
+ serverId: capabilityServerIdSchema,
39205
+ toolName: capabilityToolNameSchema
39206
+ });
39207
+ var capabilityToolClassOverrideSchema = external_exports.object({ toolClass: CapabilityReversibilitySchema });
39208
+ var capabilityGrantResponseSchema = external_exports.object({ data: CapabilityGrantSchema });
39209
+ var capabilityGrantListResponseSchema = external_exports.object({ data: external_exports.array(CapabilityGrantSchema) });
39210
+ var capabilityRouteResponseSchema = external_exports.object({ data: CapabilityRouteSchema });
39211
+ var capabilityRouteListResponseSchema = external_exports.object({ data: external_exports.array(CapabilityRouteSchema) });
39212
+ var capabilityToolClassResponseSchema = external_exports.object({ data: CapabilityToolClassSchema });
39213
+ var capabilityUseListResponseSchema = external_exports.object({ data: external_exports.array(CapabilityUseSchema) });
39050
39214
 
39051
39215
  // ../../packages/agent-client/dist/providers/codex/render-context-bundle.js
39052
39216
  var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
@@ -40891,6 +41055,18 @@ function createMrrlinClient(config2) {
40891
41055
  throw error51;
40892
41056
  }
40893
41057
  },
41058
+ async listCapabilityGrants(projectSlug) {
41059
+ const body = await request(`${projectPath(projectSlug)}/capability-grants`);
41060
+ return capabilityGrantListResponseSchema.parse(body).data;
41061
+ },
41062
+ async listCapabilityUses(projectSlug) {
41063
+ const body = await request(`${projectPath(projectSlug)}/capability-uses`);
41064
+ return capabilityUseListResponseSchema.parse(body).data;
41065
+ },
41066
+ async revokeCapabilityGrant(projectSlug, serverId) {
41067
+ const body = await request(`${projectPath(projectSlug)}/capability-grants/${encodeURIComponent(serverId)}`, { method: "DELETE" });
41068
+ return capabilityGrantResponseSchema.parse(body).data;
41069
+ },
40894
41070
  async listInboxItems(projectSlug, filters = {}) {
40895
41071
  const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
40896
41072
  groupId: filters.groupId,
@@ -40960,6 +41136,12 @@ function createMrrlinClient(config2) {
40960
41136
  throw error51;
40961
41137
  }
40962
41138
  },
41139
+ async stopExecutionRun(projectSlug, runId) {
41140
+ const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/stop`, {
41141
+ method: "POST"
41142
+ });
41143
+ return executionRunResponseSchema.parse(body).data;
41144
+ },
40963
41145
  async requeueExecutionRun(projectSlug, runId, staleSeconds) {
40964
41146
  try {
40965
41147
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/requeue`, {
@@ -41889,7 +42071,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41889
42071
  }
41890
42072
 
41891
42073
  // src/_generated/version.ts
41892
- var PKG_VERSION = "0.4.33";
42074
+ var PKG_VERSION = "0.4.37";
41893
42075
 
41894
42076
  // src/api-base-url-migrate.ts
41895
42077
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -45693,6 +45875,19 @@ async function findInboxQuestion(client, projectSlug, itemId) {
45693
45875
  const items = await client.listInboxItems(projectSlug, { kind: "question" });
45694
45876
  return items.find((item) => item.id === itemId) ?? null;
45695
45877
  }
45878
+ async function cancelDeployGraceWindow(client, projectSlug, runId) {
45879
+ try {
45880
+ await client.decideInboxItem(projectSlug, graceCancelItemId(runId), {
45881
+ verdict: "rejected",
45882
+ decidedBy: "deploy-gate",
45883
+ reason: "operator_stopped"
45884
+ });
45885
+ return true;
45886
+ } catch (error51) {
45887
+ if (error51 instanceof MrrlinApiError && (error51.status === 404 || error51.status === 409)) return false;
45888
+ throw error51;
45889
+ }
45890
+ }
45696
45891
  async function autonomyPaused(client) {
45697
45892
  try {
45698
45893
  const settings = await client.getOperatorSettings();
@@ -46631,6 +46826,33 @@ var BASE_INSTRUCTIONS_PREFIX = [
46631
46826
  "concrete progress via MCP tools rather than only narrating it.",
46632
46827
  "",
46633
46828
  "Completion contract (the run is gated on this):",
46829
+ "- Act now, in this turn, whenever the tools to do the work are in reach, the work does not",
46830
+ " depend on waiting for external state, and any irreversible outward action it takes is already",
46831
+ " covered by this turn's own recording obligation. The conveyor -- a scheduled follow-up task or",
46832
+ " plan -- is the exception, entered only for a build-and-test cycle, work still unfinished when",
46833
+ " the turn ends, or a decision only the operator can make. A task or plan exists to keep",
46834
+ " unfinished work from being lost, not to authorize doing it later when it could be done now.",
46835
+ "- Never file a clarifying question about anything the operator already answered in this thread.",
46836
+ " A complete instruction whose work is reachable in this turn gets done, then recorded -- it does",
46837
+ " not become a question.",
46838
+ // Final-gate finding (round 2, corrected after repo verification -- round 1's comment said
46839
+ // "unconditionally", which overstates it): this line tells the Director not to invoke
46840
+ // run_spec_consensus for such a task. Its OWN mark_spec_ready MCP tool (tools.ts) refuses to
46841
+ // flip spec_ready for the Director-LLM without a CONVERGED review UNLESS the project has
46842
+ // consensus disabled, in which case a DIFFERENT server-side path (the scheduler tick's
46843
+ // auto-promote under the consensus-off policy, tools.ts's own mark_spec_ready comment) can
46844
+ // promote it instead -- that is not a Director-invoked path either. The plain markSpecReady
46845
+ // API/client method (packages/client) has no CONVERGED-review requirement at all and IS directly
46846
+ // reachable (this is what seedApprovedTask in scripts/persona-smoke-agnostic-delivery.ts uses),
46847
+ // but it is not exposed to the Director as an MCP tool. So this instruction does not mean "the
46848
+ // Director marks it ready some other way" -- it means the Director stops short of spec_ready for
46849
+ // a pure form-filling task and leaves that promotion to whichever of the above actually applies
46850
+ // (an operator via the plain route, or the scheduler tick if consensus is disabled for the
46851
+ // project), consistent with such a task being too trivial to need Director-driven review in the
46852
+ // first place. declaredOutcome + the receipt gate (Group 2/3) are unaffected either way -- they
46853
+ // gate on specApproved, which is reachable once spec_ready is set by any caller.
46854
+ "- When a task's entire declared outcome is a submitted form, spec review is unreachable for it --",
46855
+ " do not route a pure form-filling task through spec review.",
46634
46856
  "- Record evidence with `append_execution_artifact` against the run id shown in the",
46635
46857
  ' "## Open run" section of the context bundle.',
46636
46858
  "- Artifact kinds that count as completion evidence are exactly: checkpoint, test_result, diff_summary, self_review, handoff_judgement.",
@@ -48042,13 +48264,20 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
48042
48264
  return;
48043
48265
  }
48044
48266
  }
48267
+ const receipt = parsedResult.outcome === "completed" && task.declaredOutcome != null ? buildReceipt({
48268
+ declaredOutcome: task.declaredOutcome,
48269
+ evidenceType: "url",
48270
+ evidenceValue: pageId,
48271
+ coveredKinds: ["page"]
48272
+ }) : void 0;
48045
48273
  try {
48046
48274
  const settled = await deps.client.settleExecutionRunOutcome(projectSlug, runId, {
48047
48275
  leaseId,
48048
48276
  outcome: parsedResult.outcome,
48049
48277
  resultSummary: parsedResult.resultSummary,
48050
48278
  deliverablePageId: pageId,
48051
- contentHash
48279
+ contentHash,
48280
+ ...receipt ? { receipt } : {}
48052
48281
  });
48053
48282
  if (!settled && await runHasLease(deps, projectSlug, runId, leaseId)) {
48054
48283
  await failRun(
@@ -48331,6 +48560,12 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48331
48560
  await drive(slug, run3.id);
48332
48561
  }
48333
48562
  if (deps.deployGate) {
48563
+ let autonomyPaused2 = false;
48564
+ try {
48565
+ autonomyPaused2 = (await deps.client.getOperatorSettings()).autonomyPaused === true;
48566
+ } catch {
48567
+ autonomyPaused2 = false;
48568
+ }
48334
48569
  for (const status of ["dev_deploy", "prod_deploy"]) {
48335
48570
  let inFlight = [];
48336
48571
  try {
@@ -48342,6 +48577,10 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48342
48577
  if (!isDeployGateRunStatus(run3.status)) continue;
48343
48578
  try {
48344
48579
  const task = await deps.client.getTask(slug, run3.taskId);
48580
+ if (autonomyPaused2 || mrrlinTerminalTaskStatuses.includes(task.status)) {
48581
+ await cancelDeployGraceWindow(deps.client, slug, run3.id).catch(() => {
48582
+ });
48583
+ }
48345
48584
  const gateDeps = {
48346
48585
  client: deps.client,
48347
48586
  runVerificationTurn: deps.deployGate.runVerificationTurn,
@@ -48385,6 +48624,17 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48385
48624
  }
48386
48625
  }
48387
48626
  }
48627
+ let stoppedRuns = [];
48628
+ try {
48629
+ stoppedRuns = await deps.client.listExecutionRuns(slug, { status: "handoff_requested" });
48630
+ } catch {
48631
+ stoppedRuns = [];
48632
+ }
48633
+ for (const run3 of stoppedRuns) {
48634
+ if (run3.blockingReason !== "operator_stopped") continue;
48635
+ await cancelDeployGraceWindow(deps.client, slug, run3.id).catch(() => {
48636
+ });
48637
+ }
48388
48638
  }
48389
48639
  }
48390
48640
  }
@@ -48619,6 +48869,100 @@ function redactMessage(payload, opts) {
48619
48869
  }
48620
48870
  return { payload: redactValue(payload, 0, counter), redactedCount: counter.count };
48621
48871
  }
48872
+ var OUTWARD_TOOL_NAMES = /* @__PURE__ */ new Set([
48873
+ "browser_click",
48874
+ "browser_type",
48875
+ "browser_fill_form",
48876
+ "browser_press_key",
48877
+ "browser_file_upload",
48878
+ "browser_select_option",
48879
+ "create_github_pull_request",
48880
+ "create_github_branch",
48881
+ "dispatch_github_workflow",
48882
+ "dispatch_task_deploy",
48883
+ "deliver_file",
48884
+ "record_produced_url"
48885
+ ]);
48886
+ var READ_TOOL_NAMES = /* @__PURE__ */ new Set([
48887
+ "browser_navigate",
48888
+ "browser_snapshot",
48889
+ "get_execution_run",
48890
+ "get_github_installation_repositories",
48891
+ "get_project",
48892
+ "get_project_scheduler_policy",
48893
+ "get_project_snapshot",
48894
+ "get_scheduler_pending_tasks",
48895
+ "get_task",
48896
+ "get_wiki_page",
48897
+ "list_artifact_files",
48898
+ "list_execution_artifacts",
48899
+ "list_execution_runs",
48900
+ "list_github_installations",
48901
+ "list_github_workflows",
48902
+ "list_inbox_items",
48903
+ "list_plans",
48904
+ "list_project_members",
48905
+ "list_projects",
48906
+ "list_task_dependencies",
48907
+ "list_task_events",
48908
+ "list_tasks",
48909
+ "list_taxonomy",
48910
+ "list_wiki_pages",
48911
+ "retrieve_context",
48912
+ "search_knowledge",
48913
+ "search_wiki_pages",
48914
+ "create_task",
48915
+ "update_task",
48916
+ "create_wiki_page",
48917
+ "update_wiki_page",
48918
+ "create_inbox_item",
48919
+ // Group 4 checkpoint recommendation (claude-opus-5): stop_execution_run (added in Group 3, task
48920
+ // 3.3) is unclassified here otherwise, so it fail-closes to outward -- but its effect (parking a
48921
+ // run's status) is exactly as in-product/already-audited as the 5 writes above, and nothing would
48922
+ // ever resolve it (a stop naturally has no reason to be followed by a create_task/update_task
48923
+ // call), making every turn that stops a run permanently, unfixably "unresolved".
48924
+ "stop_execution_run"
48925
+ ]);
48926
+ function normalizeToolName(toolName) {
48927
+ return toolName.replace(/^mcp__.+?__/, "");
48928
+ }
48929
+ var MRRLIN_MCP_NAMESPACE_PREFIXES = ["mcp__mrrlin__", "mcp__mrrlin-browser__"];
48930
+ function classifyToolEffect(rawToolName) {
48931
+ const toolName = normalizeToolName(rawToolName);
48932
+ if (READ_TOOL_NAMES.has(toolName)) return "read";
48933
+ if (OUTWARD_TOOL_NAMES.has(toolName)) return "outward";
48934
+ if (MRRLIN_MCP_NAMESPACE_PREFIXES.some((prefix) => rawToolName.startsWith(prefix))) return "outward";
48935
+ return "read";
48936
+ }
48937
+ var BROWSER_OUTWARD_TOOL_NAMES = /* @__PURE__ */ new Set([
48938
+ "browser_click",
48939
+ "browser_type",
48940
+ "browser_fill_form",
48941
+ "browser_press_key",
48942
+ "browser_file_upload",
48943
+ "browser_select_option"
48944
+ ]);
48945
+ var IN_PRODUCT_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["create_task", "update_task", "create_wiki_page", "update_wiki_page", "create_inbox_item"]);
48946
+ function resolveTurnRecordingObligationEnabled(env = process.env) {
48947
+ const raw = (env.MRRLIN_TURN_RECORDING_OBLIGATION ?? "").trim().toLowerCase();
48948
+ return !(raw === "0" || raw === "false");
48949
+ }
48950
+ function buildBrowserAttemptReceipt(toolName) {
48951
+ return {
48952
+ kind: "attempt",
48953
+ evidence: { type: "human_tap", value: toolName },
48954
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
48955
+ requiresCovered: []
48956
+ };
48957
+ }
48958
+ function extractSeqAndTurnId(payload) {
48959
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { seq: void 0, turnId: void 0 };
48960
+ const p = payload;
48961
+ return {
48962
+ seq: typeof p.seq === "number" ? p.seq : void 0,
48963
+ turnId: typeof p.turnId === "string" ? p.turnId : void 0
48964
+ };
48965
+ }
48622
48966
  function resolveBridgeLogEnabled(env = process.env) {
48623
48967
  const raw = (env.MRRLIN_DIRECTOR_BRIDGE_LOG ?? "").trim().toLowerCase();
48624
48968
  return !(raw === "0" || raw === "false");
@@ -48653,6 +48997,29 @@ function createBridgeLogger(stateDir, env = process.env) {
48653
48997
  if (!oldest.done) contextSlugBySession.delete(oldest.value);
48654
48998
  }
48655
48999
  };
49000
+ const turnRecordingObligationEnabled = resolveTurnRecordingObligationEnabled(env);
49001
+ const obligationBySession = /* @__PURE__ */ new Map();
49002
+ const getObligationState = (sessionId) => {
49003
+ let state = obligationBySession.get(sessionId);
49004
+ if (!state) {
49005
+ state = { pending: [], lastSeq: null, lastTurnId: null };
49006
+ } else {
49007
+ obligationBySession.delete(sessionId);
49008
+ }
49009
+ obligationBySession.set(sessionId, state);
49010
+ if (obligationBySession.size > MAX_TRACKED_SESSIONS) {
49011
+ const oldest = obligationBySession.keys().next();
49012
+ if (!oldest.done) obligationBySession.delete(oldest.value);
49013
+ }
49014
+ return state;
49015
+ };
49016
+ const checkSequenceGap = (state, turnId, seq) => {
49017
+ const isNewTurn = turnId === void 0 || turnId !== state.lastTurnId;
49018
+ const isGap = !isNewTurn && state.lastSeq !== null && seq !== state.lastSeq + 1;
49019
+ state.lastSeq = seq;
49020
+ state.lastTurnId = turnId ?? state.lastTurnId;
49021
+ return { isGap, isNewTurn };
49022
+ };
48656
49023
  const gainDay = (sessionId) => {
48657
49024
  const sessionsPath = (0, import_node_path16.join)(logsDir, ".sessions.json");
48658
49025
  let sessionsObj = {};
@@ -48699,6 +49066,48 @@ function createBridgeLogger(stateDir, env = process.env) {
48699
49066
  );
48700
49067
  }
48701
49068
  }
49069
+ let sequenceGap;
49070
+ let syntheticReceipt;
49071
+ let recordingObligationUnresolved;
49072
+ let recordingObligationMultiResolve;
49073
+ if (turnRecordingObligationEnabled && rec.sessionId !== null && dir === "out") {
49074
+ const state = getObligationState(rec.sessionId);
49075
+ const { seq, turnId } = extractSeqAndTurnId(rec.payload);
49076
+ if (seq !== void 0) {
49077
+ const { isGap, isNewTurn } = checkSequenceGap(state, turnId, seq);
49078
+ if (isGap) sequenceGap = true;
49079
+ if (isNewTurn && state.pending.length > 0) recordingObligationUnresolved = state.pending;
49080
+ if (isNewTurn) state.pending = [];
49081
+ }
49082
+ if (projection?.kind === "tool_use" && typeof projection.toolName === "string" && typeof projection.toolId === "string") {
49083
+ const toolName = normalizeToolName(projection.toolName);
49084
+ if (classifyToolEffect(projection.toolName) === "outward") {
49085
+ if (BROWSER_OUTWARD_TOOL_NAMES.has(toolName)) {
49086
+ syntheticReceipt = buildBrowserAttemptReceipt(toolName);
49087
+ } else {
49088
+ state.pending.push({
49089
+ turnSpanId: projection.turnId,
49090
+ toolName,
49091
+ toolId: projection.toolId,
49092
+ seq: projection.seq,
49093
+ armedAt: (/* @__PURE__ */ new Date()).toISOString()
49094
+ });
49095
+ }
49096
+ } else if (IN_PRODUCT_WRITE_TOOL_NAMES.has(toolName) && state.pending.length > 0) {
49097
+ if (state.pending.length > 1) {
49098
+ recordingObligationMultiResolve = {
49099
+ resolvedCount: state.pending.length,
49100
+ toolNames: state.pending.map((p) => p.toolName)
49101
+ };
49102
+ }
49103
+ state.pending = [];
49104
+ }
49105
+ }
49106
+ if (projection?.kind === "turn_lifecycle") {
49107
+ if (state.pending.length > 0) recordingObligationUnresolved = state.pending;
49108
+ state.pending = [];
49109
+ }
49110
+ }
48702
49111
  const { payload: redactedPayload, redactedCount } = redactMessage(rec.payload, { includePrompts });
48703
49112
  const line = JSON.stringify({
48704
49113
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48711,7 +49120,11 @@ function createBridgeLogger(stateDir, env = process.env) {
48711
49120
  redactedCount,
48712
49121
  projection,
48713
49122
  ...projectionFailed ? { projectionFailed: true } : {},
48714
- ...projectScopeMismatch ? { projectScopeMismatch } : {}
49123
+ ...projectScopeMismatch ? { projectScopeMismatch } : {},
49124
+ ...sequenceGap ? { sequenceGap } : {},
49125
+ ...syntheticReceipt ? { syntheticReceipt } : {},
49126
+ ...recordingObligationUnresolved ? { recordingObligationUnresolved } : {},
49127
+ ...recordingObligationMultiResolve ? { recordingObligationMultiResolve } : {}
48715
49128
  }) + "\n";
48716
49129
  (0, import_node_fs8.appendFileSync)(fileFor(), line);
48717
49130
  if (rec.sessionId !== null) gainDay(rec.sessionId);
@@ -62528,6 +62941,7 @@ var mcpToolNames = {
62528
62941
  listArtifactFiles: "list_artifact_files",
62529
62942
  reclaimExecutionRunLease: "reclaim_execution_run_lease",
62530
62943
  restartExecutionRun: "restart_execution_run",
62944
+ stopExecutionRun: "stop_execution_run",
62531
62945
  resolveHandoff: "resolve_handoff",
62532
62946
  recordProducedUrl: "record_produced_url",
62533
62947
  updateTaskState: "update_task_state",
@@ -63159,6 +63573,7 @@ var listArtifactFilesInputSchema = projectScopedSchema.merge(
63159
63573
  );
63160
63574
  var reclaimExecutionRunLeaseInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinExecutionRunRequeueSchema);
63161
63575
  var restartExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinRestartExecutionRunSchema);
63576
+ var stopExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema });
63162
63577
  var listInboxItemsInputSchema = projectScopedSchema.merge(mrrlinInboxItemListFiltersSchema);
63163
63578
  var createInboxItemInputSchema = projectScopedSchema.merge(mrrlinInboxItemCreateSchema);
63164
63579
  var decideInboxItemInputSchema = projectScopedSchema.extend({ itemId: mrrlinInboxItemIdSchema }).merge(mrrlinInboxItemDecideSchema);
@@ -63241,6 +63656,7 @@ var mcpToolInputSchemas = {
63241
63656
  [mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
63242
63657
  [mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
63243
63658
  [mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
63659
+ [mcpToolNames.stopExecutionRun]: stopExecutionRunInputSchema,
63244
63660
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
63245
63661
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
63246
63662
  [mcpToolNames.updateTaskState]: updateTaskStateInputSchema,
@@ -63310,6 +63726,7 @@ var mcpToolOperationIds = {
63310
63726
  [mcpToolNames.listArtifactFiles]: openApiOperationIds.listArtifactFiles,
63311
63727
  [mcpToolNames.reclaimExecutionRunLease]: openApiOperationIds.reclaimExecutionRunLease,
63312
63728
  [mcpToolNames.restartExecutionRun]: openApiOperationIds.restartExecutionRun,
63729
+ [mcpToolNames.stopExecutionRun]: openApiOperationIds.stopExecutionRun,
63313
63730
  [mcpToolNames.resolveHandoff]: openApiOperationIds.resolveHandoff
63314
63731
  };
63315
63732
  var mcpToolDescriptions = {
@@ -63380,6 +63797,7 @@ var mcpToolDescriptions = {
63380
63797
  [mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
63381
63798
  [mcpToolNames.reclaimExecutionRunLease]: "Clear a stale pending execution-run lease when the lease has aged past staleSeconds. Returns the reclaimed run on success, null when the lease is not stale yet, and surfaces a structured conflict when reclaim retries are exhausted.",
63382
63799
  [mcpToolNames.restartExecutionRun]: "Restart a parked execution run (handoff_requested or handoff_queued) back to pending so the queue can resume it.",
63800
+ [mcpToolNames.stopExecutionRun]: "Operator stop: park an in-flight execution run at handoff_requested with blockingReason operator_stopped. Parks rather than cancels, so restart_execution_run can resume it later. Refuses (409) a run already at a terminal status.",
63383
63801
  [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent.",
63384
63802
  [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry.",
63385
63803
  [mcpToolNames.retrieveContext]: "Rehydrate a mrrlin:// handle emitted by the compact-context layer into the full canonical Mrrlin record (task/wiki page/project snapshot/execution run/artifacts). Use whenever you need the full source \u2014 irreversible decisions (closing tasks, dispatching deploys, writing specs) MUST call this first; never act on a compact envelope alone. Returns `{ kind, data }`. Invalid or unsupported handle \u2192 structured `HANDLE_INVALID` error."
@@ -64279,6 +64697,13 @@ function createMrrlinTools(options) {
64279
64697
  "Unable to restart the execution run.",
64280
64698
  async (c, { projectSlug, runId, ...input }) => await c.restartExecutionRun(projectSlug, runId, input)
64281
64699
  ),
64700
+ [mcpToolNames.stopExecutionRun]: makeTool(
64701
+ client,
64702
+ mcpToolNames.stopExecutionRun,
64703
+ "EXECUTION_RUN_STOP_FAILED",
64704
+ "Unable to stop the execution run.",
64705
+ async (c, { projectSlug, runId }) => await c.stopExecutionRun(projectSlug, runId)
64706
+ ),
64282
64707
  [mcpToolNames.resolveHandoff]: makeTool(
64283
64708
  client,
64284
64709
  mcpToolNames.resolveHandoff,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.33",
3
+ "version": "0.4.37",
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",
24
+ "@mrrlin/agent-client": "0.0.0",
25
25
  "@mrrlin/director-e2e": "0.0.0",
26
+ "@mrrlin/client": "0.0.0",
26
27
  "@mrrlin/schemas": "0.0.0",
27
28
  "@mrrlin/tsconfig": "0.0.0",
28
- "@mrrlin/wiki": "0.0.0",
29
- "@mrrlin/agent-client": "0.0.0"
29
+ "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@iarna/toml": "^2.2.5",