@mrrlin-dev/mcp 0.4.0 → 0.4.1
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 +227 -91
- package/package.json +2 -2
package/dist/bin.cjs
CHANGED
|
@@ -36476,6 +36476,7 @@ var openApiOperationIds = {
|
|
|
36476
36476
|
getOperatorSettings: "getOperatorSettings",
|
|
36477
36477
|
updateOperatorSettings: "updateOperatorSettings",
|
|
36478
36478
|
recordOperatorPresence: "recordOperatorPresence",
|
|
36479
|
+
pauseAutopilotForLimit: "pauseAutopilotForLimit",
|
|
36479
36480
|
getAgentPool: "getAgentPool",
|
|
36480
36481
|
putAgentPool: "putAgentPool",
|
|
36481
36482
|
getConsensusConfig: "getConsensusConfig",
|
|
@@ -36522,6 +36523,13 @@ var mrrlinOperatorSettingsSchema = external_exports.object({
|
|
|
36522
36523
|
activeProjectSlug: external_exports.string().min(1).nullable(),
|
|
36523
36524
|
// The last time an authenticated BROWSER operator session was observed. Null means never seen.
|
|
36524
36525
|
lastPresenceAt: external_exports.string().datetime().nullable(),
|
|
36526
|
+
// autopilot-limit-pause: READ-ONLY status for an automatic provider-limit pause. These are never
|
|
36527
|
+
// writable via mrrlinOperatorSettingsUpdateSchema (an explicit operator write clears them
|
|
36528
|
+
// server-side). Non-null autoPauseSource => the current pause is machine-owned; autoPauseResetAt
|
|
36529
|
+
// is the absolute reset time (null = indefinite, manual resume required).
|
|
36530
|
+
autoPauseSource: external_exports.string().min(1).nullable(),
|
|
36531
|
+
autoPauseReason: external_exports.string().min(1).nullable(),
|
|
36532
|
+
autoPauseResetAt: external_exports.string().datetime().nullable(),
|
|
36525
36533
|
createdAt: external_exports.string().datetime(),
|
|
36526
36534
|
updatedAt: external_exports.string().datetime()
|
|
36527
36535
|
});
|
|
@@ -36533,6 +36541,11 @@ var mrrlinOperatorSettingsUpdateSchema = external_exports.object({
|
|
|
36533
36541
|
message: "OperatorSettingsUpdate requires at least one field"
|
|
36534
36542
|
});
|
|
36535
36543
|
var operatorSettingsResponseSchema = external_exports.object({ data: mrrlinOperatorSettingsSchema });
|
|
36544
|
+
var autopilotLimitPauseRequestSchema = external_exports.object({
|
|
36545
|
+
source: external_exports.string().min(1).default("provider_limit"),
|
|
36546
|
+
reason: external_exports.string().min(1).max(500),
|
|
36547
|
+
resetAt: external_exports.string().datetime().nullable().default(null)
|
|
36548
|
+
}).strict();
|
|
36536
36549
|
var operatorPresenceResponseSchema = external_exports.object({ data: external_exports.object({ lastPresenceAt: external_exports.string().datetime() }) });
|
|
36537
36550
|
var operatorPresenceRequestSchema = external_exports.object({ projectSlug: external_exports.string().min(1).optional() }).strict().optional();
|
|
36538
36551
|
var mrrlinConsensusConfigSchema = external_exports.object({ consensusEnabled: external_exports.boolean() });
|
|
@@ -38123,7 +38136,7 @@ var wakeDirectorResponseSchema = external_exports.object({
|
|
|
38123
38136
|
}))
|
|
38124
38137
|
})
|
|
38125
38138
|
});
|
|
38126
|
-
var mrrlinInboxItemKindSchema = external_exports.enum(["proposal", "check_in", "question", "handoff", "info"]);
|
|
38139
|
+
var mrrlinInboxItemKindSchema = external_exports.enum(["proposal", "check_in", "question", "handoff", "info", "completed"]);
|
|
38127
38140
|
var mrrlinInboxItemStatusSchema = external_exports.enum(["pending", "approved", "rejected", "acknowledged"]);
|
|
38128
38141
|
var mrrlinInboxItemIdSchema = external_exports.string().regex(/^II-[A-Za-z0-9._-]+$/);
|
|
38129
38142
|
var mrrlinInboxItemActionPayloadSchema = external_exports.object({
|
|
@@ -38583,6 +38596,36 @@ function addTurnTokenUsage(total, turn) {
|
|
|
38583
38596
|
};
|
|
38584
38597
|
}
|
|
38585
38598
|
|
|
38599
|
+
// ../../packages/codex-client/dist/rate-limit.js
|
|
38600
|
+
var EXHAUSTION_USED_PERCENT = 100;
|
|
38601
|
+
function windowExhausted(window) {
|
|
38602
|
+
if (!window)
|
|
38603
|
+
return false;
|
|
38604
|
+
return typeof window.usedPercent === "number" && Number.isFinite(window.usedPercent) && window.usedPercent >= EXHAUSTION_USED_PERCENT;
|
|
38605
|
+
}
|
|
38606
|
+
function normalizeResetEpochSeconds(resetsAt, nowMs) {
|
|
38607
|
+
if (typeof resetsAt !== "number" || !Number.isFinite(resetsAt))
|
|
38608
|
+
return null;
|
|
38609
|
+
const resetMs = resetsAt * 1e3;
|
|
38610
|
+
if (resetMs <= nowMs)
|
|
38611
|
+
return null;
|
|
38612
|
+
return new Date(resetMs).toISOString();
|
|
38613
|
+
}
|
|
38614
|
+
function detectExhaustedProviderLimit(snapshot, nowMs) {
|
|
38615
|
+
if (!snapshot)
|
|
38616
|
+
return null;
|
|
38617
|
+
const reachedType = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType.trim() : "";
|
|
38618
|
+
const exhausted = reachedType.length > 0 || windowExhausted(snapshot.primary) || windowExhausted(snapshot.secondary);
|
|
38619
|
+
if (!exhausted)
|
|
38620
|
+
return null;
|
|
38621
|
+
const candidates = [
|
|
38622
|
+
normalizeResetEpochSeconds(snapshot.primary?.resetsAt, nowMs),
|
|
38623
|
+
normalizeResetEpochSeconds(snapshot.secondary?.resetsAt, nowMs)
|
|
38624
|
+
].filter((value) => value !== null);
|
|
38625
|
+
const resetAt = candidates.length > 0 ? candidates.reduce((a, b) => a > b ? a : b) : null;
|
|
38626
|
+
return { exhausted: true, resetAt };
|
|
38627
|
+
}
|
|
38628
|
+
|
|
38586
38629
|
// ../../packages/client/dist/agent-token-provider.js
|
|
38587
38630
|
var AgentTokenProviderError = class extends Error {
|
|
38588
38631
|
code;
|
|
@@ -39493,6 +39536,15 @@ function createMrrlinClient(config2) {
|
|
|
39493
39536
|
});
|
|
39494
39537
|
return operatorSettingsResponseSchema.parse(body).data;
|
|
39495
39538
|
},
|
|
39539
|
+
async pauseAutopilotForLimit(input) {
|
|
39540
|
+
const parsed = autopilotLimitPauseRequestSchema.parse(input);
|
|
39541
|
+
const body = await request(`/operator/autopilot/limit-pause`, {
|
|
39542
|
+
body: JSON.stringify(parsed),
|
|
39543
|
+
headers: { "content-type": "application/json" },
|
|
39544
|
+
method: "POST"
|
|
39545
|
+
});
|
|
39546
|
+
return operatorSettingsResponseSchema.parse(body).data;
|
|
39547
|
+
},
|
|
39496
39548
|
async recordOperatorPresence(input) {
|
|
39497
39549
|
const projectSlug = input?.projectSlug;
|
|
39498
39550
|
const body = await request(`/operator/presence`, {
|
|
@@ -40135,7 +40187,7 @@ function startCodexProbe(deps = {}) {
|
|
|
40135
40187
|
}
|
|
40136
40188
|
|
|
40137
40189
|
// src/_generated/version.ts
|
|
40138
|
-
var PKG_VERSION = "0.4.
|
|
40190
|
+
var PKG_VERSION = "0.4.1";
|
|
40139
40191
|
|
|
40140
40192
|
// src/api-base-url-migrate.ts
|
|
40141
40193
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -42946,7 +42998,7 @@ function onComplete(outcome, input) {
|
|
|
42946
42998
|
}
|
|
42947
42999
|
const taskRef = parsed.taskTitle ? `${input.taskId} \u2014 ${parsed.taskTitle}` : input.taskId;
|
|
42948
43000
|
return {
|
|
42949
|
-
kind: "
|
|
43001
|
+
kind: "completed",
|
|
42950
43002
|
taskId: input.taskId,
|
|
42951
43003
|
body: `Spec drafted automatically for ${taskRef} \u2014 page ${parsed.pageId ?? "?"} in Specs/. The next scheduler tick will run the consensus review.`
|
|
42952
43004
|
};
|
|
@@ -44006,6 +44058,10 @@ function executionTurnBudgetMessage(budgetMs) {
|
|
|
44006
44058
|
function isExecutionTurnTimeoutReason(reason) {
|
|
44007
44059
|
return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX) || reason.startsWith(EXECUTION_TURN_STALL_PREFIX);
|
|
44008
44060
|
}
|
|
44061
|
+
function reportAutopilotLimitPause(client, reason, resetAt) {
|
|
44062
|
+
void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
|
|
44063
|
+
});
|
|
44064
|
+
}
|
|
44009
44065
|
function isDeliverableGateRetryableReason(reason) {
|
|
44010
44066
|
return DELIVERABLE_GATE_RETRYABLE_REASON_PREFIXES.some((prefix) => reason.startsWith(prefix));
|
|
44011
44067
|
}
|
|
@@ -44085,17 +44141,16 @@ var BASE_INSTRUCTIONS_PREFIX = [
|
|
|
44085
44141
|
" qualifies, produce the file; never merely offer one.",
|
|
44086
44142
|
"- Mixed output counts as retrieved: if any material part came from a retrieval, treat the whole",
|
|
44087
44143
|
" result as retrieved rather than splitting it between inline text and a file.",
|
|
44088
|
-
"- When your output is a file (markdown report, HTML
|
|
44089
|
-
" `
|
|
44090
|
-
"
|
|
44144
|
+
"- When your output is a file (markdown report, HTML), call `deliver_file`: pass the body inline",
|
|
44145
|
+
" as `content` and embed the returned markdown link. It needs no writable path, so it is the",
|
|
44146
|
+
" default even when this run path cannot write files \u2014 never inline large blobs into chat,",
|
|
44147
|
+
" spec, or note text.",
|
|
44091
44148
|
"- class=temp for one-off operator hand-offs (48h); class=durable for anything a spec",
|
|
44092
44149
|
" or evidence page will reference (180 days). Always pass taskId/runId when known.",
|
|
44093
|
-
"- Anything user-facing delivered as a file MUST
|
|
44094
|
-
" split or summarize larger outputs.",
|
|
44095
|
-
"- `upload_artifact`
|
|
44096
|
-
"
|
|
44097
|
-
" cannot write, do NOT attempt the file: put the summary in your result text and state what a",
|
|
44098
|
-
" file would have held. Never fake a path you did not write.",
|
|
44150
|
+
"- Anything user-facing delivered as a file MUST go through `deliver_file`. Files are capped at",
|
|
44151
|
+
" 5MB \u2014 split or summarize larger outputs.",
|
|
44152
|
+
"- Use `upload_artifact` only for a file already on disk (e.g. a screenshot the browser wrote);",
|
|
44153
|
+
" it needs a real local path, so never fake a path you did not write.",
|
|
44099
44154
|
"",
|
|
44100
44155
|
"Produced URLs \u2014 the operator's per-page links come only from you:",
|
|
44101
44156
|
"- Call `record_produced_url` after publishing EACH web page this task produced, so the task UI",
|
|
@@ -44425,6 +44480,17 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
|
|
|
44425
44480
|
}
|
|
44426
44481
|
const usage = !notifThreadId || notifThreadId === threadId ? extractTurnTokenUsage(method, params) : null;
|
|
44427
44482
|
if (usage) turnUsage = usage;
|
|
44483
|
+
if (method === "account/rateLimits/updated") {
|
|
44484
|
+
const snapshot = params?.rateLimits ?? null;
|
|
44485
|
+
const signal = detectExhaustedProviderLimit(snapshot, Date.now());
|
|
44486
|
+
if (signal) {
|
|
44487
|
+
reportAutopilotLimitPause(
|
|
44488
|
+
deps.client,
|
|
44489
|
+
"Codex provider limit exhausted during an autonomous execution run.",
|
|
44490
|
+
signal.resetAt
|
|
44491
|
+
);
|
|
44492
|
+
}
|
|
44493
|
+
}
|
|
44428
44494
|
const events = mapper.mapNotification(
|
|
44429
44495
|
{ method, params },
|
|
44430
44496
|
threadId
|
|
@@ -51060,10 +51126,11 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
51060
51126
|
" treat the whole answer as retrieved rather than splitting it between chat and a file.",
|
|
51061
51127
|
'- When it qualifies, PRODUCE the file. Offering one ("I can give you this as CSV/JSON") is the',
|
|
51062
51128
|
" failure, not the courtesy.",
|
|
51063
|
-
"- When it is a file:
|
|
51064
|
-
" carries meaning),
|
|
51065
|
-
"
|
|
51066
|
-
" the returned markdown link into your
|
|
51129
|
+
"- When it is a file: choose the format (`.md` for linear prose, `.html` when structure or",
|
|
51130
|
+
" layout carries meaning), then call `deliver_file` \u2014 pass the body inline as `content` and it",
|
|
51131
|
+
" needs no local file (class=temp for one-off downloads, class=durable when a spec/evidence",
|
|
51132
|
+
" page references it; pass taskId when known), and paste the returned markdown link into your",
|
|
51133
|
+
" reply. Use `upload_artifact` only for a file that already exists on disk (e.g. a screenshot).",
|
|
51067
51134
|
"- The chat reply must still stand alone as a complete answer even if the file is never opened:",
|
|
51068
51135
|
" the headline finding, the few points that matter, then the link. Never reduce it to",
|
|
51069
51136
|
' "see attached" or "details in the report".',
|
|
@@ -51300,6 +51367,10 @@ var DEFAULT_WORKTREE_RETENTION_HOURS = 48;
|
|
|
51300
51367
|
function worktreeRetentionMs() {
|
|
51301
51368
|
return readEnvInt("MRRLIN_DIRECTOR_WORKTREE_RETENTION_HOURS", DEFAULT_WORKTREE_RETENTION_HOURS) * 36e5;
|
|
51302
51369
|
}
|
|
51370
|
+
function reportAutopilotLimitPause2(client, reason, resetAt) {
|
|
51371
|
+
void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
|
|
51372
|
+
});
|
|
51373
|
+
}
|
|
51303
51374
|
async function runOrphanWorktreeSweep(client, opts) {
|
|
51304
51375
|
const cutoff = Date.now() - worktreeRetentionMs();
|
|
51305
51376
|
const isReapable = (worktreePath) => {
|
|
@@ -52310,7 +52381,13 @@ function createBridgeMessageHandler(deps) {
|
|
|
52310
52381
|
pokeActivity();
|
|
52311
52382
|
if (method === "account/rateLimits/updated") {
|
|
52312
52383
|
const snapshot = params?.rateLimits ?? null;
|
|
52313
|
-
if (snapshot)
|
|
52384
|
+
if (snapshot) {
|
|
52385
|
+
sendInTurn({ type: "rateLimits", directorSessionId, rateLimits: snapshot });
|
|
52386
|
+
const signal = detectExhaustedProviderLimit(snapshot, Date.now());
|
|
52387
|
+
if (signal) {
|
|
52388
|
+
deps.reportLimitPause?.("Codex provider limit exhausted during a Director turn.", signal.resetAt);
|
|
52389
|
+
}
|
|
52390
|
+
}
|
|
52314
52391
|
}
|
|
52315
52392
|
const events = mapper.mapNotification({ method, params }, threadId);
|
|
52316
52393
|
for (const event of events) {
|
|
@@ -52412,6 +52489,10 @@ ${effectiveText}` : effectiveText;
|
|
|
52412
52489
|
forward(event);
|
|
52413
52490
|
}
|
|
52414
52491
|
if (outputEvents === 0) {
|
|
52492
|
+
deps.reportLimitPause?.(
|
|
52493
|
+
"Director turn completed without output; Codex likely hit its usage or credits limit.",
|
|
52494
|
+
null
|
|
52495
|
+
);
|
|
52415
52496
|
sendInTurn({
|
|
52416
52497
|
type: "error",
|
|
52417
52498
|
directorSessionId,
|
|
@@ -52909,6 +52990,7 @@ async function startDirectorBridge() {
|
|
|
52909
52990
|
turnInactivity: () => ({ idleMs: chatTurnIdleMs(), maxMs: chatTurnMaxMs() }),
|
|
52910
52991
|
checkoutRegistry,
|
|
52911
52992
|
setCodexAuth: (s) => health.setCodexAuth(s),
|
|
52993
|
+
reportLimitPause: (reason, resetAt) => reportAutopilotLimitPause2(client, reason, resetAt),
|
|
52912
52994
|
getPairingQr: async () => {
|
|
52913
52995
|
if (!relayConfigured || !remoteRelay) return { status: "not-configured" };
|
|
52914
52996
|
const payload = remoteRelay.qrPayload;
|
|
@@ -57383,30 +57465,9 @@ var HUMANIZE_TEMPLATE_BY_KIND = {
|
|
|
57383
57465
|
missing_requirement: "A required part of the task is still missing from the spec. What should it say?",
|
|
57384
57466
|
acceptance_criteria_detail: "The direction is clear, but the spec still needs a sharper success check. What should count as done?"
|
|
57385
57467
|
};
|
|
57386
|
-
var HUMANIZE_CONTEXT_CAP = 200;
|
|
57387
|
-
function truncateToSentenceBoundary(text, cap) {
|
|
57388
|
-
if (text.length <= cap) return text;
|
|
57389
|
-
const sentences = text.match(/.+?[.!?](?=\s|$)/g);
|
|
57390
|
-
if (sentences) {
|
|
57391
|
-
let kept = "";
|
|
57392
|
-
let keptCount = 0;
|
|
57393
|
-
for (const sentence of sentences) {
|
|
57394
|
-
const candidate = kept ? `${kept} ${sentence.trim()}` : sentence.trim();
|
|
57395
|
-
if (candidate.length > cap) break;
|
|
57396
|
-
kept = candidate;
|
|
57397
|
-
keptCount++;
|
|
57398
|
-
}
|
|
57399
|
-
if (kept) return keptCount < sentences.length ? `${kept} \u2026` : kept;
|
|
57400
|
-
}
|
|
57401
|
-
const cutIdx = text.lastIndexOf(" ", cap);
|
|
57402
|
-
return text.slice(0, cutIdx > 0 ? cutIdx : cap).trim() + "\u2026";
|
|
57403
|
-
}
|
|
57404
57468
|
function humanizeClarificationBody(clarification) {
|
|
57405
57469
|
const prefix = HUMANIZE_TEMPLATE_BY_KIND[clarification.kind];
|
|
57406
|
-
const context =
|
|
57407
|
-
clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim(),
|
|
57408
|
-
HUMANIZE_CONTEXT_CAP
|
|
57409
|
-
);
|
|
57470
|
+
const context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
57410
57471
|
return context ? `${prefix} ${context}` : prefix;
|
|
57411
57472
|
}
|
|
57412
57473
|
var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
|
|
@@ -57701,7 +57762,7 @@ function onComplete2(outcome, input) {
|
|
|
57701
57762
|
};
|
|
57702
57763
|
}
|
|
57703
57764
|
return {
|
|
57704
|
-
kind: "
|
|
57765
|
+
kind: "completed",
|
|
57705
57766
|
...taskId ? { taskId } : {},
|
|
57706
57767
|
body: taskRef ? `Consensus CONVERGED for ${taskRef} (spec Specs/${input.pageId}); spec_ready set. Ready to create an execution run.` : `Consensus CONVERGED for Specs/${input.pageId}; spec_ready set. Ready to create an execution run.`
|
|
57707
57768
|
};
|
|
@@ -57832,7 +57893,7 @@ function onComplete3(outcome, input) {
|
|
|
57832
57893
|
};
|
|
57833
57894
|
}
|
|
57834
57895
|
return {
|
|
57835
|
-
kind: "
|
|
57896
|
+
kind: "completed",
|
|
57836
57897
|
...taskId ? { taskId } : {},
|
|
57837
57898
|
body: taskRef ? `Deliverable consensus CONVERGED for ${taskRef} (Deliverables/${input.pageId}); it meets the acceptance criteria.` : `Deliverable consensus CONVERGED for Deliverables/${input.pageId}; it meets the acceptance criteria.`
|
|
57838
57899
|
};
|
|
@@ -57955,6 +58016,7 @@ var mcpToolNames = {
|
|
|
57955
58016
|
runSpecConsensus: "run_spec_consensus",
|
|
57956
58017
|
runCodeReviewerGate: "run_code_reviewer_gate",
|
|
57957
58018
|
uploadArtifact: "upload_artifact",
|
|
58019
|
+
deliverFile: "deliver_file",
|
|
57958
58020
|
listArtifactFiles: "list_artifact_files",
|
|
57959
58021
|
reclaimExecutionRunLease: "reclaim_execution_run_lease",
|
|
57960
58022
|
restartExecutionRun: "restart_execution_run",
|
|
@@ -58540,6 +58602,20 @@ var uploadArtifactInputSchema = projectScopedSchema.merge(
|
|
|
58540
58602
|
description: external_exports.string().optional().describe("Shown in the run's artifact history; ignored when runId is not provided.")
|
|
58541
58603
|
})
|
|
58542
58604
|
);
|
|
58605
|
+
var deliverFileInputSchema = projectScopedSchema.merge(
|
|
58606
|
+
external_exports.object({
|
|
58607
|
+
filename: external_exports.string().min(1).describe(
|
|
58608
|
+
"Bare filename with extension (e.g. 'notes.md'). No path separators or '..' allowed."
|
|
58609
|
+
),
|
|
58610
|
+
content: external_exports.string().describe("UTF-8 text content of the file."),
|
|
58611
|
+
class: mrrlinArtifactFileClassSchema.describe(
|
|
58612
|
+
"temp = 48h one-off hand-off; durable = 180d, for spec/evidence references."
|
|
58613
|
+
),
|
|
58614
|
+
taskId: mrrlinTaskIdSchema.optional(),
|
|
58615
|
+
runId: mrrlinExecutionRunIdSchema.optional(),
|
|
58616
|
+
description: external_exports.string().optional().describe("Shown in the run's artifact history; ignored when runId is not provided.")
|
|
58617
|
+
})
|
|
58618
|
+
);
|
|
58543
58619
|
var listArtifactFilesInputSchema = projectScopedSchema.merge(
|
|
58544
58620
|
external_exports.object({
|
|
58545
58621
|
taskId: mrrlinTaskIdSchema.optional(),
|
|
@@ -58625,6 +58701,7 @@ var mcpToolInputSchemas = {
|
|
|
58625
58701
|
[mcpToolNames.createInboxItem]: createInboxItemInputSchema,
|
|
58626
58702
|
[mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
|
|
58627
58703
|
[mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
|
|
58704
|
+
[mcpToolNames.deliverFile]: deliverFileInputSchema,
|
|
58628
58705
|
[mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
|
|
58629
58706
|
[mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
|
|
58630
58707
|
[mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
|
|
@@ -58762,6 +58839,7 @@ var mcpToolDescriptions = {
|
|
|
58762
58839
|
[mcpToolNames.createInboxItem]: "Create an operator-inbox item (proposal, check_in, question, handoff, or info) for a project.",
|
|
58763
58840
|
[mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
|
|
58764
58841
|
[mcpToolNames.uploadArtifact]: "Upload a local file (md/txt/html/csv/json/pdf/png/jpg/webp/gif, <=5MB) as a project artifact. Returns a stable markdown link to embed in chat/spec/handoff text. Use class=temp for one-off hand-offs, class=durable for anything referenced by specs or evidence. Pass runId to record the file in that run's artifact history; pass taskId to surface it on the task's Artifacts list.",
|
|
58842
|
+
[mcpToolNames.deliverFile]: "This is the DEFAULT way to deliver a file the agent just composed; it takes the body inline as UTF-8 `content` in one call and needs no local file or writable path; `upload_artifact` is for a file that already exists on disk; use class=temp / class=durable and taskId/runId semantics with a 5MB cap.",
|
|
58765
58843
|
[mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
|
|
58766
58844
|
[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.",
|
|
58767
58845
|
[mcpToolNames.restartExecutionRun]: "Restart a parked execution run (handoff_requested or handoff_queued) back to pending so the queue can resume it.",
|
|
@@ -58908,6 +58986,68 @@ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, proj
|
|
|
58908
58986
|
}
|
|
58909
58987
|
function createMrrlinTools(options) {
|
|
58910
58988
|
const client = createMrrlinClient(withAuth(options));
|
|
58989
|
+
async function uploadArtifactBytes(c, args) {
|
|
58990
|
+
const { bytes: bytes3, class: artifactClass, contentType, description, filename, projectSlug, runId, taskId } = args;
|
|
58991
|
+
const created = await c.createArtifactFile(projectSlug, {
|
|
58992
|
+
class: artifactClass,
|
|
58993
|
+
contentType,
|
|
58994
|
+
filename,
|
|
58995
|
+
runId,
|
|
58996
|
+
sizeBytes: bytes3.byteLength,
|
|
58997
|
+
taskId
|
|
58998
|
+
});
|
|
58999
|
+
let stage = "upload";
|
|
59000
|
+
try {
|
|
59001
|
+
if (created.upload.kind === "presigned-post") {
|
|
59002
|
+
const form = new FormData();
|
|
59003
|
+
for (const [field, value] of Object.entries(created.upload.fields)) {
|
|
59004
|
+
form.append(field, value);
|
|
59005
|
+
}
|
|
59006
|
+
form.append("file", new Blob([bytes3], { type: contentType }), filename);
|
|
59007
|
+
const bareFetch = options.fetch ?? globalThis.fetch;
|
|
59008
|
+
const uploadResponse = await bareFetch(created.upload.url, { body: form, method: "POST" });
|
|
59009
|
+
if (!uploadResponse.ok) {
|
|
59010
|
+
throw new Error(
|
|
59011
|
+
`Presigned upload failed: ${uploadResponse.status} ${await uploadResponse.text()}`
|
|
59012
|
+
);
|
|
59013
|
+
}
|
|
59014
|
+
} else {
|
|
59015
|
+
await c.uploadArtifactFileContent(projectSlug, created.artifact.id, bytes3, contentType);
|
|
59016
|
+
}
|
|
59017
|
+
stage = "complete";
|
|
59018
|
+
const completed = await c.completeArtifactFile(projectSlug, created.artifact.id);
|
|
59019
|
+
if (completed.runId) {
|
|
59020
|
+
stage = "append-run-artifact";
|
|
59021
|
+
await c.appendExecutionArtifact(projectSlug, completed.runId, {
|
|
59022
|
+
id: `EA-${completed.id}`,
|
|
59023
|
+
kind: "file",
|
|
59024
|
+
payload: {
|
|
59025
|
+
artifactFileId: completed.id,
|
|
59026
|
+
class: completed.class,
|
|
59027
|
+
contentType: completed.contentType,
|
|
59028
|
+
description: description ?? null,
|
|
59029
|
+
filename: completed.filename,
|
|
59030
|
+
sizeBytes: completed.sizeBytes,
|
|
59031
|
+
stableUrl: created.stableUrl
|
|
59032
|
+
}
|
|
59033
|
+
});
|
|
59034
|
+
}
|
|
59035
|
+
const markdown = contentType.startsWith("image/") ? `` : `[${completed.filename}](${created.stableUrl})`;
|
|
59036
|
+
return {
|
|
59037
|
+
artifactFile: completed,
|
|
59038
|
+
markdown,
|
|
59039
|
+
stableUrl: created.stableUrl
|
|
59040
|
+
};
|
|
59041
|
+
} catch (error51) {
|
|
59042
|
+
const original = error51 instanceof MrrlinApiError ? error51.code : error51 instanceof McpToolCodedError ? error51.code : "ARTIFACT_FILE_UPLOAD_FAILED";
|
|
59043
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
59044
|
+
const note = stage === "append-run-artifact" ? "file uploaded and completed successfully; only the run-history append failed \u2014 do NOT re-upload, reference the stableUrl directly" : "a pending row may remain; it is reaped automatically after 1h";
|
|
59045
|
+
throw new McpToolCodedError(
|
|
59046
|
+
original,
|
|
59047
|
+
`artifactFileId=${created.artifact.id} stableUrl=${created.stableUrl} (failed at ${stage}; ${note}): ${message}`
|
|
59048
|
+
);
|
|
59049
|
+
}
|
|
59050
|
+
}
|
|
58911
59051
|
return {
|
|
58912
59052
|
[mcpToolNames.archivePlan]: makeTool(
|
|
58913
59053
|
client,
|
|
@@ -59475,65 +59615,61 @@ function createMrrlinTools(options) {
|
|
|
59475
59615
|
);
|
|
59476
59616
|
}
|
|
59477
59617
|
const filename = import_node_path20.default.basename(filePath);
|
|
59478
|
-
|
|
59618
|
+
return uploadArtifactBytes(c, {
|
|
59619
|
+
bytes: bytes3,
|
|
59479
59620
|
class: artifactClass,
|
|
59480
59621
|
contentType,
|
|
59622
|
+
description,
|
|
59481
59623
|
filename,
|
|
59624
|
+
projectSlug,
|
|
59482
59625
|
runId,
|
|
59483
|
-
sizeBytes: bytes3.byteLength,
|
|
59484
59626
|
taskId
|
|
59485
59627
|
});
|
|
59486
|
-
|
|
59487
|
-
|
|
59488
|
-
|
|
59489
|
-
|
|
59490
|
-
|
|
59491
|
-
|
|
59492
|
-
|
|
59493
|
-
|
|
59494
|
-
|
|
59495
|
-
const uploadResponse = await bareFetch(created.upload.url, { body: form, method: "POST" });
|
|
59496
|
-
if (!uploadResponse.ok) {
|
|
59497
|
-
throw new Error(
|
|
59498
|
-
`Presigned upload failed: ${uploadResponse.status} ${await uploadResponse.text()}`
|
|
59499
|
-
);
|
|
59500
|
-
}
|
|
59501
|
-
} else {
|
|
59502
|
-
await c.uploadArtifactFileContent(projectSlug, created.artifact.id, bytes3, contentType);
|
|
59503
|
-
}
|
|
59504
|
-
stage = "complete";
|
|
59505
|
-
const completed = await c.completeArtifactFile(projectSlug, created.artifact.id);
|
|
59506
|
-
if (completed.runId) {
|
|
59507
|
-
stage = "append-run-artifact";
|
|
59508
|
-
await c.appendExecutionArtifact(projectSlug, completed.runId, {
|
|
59509
|
-
id: `EA-${completed.id}`,
|
|
59510
|
-
kind: "file",
|
|
59511
|
-
payload: {
|
|
59512
|
-
artifactFileId: completed.id,
|
|
59513
|
-
class: completed.class,
|
|
59514
|
-
contentType: completed.contentType,
|
|
59515
|
-
description: description ?? null,
|
|
59516
|
-
filename: completed.filename,
|
|
59517
|
-
sizeBytes: completed.sizeBytes,
|
|
59518
|
-
stableUrl: created.stableUrl
|
|
59519
|
-
}
|
|
59520
|
-
});
|
|
59521
|
-
}
|
|
59522
|
-
const markdown = contentType.startsWith("image/") ? `` : `[${completed.filename}](${created.stableUrl})`;
|
|
59523
|
-
return {
|
|
59524
|
-
artifactFile: completed,
|
|
59525
|
-
markdown,
|
|
59526
|
-
stableUrl: created.stableUrl
|
|
59527
|
-
};
|
|
59528
|
-
} catch (error51) {
|
|
59529
|
-
const original = error51 instanceof MrrlinApiError ? error51.code : error51 instanceof McpToolCodedError ? error51.code : "ARTIFACT_FILE_UPLOAD_FAILED";
|
|
59530
|
-
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
59531
|
-
const note = stage === "append-run-artifact" ? "file uploaded and completed successfully; only the run-history append failed \u2014 do NOT re-upload, reference the stableUrl directly" : "a pending row may remain; it is reaped automatically after 1h";
|
|
59628
|
+
}
|
|
59629
|
+
),
|
|
59630
|
+
[mcpToolNames.deliverFile]: makeTool(
|
|
59631
|
+
client,
|
|
59632
|
+
mcpToolNames.deliverFile,
|
|
59633
|
+
"ARTIFACT_FILE_UPLOAD_FAILED",
|
|
59634
|
+
"Unable to deliver artifact file.",
|
|
59635
|
+
async (c, { projectSlug, filename, content, class: artifactClass, taskId, runId, description }) => {
|
|
59636
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
59532
59637
|
throw new McpToolCodedError(
|
|
59533
|
-
|
|
59534
|
-
`
|
|
59638
|
+
"ARTIFACT_FILE_NAME_INVALID",
|
|
59639
|
+
`Invalid filename '${filename}'; provide a bare filename with no path separators or '..'.`
|
|
59535
59640
|
);
|
|
59536
59641
|
}
|
|
59642
|
+
const extension2 = import_node_path20.default.extname(filename).toLowerCase();
|
|
59643
|
+
const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
|
|
59644
|
+
if (!contentType) {
|
|
59645
|
+
throw new McpToolCodedError(
|
|
59646
|
+
"ARTIFACT_FILE_TYPE_UNSUPPORTED",
|
|
59647
|
+
`Unsupported artifact extension '${extension2}'. Allowed: ${Object.keys(ARTIFACT_EXTENSION_TYPES).join(", ")}.`
|
|
59648
|
+
);
|
|
59649
|
+
}
|
|
59650
|
+
const bytes3 = new Uint8Array(Buffer.from(content, "utf8"));
|
|
59651
|
+
if (bytes3.byteLength === 0) {
|
|
59652
|
+
throw new McpToolCodedError(
|
|
59653
|
+
"ARTIFACT_FILE_EMPTY",
|
|
59654
|
+
"File is empty (0 bytes); write content before uploading."
|
|
59655
|
+
);
|
|
59656
|
+
}
|
|
59657
|
+
if (bytes3.byteLength > MRRLIN_ARTIFACT_FILE_MAX_BYTES) {
|
|
59658
|
+
throw new McpToolCodedError(
|
|
59659
|
+
"ARTIFACT_FILE_TOO_LARGE",
|
|
59660
|
+
"Artifact exceeds 5MB; split or summarize instead."
|
|
59661
|
+
);
|
|
59662
|
+
}
|
|
59663
|
+
return uploadArtifactBytes(c, {
|
|
59664
|
+
bytes: bytes3,
|
|
59665
|
+
class: artifactClass,
|
|
59666
|
+
contentType,
|
|
59667
|
+
description,
|
|
59668
|
+
filename,
|
|
59669
|
+
projectSlug,
|
|
59670
|
+
runId,
|
|
59671
|
+
taskId
|
|
59672
|
+
});
|
|
59537
59673
|
}
|
|
59538
59674
|
),
|
|
59539
59675
|
[mcpToolNames.listArtifactFiles]: makeTool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dev": "tsx watch src/bin.ts serve",
|
|
48
48
|
"lint": "eslint .",
|
|
49
49
|
"start": "node dist/bin.cjs serve",
|
|
50
|
-
"test": "node scripts/write-version.mjs && node --import tsx --test $(find src -name '*.test.ts' | sort | tr '\\n' ' ')",
|
|
50
|
+
"test": "pnpm --filter @mrrlin/codex-client build && node scripts/write-version.mjs && node --import tsx --test $(find src -name '*.test.ts' | sort | tr '\\n' ' ')",
|
|
51
51
|
"typecheck": "node scripts/write-version.mjs && pnpm --filter @mrrlin/schemas build && pnpm --filter @mrrlin/client build && pnpm --filter @mrrlin/wiki build && pnpm --filter @mrrlin/codex-client build && pnpm --filter @mrrlin/director-e2e build && tsc -p tsconfig.json --noEmit"
|
|
52
52
|
}
|
|
53
53
|
}
|