@mrrlin-dev/mcp 0.4.42 → 0.4.43

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 +414 -18
  2. package/package.json +4 -4
package/dist/bin.cjs CHANGED
@@ -36816,6 +36816,17 @@ var ARTIFACT_CHECKS = {
36816
36816
  structured_data: null,
36817
36817
  message: { verifier: "status" }
36818
36818
  };
36819
+ var ARTIFACT_DESTINATIONS = {
36820
+ document: "wiki",
36821
+ page: "git",
36822
+ file_set: null,
36823
+ code_change: "git",
36824
+ structured_data: null,
36825
+ message: null
36826
+ };
36827
+ function resolveKindCapability(kind) {
36828
+ return ARTIFACT_DESTINATIONS[kind];
36829
+ }
36819
36830
 
36820
36831
  // ../../packages/schemas/dist/operationIds.js
36821
36832
  var openApiOperationIds = {
@@ -36957,7 +36968,11 @@ var openApiOperationIds = {
36957
36968
  upsertCapabilityRoute: "upsertCapabilityRoute",
36958
36969
  resolveCapabilityRoute: "resolveCapabilityRoute",
36959
36970
  overrideCapabilityToolClass: "overrideCapabilityToolClass",
36960
- listCapabilityUses: "listCapabilityUses"
36971
+ listCapabilityUses: "listCapabilityUses",
36972
+ confirmFirstWrite: "confirmFirstWrite",
36973
+ resolveArtifactKindRoute: "resolveArtifactKindRoute",
36974
+ resolveDeliveryForArtifactKind: "resolveDeliveryForArtifactKind",
36975
+ recordCapabilityUse: "recordCapabilityUse"
36961
36976
  };
36962
36977
 
36963
36978
  // ../../packages/schemas/dist/session.js
@@ -37533,6 +37548,18 @@ var mrrlinDeclaredOutcomeSchema = mrrlinDeclaredOutcomeShapeSchema.superRefine((
37533
37548
  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.`
37534
37549
  });
37535
37550
  }
37551
+ const distinctCapabilities = new Set(value.requires.map((kind) => resolveKindCapability(kind)).filter((capability) => capability !== null));
37552
+ if (distinctCapabilities.size > 1) {
37553
+ const spread = value.requires.map((kind) => {
37554
+ const capability = resolveKindCapability(kind);
37555
+ return capability === null ? `${kind} has no destination` : `${kind} routes to ${capability}`;
37556
+ }).join(", ");
37557
+ ctx.addIssue({
37558
+ code: external_exports.ZodIssueCode.custom,
37559
+ path: ["requires"],
37560
+ message: `requires spans more than one capability, so no single delivery can satisfy it (${spread}). Split into linked tasks, one per destination.`
37561
+ });
37562
+ }
37536
37563
  const hasVerifiableKind = value.requires.some((kind) => ARTIFACT_CHECKS[kind] !== null);
37537
37564
  const hasUnverifiableKind = value.requires.some((kind) => ARTIFACT_CHECKS[kind] === null);
37538
37565
  if (value.verification === "human_tap" && hasVerifiableKind) {
@@ -37660,6 +37687,13 @@ var mrrlinRunBlockingReasonCodeSchema = external_exports.enum([
37660
37687
  // with a task-content block; before it existed such a failure fell through to `unknown` and was
37661
37688
  // handed off to the operator as though the Director had judged the task.
37662
37689
  "provider_limit_exhausted",
37690
+ // Крок 3, task 8.1: the dispatch pre-flight's own refusal — the task's declared outcome requires
37691
+ // an artifact kind with no capability route at all (client-side, resolveKindCapability) or no
37692
+ // configured route in this project (server-side, resolveArtifactKindRoute). Distinct from
37693
+ // `task_blocked` (which is about blocker TASKS, not this run's own delivery destination): a task
37694
+ // whose blockers clear can start; a task whose required kind cannot route needs an operator to
37695
+ // configure the missing route before any retry would succeed.
37696
+ "capability_not_routed",
37663
37697
  "unknown"
37664
37698
  ]);
37665
37699
  var mrrlinExecutionArtifactKindSchema = external_exports.enum([
@@ -37740,6 +37774,13 @@ var mrrlinStopReasonSchema = external_exports.enum([
37740
37774
  "consensus_disabled_spec_not_ready",
37741
37775
  "spec_approval_pending",
37742
37776
  "awaiting_handoff",
37777
+ // Крок 3 (delivery-as-capability-call), task 3.1: the parked reason for an open capability
37778
+ // question (origin: "capability"), the same shape as awaiting_clarification's own open gate
37779
+ // question (origin: "gate") one line above -- caught by the whole-change gate (task 9.3), not
37780
+ // task 3.1 itself: this schema and mrrlinSchedulerParkedReasonSchema are siblings apps/web's
37781
+ // autopilotQueue.ts assumes stay in lockstep (every parked reason not explicitly remapped is
37782
+ // used as a stop reason directly), and no per-package typecheck in this change ever ran apps/web.
37783
+ "awaiting_capability_decision",
37743
37784
  "assigned_to_other",
37744
37785
  "backlog",
37745
37786
  "attempts_exhausted",
@@ -38923,7 +38964,11 @@ var mrrlinSchedulerParkedReasonSchema = external_exports.enum([
38923
38964
  "awaiting_handoff",
38924
38965
  "awaiting_clarification",
38925
38966
  "spec_awaiting_approval",
38926
- "blocked_by_active"
38967
+ "blocked_by_active",
38968
+ // Derived, not stored (design.md, "Parking is derived"): an open `question` inbox item with
38969
+ // `origin === "capability"` yields this reason, exactly as an `origin === "gate"` one already
38970
+ // yields `awaiting_clarification`. No column, and nobody but the derivation may set it.
38971
+ "awaiting_capability_decision"
38927
38972
  ]);
38928
38973
  var mrrlinSchedulerParkedTaskSchema = external_exports.object({
38929
38974
  task: mrrlinTaskSchema,
@@ -39262,6 +39307,16 @@ var capabilityRouteResponseSchema = external_exports.object({ data: CapabilityRo
39262
39307
  var capabilityRouteListResponseSchema = external_exports.object({ data: external_exports.array(CapabilityRouteSchema) });
39263
39308
  var capabilityToolClassResponseSchema = external_exports.object({ data: CapabilityToolClassSchema });
39264
39309
  var capabilityUseListResponseSchema = external_exports.object({ data: external_exports.array(CapabilityUseSchema) });
39310
+ var capabilityUseResponseSchema = external_exports.object({ data: CapabilityUseSchema.nullable() });
39311
+ var capabilityUseCreateSchema = external_exports.object({
39312
+ serverId: capabilityServerIdSchema,
39313
+ toolName: capabilityToolNameSchema,
39314
+ taskId: mrrlinTaskIdSchema.nullable(),
39315
+ outcome: capabilityUseOutcomeSchema
39316
+ });
39317
+ var deliveryResolutionResponseSchema = external_exports.object({
39318
+ data: external_exports.object({ route: CapabilityRouteSchema, verdict: CapabilityGateVerdictSchema })
39319
+ });
39265
39320
 
39266
39321
  // ../../packages/agent-client/dist/providers/codex/render-context-bundle.js
39267
39322
  var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
@@ -40551,6 +40606,12 @@ function createAgentTokenProvider(opts) {
40551
40606
  }
40552
40607
 
40553
40608
  // ../../packages/client/dist/index.js
40609
+ var CAPABILITY_ROUTE_RESOLUTION_REASON_BY_CODE = {
40610
+ NO_ROUTE: "no_route",
40611
+ NO_GRANT: "no_grant",
40612
+ PROJECT_MISMATCH: "project_mismatch",
40613
+ AMBIGUOUS_ROUTE: "ambiguous_route"
40614
+ };
40554
40615
  function appendQuery(url2, params) {
40555
40616
  const search = new URLSearchParams();
40556
40617
  for (const [key, value] of Object.entries(params)) {
@@ -41180,6 +41241,52 @@ function createMrrlinClient(config2) {
41180
41241
  const body = await request(`${projectPath(projectSlug)}/capability-grants/${encodeURIComponent(serverId)}`, { method: "DELETE" });
41181
41242
  return capabilityGrantResponseSchema.parse(body).data;
41182
41243
  },
41244
+ async confirmFirstWrite(projectSlug, serverId) {
41245
+ try {
41246
+ const body = await request(`${projectPath(projectSlug)}/capability-grants/${encodeURIComponent(serverId)}/first-write-confirmation`, { method: "POST" });
41247
+ return capabilityGrantResponseSchema.parse(body).data;
41248
+ } catch (error51) {
41249
+ if (error51 instanceof MrrlinApiError && error51.code === "NO_GRANT")
41250
+ return null;
41251
+ throw error51;
41252
+ }
41253
+ },
41254
+ async resolveArtifactKindRoute(projectSlug, artifactKind, taskProjectSlug) {
41255
+ try {
41256
+ const body = await request(`${projectPath(projectSlug)}/artifact-kinds/${encodeURIComponent(artifactKind)}/resolution?taskProjectSlug=${encodeURIComponent(taskProjectSlug)}`, { method: "POST" });
41257
+ return { ok: true, route: capabilityRouteResponseSchema.parse(body).data };
41258
+ } catch (error51) {
41259
+ if (error51 instanceof MrrlinApiError) {
41260
+ const reason = CAPABILITY_ROUTE_RESOLUTION_REASON_BY_CODE[error51.code];
41261
+ if (reason)
41262
+ return { ok: false, reason, message: error51.message };
41263
+ }
41264
+ throw error51;
41265
+ }
41266
+ },
41267
+ async resolveDeliveryForArtifactKind(projectSlug, artifactKind, taskProjectSlug, taskId) {
41268
+ try {
41269
+ const url2 = appendQuery(`${projectPath(projectSlug)}/artifact-kinds/${encodeURIComponent(artifactKind)}/delivery-resolution`, { taskProjectSlug, taskId });
41270
+ const body = await request(url2, { method: "POST" });
41271
+ const { route, verdict } = deliveryResolutionResponseSchema.parse(body).data;
41272
+ return { ok: true, route, verdict };
41273
+ } catch (error51) {
41274
+ if (error51 instanceof MrrlinApiError) {
41275
+ const reason = CAPABILITY_ROUTE_RESOLUTION_REASON_BY_CODE[error51.code];
41276
+ if (reason)
41277
+ return { ok: false, reason, message: error51.message };
41278
+ }
41279
+ throw error51;
41280
+ }
41281
+ },
41282
+ async recordCapabilityUse(projectSlug, input) {
41283
+ const body = await request(`${projectPath(projectSlug)}/capability-uses`, {
41284
+ body: JSON.stringify(input),
41285
+ headers: { "content-type": "application/json" },
41286
+ method: "POST"
41287
+ });
41288
+ return capabilityUseResponseSchema.parse(body).data;
41289
+ },
41183
41290
  async listInboxItems(projectSlug, filters = {}) {
41184
41291
  const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
41185
41292
  groupId: filters.groupId,
@@ -42188,7 +42295,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
42188
42295
  }
42189
42296
 
42190
42297
  // src/_generated/version.ts
42191
- var PKG_VERSION = "0.4.42";
42298
+ var PKG_VERSION = "0.4.43";
42192
42299
 
42193
42300
  // src/api-base-url-migrate.ts
42194
42301
  var import_node_fs3 = __toESM(require("node:fs"), 1);
@@ -47598,6 +47705,35 @@ async function driveRun(deps, projectSlug, runId) {
47598
47705
  return legacy != null ? Math.min(legacy, remaining) : remaining;
47599
47706
  };
47600
47707
  const task = await deps.client.getTask(projectSlug, claimed.taskId);
47708
+ if (task.declaredOutcome != null) {
47709
+ for (const kind of task.declaredOutcome.requires) {
47710
+ if (resolveKindCapability(kind) === null) {
47711
+ await failRun(
47712
+ deps,
47713
+ projectSlug,
47714
+ task,
47715
+ runId,
47716
+ leaseId,
47717
+ `capability_not_routed: run ${runId} targets ${task.id}, which requires artifact kind "${kind}" -- that kind has no capability at all`,
47718
+ "capability_not_routed"
47719
+ );
47720
+ return;
47721
+ }
47722
+ const routeResolution = await deps.client.resolveArtifactKindRoute(projectSlug, kind, projectSlug);
47723
+ if (!routeResolution.ok) {
47724
+ await failRun(
47725
+ deps,
47726
+ projectSlug,
47727
+ task,
47728
+ runId,
47729
+ leaseId,
47730
+ `capability_not_routed: run ${runId} targets ${task.id}, which requires artifact kind "${kind}" -- no route (${routeResolution.reason})`,
47731
+ "capability_not_routed"
47732
+ );
47733
+ return;
47734
+ }
47735
+ }
47736
+ }
47601
47737
  const blockerTasks = task.blockedBy.length ? await Promise.all(task.blockedBy.map((blockerTaskId) => deps.client.getTask(projectSlug, blockerTaskId))) : [];
47602
47738
  const missingBlockers = task.blockedBy.filter((_, index) => !blockerTasks[index]);
47603
47739
  if (missingBlockers.length > 0) {
@@ -57305,7 +57441,7 @@ async function startDirectorBridge() {
57305
57441
  const relayUrl = process.env.MRRLIN_RELAY_URL?.trim();
57306
57442
  const relayConfigured = Boolean(agentSecret && relayUrl);
57307
57443
  const buildReseedPrefix = async (args) => {
57308
- const refuse = (reason) => {
57444
+ const refuse2 = (reason) => {
57309
57445
  bridgeLogger.logOut({
57310
57446
  spanId: "reseed",
57311
57447
  sessionId: args.directorSessionId,
@@ -57314,8 +57450,8 @@ async function startDirectorBridge() {
57314
57450
  });
57315
57451
  return NO_HISTORY_BLOCK;
57316
57452
  };
57317
- if (!resolveBridgeLogEnabled()) return refuse("log-disabled");
57318
- if (!resolvePromptsEnabled()) return refuse("prompts-not-logged");
57453
+ if (!resolveBridgeLogEnabled()) return refuse2("log-disabled");
57454
+ if (!resolvePromptsEnabled()) return refuse2("prompts-not-logged");
57319
57455
  const retentionWindowMs = readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30) * 864e5;
57320
57456
  const reseedWindowMs = Math.min(config2.idleThreadTtlMs, retentionWindowMs);
57321
57457
  if (reseedWindowMs < config2.idleThreadTtlMs) {
@@ -57334,13 +57470,13 @@ async function startDirectorBridge() {
57334
57470
  const sessionIds = [args.directorSessionId, ...args.priorSessionIds];
57335
57471
  const { lines: lines2, missingFiles } = await readSessionRows(import_node_path27.default.join(config2.stateDir, "logs"), files, sessionIds);
57336
57472
  const { items, skipped: skipped2 } = extractItems(lines2, sessionIds);
57337
- if (items.length === 0) return refuse("no-rows");
57473
+ if (items.length === 0) return refuse2("no-rows");
57338
57474
  const budget = Math.max(0, Math.min(config2.reseedMaxTokens, config2.turnMaxTokens - args.otherBytes));
57339
57475
  const block = renderReseedBlock(items, {
57340
57476
  maxTokens: budget,
57341
57477
  minConversationalItems: config2.reseedMinItems
57342
57478
  });
57343
- if (!block) return refuse("below-minimum-after-truncation");
57479
+ if (!block) return refuse2("below-minimum-after-truncation");
57344
57480
  bridgeLogger.logOut({
57345
57481
  spanId: "reseed",
57346
57482
  sessionId: args.directorSessionId,
@@ -61997,6 +62133,245 @@ async function resolveActivePanel(input, deps) {
61997
62133
  }
61998
62134
  }
61999
62135
 
62136
+ // src/delivery/wiki.ts
62137
+ async function deliverToWiki(client, projectSlug, payload) {
62138
+ if (payload.operation === "create") return client.createWikiPage(projectSlug, payload.page);
62139
+ return client.updateWikiPage(projectSlug, payload.pageId, payload.patch);
62140
+ }
62141
+
62142
+ // src/delivery/git.ts
62143
+ async function deliverToGit(client, projectSlug, payload) {
62144
+ if (payload.operation === "branch") {
62145
+ return client.createGithubBranch(projectSlug, { baseBranch: payload.baseBranch, branchName: payload.branchName });
62146
+ }
62147
+ return client.createGithubPullRequest(projectSlug, {
62148
+ baseBranch: payload.baseBranch,
62149
+ body: payload.body,
62150
+ draft: payload.draft,
62151
+ headBranch: payload.headBranch,
62152
+ title: payload.title
62153
+ });
62154
+ }
62155
+
62156
+ // src/delivery/index.ts
62157
+ async function deliverThroughCapability(client, input) {
62158
+ if (resolveKindCapability(input.artifactKind) === null) {
62159
+ return { ok: false, reason: "blocked_kind" };
62160
+ }
62161
+ let resolution;
62162
+ try {
62163
+ resolution = await client.resolveDeliveryForArtifactKind(
62164
+ input.projectSlug,
62165
+ input.artifactKind,
62166
+ input.taskProjectSlug,
62167
+ input.taskId ?? void 0
62168
+ );
62169
+ } catch {
62170
+ return { ok: false, reason: "transport_error" };
62171
+ }
62172
+ if (!resolution.ok) {
62173
+ return { ok: false, reason: resolution.reason };
62174
+ }
62175
+ const { route, verdict } = resolution;
62176
+ if (verdict === "silent") {
62177
+ return proceedToDestination(client, route, input);
62178
+ }
62179
+ if (verdict === "ask_once") {
62180
+ return handleAskOnce(client, route, input);
62181
+ }
62182
+ if (verdict === "window") {
62183
+ return handleWindow(client, route, input);
62184
+ }
62185
+ if (verdict === "approval") {
62186
+ return handleApproval(client, route, input);
62187
+ }
62188
+ await recordUse(client, route, input, "refused");
62189
+ return { ok: false, reason: "gate_refused" };
62190
+ }
62191
+ async function proceedToDestination(client, route, input) {
62192
+ try {
62193
+ const result = await callDestination(client, input.taskProjectSlug, route.capability, input.payload);
62194
+ const recorded = await recordUse(client, route, input, "ok");
62195
+ return { ok: true, use: recorded, result };
62196
+ } catch {
62197
+ await recordUse(client, route, input, "error");
62198
+ return { ok: false, reason: "delivery_failed" };
62199
+ }
62200
+ }
62201
+ async function refuse(client, route, input) {
62202
+ await recordUse(client, route, input, "refused");
62203
+ return { ok: false, reason: "gate_refused" };
62204
+ }
62205
+ async function handleAskOnce(client, route, input) {
62206
+ const groupId = `capability:ask-once:${input.taskProjectSlug}:${route.serverId}`;
62207
+ try {
62208
+ await client.createInboxItem(input.taskProjectSlug, {
62209
+ id: `II-capability-ask-once-${input.taskProjectSlug}-${route.serverId}`,
62210
+ kind: "question",
62211
+ origin: "capability",
62212
+ groupId,
62213
+ body: `The first write to ${route.serverId} (via ${route.toolName}) needs a one-time confirmation before it can proceed.`,
62214
+ taskId: input.taskId
62215
+ });
62216
+ } catch {
62217
+ }
62218
+ return refuse(client, route, input);
62219
+ }
62220
+ async function alreadyConsumed(client, route, input, after) {
62221
+ let uses;
62222
+ try {
62223
+ uses = await client.listCapabilityUses(input.taskProjectSlug);
62224
+ } catch {
62225
+ return true;
62226
+ }
62227
+ const afterMs = Date.parse(after);
62228
+ if (!Number.isFinite(afterMs)) return true;
62229
+ return uses.some((use) => {
62230
+ if (use.serverId !== route.serverId || use.toolName !== route.toolName || use.taskId !== input.taskId || use.outcome !== "ok") {
62231
+ return false;
62232
+ }
62233
+ const useAtMs = Date.parse(use.at);
62234
+ if (!Number.isFinite(useAtMs)) return true;
62235
+ return useAtMs > afterMs;
62236
+ });
62237
+ }
62238
+ async function handleApproval(client, route, input) {
62239
+ const scopeId = input.runId ?? input.taskId;
62240
+ if (scopeId === null) {
62241
+ return refuse(client, route, input);
62242
+ }
62243
+ const groupId = `capability:approval:${scopeId}:${route.serverId}:${route.toolName}`;
62244
+ const id = `II-capability-approval-${scopeId}-${route.serverId}-${route.toolName}`;
62245
+ let items;
62246
+ try {
62247
+ items = await client.listInboxItems(input.taskProjectSlug, { groupId });
62248
+ } catch {
62249
+ return refuse(client, route, input);
62250
+ }
62251
+ const existing = items[0];
62252
+ if (!existing) {
62253
+ try {
62254
+ await client.createInboxItem(input.taskProjectSlug, {
62255
+ id,
62256
+ kind: "question",
62257
+ origin: "capability",
62258
+ groupId,
62259
+ body: `Approve ${route.toolName} on ${route.serverId}?`,
62260
+ taskId: input.taskId
62261
+ });
62262
+ } catch {
62263
+ }
62264
+ return refuse(client, route, input);
62265
+ }
62266
+ if (existing.status !== "approved") {
62267
+ return refuse(client, route, input);
62268
+ }
62269
+ if (existing.decidedAt === null) {
62270
+ return refuse(client, route, input);
62271
+ }
62272
+ if (await alreadyConsumed(client, route, input, existing.decidedAt)) {
62273
+ return refuse(client, route, input);
62274
+ }
62275
+ return proceedToDestination(client, route, input);
62276
+ }
62277
+ async function handleWindow(client, route, input) {
62278
+ if (input.taskId === null) {
62279
+ return refuse(client, route, input);
62280
+ }
62281
+ const taskId = input.taskId;
62282
+ const scopeId = input.runId ?? taskId;
62283
+ const groupId = `capability:window:${scopeId}:${route.serverId}:${route.toolName}`;
62284
+ const id = `II-capability-window-${scopeId}-${route.serverId}-${route.toolName}`;
62285
+ let items;
62286
+ try {
62287
+ items = await client.listInboxItems(input.taskProjectSlug, { groupId });
62288
+ } catch {
62289
+ return refuse(client, route, input);
62290
+ }
62291
+ const existing = items[0];
62292
+ if (!existing) {
62293
+ try {
62294
+ await client.createInboxItem(input.taskProjectSlug, {
62295
+ id,
62296
+ kind: "question",
62297
+ origin: "capability",
62298
+ groupId,
62299
+ body: `Waiting for the ${route.toolName} window on ${route.serverId} to elapse.`,
62300
+ taskId
62301
+ });
62302
+ } catch {
62303
+ }
62304
+ await extendFollowUpAt(client, input.taskProjectSlug, taskId);
62305
+ return refuse(client, route, input);
62306
+ }
62307
+ if (existing.status === "rejected") {
62308
+ return refuse(client, route, input);
62309
+ }
62310
+ let task;
62311
+ try {
62312
+ task = await client.getTask(input.taskProjectSlug, taskId);
62313
+ } catch {
62314
+ return refuse(client, route, input);
62315
+ }
62316
+ const elapsed = task.followUpAt !== null && Date.parse(task.followUpAt) <= Date.now();
62317
+ if (!elapsed) {
62318
+ return refuse(client, route, input);
62319
+ }
62320
+ if (await alreadyConsumed(client, route, input, existing.createdAt)) {
62321
+ return refuse(client, route, input);
62322
+ }
62323
+ const result = await proceedToDestination(client, route, input);
62324
+ if (result.ok) {
62325
+ try {
62326
+ await client.decideInboxItem(input.taskProjectSlug, existing.id, { verdict: "acknowledged", decidedBy: "system" });
62327
+ } catch {
62328
+ }
62329
+ }
62330
+ return result;
62331
+ }
62332
+ async function extendFollowUpAt(client, taskProjectSlug, taskId) {
62333
+ try {
62334
+ const task = await client.getTask(taskProjectSlug, taskId);
62335
+ const existingMs = task.followUpAt !== null ? Date.parse(task.followUpAt) : -Infinity;
62336
+ const candidateMs = Date.now() + 5 * 60 * 1e3;
62337
+ if (candidateMs > existingMs) {
62338
+ await client.updateTask(taskProjectSlug, taskId, { followUpAt: new Date(candidateMs).toISOString() });
62339
+ }
62340
+ } catch {
62341
+ }
62342
+ }
62343
+ async function recordUse(client, route, input, outcome) {
62344
+ try {
62345
+ return await client.recordCapabilityUse(input.taskProjectSlug, {
62346
+ serverId: route.serverId,
62347
+ toolName: route.toolName,
62348
+ taskId: input.taskId,
62349
+ outcome
62350
+ });
62351
+ } catch {
62352
+ return null;
62353
+ }
62354
+ }
62355
+ async function callDestination(client, projectSlug, capability, payload) {
62356
+ const operation = isRecord4(payload) ? payload.operation : void 0;
62357
+ if (capability === "wiki") {
62358
+ if (operation !== "create" && operation !== "update") {
62359
+ throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for wiki`);
62360
+ }
62361
+ return deliverToWiki(client, projectSlug, payload);
62362
+ }
62363
+ if (capability === "git") {
62364
+ if (operation !== "branch" && operation !== "pull_request") {
62365
+ throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for git`);
62366
+ }
62367
+ return deliverToGit(client, projectSlug, payload);
62368
+ }
62369
+ throw new Error(`callDestination: no destination wired for capability "${capability}"`);
62370
+ }
62371
+ function isRecord4(value) {
62372
+ return typeof value === "object" && value !== null;
62373
+ }
62374
+
62000
62375
  // src/decision-consensus.ts
62001
62376
  var ANCHOR_TIMEOUT_MS = Number(process.env["MRRLIN_DECISION_TIMEOUT_MS"] ?? 9e4);
62002
62377
  var PICK_TIMEOUT_MS3 = 15e3;
@@ -63040,7 +63415,7 @@ var import_node_path29 = require("node:path");
63040
63415
  function evidenceDedupKey(sessionId, reportId) {
63041
63416
  return `${sessionId}:${reportId}`;
63042
63417
  }
63043
- function isRecord4(v) {
63418
+ function isRecord5(v) {
63044
63419
  return v !== null && typeof v === "object";
63045
63420
  }
63046
63421
  async function readDayLines(stateDir, day, sessionId) {
@@ -63059,7 +63434,7 @@ async function readDayLines(stateDir, day, sessionId) {
63059
63434
  } catch {
63060
63435
  continue;
63061
63436
  }
63062
- if (!isRecord4(rec) || !isRecord4(rec.payload)) continue;
63437
+ if (!isRecord5(rec) || !isRecord5(rec.payload)) continue;
63063
63438
  if (rec.payload.directorSessionId !== sessionId) continue;
63064
63439
  kept.push(rec);
63065
63440
  }
@@ -63129,7 +63504,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
63129
63504
  } catch {
63130
63505
  continue;
63131
63506
  }
63132
- if (!isRecord4(rec) || !Array.isArray(rec.daysRemoved)) continue;
63507
+ if (!isRecord5(rec) || !Array.isArray(rec.daysRemoved)) continue;
63133
63508
  for (const removedDay of rec.daysRemoved) {
63134
63509
  if (daysRead.includes(removedDay)) {
63135
63510
  windowPruned = true;
@@ -64231,6 +64606,20 @@ function pingAsyncWorkerDispatch(projectSlug, jobId) {
64231
64606
  }).catch(() => {
64232
64607
  });
64233
64608
  }
64609
+ async function deliverBuiltIn(client, projectSlug, artifactKind, payload) {
64610
+ const result = await deliverThroughCapability(client, {
64611
+ projectSlug,
64612
+ taskProjectSlug: projectSlug,
64613
+ artifactKind,
64614
+ taskId: null,
64615
+ runId: null,
64616
+ payload
64617
+ });
64618
+ if (!result.ok) {
64619
+ throw new McpToolCodedError(`CAPABILITY_${result.reason.toUpperCase()}`, `Delivery refused: ${result.reason}`);
64620
+ }
64621
+ return result.result;
64622
+ }
64234
64623
  function makeTool(client, name, fallbackCode, fallbackMessage, handler) {
64235
64624
  return {
64236
64625
  description: mcpToolDescriptions[name],
@@ -64400,19 +64789,24 @@ function createMrrlinTools(options) {
64400
64789
  "Unable to bind project repository.",
64401
64790
  async (c, { projectSlug, ...input }) => await c.bindProjectGithubRepo(projectSlug, input)
64402
64791
  ),
64792
+ // Task 8.2: routed through deliverBuiltIn, not c.createGithubBranch directly. artifactKind
64793
+ // "code_change" (not "page"): this tool is a generic git operation, not specifically about
64794
+ // publishing a website page (design.md, "`page` routes to git, not the wiki") -- there is no
64795
+ // dedicated page-publishing tool in this change's scope, so a task requiring "page" has no path
64796
+ // through these generic tools today, an accepted limitation of the current scope.
64403
64797
  [mcpToolNames.createGithubBranch]: makeTool(
64404
64798
  client,
64405
64799
  mcpToolNames.createGithubBranch,
64406
64800
  "GITHUB_BRANCH_CREATE_FAILED",
64407
64801
  "Unable to create GitHub branch.",
64408
- async (c, { projectSlug, ...input }) => await c.createGithubBranch(projectSlug, input)
64802
+ async (c, { projectSlug, ...input }) => await deliverBuiltIn(c, projectSlug, "code_change", { operation: "branch", ...input })
64409
64803
  ),
64410
64804
  [mcpToolNames.createGithubPullRequest]: makeTool(
64411
64805
  client,
64412
64806
  mcpToolNames.createGithubPullRequest,
64413
64807
  "GITHUB_PULL_REQUEST_CREATE_FAILED",
64414
64808
  "Unable to create GitHub pull request.",
64415
- async (c, { projectSlug, ...input }) => await c.createGithubPullRequest(projectSlug, input)
64809
+ async (c, { projectSlug, ...input }) => await deliverBuiltIn(c, projectSlug, "code_change", { operation: "pull_request", ...input })
64416
64810
  ),
64417
64811
  [mcpToolNames.createPlan]: makeTool(
64418
64812
  client,
@@ -64477,12 +64871,13 @@ function createMrrlinTools(options) {
64477
64871
  "Unable to append task event.",
64478
64872
  async (c, { projectSlug, taskId, ...input }) => await c.appendTaskEvent(projectSlug, taskId, input)
64479
64873
  ),
64874
+ // Task 8.2: routed through deliverBuiltIn, not c.createWikiPage directly.
64480
64875
  [mcpToolNames.createWikiPage]: makeTool(
64481
64876
  client,
64482
64877
  mcpToolNames.createWikiPage,
64483
64878
  "WIKI_PAGE_CREATE_FAILED",
64484
64879
  "Unable to create wiki page.",
64485
- async (c, { projectSlug, ...input }) => await c.createWikiPage(projectSlug, input)
64880
+ async (c, { projectSlug, ...input }) => await deliverBuiltIn(c, projectSlug, "document", { operation: "create", page: input })
64486
64881
  ),
64487
64882
  [mcpToolNames.deleteTaskDependency]: makeTool(
64488
64883
  client,
@@ -64665,12 +65060,13 @@ function createMrrlinTools(options) {
64665
65060
  "Unable to close task as won't-do.",
64666
65061
  async (c, { projectSlug, taskId, reason }) => await c.closeTaskAsWontDo(projectSlug, taskId, reason)
64667
65062
  ),
65063
+ // Task 8.2: routed through deliverBuiltIn, not c.updateWikiPage directly.
64668
65064
  [mcpToolNames.updateWikiPage]: makeTool(
64669
65065
  client,
64670
65066
  mcpToolNames.updateWikiPage,
64671
65067
  "WIKI_PAGE_UPDATE_FAILED",
64672
65068
  "Unable to update wiki page.",
64673
- async (c, { projectSlug, pageId, ...patch }) => await c.updateWikiPage(projectSlug, pageId, patch)
65069
+ async (c, { projectSlug, pageId, ...patch }) => await deliverBuiltIn(c, projectSlug, "document", { operation: "update", pageId, patch })
64674
65070
  ),
64675
65071
  [mcpToolNames.markSpecReady]: makeTool(
64676
65072
  client,
@@ -65515,7 +65911,7 @@ function parseArgs(args) {
65515
65911
  }
65516
65912
  return map2;
65517
65913
  }
65518
- function isRecord5(v) {
65914
+ function isRecord6(v) {
65519
65915
  return v !== null && typeof v === "object";
65520
65916
  }
65521
65917
  async function runExtract(args, deps) {
@@ -65552,8 +65948,8 @@ Missing required flag(s): ${missing.join(
65552
65948
  } catch {
65553
65949
  continue;
65554
65950
  }
65555
- const payload = isRecord5(parsed) ? parsed.payload : void 0;
65556
- if (isRecord5(payload) && payload.directorSessionId === sessionId) {
65951
+ const payload = isRecord6(parsed) ? parsed.payload : void 0;
65952
+ if (isRecord6(payload) && payload.directorSessionId === sessionId) {
65557
65953
  matchingLines.push(line);
65558
65954
  }
65559
65955
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.42",
3
+ "version": "0.4.43",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -22,11 +22,11 @@
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",
26
+ "@mrrlin/director-e2e": "0.0.0",
25
27
  "@mrrlin/schemas": "0.0.0",
26
28
  "@mrrlin/tsconfig": "0.0.0",
27
- "@mrrlin/wiki": "0.0.0",
28
- "@mrrlin/director-e2e": "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",