@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.
- package/dist/bin.cjs +414 -18
- 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.
|
|
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
|
|
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
|
|
57318
|
-
if (!resolvePromptsEnabled()) return
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
65556
|
-
if (
|
|
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.
|
|
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",
|