@mrrlin-dev/mcp 0.4.32 → 0.4.36

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 +546 -8
  2. package/package.json +2 -2
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,16 @@ 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",
37606
+ // The agent provider refused work MID-RUN because its usage quota is exhausted — distinct from
37607
+ // `provider_not_ready`, which is the pre-flight readiness gate refusing to start. A quota is
37608
+ // transient and the run is retriable once the limit resets, so this code must never be conflated
37609
+ // with a task-content block; before it existed such a failure fell through to `unknown` and was
37610
+ // handed off to the operator as though the Director had judged the task.
37611
+ "provider_limit_exhausted",
37523
37612
  "unknown"
37524
37613
  ]);
37525
37614
  var mrrlinExecutionArtifactKindSchema = external_exports.enum([
@@ -37632,6 +37721,14 @@ var mrrlinTaskSchema = external_exports.object({
37632
37721
  // resolve it via taskRowToTask. Null reads as the pre-existing default provider and is never
37633
37722
  // backfilled -- it is NOT a mismatch against whatever provider is currently selected.
37634
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(),
37635
37732
  dueDate: external_exports.string().date().nullable(),
37636
37733
  followUpAt: external_exports.string().datetime().nullable(),
37637
37734
  id: mrrlinTaskIdSchema,
@@ -37968,6 +38065,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
37968
38065
  directorSessionId: external_exports.string().min(1).optional(),
37969
38066
  directorThreadId: external_exports.string().min(1).nullable().optional(),
37970
38067
  directorThreadProvider: external_exports.enum(["codex", "claude"]).nullable().optional(),
38068
+ declaredOutcome: mrrlinDeclaredOutcomeSchema.nullable().optional(),
37971
38069
  dueDate: external_exports.string().date().nullable().optional(),
37972
38070
  impact: mrrlinTaskImpactSchema.optional(),
37973
38071
  notes: external_exports.string().nullable().optional(),
@@ -38475,7 +38573,11 @@ var mrrlinSettleExecutionRunOutcomeSchema = external_exports.object({
38475
38573
  resultSummary: external_exports.string().trim().min(1).max(300),
38476
38574
  deliverablePageId: mrrlinWikiPageIdSchema,
38477
38575
  contentHash: external_exports.string().min(1),
38478
- 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()
38479
38581
  });
38480
38582
  var mrrlinExecutionArtifactSchema = external_exports.object({
38481
38583
  createdAt: external_exports.string().datetime(),
@@ -39041,6 +39143,74 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
39041
39143
  });
39042
39144
  var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
39043
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) });
39044
39214
 
39045
39215
  // ../../packages/agent-client/dist/providers/codex/render-context-bundle.js
39046
39216
  var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
@@ -39339,6 +39509,36 @@ function detectExhaustedProviderLimit(snapshot, nowMs) {
39339
39509
  const resetAt = candidates.length > 0 ? candidates.reduce((a, b) => a > b ? a : b) : null;
39340
39510
  return { exhausted: true, resetAt };
39341
39511
  }
39512
+ var PROVIDER_LIMIT_ERROR_PATTERNS = [
39513
+ // OpenAI / Codex, verbatim from the observed event.
39514
+ /you'?ve hit your usage limit/i,
39515
+ // Anthropic phrasing for the same condition.
39516
+ /you have exceeded your (?:rate limit|usage limit)/i,
39517
+ /credit balance is too low/i,
39518
+ // START-ANCHORED quota wording. Round 2 split on this: the unanchored version was removed in
39519
+ // round 1 for matching task prose ("the quota is being exceeded by another worker"), and a round-2
39520
+ // reviewer then objected that removing it also drops real provider messages that LEAD with the
39521
+ // condition ("Monthly quota exceeded."). Anchoring resolves both — a provider error states the
39522
+ // condition first, prose embeds it — so the pattern is kept, but only at the head of the message.
39523
+ // Note this is a second line of defence either way: a provider that emits a structured
39524
+ // `rate_limits` snapshot is caught by detectExhaustedProviderLimit regardless of wording.
39525
+ // NOTE an unanchored `/usage limit reached/i` used to sit above, and it was the one real leak a
39526
+ // round-4 reviewer's finding pointed at (though not the example they gave): it matched
39527
+ // "[ERROR] memory usage limit reached", where the limit belongs to memory, not the provider. The
39528
+ // anchored pattern below already covers "Usage limit reached …", so the unanchored one was pure
39529
+ // false-positive surface and is gone.
39530
+ //
39531
+ // The optional prefix covers the log decorations providers actually ship with — "Error: ",
39532
+ // "[ERROR] ", "(fatal) " — without letting the condition sit anywhere in a sentence. The noun set
39533
+ // covers "rate limit"/"api limit" forms that carry no "quota" or "credits" at all.
39534
+ /^\s*(?:[[(]?\s*(?:error|fatal|warn(?:ing)?)\s*[\])]?\s*[:-]?\s*)?(?:monthly |daily |monthly\/daily )?(?:quota|usage limit|rate limit|api limit|api credits?|credits?) (?:has been |have been |is |are )?(?:exceeded|exhausted|used up|reached)\b/i
39535
+ ];
39536
+ function detectProviderLimitFromErrorText(text) {
39537
+ if (typeof text !== "string" || text.trim().length === 0)
39538
+ return null;
39539
+ const matched = PROVIDER_LIMIT_ERROR_PATTERNS.some((pattern) => pattern.test(text));
39540
+ return matched ? { exhausted: true, resetAt: null } : null;
39541
+ }
39342
39542
 
39343
39543
  // ../../packages/agent-client/dist/providers/codex/adapter.js
39344
39544
  var CodexProviderAdapter = class {
@@ -40855,6 +41055,18 @@ function createMrrlinClient(config2) {
40855
41055
  throw error51;
40856
41056
  }
40857
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
+ },
40858
41070
  async listInboxItems(projectSlug, filters = {}) {
40859
41071
  const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
40860
41072
  groupId: filters.groupId,
@@ -40924,6 +41136,12 @@ function createMrrlinClient(config2) {
40924
41136
  throw error51;
40925
41137
  }
40926
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
+ },
40927
41145
  async requeueExecutionRun(projectSlug, runId, staleSeconds) {
40928
41146
  try {
40929
41147
  const body = await request(`${projectPath(projectSlug)}/execution-runs/${encodeURIComponent(runId)}/requeue`, {
@@ -41853,7 +42071,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
41853
42071
  }
41854
42072
 
41855
42073
  // src/_generated/version.ts
41856
- var PKG_VERSION = "0.4.32";
42074
+ var PKG_VERSION = "0.4.36";
41857
42075
 
41858
42076
  // src/api-base-url-migrate.ts
41859
42077
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -45657,6 +45875,19 @@ async function findInboxQuestion(client, projectSlug, itemId) {
45657
45875
  const items = await client.listInboxItems(projectSlug, { kind: "question" });
45658
45876
  return items.find((item) => item.id === itemId) ?? null;
45659
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
+ }
45660
45891
  async function autonomyPaused(client) {
45661
45892
  try {
45662
45893
  const settings = await client.getOperatorSettings();
@@ -46515,10 +46746,31 @@ function executionTurnBudgetMessage(budgetMs) {
46515
46746
  function isExecutionTurnTimeoutReason(reason) {
46516
46747
  return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX) || reason.startsWith(EXECUTION_TURN_STALL_PREFIX);
46517
46748
  }
46749
+ var PROVIDER_LIMIT_RETRY_CURSOR_PREFIX = "provider_limit_retry:";
46750
+ var PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS = 1;
46751
+ var PROVIDER_LIMIT_RETRY_CODE = "provider_limit_retry";
46752
+ var PROVIDER_LIMIT_PAUSE_REASON = "Agent provider usage limit exhausted during an autonomous execution run.";
46753
+ var PROVIDER_LIMIT_HANDOFF_REASON = "The agent provider's usage limit stayed exhausted across every automatic retry, so this run could not proceed. This is a provider quota problem, not a problem with the task.";
46754
+ function providerLimitRetryCursor(attempt) {
46755
+ return `${PROVIDER_LIMIT_RETRY_CURSOR_PREFIX}${attempt}`;
46756
+ }
46757
+ function providerLimitRetryAttempt(checkpointCursor) {
46758
+ if (!checkpointCursor || !checkpointCursor.startsWith(PROVIDER_LIMIT_RETRY_CURSOR_PREFIX)) return 0;
46759
+ const parsed = Number.parseInt(checkpointCursor.slice(PROVIDER_LIMIT_RETRY_CURSOR_PREFIX.length), 10);
46760
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
46761
+ }
46518
46762
  function reportAutopilotLimitPause(client, reason, resetAt) {
46519
46763
  void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
46520
46764
  });
46521
46765
  }
46766
+ async function applyAutopilotLimitPause(client, reason, resetAt) {
46767
+ try {
46768
+ await client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt });
46769
+ return true;
46770
+ } catch {
46771
+ return false;
46772
+ }
46773
+ }
46522
46774
  function isDeliverableGateRetryableReason(reason) {
46523
46775
  return DELIVERABLE_GATE_RETRYABLE_REASON_PREFIXES.some((prefix) => reason.startsWith(prefix));
46524
46776
  }
@@ -46574,6 +46826,33 @@ var BASE_INSTRUCTIONS_PREFIX = [
46574
46826
  "concrete progress via MCP tools rather than only narrating it.",
46575
46827
  "",
46576
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.",
46577
46856
  "- Record evidence with `append_execution_artifact` against the run id shown in the",
46578
46857
  ' "## Open run" section of the context bundle.',
46579
46858
  "- Artifact kinds that count as completion evidence are exactly: checkpoint, test_result, diff_summary, self_review, handoff_judgement.",
@@ -47985,13 +48264,20 @@ async function driveNonCodeRun(deps, projectSlug, runId, leaseId, task, domain2)
47985
48264
  return;
47986
48265
  }
47987
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;
47988
48273
  try {
47989
48274
  const settled = await deps.client.settleExecutionRunOutcome(projectSlug, runId, {
47990
48275
  leaseId,
47991
48276
  outcome: parsedResult.outcome,
47992
48277
  resultSummary: parsedResult.resultSummary,
47993
48278
  deliverablePageId: pageId,
47994
- contentHash
48279
+ contentHash,
48280
+ ...receipt ? { receipt } : {}
47995
48281
  });
47996
48282
  if (!settled && await runHasLease(deps, projectSlug, runId, leaseId)) {
47997
48283
  await failRun(
@@ -48027,6 +48313,62 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
48027
48313
  return;
48028
48314
  }
48029
48315
  if (currentRun.status !== "running" || currentRun.leaseId !== leaseId) return;
48316
+ let handoffReason = reason;
48317
+ let handoffReasonCode = reasonCode;
48318
+ const providerLimit = detectProviderLimitFromErrorText(reason);
48319
+ if (providerLimit) {
48320
+ const autopilotPaused = await applyAutopilotLimitPause(
48321
+ deps.client,
48322
+ PROVIDER_LIMIT_PAUSE_REASON,
48323
+ providerLimit.resetAt
48324
+ );
48325
+ const attempt = providerLimitRetryAttempt(currentRun.checkpointCursor) + 1;
48326
+ if (attempt <= PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS) {
48327
+ try {
48328
+ await deps.client.updateExecutionRun(projectSlug, runId, {
48329
+ status: "pending",
48330
+ leaseId: null,
48331
+ checkpointCursor: providerLimitRetryCursor(attempt)
48332
+ });
48333
+ await deps.client.appendExecutionArtifact(projectSlug, runId, {
48334
+ id: newArtifactId(),
48335
+ kind: "error",
48336
+ payload: {
48337
+ code: PROVIDER_LIMIT_RETRY_CODE,
48338
+ source: "execution-consumer",
48339
+ attempt,
48340
+ maxAttempts: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48341
+ // The provider's raw sentence belongs HERE — diagnostic detail on an error artifact —
48342
+ // and never in a handoff `reason`, which the DB trigger caches as the task's judgement.
48343
+ providerMessage: reason,
48344
+ // Whether the brake actually engaged. If it did not, the requeued run is only held
48345
+ // back by its own attempt budget, so this is the difference between "waiting for the
48346
+ // quota" and "about to fail once more"; it must be visible, not swallowed.
48347
+ autopilotPaused,
48348
+ summary: autopilotPaused ? `Agent provider usage limit exhausted; autopilot paused and the run requeued for retry ${attempt}/${PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS}.` : `Agent provider usage limit exhausted; the run was requeued for retry ${attempt}/${PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS}, but pausing autopilot FAILED, so nothing is holding it back.`
48349
+ }
48350
+ }).catch(() => {
48351
+ });
48352
+ return;
48353
+ } catch {
48354
+ }
48355
+ }
48356
+ handoffReason = PROVIDER_LIMIT_HANDOFF_REASON;
48357
+ handoffReasonCode = "provider_limit_exhausted";
48358
+ await deps.client.appendExecutionArtifact(projectSlug, runId, {
48359
+ id: newArtifactId(),
48360
+ kind: "error",
48361
+ payload: {
48362
+ code: PROVIDER_LIMIT_RETRY_CODE,
48363
+ source: "execution-consumer",
48364
+ attempt: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48365
+ maxAttempts: PROVIDER_LIMIT_RETRY_MAX_ATTEMPTS,
48366
+ providerMessage: reason,
48367
+ summary: "Agent provider usage limit still exhausted after every retry; handing off to the operator."
48368
+ }
48369
+ }).catch(() => {
48370
+ });
48371
+ }
48030
48372
  if (isExecutionTurnTimeoutReason(reason) && currentRun.checkpointCursor !== EXECUTION_TURN_TIMEOUT_RETRY_CURSOR) {
48031
48373
  try {
48032
48374
  await deps.client.updateExecutionRun(projectSlug, runId, {
@@ -48079,11 +48421,11 @@ async function failRun(deps, projectSlug, task, runId, leaseId, reason, reasonCo
48079
48421
  }
48080
48422
  try {
48081
48423
  await deps.client.createExecutionRunHandoff(projectSlug, runId, {
48082
- reason,
48083
- ...reasonCode ? { reasonCode } : {}
48424
+ reason: handoffReason,
48425
+ ...handoffReasonCode ? { reasonCode: handoffReasonCode } : {}
48084
48426
  });
48085
48427
  } catch {
48086
- await notifyOperatorHandoff(deps, projectSlug, task, runId, reason);
48428
+ await notifyOperatorHandoff(deps, projectSlug, task, runId, handoffReason);
48087
48429
  }
48088
48430
  }
48089
48431
  async function reconcileTaskAwaitingMerge(deps, projectSlug, task, runId, reason) {
@@ -48218,6 +48560,12 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48218
48560
  await drive(slug, run3.id);
48219
48561
  }
48220
48562
  if (deps.deployGate) {
48563
+ let autonomyPaused2 = false;
48564
+ try {
48565
+ autonomyPaused2 = (await deps.client.getOperatorSettings()).autonomyPaused === true;
48566
+ } catch {
48567
+ autonomyPaused2 = false;
48568
+ }
48221
48569
  for (const status of ["dev_deploy", "prod_deploy"]) {
48222
48570
  let inFlight = [];
48223
48571
  try {
@@ -48229,6 +48577,10 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48229
48577
  if (!isDeployGateRunStatus(run3.status)) continue;
48230
48578
  try {
48231
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
+ }
48232
48584
  const gateDeps = {
48233
48585
  client: deps.client,
48234
48586
  runVerificationTurn: deps.deployGate.runVerificationTurn,
@@ -48272,6 +48624,17 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
48272
48624
  }
48273
48625
  }
48274
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
+ }
48275
48638
  }
48276
48639
  }
48277
48640
  }
@@ -48506,6 +48869,100 @@ function redactMessage(payload, opts) {
48506
48869
  }
48507
48870
  return { payload: redactValue(payload, 0, counter), redactedCount: counter.count };
48508
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
+ }
48509
48966
  function resolveBridgeLogEnabled(env = process.env) {
48510
48967
  const raw = (env.MRRLIN_DIRECTOR_BRIDGE_LOG ?? "").trim().toLowerCase();
48511
48968
  return !(raw === "0" || raw === "false");
@@ -48540,6 +48997,29 @@ function createBridgeLogger(stateDir, env = process.env) {
48540
48997
  if (!oldest.done) contextSlugBySession.delete(oldest.value);
48541
48998
  }
48542
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
+ };
48543
49023
  const gainDay = (sessionId) => {
48544
49024
  const sessionsPath = (0, import_node_path16.join)(logsDir, ".sessions.json");
48545
49025
  let sessionsObj = {};
@@ -48586,6 +49066,48 @@ function createBridgeLogger(stateDir, env = process.env) {
48586
49066
  );
48587
49067
  }
48588
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
+ }
48589
49111
  const { payload: redactedPayload, redactedCount } = redactMessage(rec.payload, { includePrompts });
48590
49112
  const line = JSON.stringify({
48591
49113
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48598,7 +49120,11 @@ function createBridgeLogger(stateDir, env = process.env) {
48598
49120
  redactedCount,
48599
49121
  projection,
48600
49122
  ...projectionFailed ? { projectionFailed: true } : {},
48601
- ...projectScopeMismatch ? { projectScopeMismatch } : {}
49123
+ ...projectScopeMismatch ? { projectScopeMismatch } : {},
49124
+ ...sequenceGap ? { sequenceGap } : {},
49125
+ ...syntheticReceipt ? { syntheticReceipt } : {},
49126
+ ...recordingObligationUnresolved ? { recordingObligationUnresolved } : {},
49127
+ ...recordingObligationMultiResolve ? { recordingObligationMultiResolve } : {}
48602
49128
  }) + "\n";
48603
49129
  (0, import_node_fs8.appendFileSync)(fileFor(), line);
48604
49130
  if (rec.sessionId !== null) gainDay(rec.sessionId);
@@ -62415,6 +62941,7 @@ var mcpToolNames = {
62415
62941
  listArtifactFiles: "list_artifact_files",
62416
62942
  reclaimExecutionRunLease: "reclaim_execution_run_lease",
62417
62943
  restartExecutionRun: "restart_execution_run",
62944
+ stopExecutionRun: "stop_execution_run",
62418
62945
  resolveHandoff: "resolve_handoff",
62419
62946
  recordProducedUrl: "record_produced_url",
62420
62947
  updateTaskState: "update_task_state",
@@ -63046,6 +63573,7 @@ var listArtifactFilesInputSchema = projectScopedSchema.merge(
63046
63573
  );
63047
63574
  var reclaimExecutionRunLeaseInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinExecutionRunRequeueSchema);
63048
63575
  var restartExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinRestartExecutionRunSchema);
63576
+ var stopExecutionRunInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema });
63049
63577
  var listInboxItemsInputSchema = projectScopedSchema.merge(mrrlinInboxItemListFiltersSchema);
63050
63578
  var createInboxItemInputSchema = projectScopedSchema.merge(mrrlinInboxItemCreateSchema);
63051
63579
  var decideInboxItemInputSchema = projectScopedSchema.extend({ itemId: mrrlinInboxItemIdSchema }).merge(mrrlinInboxItemDecideSchema);
@@ -63128,6 +63656,7 @@ var mcpToolInputSchemas = {
63128
63656
  [mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
63129
63657
  [mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
63130
63658
  [mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
63659
+ [mcpToolNames.stopExecutionRun]: stopExecutionRunInputSchema,
63131
63660
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
63132
63661
  [mcpToolNames.recordProducedUrl]: recordProducedUrlInputSchema,
63133
63662
  [mcpToolNames.updateTaskState]: updateTaskStateInputSchema,
@@ -63197,6 +63726,7 @@ var mcpToolOperationIds = {
63197
63726
  [mcpToolNames.listArtifactFiles]: openApiOperationIds.listArtifactFiles,
63198
63727
  [mcpToolNames.reclaimExecutionRunLease]: openApiOperationIds.reclaimExecutionRunLease,
63199
63728
  [mcpToolNames.restartExecutionRun]: openApiOperationIds.restartExecutionRun,
63729
+ [mcpToolNames.stopExecutionRun]: openApiOperationIds.stopExecutionRun,
63200
63730
  [mcpToolNames.resolveHandoff]: openApiOperationIds.resolveHandoff
63201
63731
  };
63202
63732
  var mcpToolDescriptions = {
@@ -63267,6 +63797,7 @@ var mcpToolDescriptions = {
63267
63797
  [mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
63268
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.",
63269
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.",
63270
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.",
63271
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.",
63272
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."
@@ -64166,6 +64697,13 @@ function createMrrlinTools(options) {
64166
64697
  "Unable to restart the execution run.",
64167
64698
  async (c, { projectSlug, runId, ...input }) => await c.restartExecutionRun(projectSlug, runId, input)
64168
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
+ ),
64169
64707
  [mcpToolNames.resolveHandoff]: makeTool(
64170
64708
  client,
64171
64709
  mcpToolNames.resolveHandoff,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.32",
3
+ "version": "0.4.36",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -22,10 +22,10 @@
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
24
  "@mrrlin/client": "0.0.0",
25
+ "@mrrlin/agent-client": "0.0.0",
25
26
  "@mrrlin/director-e2e": "0.0.0",
26
27
  "@mrrlin/schemas": "0.0.0",
27
28
  "@mrrlin/tsconfig": "0.0.0",
28
- "@mrrlin/agent-client": "0.0.0",
29
29
  "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {