@mrrlin-dev/mcp 0.4.122 → 0.4.124
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 +209 -92
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -39249,6 +39249,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
|
|
|
39249
39249
|
directorThreadProvider: external_exports.enum(["codex", "claude", "agy"]).nullable().optional(),
|
|
39250
39250
|
declaredOutcome: mrrlinDeclaredOutcomeSchema.nullable().optional(),
|
|
39251
39251
|
dueDate: external_exports.string().date().nullable().optional(),
|
|
39252
|
+
expectedUpdatedAt: external_exports.string().datetime().optional(),
|
|
39252
39253
|
impact: mrrlinTaskImpactSchema.optional(),
|
|
39253
39254
|
notes: external_exports.string().nullable().optional(),
|
|
39254
39255
|
planId: mrrlinPlanIdSchema.nullable().optional(),
|
|
@@ -40730,83 +40731,132 @@ function createMrrlinClient(config2) {
|
|
|
40730
40731
|
const customFetch = config2.fetch ?? globalThis.fetch;
|
|
40731
40732
|
const baseUrl = config2.baseUrl.replace(/\/$/, "");
|
|
40732
40733
|
const token = config2.token;
|
|
40733
|
-
|
|
40734
|
-
const
|
|
40734
|
+
function parseRetryAfter(response) {
|
|
40735
|
+
const header = response.headers.get("retry-after");
|
|
40736
|
+
if (!header)
|
|
40737
|
+
return null;
|
|
40738
|
+
const seconds = Number(header);
|
|
40739
|
+
if (!isNaN(seconds) && seconds >= 0) {
|
|
40740
|
+
return seconds * 1e3;
|
|
40741
|
+
}
|
|
40742
|
+
const dateMs = Date.parse(header);
|
|
40743
|
+
if (!isNaN(dateMs)) {
|
|
40744
|
+
const diff = dateMs - Date.now();
|
|
40745
|
+
return diff > 0 ? diff : 0;
|
|
40746
|
+
}
|
|
40747
|
+
return null;
|
|
40748
|
+
}
|
|
40749
|
+
const RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
40750
|
+
async function abortableDelay(ms, signal) {
|
|
40751
|
+
if (signal?.aborted) {
|
|
40752
|
+
throw new DOMException("The user aborted a request.", "AbortError");
|
|
40753
|
+
}
|
|
40754
|
+
return new Promise((resolve2, reject) => {
|
|
40755
|
+
const timer = setTimeout(() => {
|
|
40756
|
+
if (signal)
|
|
40757
|
+
signal.removeEventListener("abort", onAbort);
|
|
40758
|
+
resolve2();
|
|
40759
|
+
}, ms);
|
|
40760
|
+
function onAbort() {
|
|
40761
|
+
clearTimeout(timer);
|
|
40762
|
+
signal?.removeEventListener("abort", onAbort);
|
|
40763
|
+
reject(new DOMException("The user aborted a request.", "AbortError"));
|
|
40764
|
+
}
|
|
40765
|
+
if (signal) {
|
|
40766
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
40767
|
+
}
|
|
40768
|
+
});
|
|
40769
|
+
}
|
|
40770
|
+
async function executeFetch(path33, init) {
|
|
40771
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
40735
40772
|
const headers = new Headers(init?.headers ?? {});
|
|
40736
40773
|
if (token && !headers.has("authorization")) {
|
|
40737
40774
|
headers.set("authorization", `Bearer ${token}`);
|
|
40738
40775
|
}
|
|
40739
|
-
const
|
|
40740
|
-
|
|
40741
|
-
|
|
40742
|
-
|
|
40743
|
-
let violations = null;
|
|
40744
|
-
let message = `Mrrlin API ${method} ${path33} failed: ${response.status}`;
|
|
40776
|
+
const url2 = `${baseUrl}${path33}`;
|
|
40777
|
+
const isIdempotent = method === "GET" || method === "HEAD";
|
|
40778
|
+
const maxAttempts = isIdempotent ? 3 : 1;
|
|
40779
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
40745
40780
|
try {
|
|
40746
|
-
|
|
40747
|
-
|
|
40748
|
-
|
|
40749
|
-
|
|
40750
|
-
|
|
40751
|
-
|
|
40752
|
-
}
|
|
40753
|
-
|
|
40754
|
-
|
|
40755
|
-
|
|
40756
|
-
|
|
40781
|
+
if (init?.signal?.aborted) {
|
|
40782
|
+
throw new DOMException("The user aborted a request.", "AbortError");
|
|
40783
|
+
}
|
|
40784
|
+
const response = await customFetch(url2, { ...init, headers });
|
|
40785
|
+
if (response.ok) {
|
|
40786
|
+
return response;
|
|
40787
|
+
}
|
|
40788
|
+
if (isIdempotent && RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxAttempts - 1) {
|
|
40789
|
+
const retryAfterMs = parseRetryAfter(response);
|
|
40790
|
+
if (retryAfterMs !== null && retryAfterMs > 1e4) {
|
|
40791
|
+
return response;
|
|
40792
|
+
}
|
|
40793
|
+
try {
|
|
40794
|
+
await response.body?.cancel();
|
|
40795
|
+
} catch {
|
|
40757
40796
|
}
|
|
40797
|
+
const backoffMs = retryAfterMs ?? Math.floor(Math.random() * Math.min(2e3, 250 * Math.pow(2, attempt)));
|
|
40798
|
+
await abortableDelay(backoffMs, init?.signal);
|
|
40799
|
+
continue;
|
|
40758
40800
|
}
|
|
40759
|
-
|
|
40801
|
+
return response;
|
|
40802
|
+
} catch (err) {
|
|
40803
|
+
if (init?.signal?.aborted || err?.name === "AbortError") {
|
|
40804
|
+
throw err;
|
|
40805
|
+
}
|
|
40806
|
+
if (isIdempotent && attempt < maxAttempts - 1) {
|
|
40807
|
+
const backoffMs = Math.floor(Math.random() * Math.min(2e3, 250 * Math.pow(2, attempt)));
|
|
40808
|
+
await abortableDelay(backoffMs, init?.signal);
|
|
40809
|
+
continue;
|
|
40810
|
+
}
|
|
40811
|
+
throw err;
|
|
40760
40812
|
}
|
|
40761
|
-
|
|
40762
|
-
|
|
40763
|
-
|
|
40764
|
-
|
|
40765
|
-
|
|
40766
|
-
|
|
40767
|
-
|
|
40768
|
-
|
|
40769
|
-
|
|
40813
|
+
}
|
|
40814
|
+
throw new Error(`Failed to execute ${method} ${path33}`);
|
|
40815
|
+
}
|
|
40816
|
+
async function handleResponseError(response, method, path33) {
|
|
40817
|
+
const body = await response.text();
|
|
40818
|
+
let code = `HTTP_${response.status}`;
|
|
40819
|
+
let violations = null;
|
|
40820
|
+
let message = `Mrrlin API ${method} ${path33} failed: ${response.status}`;
|
|
40821
|
+
try {
|
|
40822
|
+
const parsed = JSON.parse(body);
|
|
40823
|
+
const violationsParsed = apiViolationsErrorSchema.safeParse(parsed);
|
|
40824
|
+
if (violationsParsed.success) {
|
|
40825
|
+
code = violationsParsed.data.code;
|
|
40826
|
+
violations = violationsParsed.data.violations;
|
|
40827
|
+
message = violationsParsed.data.message;
|
|
40828
|
+
} else {
|
|
40829
|
+
const errorParsed = apiErrorSchema.safeParse(parsed);
|
|
40830
|
+
if (errorParsed.success) {
|
|
40831
|
+
code = errorParsed.data.code;
|
|
40832
|
+
message = errorParsed.data.message;
|
|
40833
|
+
}
|
|
40834
|
+
}
|
|
40835
|
+
} catch {
|
|
40836
|
+
}
|
|
40837
|
+
throw new MrrlinApiError({
|
|
40838
|
+
code,
|
|
40839
|
+
method,
|
|
40840
|
+
path: path33,
|
|
40841
|
+
responseBody: body,
|
|
40842
|
+
status: response.status,
|
|
40843
|
+
violations,
|
|
40844
|
+
message
|
|
40845
|
+
});
|
|
40846
|
+
}
|
|
40847
|
+
async function request(path33, init) {
|
|
40848
|
+
const method = init?.method ?? "GET";
|
|
40849
|
+
const response = await executeFetch(path33, init);
|
|
40850
|
+
if (!response.ok) {
|
|
40851
|
+
await handleResponseError(response, method, path33);
|
|
40770
40852
|
}
|
|
40771
40853
|
return response.json();
|
|
40772
40854
|
}
|
|
40773
40855
|
async function requestText(path33, init) {
|
|
40774
40856
|
const method = init?.method ?? "GET";
|
|
40775
|
-
const
|
|
40776
|
-
if (token && !headers.has("authorization")) {
|
|
40777
|
-
headers.set("authorization", `Bearer ${token}`);
|
|
40778
|
-
}
|
|
40779
|
-
const response = await customFetch(`${baseUrl}${path33}`, { ...init, headers });
|
|
40857
|
+
const response = await executeFetch(path33, init);
|
|
40780
40858
|
if (!response.ok) {
|
|
40781
|
-
|
|
40782
|
-
let code = `HTTP_${response.status}`;
|
|
40783
|
-
let violations = null;
|
|
40784
|
-
let message = `Mrrlin API ${method} ${path33} failed: ${response.status}`;
|
|
40785
|
-
try {
|
|
40786
|
-
const parsed = JSON.parse(body);
|
|
40787
|
-
const violationsParsed = apiViolationsErrorSchema.safeParse(parsed);
|
|
40788
|
-
if (violationsParsed.success) {
|
|
40789
|
-
code = violationsParsed.data.code;
|
|
40790
|
-
violations = violationsParsed.data.violations;
|
|
40791
|
-
message = violationsParsed.data.message;
|
|
40792
|
-
} else {
|
|
40793
|
-
const errorParsed = apiErrorSchema.safeParse(parsed);
|
|
40794
|
-
if (errorParsed.success) {
|
|
40795
|
-
code = errorParsed.data.code;
|
|
40796
|
-
message = errorParsed.data.message;
|
|
40797
|
-
}
|
|
40798
|
-
}
|
|
40799
|
-
} catch {
|
|
40800
|
-
}
|
|
40801
|
-
throw new MrrlinApiError({
|
|
40802
|
-
code,
|
|
40803
|
-
method,
|
|
40804
|
-
path: path33,
|
|
40805
|
-
responseBody: body,
|
|
40806
|
-
status: response.status,
|
|
40807
|
-
violations,
|
|
40808
|
-
message
|
|
40809
|
-
});
|
|
40859
|
+
await handleResponseError(response, method, path33);
|
|
40810
40860
|
}
|
|
40811
40861
|
return response.text();
|
|
40812
40862
|
}
|
|
@@ -41968,6 +42018,48 @@ function createMrrlinClient(config2) {
|
|
|
41968
42018
|
var import_promises3 = __toESM(require("node:fs/promises"), 1);
|
|
41969
42019
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
41970
42020
|
var INCLUDED_DOC_FOLDERS = /* @__PURE__ */ new Set(["_meta", "explanation", "how-to", "reference", "sdd", "tutorials"]);
|
|
42021
|
+
var cachedPreparedIndex = null;
|
|
42022
|
+
var preparedIndexMap = /* @__PURE__ */ new WeakMap();
|
|
42023
|
+
function prepareWikiChunks(chunks) {
|
|
42024
|
+
const documents = chunks.map((chunk) => tokenize(`${chunk.heading_path.join(" ")} ${chunk.chunk_text}`));
|
|
42025
|
+
const averageLength = documents.reduce((sum, tokens2) => sum + tokens2.length, 0) / Math.max(documents.length, 1);
|
|
42026
|
+
const documentFrequency = /* @__PURE__ */ new Map();
|
|
42027
|
+
const termFrequencies = documents.map((tokens2) => {
|
|
42028
|
+
const freqs = /* @__PURE__ */ new Map();
|
|
42029
|
+
for (const token of tokens2) {
|
|
42030
|
+
freqs.set(token, (freqs.get(token) ?? 0) + 1);
|
|
42031
|
+
}
|
|
42032
|
+
for (const token of freqs.keys()) {
|
|
42033
|
+
documentFrequency.set(token, (documentFrequency.get(token) ?? 0) + 1);
|
|
42034
|
+
}
|
|
42035
|
+
return freqs;
|
|
42036
|
+
});
|
|
42037
|
+
return {
|
|
42038
|
+
chunks,
|
|
42039
|
+
documents,
|
|
42040
|
+
termFrequencies,
|
|
42041
|
+
documentFrequency,
|
|
42042
|
+
averageLength
|
|
42043
|
+
};
|
|
42044
|
+
}
|
|
42045
|
+
async function getOrPrepareWikiIndex(indexPath) {
|
|
42046
|
+
const resolvedIndexPath = await resolveIndexPath(indexPath);
|
|
42047
|
+
const stat3 = await import_promises3.default.stat(resolvedIndexPath);
|
|
42048
|
+
if (cachedPreparedIndex && cachedPreparedIndex.filePath === resolvedIndexPath && cachedPreparedIndex.mtimeMs === stat3.mtimeMs && cachedPreparedIndex.size === stat3.size) {
|
|
42049
|
+
return cachedPreparedIndex;
|
|
42050
|
+
}
|
|
42051
|
+
const raw = await import_promises3.default.readFile(resolvedIndexPath, "utf8");
|
|
42052
|
+
const chunks = wikiKnowledgeIndexSchema.parse(JSON.parse(raw));
|
|
42053
|
+
const prepared = {
|
|
42054
|
+
...prepareWikiChunks(chunks),
|
|
42055
|
+
filePath: resolvedIndexPath,
|
|
42056
|
+
mtimeMs: stat3.mtimeMs,
|
|
42057
|
+
size: stat3.size
|
|
42058
|
+
};
|
|
42059
|
+
cachedPreparedIndex = prepared;
|
|
42060
|
+
preparedIndexMap.set(chunks, prepared);
|
|
42061
|
+
return prepared;
|
|
42062
|
+
}
|
|
41971
42063
|
function toPosixPath(input) {
|
|
41972
42064
|
return input.split(import_node_path5.default.sep).join("/");
|
|
41973
42065
|
}
|
|
@@ -42115,50 +42207,51 @@ async function buildWikiKnowledgeIndex(options = {}) {
|
|
|
42115
42207
|
const documents = await listWikiDocuments(options);
|
|
42116
42208
|
return wikiKnowledgeIndexSchema.parse(documents.flatMap(buildChunksForDocument));
|
|
42117
42209
|
}
|
|
42118
|
-
async function loadBuiltWikiKnowledgeIndex(indexPath) {
|
|
42119
|
-
const resolvedIndexPath = await resolveIndexPath(indexPath);
|
|
42120
|
-
const raw = await import_promises3.default.readFile(resolvedIndexPath, "utf8");
|
|
42121
|
-
return wikiKnowledgeIndexSchema.parse(JSON.parse(raw));
|
|
42122
|
-
}
|
|
42123
42210
|
function tokenize(input) {
|
|
42124
42211
|
return input.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
42125
42212
|
}
|
|
42126
|
-
function
|
|
42127
|
-
const queryTokens = tokenize(query);
|
|
42128
|
-
if (queryTokens.length === 0)
|
|
42129
|
-
return [];
|
|
42130
|
-
const documents = index.map((chunk) => tokenize(`${chunk.heading_path.join(" ")} ${chunk.chunk_text}`));
|
|
42131
|
-
const averageLength = documents.reduce((sum, tokens2) => sum + tokens2.length, 0) / Math.max(documents.length, 1);
|
|
42132
|
-
const documentFrequency = /* @__PURE__ */ new Map();
|
|
42133
|
-
for (const tokens2 of documents) {
|
|
42134
|
-
for (const token of new Set(tokens2)) {
|
|
42135
|
-
documentFrequency.set(token, (documentFrequency.get(token) ?? 0) + 1);
|
|
42136
|
-
}
|
|
42137
|
-
}
|
|
42213
|
+
function scorePreparedIndex(prepared, queryTokens, limit = 5) {
|
|
42138
42214
|
const k1 = 1.2;
|
|
42139
42215
|
const b = 0.75;
|
|
42140
|
-
const
|
|
42141
|
-
|
|
42142
|
-
|
|
42143
|
-
|
|
42144
|
-
|
|
42145
|
-
}
|
|
42216
|
+
const { chunks, documents, termFrequencies, documentFrequency, averageLength } = prepared;
|
|
42217
|
+
const totalDocs = documents.length;
|
|
42218
|
+
const scored = chunks.map((chunk, chunkIndex) => {
|
|
42219
|
+
const frequencies = termFrequencies[chunkIndex] ?? /* @__PURE__ */ new Map();
|
|
42220
|
+
const docLen = documents[chunkIndex]?.length ?? 0;
|
|
42146
42221
|
const score = queryTokens.reduce((sum, token) => {
|
|
42147
42222
|
const tf = frequencies.get(token) ?? 0;
|
|
42148
42223
|
if (tf === 0)
|
|
42149
42224
|
return sum;
|
|
42150
42225
|
const df = documentFrequency.get(token) ?? 0;
|
|
42151
|
-
const idf = Math.log(1 + (
|
|
42152
|
-
const denominator = tf + k1 * (1 - b + b * (
|
|
42226
|
+
const idf = Math.log(1 + (totalDocs - df + 0.5) / (df + 0.5));
|
|
42227
|
+
const denominator = tf + k1 * (1 - b + b * (docLen / Math.max(averageLength, 1)));
|
|
42153
42228
|
return sum + idf * (tf * (k1 + 1) / denominator);
|
|
42154
42229
|
}, 0);
|
|
42155
42230
|
return { ...chunk, score };
|
|
42156
42231
|
});
|
|
42157
42232
|
return scored.filter((result) => result.score > 0).sort((a, b2) => b2.score - a.score || a.wiki_url.localeCompare(b2.wiki_url)).slice(0, limit).map((result) => wikiKnowledgeSearchResultSchema.parse(result));
|
|
42158
42233
|
}
|
|
42234
|
+
function searchKnowledgeIndex(index, query, limit = 5) {
|
|
42235
|
+
const queryTokens = tokenize(query);
|
|
42236
|
+
if (queryTokens.length === 0)
|
|
42237
|
+
return [];
|
|
42238
|
+
let prepared = preparedIndexMap.get(index);
|
|
42239
|
+
if (!prepared) {
|
|
42240
|
+
prepared = prepareWikiChunks(index);
|
|
42241
|
+
preparedIndexMap.set(index, prepared);
|
|
42242
|
+
}
|
|
42243
|
+
return scorePreparedIndex(prepared, queryTokens, limit);
|
|
42244
|
+
}
|
|
42159
42245
|
async function searchKnowledge(query, options = {}) {
|
|
42160
|
-
|
|
42161
|
-
|
|
42246
|
+
if (options.docsRoot) {
|
|
42247
|
+
const index = await buildWikiKnowledgeIndex({ docsRoot: options.docsRoot });
|
|
42248
|
+
return searchKnowledgeIndex(index, query, options.limit ?? 5);
|
|
42249
|
+
}
|
|
42250
|
+
const prepared = await getOrPrepareWikiIndex(options.indexPath);
|
|
42251
|
+
const queryTokens = tokenize(query);
|
|
42252
|
+
if (queryTokens.length === 0)
|
|
42253
|
+
return [];
|
|
42254
|
+
return scorePreparedIndex(prepared, queryTokens, options.limit ?? 5);
|
|
42162
42255
|
}
|
|
42163
42256
|
|
|
42164
42257
|
// src/consensus/verdict.ts
|
|
@@ -62120,7 +62213,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
|
|
|
62120
62213
|
}
|
|
62121
62214
|
|
|
62122
62215
|
// src/_generated/version.ts
|
|
62123
|
-
var PKG_VERSION = "0.4.
|
|
62216
|
+
var PKG_VERSION = "0.4.124";
|
|
62124
62217
|
|
|
62125
62218
|
// src/receipt-signing.ts
|
|
62126
62219
|
var import_node_crypto13 = __toESM(require("node:crypto"), 1);
|
|
@@ -68082,7 +68175,7 @@ async function askCodeHostOnce(deps, projectSlug, taskId) {
|
|
|
68082
68175
|
try {
|
|
68083
68176
|
const snapshot = await deps.client.getProjectSnapshot(projectSlug);
|
|
68084
68177
|
if (snapshot.project.codeHost != null) return;
|
|
68085
|
-
await deps.client.createInboxItem(projectSlug, {
|
|
68178
|
+
const item = await deps.client.createInboxItem(projectSlug, {
|
|
68086
68179
|
id: codeHostQuestionId(projectSlug),
|
|
68087
68180
|
kind: "question",
|
|
68088
68181
|
taskId,
|
|
@@ -68093,6 +68186,30 @@ async function askCodeHostOnce(deps, projectSlug, taskId) {
|
|
|
68093
68186
|
{ label: "Also put it on GitLab", answer: "gitlab", description: "You will be asked to connect GitLab." }
|
|
68094
68187
|
]
|
|
68095
68188
|
});
|
|
68189
|
+
if (item?.answer === "github" || item?.answer === "gitlab") {
|
|
68190
|
+
const host = item.answer === "github" ? "GitHub" : "GitLab";
|
|
68191
|
+
await deps.client.createInboxItem(projectSlug, {
|
|
68192
|
+
id: `${codeHostQuestionId(projectSlug)}-connect`,
|
|
68193
|
+
kind: "info",
|
|
68194
|
+
// Project-scoped on purpose: closing a task acknowledges that task's pending info items, which
|
|
68195
|
+
// would hide this note before the operator saw it (and its stable id prevents re-filing).
|
|
68196
|
+
body: `You asked for this project's code to also go to ${host}. Until ${host} is connected, code keeps being saved only on this computer. Connect it in Settings \u2192 Integrations, or ask the Director to help you connect ${host}.`
|
|
68197
|
+
});
|
|
68198
|
+
return;
|
|
68199
|
+
}
|
|
68200
|
+
if (item?.answer === "local") {
|
|
68201
|
+
try {
|
|
68202
|
+
await deps.client.renameProject(projectSlug, { codeHost: "local", codeHostOnlyIfUnset: true });
|
|
68203
|
+
} catch (error51) {
|
|
68204
|
+
console.warn(
|
|
68205
|
+
JSON.stringify({
|
|
68206
|
+
event: "code_host_answer_apply_failed",
|
|
68207
|
+
projectSlug,
|
|
68208
|
+
error: error51 instanceof Error ? error51.message : String(error51)
|
|
68209
|
+
})
|
|
68210
|
+
);
|
|
68211
|
+
}
|
|
68212
|
+
}
|
|
68096
68213
|
} catch {
|
|
68097
68214
|
}
|
|
68098
68215
|
}
|