@mrrlin-dev/mcp 0.4.0 → 0.4.2
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 +317 -100
- package/package.json +5 -5
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({
|
|
@@ -38174,6 +38187,9 @@ var mrrlinInboxQuestionOptionsSchema = external_exports.array(mrrlinInboxQuestio
|
|
|
38174
38187
|
});
|
|
38175
38188
|
}
|
|
38176
38189
|
});
|
|
38190
|
+
var REVIEW_AGAIN_ANSWER = "Review the current spec again without changing it.";
|
|
38191
|
+
var EDIT_SPEC_ANSWER = "Hold the task until the spec is edited by hand.";
|
|
38192
|
+
var PROCEED_ANYWAY_ANSWER = "Proceed without agreement; record the unsettled points on the spec.";
|
|
38177
38193
|
var mrrlinInboxItemSchema = external_exports.object({
|
|
38178
38194
|
actionPayload: mrrlinInboxItemActionPayloadSchema.nullable(),
|
|
38179
38195
|
answer: external_exports.string().nullable(),
|
|
@@ -38583,6 +38599,36 @@ function addTurnTokenUsage(total, turn) {
|
|
|
38583
38599
|
};
|
|
38584
38600
|
}
|
|
38585
38601
|
|
|
38602
|
+
// ../../packages/codex-client/dist/rate-limit.js
|
|
38603
|
+
var EXHAUSTION_USED_PERCENT = 100;
|
|
38604
|
+
function windowExhausted(window) {
|
|
38605
|
+
if (!window)
|
|
38606
|
+
return false;
|
|
38607
|
+
return typeof window.usedPercent === "number" && Number.isFinite(window.usedPercent) && window.usedPercent >= EXHAUSTION_USED_PERCENT;
|
|
38608
|
+
}
|
|
38609
|
+
function normalizeResetEpochSeconds(resetsAt, nowMs) {
|
|
38610
|
+
if (typeof resetsAt !== "number" || !Number.isFinite(resetsAt))
|
|
38611
|
+
return null;
|
|
38612
|
+
const resetMs = resetsAt * 1e3;
|
|
38613
|
+
if (resetMs <= nowMs)
|
|
38614
|
+
return null;
|
|
38615
|
+
return new Date(resetMs).toISOString();
|
|
38616
|
+
}
|
|
38617
|
+
function detectExhaustedProviderLimit(snapshot, nowMs) {
|
|
38618
|
+
if (!snapshot)
|
|
38619
|
+
return null;
|
|
38620
|
+
const reachedType = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType.trim() : "";
|
|
38621
|
+
const exhausted = reachedType.length > 0 || windowExhausted(snapshot.primary) || windowExhausted(snapshot.secondary);
|
|
38622
|
+
if (!exhausted)
|
|
38623
|
+
return null;
|
|
38624
|
+
const candidates = [
|
|
38625
|
+
normalizeResetEpochSeconds(snapshot.primary?.resetsAt, nowMs),
|
|
38626
|
+
normalizeResetEpochSeconds(snapshot.secondary?.resetsAt, nowMs)
|
|
38627
|
+
].filter((value) => value !== null);
|
|
38628
|
+
const resetAt = candidates.length > 0 ? candidates.reduce((a, b) => a > b ? a : b) : null;
|
|
38629
|
+
return { exhausted: true, resetAt };
|
|
38630
|
+
}
|
|
38631
|
+
|
|
38586
38632
|
// ../../packages/client/dist/agent-token-provider.js
|
|
38587
38633
|
var AgentTokenProviderError = class extends Error {
|
|
38588
38634
|
code;
|
|
@@ -39493,6 +39539,15 @@ function createMrrlinClient(config2) {
|
|
|
39493
39539
|
});
|
|
39494
39540
|
return operatorSettingsResponseSchema.parse(body).data;
|
|
39495
39541
|
},
|
|
39542
|
+
async pauseAutopilotForLimit(input) {
|
|
39543
|
+
const parsed = autopilotLimitPauseRequestSchema.parse(input);
|
|
39544
|
+
const body = await request(`/operator/autopilot/limit-pause`, {
|
|
39545
|
+
body: JSON.stringify(parsed),
|
|
39546
|
+
headers: { "content-type": "application/json" },
|
|
39547
|
+
method: "POST"
|
|
39548
|
+
});
|
|
39549
|
+
return operatorSettingsResponseSchema.parse(body).data;
|
|
39550
|
+
},
|
|
39496
39551
|
async recordOperatorPresence(input) {
|
|
39497
39552
|
const projectSlug = input?.projectSlug;
|
|
39498
39553
|
const body = await request(`/operator/presence`, {
|
|
@@ -40135,7 +40190,7 @@ function startCodexProbe(deps = {}) {
|
|
|
40135
40190
|
}
|
|
40136
40191
|
|
|
40137
40192
|
// src/_generated/version.ts
|
|
40138
|
-
var PKG_VERSION = "0.4.
|
|
40193
|
+
var PKG_VERSION = "0.4.2";
|
|
40139
40194
|
|
|
40140
40195
|
// src/api-base-url-migrate.ts
|
|
40141
40196
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
@@ -41260,7 +41315,7 @@ async function runConsensus(input) {
|
|
|
41260
41315
|
let decision;
|
|
41261
41316
|
for (let round = 1; round <= effectiveRounds; round++) {
|
|
41262
41317
|
if (calls + input.heads.length > input.maxHeadCalls) {
|
|
41263
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41318
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round - 1, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41264
41319
|
}
|
|
41265
41320
|
const dismissedBlock = dismissedFwd.length ? `PREVIOUSLY DISMISSED (do not re-raise unless new information):
|
|
41266
41321
|
${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
@@ -41282,7 +41337,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41282
41337
|
(r) => r.res.ok
|
|
41283
41338
|
);
|
|
41284
41339
|
if (responding.length === 0) {
|
|
41285
|
-
return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history };
|
|
41340
|
+
return { outcome: "CONSENSUS_UNAVAILABLE", rounds: round, finalArtifact: artifact, history, unresolved: [] };
|
|
41286
41341
|
}
|
|
41287
41342
|
const headOutcomes = input.heads.map((persona) => ({
|
|
41288
41343
|
persona,
|
|
@@ -41308,7 +41363,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41308
41363
|
let adj;
|
|
41309
41364
|
if (issues.length > 0) {
|
|
41310
41365
|
if (calls + 1 > input.maxHeadCalls) {
|
|
41311
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41366
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41312
41367
|
}
|
|
41313
41368
|
calls++;
|
|
41314
41369
|
try {
|
|
@@ -41322,7 +41377,7 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41322
41377
|
history.push({ round, accepted: adj.accepted, dismissed: adj.dismissed, headOutcomes });
|
|
41323
41378
|
adj.dismissed.forEach((d) => dismissedFwd.push(`- ${d.id}: ${d.reason}`));
|
|
41324
41379
|
if (adj.accepted.length === 0) {
|
|
41325
|
-
return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41380
|
+
return { outcome: "CONVERGED", rounds: round, finalArtifact: artifact, history, unresolved: [], ...decision ? { escalation: decision } : {} };
|
|
41326
41381
|
}
|
|
41327
41382
|
if (round === effectiveRounds) {
|
|
41328
41383
|
if (!decision) {
|
|
@@ -41332,12 +41387,12 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
41332
41387
|
escalated = true;
|
|
41333
41388
|
effectiveRounds = Math.min(effectiveRounds + decision.grantedRounds, input.rounds * 2);
|
|
41334
41389
|
} else {
|
|
41335
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, ...decision ? { escalation: decision } : {} };
|
|
41390
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: round, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [], ...decision ? { escalation: decision } : {} };
|
|
41336
41391
|
}
|
|
41337
41392
|
}
|
|
41338
41393
|
artifact = await input.revise(artifact, adj.accepted);
|
|
41339
41394
|
}
|
|
41340
|
-
return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history };
|
|
41395
|
+
return { outcome: "CONSENSUS_BLOCKED", rounds: effectiveRounds, finalArtifact: artifact, history, unresolved: history[history.length - 1]?.accepted ?? [] };
|
|
41341
41396
|
}
|
|
41342
41397
|
|
|
41343
41398
|
// src/consensus/spec-identity.ts
|
|
@@ -41636,7 +41691,8 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41636
41691
|
outcome: "CONSENSUS_BLOCKED",
|
|
41637
41692
|
...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
|
|
41638
41693
|
...droppedByCap.length > 0 ? { droppedByCap } : {},
|
|
41639
|
-
...result.escalation ? { escalation: result.escalation } : {}
|
|
41694
|
+
...result.escalation ? { escalation: result.escalation } : {},
|
|
41695
|
+
unresolved: result.unresolved
|
|
41640
41696
|
};
|
|
41641
41697
|
}
|
|
41642
41698
|
return { outcome: result.outcome };
|
|
@@ -42946,7 +43002,7 @@ function onComplete(outcome, input) {
|
|
|
42946
43002
|
}
|
|
42947
43003
|
const taskRef = parsed.taskTitle ? `${input.taskId} \u2014 ${parsed.taskTitle}` : input.taskId;
|
|
42948
43004
|
return {
|
|
42949
|
-
kind: "
|
|
43005
|
+
kind: "completed",
|
|
42950
43006
|
taskId: input.taskId,
|
|
42951
43007
|
body: `Spec drafted automatically for ${taskRef} \u2014 page ${parsed.pageId ?? "?"} in Specs/. The next scheduler tick will run the consensus review.`
|
|
42952
43008
|
};
|
|
@@ -44006,6 +44062,10 @@ function executionTurnBudgetMessage(budgetMs) {
|
|
|
44006
44062
|
function isExecutionTurnTimeoutReason(reason) {
|
|
44007
44063
|
return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX) || reason.startsWith(EXECUTION_TURN_STALL_PREFIX);
|
|
44008
44064
|
}
|
|
44065
|
+
function reportAutopilotLimitPause(client, reason, resetAt) {
|
|
44066
|
+
void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
|
|
44067
|
+
});
|
|
44068
|
+
}
|
|
44009
44069
|
function isDeliverableGateRetryableReason(reason) {
|
|
44010
44070
|
return DELIVERABLE_GATE_RETRYABLE_REASON_PREFIXES.some((prefix) => reason.startsWith(prefix));
|
|
44011
44071
|
}
|
|
@@ -44085,17 +44145,16 @@ var BASE_INSTRUCTIONS_PREFIX = [
|
|
|
44085
44145
|
" qualifies, produce the file; never merely offer one.",
|
|
44086
44146
|
"- Mixed output counts as retrieved: if any material part came from a retrieval, treat the whole",
|
|
44087
44147
|
" 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
|
-
"
|
|
44148
|
+
"- When your output is a file (markdown report, HTML), call `deliver_file`: pass the body inline",
|
|
44149
|
+
" as `content` and embed the returned markdown link. It needs no writable path, so it is the",
|
|
44150
|
+
" default even when this run path cannot write files \u2014 never inline large blobs into chat,",
|
|
44151
|
+
" spec, or note text.",
|
|
44091
44152
|
"- class=temp for one-off operator hand-offs (48h); class=durable for anything a spec",
|
|
44092
44153
|
" 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.",
|
|
44154
|
+
"- Anything user-facing delivered as a file MUST go through `deliver_file`. Files are capped at",
|
|
44155
|
+
" 5MB \u2014 split or summarize larger outputs.",
|
|
44156
|
+
"- Use `upload_artifact` only for a file already on disk (e.g. a screenshot the browser wrote);",
|
|
44157
|
+
" it needs a real local path, so never fake a path you did not write.",
|
|
44099
44158
|
"",
|
|
44100
44159
|
"Produced URLs \u2014 the operator's per-page links come only from you:",
|
|
44101
44160
|
"- Call `record_produced_url` after publishing EACH web page this task produced, so the task UI",
|
|
@@ -44425,6 +44484,17 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
|
|
|
44425
44484
|
}
|
|
44426
44485
|
const usage = !notifThreadId || notifThreadId === threadId ? extractTurnTokenUsage(method, params) : null;
|
|
44427
44486
|
if (usage) turnUsage = usage;
|
|
44487
|
+
if (method === "account/rateLimits/updated") {
|
|
44488
|
+
const snapshot = params?.rateLimits ?? null;
|
|
44489
|
+
const signal = detectExhaustedProviderLimit(snapshot, Date.now());
|
|
44490
|
+
if (signal) {
|
|
44491
|
+
reportAutopilotLimitPause(
|
|
44492
|
+
deps.client,
|
|
44493
|
+
"Codex provider limit exhausted during an autonomous execution run.",
|
|
44494
|
+
signal.resetAt
|
|
44495
|
+
);
|
|
44496
|
+
}
|
|
44497
|
+
}
|
|
44428
44498
|
const events = mapper.mapNotification(
|
|
44429
44499
|
{ method, params },
|
|
44430
44500
|
threadId
|
|
@@ -51060,10 +51130,11 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
51060
51130
|
" treat the whole answer as retrieved rather than splitting it between chat and a file.",
|
|
51061
51131
|
'- When it qualifies, PRODUCE the file. Offering one ("I can give you this as CSV/JSON") is the',
|
|
51062
51132
|
" failure, not the courtesy.",
|
|
51063
|
-
"- When it is a file:
|
|
51064
|
-
" carries meaning),
|
|
51065
|
-
"
|
|
51066
|
-
" the returned markdown link into your
|
|
51133
|
+
"- When it is a file: choose the format (`.md` for linear prose, `.html` when structure or",
|
|
51134
|
+
" layout carries meaning), then call `deliver_file` \u2014 pass the body inline as `content` and it",
|
|
51135
|
+
" needs no local file (class=temp for one-off downloads, class=durable when a spec/evidence",
|
|
51136
|
+
" page references it; pass taskId when known), and paste the returned markdown link into your",
|
|
51137
|
+
" reply. Use `upload_artifact` only for a file that already exists on disk (e.g. a screenshot).",
|
|
51067
51138
|
"- The chat reply must still stand alone as a complete answer even if the file is never opened:",
|
|
51068
51139
|
" the headline finding, the few points that matter, then the link. Never reduce it to",
|
|
51069
51140
|
' "see attached" or "details in the report".',
|
|
@@ -51300,6 +51371,10 @@ var DEFAULT_WORKTREE_RETENTION_HOURS = 48;
|
|
|
51300
51371
|
function worktreeRetentionMs() {
|
|
51301
51372
|
return readEnvInt("MRRLIN_DIRECTOR_WORKTREE_RETENTION_HOURS", DEFAULT_WORKTREE_RETENTION_HOURS) * 36e5;
|
|
51302
51373
|
}
|
|
51374
|
+
function reportAutopilotLimitPause2(client, reason, resetAt) {
|
|
51375
|
+
void client.pauseAutopilotForLimit({ source: "provider_limit", reason, resetAt }).catch(() => {
|
|
51376
|
+
});
|
|
51377
|
+
}
|
|
51303
51378
|
async function runOrphanWorktreeSweep(client, opts) {
|
|
51304
51379
|
const cutoff = Date.now() - worktreeRetentionMs();
|
|
51305
51380
|
const isReapable = (worktreePath) => {
|
|
@@ -52310,7 +52385,13 @@ function createBridgeMessageHandler(deps) {
|
|
|
52310
52385
|
pokeActivity();
|
|
52311
52386
|
if (method === "account/rateLimits/updated") {
|
|
52312
52387
|
const snapshot = params?.rateLimits ?? null;
|
|
52313
|
-
if (snapshot)
|
|
52388
|
+
if (snapshot) {
|
|
52389
|
+
sendInTurn({ type: "rateLimits", directorSessionId, rateLimits: snapshot });
|
|
52390
|
+
const signal = detectExhaustedProviderLimit(snapshot, Date.now());
|
|
52391
|
+
if (signal) {
|
|
52392
|
+
deps.reportLimitPause?.("Codex provider limit exhausted during a Director turn.", signal.resetAt);
|
|
52393
|
+
}
|
|
52394
|
+
}
|
|
52314
52395
|
}
|
|
52315
52396
|
const events = mapper.mapNotification({ method, params }, threadId);
|
|
52316
52397
|
for (const event of events) {
|
|
@@ -52412,6 +52493,10 @@ ${effectiveText}` : effectiveText;
|
|
|
52412
52493
|
forward(event);
|
|
52413
52494
|
}
|
|
52414
52495
|
if (outputEvents === 0) {
|
|
52496
|
+
deps.reportLimitPause?.(
|
|
52497
|
+
"Director turn completed without output; Codex likely hit its usage or credits limit.",
|
|
52498
|
+
null
|
|
52499
|
+
);
|
|
52415
52500
|
sendInTurn({
|
|
52416
52501
|
type: "error",
|
|
52417
52502
|
directorSessionId,
|
|
@@ -52909,6 +52994,7 @@ async function startDirectorBridge() {
|
|
|
52909
52994
|
turnInactivity: () => ({ idleMs: chatTurnIdleMs(), maxMs: chatTurnMaxMs() }),
|
|
52910
52995
|
checkoutRegistry,
|
|
52911
52996
|
setCodexAuth: (s) => health.setCodexAuth(s),
|
|
52997
|
+
reportLimitPause: (reason, resetAt) => reportAutopilotLimitPause2(client, reason, resetAt),
|
|
52912
52998
|
getPairingQr: async () => {
|
|
52913
52999
|
if (!relayConfigured || !remoteRelay) return { status: "not-configured" };
|
|
52914
53000
|
const payload = remoteRelay.qrPayload;
|
|
@@ -57383,32 +57469,49 @@ var HUMANIZE_TEMPLATE_BY_KIND = {
|
|
|
57383
57469
|
missing_requirement: "A required part of the task is still missing from the spec. What should it say?",
|
|
57384
57470
|
acceptance_criteria_detail: "The direction is clear, but the spec still needs a sharper success check. What should count as done?"
|
|
57385
57471
|
};
|
|
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
57472
|
function humanizeClarificationBody(clarification) {
|
|
57405
57473
|
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
|
-
);
|
|
57474
|
+
const context = clarification.rawQuestion.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
57410
57475
|
return context ? `${prefix} ${context}` : prefix;
|
|
57411
57476
|
}
|
|
57477
|
+
var BLOCK_DECISION_OPENER = "The reviewers could not agree on this spec";
|
|
57478
|
+
var BLOCK_BODY_TRAILER = " How should I proceed?";
|
|
57479
|
+
function renderUnresolvedPoints(unresolved) {
|
|
57480
|
+
return unresolved.map(
|
|
57481
|
+
(issue2) => issue2.text.replace(/\b(?:parseable|SPEC_IDENTITY_MISMATCH|machine-usable)\b/gi, "").replace(/\s+/g, " ").trim()
|
|
57482
|
+
).join("; ");
|
|
57483
|
+
}
|
|
57484
|
+
function renderBlockDecisionBody(taskRef, unresolved) {
|
|
57485
|
+
if (unresolved.length === 0) {
|
|
57486
|
+
return `${BLOCK_DECISION_OPENER} (task ${taskRef}), and their objections could not be read back.${BLOCK_BODY_TRAILER}`;
|
|
57487
|
+
}
|
|
57488
|
+
return `${BLOCK_DECISION_OPENER} (task ${taskRef}). Still unsettled: ${renderUnresolvedPoints(unresolved)}.${BLOCK_BODY_TRAILER}`;
|
|
57489
|
+
}
|
|
57490
|
+
function buildBlockDecisionOptions(diagnosis, reviewAgainAvailable) {
|
|
57491
|
+
const recommendReviewAgain = reviewAgainAvailable && (diagnosis === "INFRA" || diagnosis === "SUBSTANCE");
|
|
57492
|
+
const recommendEditSpec = !reviewAgainAvailable || diagnosis === "AMBIGUITY";
|
|
57493
|
+
const options = [];
|
|
57494
|
+
if (reviewAgainAvailable) {
|
|
57495
|
+
options.push({
|
|
57496
|
+
label: "Review again",
|
|
57497
|
+
description: "Send the current spec back for another review.",
|
|
57498
|
+
answer: REVIEW_AGAIN_ANSWER,
|
|
57499
|
+
...recommendReviewAgain ? { recommended: true } : {}
|
|
57500
|
+
});
|
|
57501
|
+
}
|
|
57502
|
+
options.push({
|
|
57503
|
+
label: "Edit spec",
|
|
57504
|
+
description: "Open the spec and change it by hand; the review restarts on save.",
|
|
57505
|
+
answer: EDIT_SPEC_ANSWER,
|
|
57506
|
+
...recommendEditSpec ? { recommended: true } : {}
|
|
57507
|
+
});
|
|
57508
|
+
options.push({
|
|
57509
|
+
label: "Proceed anyway",
|
|
57510
|
+
description: "Start the work with these points unsettled and recorded.",
|
|
57511
|
+
answer: PROCEED_ANYWAY_ANSWER
|
|
57512
|
+
});
|
|
57513
|
+
return options;
|
|
57514
|
+
}
|
|
57412
57515
|
var defaultNativeSpecGateBuilders = { buildSpecGateDeps, runSpecGate };
|
|
57413
57516
|
async function runNativeSpecGate(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
57414
57517
|
const deps = builders.buildSpecGateDeps({
|
|
@@ -57455,6 +57558,7 @@ function stripHumanizeOpener(body) {
|
|
|
57455
57558
|
}
|
|
57456
57559
|
return body;
|
|
57457
57560
|
}
|
|
57561
|
+
var DID_NOT_CONVERGE_ERROR = "Consensus blocked: reviewers did not converge within budget.";
|
|
57458
57562
|
async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
57459
57563
|
const operatorClarifications = result.operatorClarifications ?? [];
|
|
57460
57564
|
const diagnostics = result.diagnostics ?? [];
|
|
@@ -57462,9 +57566,10 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57462
57566
|
const taskId = page?.taskId ?? null;
|
|
57463
57567
|
if (!resolvedTaskId && taskId) resolvedTaskId = taskId;
|
|
57464
57568
|
const adjudicated = [];
|
|
57569
|
+
let taskInboxItems = [];
|
|
57465
57570
|
if (taskId) {
|
|
57466
57571
|
try {
|
|
57467
|
-
|
|
57572
|
+
taskInboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId });
|
|
57468
57573
|
for (const item of taskInboxItems) {
|
|
57469
57574
|
if (item.origin === "gate" && (item.status === "acknowledged" || item.status === "rejected")) {
|
|
57470
57575
|
adjudicated.push({ body: stripHumanizeOpener(item.body), answer: item.answer, status: item.status });
|
|
@@ -57533,6 +57638,35 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57533
57638
|
filedQuestionCount = toFile.length;
|
|
57534
57639
|
}
|
|
57535
57640
|
}
|
|
57641
|
+
if (filedQuestionCount === 0 && diagnostics.length === 0 && taskId && page) {
|
|
57642
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
57643
|
+
id: page.id,
|
|
57644
|
+
taskId: page.taskId ?? null
|
|
57645
|
+
});
|
|
57646
|
+
const reviewHash = domainScopedReviewHash2(page.contentHash, domain2);
|
|
57647
|
+
const baseBlockItemId = `II-${taskId}-gateblock-${(0, import_node_crypto9.createHash)("sha256").update(reviewHash).digest("hex").slice(0, 10)}`;
|
|
57648
|
+
let taskRef = taskId;
|
|
57649
|
+
try {
|
|
57650
|
+
const t = await ctx.client.getTask(input.projectSlug, taskId);
|
|
57651
|
+
if (t?.title) taskRef = `${taskId} \u2014 ${t.title}`;
|
|
57652
|
+
} catch {
|
|
57653
|
+
}
|
|
57654
|
+
const reviewAgainAvailable = !taskInboxItems.some(
|
|
57655
|
+
(item) => item.id === baseBlockItemId && item.status !== "pending" && item.answer === REVIEW_AGAIN_ANSWER
|
|
57656
|
+
);
|
|
57657
|
+
const blockItemId = reviewAgainAvailable ? baseBlockItemId : `${baseBlockItemId}-2`;
|
|
57658
|
+
try {
|
|
57659
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
57660
|
+
id: blockItemId,
|
|
57661
|
+
kind: "question",
|
|
57662
|
+
taskId,
|
|
57663
|
+
body: renderBlockDecisionBody(taskRef, result.unresolved ?? []),
|
|
57664
|
+
origin: "gate_block",
|
|
57665
|
+
questionOptions: buildBlockDecisionOptions(result.escalation?.diagnosis, reviewAgainAvailable)
|
|
57666
|
+
});
|
|
57667
|
+
} catch {
|
|
57668
|
+
}
|
|
57669
|
+
}
|
|
57536
57670
|
if (operatorClarifications.length > 0) {
|
|
57537
57671
|
try {
|
|
57538
57672
|
if (page) {
|
|
@@ -57573,7 +57707,7 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
57573
57707
|
} else if (operatorClarifications.length > 0) {
|
|
57574
57708
|
error51 = `Consensus blocked on ${operatorClarifications.length} clarification(s), but the spec page is not linked to a task or plan, so none could be filed as questions.`;
|
|
57575
57709
|
} else {
|
|
57576
|
-
error51 =
|
|
57710
|
+
error51 = DID_NOT_CONVERGE_ERROR;
|
|
57577
57711
|
}
|
|
57578
57712
|
if (result.escalation) {
|
|
57579
57713
|
error51 += ` (diagnosis: ${result.escalation.diagnosis})`;
|
|
@@ -57701,7 +57835,7 @@ function onComplete2(outcome, input) {
|
|
|
57701
57835
|
};
|
|
57702
57836
|
}
|
|
57703
57837
|
return {
|
|
57704
|
-
kind: "
|
|
57838
|
+
kind: "completed",
|
|
57705
57839
|
...taskId ? { taskId } : {},
|
|
57706
57840
|
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
57841
|
};
|
|
@@ -57715,6 +57849,14 @@ function onComplete2(outcome, input) {
|
|
|
57715
57849
|
body: taskRef ? `Consensus blocked on ${taskRef} (Specs/${input.pageId}); filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.` : `Consensus blocked on Specs/${input.pageId}; filed ${n} clarifying question${n === 1 ? "" : "s"} \u2014 answer to unblock the run.`
|
|
57716
57850
|
};
|
|
57717
57851
|
}
|
|
57852
|
+
if (outcome.kind === "failed" && outcome.error.startsWith(DID_NOT_CONVERGE_ERROR) && taskId) {
|
|
57853
|
+
return {
|
|
57854
|
+
kind: "info",
|
|
57855
|
+
suppressInbox: true,
|
|
57856
|
+
...taskId ? { taskId } : {},
|
|
57857
|
+
body: taskRef ? `Consensus blocked on ${taskRef} (Specs/${input.pageId}); the reviewers did not agree \u2014 answer the decision question to unblock the run.` : `Consensus blocked on Specs/${input.pageId}; the reviewers did not agree \u2014 answer the decision question to unblock the run.`
|
|
57858
|
+
};
|
|
57859
|
+
}
|
|
57718
57860
|
const handoffTaskRef = taskRef ? ` (${taskRef})` : "";
|
|
57719
57861
|
return {
|
|
57720
57862
|
kind: "handoff",
|
|
@@ -57832,7 +57974,7 @@ function onComplete3(outcome, input) {
|
|
|
57832
57974
|
};
|
|
57833
57975
|
}
|
|
57834
57976
|
return {
|
|
57835
|
-
kind: "
|
|
57977
|
+
kind: "completed",
|
|
57836
57978
|
...taskId ? { taskId } : {},
|
|
57837
57979
|
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
57980
|
};
|
|
@@ -57955,6 +58097,7 @@ var mcpToolNames = {
|
|
|
57955
58097
|
runSpecConsensus: "run_spec_consensus",
|
|
57956
58098
|
runCodeReviewerGate: "run_code_reviewer_gate",
|
|
57957
58099
|
uploadArtifact: "upload_artifact",
|
|
58100
|
+
deliverFile: "deliver_file",
|
|
57958
58101
|
listArtifactFiles: "list_artifact_files",
|
|
57959
58102
|
reclaimExecutionRunLease: "reclaim_execution_run_lease",
|
|
57960
58103
|
restartExecutionRun: "restart_execution_run",
|
|
@@ -58540,6 +58683,20 @@ var uploadArtifactInputSchema = projectScopedSchema.merge(
|
|
|
58540
58683
|
description: external_exports.string().optional().describe("Shown in the run's artifact history; ignored when runId is not provided.")
|
|
58541
58684
|
})
|
|
58542
58685
|
);
|
|
58686
|
+
var deliverFileInputSchema = projectScopedSchema.merge(
|
|
58687
|
+
external_exports.object({
|
|
58688
|
+
filename: external_exports.string().min(1).describe(
|
|
58689
|
+
"Bare filename with extension (e.g. 'notes.md'). No path separators or '..' allowed."
|
|
58690
|
+
),
|
|
58691
|
+
content: external_exports.string().describe("UTF-8 text content of the file."),
|
|
58692
|
+
class: mrrlinArtifactFileClassSchema.describe(
|
|
58693
|
+
"temp = 48h one-off hand-off; durable = 180d, for spec/evidence references."
|
|
58694
|
+
),
|
|
58695
|
+
taskId: mrrlinTaskIdSchema.optional(),
|
|
58696
|
+
runId: mrrlinExecutionRunIdSchema.optional(),
|
|
58697
|
+
description: external_exports.string().optional().describe("Shown in the run's artifact history; ignored when runId is not provided.")
|
|
58698
|
+
})
|
|
58699
|
+
);
|
|
58543
58700
|
var listArtifactFilesInputSchema = projectScopedSchema.merge(
|
|
58544
58701
|
external_exports.object({
|
|
58545
58702
|
taskId: mrrlinTaskIdSchema.optional(),
|
|
@@ -58625,6 +58782,7 @@ var mcpToolInputSchemas = {
|
|
|
58625
58782
|
[mcpToolNames.createInboxItem]: createInboxItemInputSchema,
|
|
58626
58783
|
[mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
|
|
58627
58784
|
[mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
|
|
58785
|
+
[mcpToolNames.deliverFile]: deliverFileInputSchema,
|
|
58628
58786
|
[mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
|
|
58629
58787
|
[mcpToolNames.reclaimExecutionRunLease]: reclaimExecutionRunLeaseInputSchema,
|
|
58630
58788
|
[mcpToolNames.restartExecutionRun]: restartExecutionRunInputSchema,
|
|
@@ -58762,6 +58920,7 @@ var mcpToolDescriptions = {
|
|
|
58762
58920
|
[mcpToolNames.createInboxItem]: "Create an operator-inbox item (proposal, check_in, question, handoff, or info) for a project.",
|
|
58763
58921
|
[mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
|
|
58764
58922
|
[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.",
|
|
58923
|
+
[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
58924
|
[mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
|
|
58766
58925
|
[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
58926
|
[mcpToolNames.restartExecutionRun]: "Restart a parked execution run (handoff_requested or handoff_queued) back to pending so the queue can resume it.",
|
|
@@ -58908,6 +59067,68 @@ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, proj
|
|
|
58908
59067
|
}
|
|
58909
59068
|
function createMrrlinTools(options) {
|
|
58910
59069
|
const client = createMrrlinClient(withAuth(options));
|
|
59070
|
+
async function uploadArtifactBytes(c, args) {
|
|
59071
|
+
const { bytes: bytes3, class: artifactClass, contentType, description, filename, projectSlug, runId, taskId } = args;
|
|
59072
|
+
const created = await c.createArtifactFile(projectSlug, {
|
|
59073
|
+
class: artifactClass,
|
|
59074
|
+
contentType,
|
|
59075
|
+
filename,
|
|
59076
|
+
runId,
|
|
59077
|
+
sizeBytes: bytes3.byteLength,
|
|
59078
|
+
taskId
|
|
59079
|
+
});
|
|
59080
|
+
let stage = "upload";
|
|
59081
|
+
try {
|
|
59082
|
+
if (created.upload.kind === "presigned-post") {
|
|
59083
|
+
const form = new FormData();
|
|
59084
|
+
for (const [field, value] of Object.entries(created.upload.fields)) {
|
|
59085
|
+
form.append(field, value);
|
|
59086
|
+
}
|
|
59087
|
+
form.append("file", new Blob([bytes3], { type: contentType }), filename);
|
|
59088
|
+
const bareFetch = options.fetch ?? globalThis.fetch;
|
|
59089
|
+
const uploadResponse = await bareFetch(created.upload.url, { body: form, method: "POST" });
|
|
59090
|
+
if (!uploadResponse.ok) {
|
|
59091
|
+
throw new Error(
|
|
59092
|
+
`Presigned upload failed: ${uploadResponse.status} ${await uploadResponse.text()}`
|
|
59093
|
+
);
|
|
59094
|
+
}
|
|
59095
|
+
} else {
|
|
59096
|
+
await c.uploadArtifactFileContent(projectSlug, created.artifact.id, bytes3, contentType);
|
|
59097
|
+
}
|
|
59098
|
+
stage = "complete";
|
|
59099
|
+
const completed = await c.completeArtifactFile(projectSlug, created.artifact.id);
|
|
59100
|
+
if (completed.runId) {
|
|
59101
|
+
stage = "append-run-artifact";
|
|
59102
|
+
await c.appendExecutionArtifact(projectSlug, completed.runId, {
|
|
59103
|
+
id: `EA-${completed.id}`,
|
|
59104
|
+
kind: "file",
|
|
59105
|
+
payload: {
|
|
59106
|
+
artifactFileId: completed.id,
|
|
59107
|
+
class: completed.class,
|
|
59108
|
+
contentType: completed.contentType,
|
|
59109
|
+
description: description ?? null,
|
|
59110
|
+
filename: completed.filename,
|
|
59111
|
+
sizeBytes: completed.sizeBytes,
|
|
59112
|
+
stableUrl: created.stableUrl
|
|
59113
|
+
}
|
|
59114
|
+
});
|
|
59115
|
+
}
|
|
59116
|
+
const markdown = contentType.startsWith("image/") ? `` : `[${completed.filename}](${created.stableUrl})`;
|
|
59117
|
+
return {
|
|
59118
|
+
artifactFile: completed,
|
|
59119
|
+
markdown,
|
|
59120
|
+
stableUrl: created.stableUrl
|
|
59121
|
+
};
|
|
59122
|
+
} catch (error51) {
|
|
59123
|
+
const original = error51 instanceof MrrlinApiError ? error51.code : error51 instanceof McpToolCodedError ? error51.code : "ARTIFACT_FILE_UPLOAD_FAILED";
|
|
59124
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
59125
|
+
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";
|
|
59126
|
+
throw new McpToolCodedError(
|
|
59127
|
+
original,
|
|
59128
|
+
`artifactFileId=${created.artifact.id} stableUrl=${created.stableUrl} (failed at ${stage}; ${note}): ${message}`
|
|
59129
|
+
);
|
|
59130
|
+
}
|
|
59131
|
+
}
|
|
58911
59132
|
return {
|
|
58912
59133
|
[mcpToolNames.archivePlan]: makeTool(
|
|
58913
59134
|
client,
|
|
@@ -59475,65 +59696,61 @@ function createMrrlinTools(options) {
|
|
|
59475
59696
|
);
|
|
59476
59697
|
}
|
|
59477
59698
|
const filename = import_node_path20.default.basename(filePath);
|
|
59478
|
-
|
|
59699
|
+
return uploadArtifactBytes(c, {
|
|
59700
|
+
bytes: bytes3,
|
|
59479
59701
|
class: artifactClass,
|
|
59480
59702
|
contentType,
|
|
59703
|
+
description,
|
|
59481
59704
|
filename,
|
|
59705
|
+
projectSlug,
|
|
59482
59706
|
runId,
|
|
59483
|
-
sizeBytes: bytes3.byteLength,
|
|
59484
59707
|
taskId
|
|
59485
59708
|
});
|
|
59486
|
-
|
|
59487
|
-
|
|
59488
|
-
|
|
59489
|
-
|
|
59490
|
-
|
|
59491
|
-
|
|
59492
|
-
|
|
59493
|
-
|
|
59494
|
-
|
|
59495
|
-
|
|
59496
|
-
|
|
59497
|
-
|
|
59498
|
-
|
|
59499
|
-
|
|
59500
|
-
|
|
59501
|
-
|
|
59502
|
-
|
|
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";
|
|
59709
|
+
}
|
|
59710
|
+
),
|
|
59711
|
+
[mcpToolNames.deliverFile]: makeTool(
|
|
59712
|
+
client,
|
|
59713
|
+
mcpToolNames.deliverFile,
|
|
59714
|
+
"ARTIFACT_FILE_UPLOAD_FAILED",
|
|
59715
|
+
"Unable to deliver artifact file.",
|
|
59716
|
+
async (c, { projectSlug, filename, content, class: artifactClass, taskId, runId, description }) => {
|
|
59717
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
59718
|
+
throw new McpToolCodedError(
|
|
59719
|
+
"ARTIFACT_FILE_NAME_INVALID",
|
|
59720
|
+
`Invalid filename '${filename}'; provide a bare filename with no path separators or '..'.`
|
|
59721
|
+
);
|
|
59722
|
+
}
|
|
59723
|
+
const extension2 = import_node_path20.default.extname(filename).toLowerCase();
|
|
59724
|
+
const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
|
|
59725
|
+
if (!contentType) {
|
|
59532
59726
|
throw new McpToolCodedError(
|
|
59533
|
-
|
|
59534
|
-
`
|
|
59727
|
+
"ARTIFACT_FILE_TYPE_UNSUPPORTED",
|
|
59728
|
+
`Unsupported artifact extension '${extension2}'. Allowed: ${Object.keys(ARTIFACT_EXTENSION_TYPES).join(", ")}.`
|
|
59535
59729
|
);
|
|
59536
59730
|
}
|
|
59731
|
+
const bytes3 = new Uint8Array(Buffer.from(content, "utf8"));
|
|
59732
|
+
if (bytes3.byteLength === 0) {
|
|
59733
|
+
throw new McpToolCodedError(
|
|
59734
|
+
"ARTIFACT_FILE_EMPTY",
|
|
59735
|
+
"File is empty (0 bytes); write content before uploading."
|
|
59736
|
+
);
|
|
59737
|
+
}
|
|
59738
|
+
if (bytes3.byteLength > MRRLIN_ARTIFACT_FILE_MAX_BYTES) {
|
|
59739
|
+
throw new McpToolCodedError(
|
|
59740
|
+
"ARTIFACT_FILE_TOO_LARGE",
|
|
59741
|
+
"Artifact exceeds 5MB; split or summarize instead."
|
|
59742
|
+
);
|
|
59743
|
+
}
|
|
59744
|
+
return uploadArtifactBytes(c, {
|
|
59745
|
+
bytes: bytes3,
|
|
59746
|
+
class: artifactClass,
|
|
59747
|
+
contentType,
|
|
59748
|
+
description,
|
|
59749
|
+
filename,
|
|
59750
|
+
projectSlug,
|
|
59751
|
+
runId,
|
|
59752
|
+
taskId
|
|
59753
|
+
});
|
|
59537
59754
|
}
|
|
59538
59755
|
),
|
|
59539
59756
|
[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.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"tsx": "^4.22.3",
|
|
24
24
|
"@mrrlin/client": "0.0.0",
|
|
25
25
|
"@mrrlin/codex-client": "0.0.0",
|
|
26
|
-
"@mrrlin/director-e2e": "0.0.0",
|
|
27
|
-
"@mrrlin/schemas": "0.0.0",
|
|
28
26
|
"@mrrlin/tsconfig": "0.0.0",
|
|
29
|
-
"@mrrlin/wiki": "0.0.0"
|
|
27
|
+
"@mrrlin/wiki": "0.0.0",
|
|
28
|
+
"@mrrlin/schemas": "0.0.0",
|
|
29
|
+
"@mrrlin/director-e2e": "0.0.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@iarna/toml": "^2.2.5",
|
|
@@ -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
|
}
|