@mrrlin-dev/mcp 0.3.13 → 0.3.14
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/README.md +2 -1
- package/dist/bin.cjs +107 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ The bridge auto-picks up new `@mrrlin-dev/mcp` versions after `npm install -g`.
|
|
|
10
10
|
|
|
11
11
|
- **L1 (self-update loop):** every 15 minutes (env-tunable) the bridge hashes its own `bin.cjs` and, on change, drains the current Codex turn cleanly and exits via `bridgeShutdown`. PM2 restarts. New code loads.
|
|
12
12
|
- **L2 (postinstall nudge):** `npm install -g @mrrlin-dev/mcp@latest` triggers a `SIGUSR2` to every running PM2 bridge, collapsing the L1 wait to seconds.
|
|
13
|
-
- **L3 (in-chat update offer):** every 6 hours the bridge checks `https://registry.npmjs.org/@mrrlin-dev/mcp/latest`. When a newer version is published, on your next chat turn the Director
|
|
13
|
+
- **L3 (in-chat update offer):** every 6 hours the bridge checks `https://registry.npmjs.org/@mrrlin-dev/mcp/latest`. When a newer version is published, on your next chat turn the Director explains *why* to update (latest fixes/improvements, changelog link) and offers a choice — let it run the update, or run `npm install -g @mrrlin-dev/mcp@latest` yourself. Say "yes" → Director runs the install itself. 48h cooldown if you decline.
|
|
14
|
+
- **L3.3 (inbox mirror):** the same fire also files a persistent `question` in the active project's inbox ("A newer version of the mrrlin bridge is available…", with the changelog link), so the update isn't only an ephemeral per-turn aside. The body is version-agnostic on purpose: the inbox dedup collapses it to one open row per project (reused across versions), and it re-appears at most once per 48h cooldown after you decide it. It's an opportunistic per-project signal, not a guaranteed cross-project notice.
|
|
14
15
|
- **L4 (process hygiene):** the bridge refuses to start a second instance on the same port and warns about orphan `mrrlin-mcp director-bridge` processes at startup.
|
|
15
16
|
|
|
16
17
|
### Env tuning
|
package/dist/bin.cjs
CHANGED
|
@@ -38336,6 +38336,7 @@ var DEFAULT_STARTUP_JITTER_MS = 30 * 1e3;
|
|
|
38336
38336
|
var DEFAULT_COOLDOWN_MS = 48 * 60 * 60 * 1e3;
|
|
38337
38337
|
var REGISTRY_URL_DEFAULT = "https://registry.npmjs.org/@mrrlin-dev/mcp/latest";
|
|
38338
38338
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+(-[A-Za-z0-9.-]+)?(\+[A-Za-z0-9.-]+)?$/;
|
|
38339
|
+
var UPDATE_INBOX_BODY = "A newer version of the mrrlin bridge is available. Updating pulls the latest fixes and improvements. Changelog: https://mrrlin-changelog.s3.eu-central-1.amazonaws.com/index.html . Ask the Director to update, or run `npm install -g @mrrlin-dev/mcp@latest` yourself.";
|
|
38339
38340
|
function isDisabled() {
|
|
38340
38341
|
return process.env.MRRLIN_DIRECTOR_BRIDGE_SELF_UPDATE === "0";
|
|
38341
38342
|
}
|
|
@@ -38382,9 +38383,15 @@ function startSelfUpdate(deps) {
|
|
|
38382
38383
|
});
|
|
38383
38384
|
return `[bridge:update] An update for @mrrlin-dev/mcp is available: ${pkgVersion} -> ${latestVersion}.
|
|
38384
38385
|
|
|
38385
|
-
This turn, at a natural pause,
|
|
38386
|
+
This turn, at a natural pause, RAISE this with the operator IN THEIR LANGUAGE (mirror the language they are chatting in). Frame it as a short offer with a choice, and ALWAYS explain WHY it is worth doing:
|
|
38386
38387
|
|
|
38387
|
-
|
|
38388
|
+
1. WHY (never skip this): give a one-line reason the update matters - do NOT present a bare version bump. Prefer specifics: if you can read the release notes, summarize in one short sentence what is new/fixed. The changelog is at https://mrrlin-changelog.s3.eu-central-1.amazonaws.com/index.html. If you cannot get specifics, fall back to a generic but honest reason, e.g. "it pulls the latest bug fixes and improvements for the bridge".
|
|
38389
|
+
|
|
38390
|
+
2. THE CHOICE: offer both paths and let the operator pick - "want me to update it for you now?" OR "you can run it yourself: \`npm install -g @mrrlin-dev/mcp@latest\`". Phrase it as a yes/no, e.g. in English: "The bridge is on ${pkgVersion} but npm already has ${latestVersion}. It brings <why> - want me to run \`npm install -g @mrrlin-dev/mcp@latest\`, or would you rather run it yourself?".
|
|
38391
|
+
|
|
38392
|
+
ASK the operator first - do NOT install without explicit confirmation.
|
|
38393
|
+
|
|
38394
|
+
If the operator confirms you should do it: immediately run \`npm install -g @mrrlin-dev/mcp@latest\` via your shell tool and report the outcome in chat. After a successful install, the bridge auto-restarts via its self-update loop within a couple of minutes - you do NOT need to call pm2.
|
|
38388
38395
|
|
|
38389
38396
|
If the operator declines / says "later": acknowledge briefly and do not bring it up again this turn. The bridge has a cooldown and will re-offer in 48h.
|
|
38390
38397
|
|
|
@@ -38392,6 +38399,12 @@ If the install fails (permission denied, registry pinned, sudo required, network
|
|
|
38392
38399
|
|
|
38393
38400
|
Do NOT derail the current task - if the operator has an in-flight question, answer that first; the update offer is a brief aside, not the main subject.`;
|
|
38394
38401
|
}
|
|
38402
|
+
function updateInboxBody() {
|
|
38403
|
+
if (disabled) return null;
|
|
38404
|
+
if (latestVersion === null) return null;
|
|
38405
|
+
if (!import_semver.default.gt(latestVersion, pkgVersion)) return null;
|
|
38406
|
+
return UPDATE_INBOX_BODY;
|
|
38407
|
+
}
|
|
38395
38408
|
let startupBinSha = null;
|
|
38396
38409
|
try {
|
|
38397
38410
|
startupBinSha = sha256(readFile2(deps.binPath));
|
|
@@ -38545,12 +38558,13 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
|
|
|
38545
38558
|
getDrainOnQuiet: () => drainOnQuiet,
|
|
38546
38559
|
getLatestVersion: () => latestVersion,
|
|
38547
38560
|
isStale: () => latestVersion !== null && import_semver.default.gt(latestVersion, pkgVersion),
|
|
38548
|
-
maybeUpdateOffer
|
|
38561
|
+
maybeUpdateOffer,
|
|
38562
|
+
updateInboxBody
|
|
38549
38563
|
};
|
|
38550
38564
|
}
|
|
38551
38565
|
|
|
38552
38566
|
// src/_generated/version.ts
|
|
38553
|
-
var PKG_VERSION = "0.3.
|
|
38567
|
+
var PKG_VERSION = "0.3.14";
|
|
38554
38568
|
|
|
38555
38569
|
// src/api-base-url.ts
|
|
38556
38570
|
var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
|
|
@@ -38975,6 +38989,10 @@ var DEFAULT_TOUCH_INTERVAL_MS = 6e4;
|
|
|
38975
38989
|
var DEFAULT_CLAIM_INTERVAL_SECONDS = 30;
|
|
38976
38990
|
var DEFAULT_EXEC_TURN_TIMEOUT_SECONDS = 900;
|
|
38977
38991
|
var DEFAULT_CONTEXT_MAX_ARTIFACTS = 50;
|
|
38992
|
+
var EXECUTION_TURN_TIMEOUT_PREFIX = "Execution turn did not complete within";
|
|
38993
|
+
var EXECUTION_TURN_TIMEOUT_RETRY_CODE = "execution_turn_timeout_retry";
|
|
38994
|
+
var EXECUTION_TURN_TIMEOUT_RETRY_SUMMARY = "Execution turn timed out; requeued for one automatic retry.";
|
|
38995
|
+
var EXECUTION_TURN_TIMEOUT_RETRY_CURSOR = "execution_turn_timeout_retry:1";
|
|
38978
38996
|
function readPositiveIntEnv(name, fallback) {
|
|
38979
38997
|
const raw = (process.env[name] ?? "").trim();
|
|
38980
38998
|
if (!raw) return fallback;
|
|
@@ -38984,6 +39002,12 @@ function readPositiveIntEnv(name, fallback) {
|
|
|
38984
39002
|
function execTurnTimeoutMs() {
|
|
38985
39003
|
return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_EXEC_TURN_TIMEOUT_SECONDS", DEFAULT_EXEC_TURN_TIMEOUT_SECONDS) * 1e3;
|
|
38986
39004
|
}
|
|
39005
|
+
function executionTurnTimeoutMessage(timeoutMs) {
|
|
39006
|
+
return `${EXECUTION_TURN_TIMEOUT_PREFIX} ${timeoutMs}ms.`;
|
|
39007
|
+
}
|
|
39008
|
+
function isExecutionTurnTimeoutReason(reason) {
|
|
39009
|
+
return reason.startsWith(EXECUTION_TURN_TIMEOUT_PREFIX);
|
|
39010
|
+
}
|
|
38987
39011
|
function contextMaxArtifacts() {
|
|
38988
39012
|
return readPositiveIntEnv("MRRLIN_DIRECTOR_BRIDGE_CONTEXT_MAX_ARTIFACTS", DEFAULT_CONTEXT_MAX_ARTIFACTS);
|
|
38989
39013
|
}
|
|
@@ -39193,7 +39217,7 @@ async function driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, inp
|
|
|
39193
39217
|
},
|
|
39194
39218
|
interrupt: () => codex.turn.interrupt({ threadId }),
|
|
39195
39219
|
timeoutMs,
|
|
39196
|
-
timeoutMessage:
|
|
39220
|
+
timeoutMessage: executionTurnTimeoutMessage(timeoutMs),
|
|
39197
39221
|
onInterruptFailed: () => {
|
|
39198
39222
|
interruptFailed = true;
|
|
39199
39223
|
}
|
|
@@ -39452,7 +39476,36 @@ async function resetWedgedThread(deps, projectSlug, taskId, interruptFailed) {
|
|
|
39452
39476
|
});
|
|
39453
39477
|
}
|
|
39454
39478
|
async function failRun(deps, projectSlug, task, runId, leaseId, reason) {
|
|
39455
|
-
|
|
39479
|
+
let currentRun;
|
|
39480
|
+
try {
|
|
39481
|
+
currentRun = await deps.client.getExecutionRun(projectSlug, runId);
|
|
39482
|
+
} catch {
|
|
39483
|
+
return;
|
|
39484
|
+
}
|
|
39485
|
+
if (currentRun.status !== "running" || currentRun.leaseId !== leaseId) return;
|
|
39486
|
+
if (isExecutionTurnTimeoutReason(reason) && currentRun.checkpointCursor !== EXECUTION_TURN_TIMEOUT_RETRY_CURSOR) {
|
|
39487
|
+
try {
|
|
39488
|
+
await deps.client.updateExecutionRun(projectSlug, runId, {
|
|
39489
|
+
status: "pending",
|
|
39490
|
+
leaseId: null,
|
|
39491
|
+
checkpointCursor: EXECUTION_TURN_TIMEOUT_RETRY_CURSOR
|
|
39492
|
+
});
|
|
39493
|
+
await deps.client.appendExecutionArtifact(projectSlug, runId, {
|
|
39494
|
+
id: newArtifactId(),
|
|
39495
|
+
kind: "error",
|
|
39496
|
+
payload: {
|
|
39497
|
+
code: EXECUTION_TURN_TIMEOUT_RETRY_CODE,
|
|
39498
|
+
source: "execution-consumer",
|
|
39499
|
+
attempt: 1,
|
|
39500
|
+
reason,
|
|
39501
|
+
summary: EXECUTION_TURN_TIMEOUT_RETRY_SUMMARY
|
|
39502
|
+
}
|
|
39503
|
+
}).catch(() => {
|
|
39504
|
+
});
|
|
39505
|
+
return;
|
|
39506
|
+
} catch {
|
|
39507
|
+
}
|
|
39508
|
+
}
|
|
39456
39509
|
try {
|
|
39457
39510
|
await deps.client.createExecutionRunHandoff(projectSlug, runId, { reason });
|
|
39458
39511
|
} catch {
|
|
@@ -44136,6 +44189,24 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
44136
44189
|
"- Do not create a handoff for transient provider quota/runtime failures, retriable tool",
|
|
44137
44190
|
" errors, or vague uncertainty; retry cheaply or explain the transient issue in chat.",
|
|
44138
44191
|
"",
|
|
44192
|
+
"Deferred-verification contract \u2014 when the RESULT can only be judged later:",
|
|
44193
|
+
"- When a task's implementation is DONE but its outcome cannot be confirmed now because it",
|
|
44194
|
+
" depends on a delay you do not control \u2014 SEO ranking/traffic effects after (re)indexing,",
|
|
44195
|
+
" ad-campaign performance data accumulating, cache/DNS propagation, a scheduled external",
|
|
44196
|
+
" event \u2014 do NOT close the task and do NOT leave it `in_progress`. Move it to",
|
|
44197
|
+
" `awaiting_verification` via `update_task` and set `verificationDueAt` to the ISO-8601",
|
|
44198
|
+
" datetime when the effect should be measurable (e.g. ~2 weeks out for an SEO article).",
|
|
44199
|
+
" Use `followUpAt` instead when it is a softer check-in rather than a hard verification.",
|
|
44200
|
+
"- In the same `update_task`, or in the task notes, record WHAT to measure and HOW so a later",
|
|
44201
|
+
" turn can verify objectively (the metric, the baseline, the success threshold).",
|
|
44202
|
+
"- Do NOT use `awaiting_verification` for results you CAN confirm now \u2014 verify immediately",
|
|
44203
|
+
" (browser/HTTP/tests) and close or advance the task instead. It is only for genuinely",
|
|
44204
|
+
" time-delayed evidence, not for avoiding verification work.",
|
|
44205
|
+
"- When the task is later woken (its `verificationDueAt`/`followUpAt` has passed), gather the",
|
|
44206
|
+
" evidence, then close it \u2014 `closed_done` if the outcome met the bar, or `closed_no_result`",
|
|
44207
|
+
" if it did not (that state REQUIRES a linked Evidence wiki page); or, if the effect still",
|
|
44208
|
+
" is not measurable, re-set `verificationDueAt` to a new date with a reason.",
|
|
44209
|
+
"",
|
|
44139
44210
|
"File artifacts: when you produce a file for the operator (report, HTML, screenshot),",
|
|
44140
44211
|
"upload it with `upload_artifact` (class=temp for one-off downloads, class=durable when a",
|
|
44141
44212
|
"spec/evidence page references it; pass taskId when known) and paste the returned markdown",
|
|
@@ -45016,6 +45087,10 @@ function createBridgeMessageHandler(deps) {
|
|
|
45016
45087
|
const effectiveText = inputText.length > 0 ? inputText : imageDataUri ? DEFAULT_IMAGE_ONLY_PROMPT : "";
|
|
45017
45088
|
if (effectiveText.length > 0) {
|
|
45018
45089
|
const updateOffer = deps.maybeUpdateOffer ? deps.maybeUpdateOffer() : null;
|
|
45090
|
+
const updateOfferProjectSlug = msg.context?.projectSlug?.trim();
|
|
45091
|
+
if (updateOffer !== null && updateOfferProjectSlug && deps.fileUpdateInboxItem) {
|
|
45092
|
+
void deps.fileUpdateInboxItem(updateOfferProjectSlug);
|
|
45093
|
+
}
|
|
45019
45094
|
const composedPrefix = updateOffer ? contextPrefix ? `${updateOffer}
|
|
45020
45095
|
|
|
45021
45096
|
${contextPrefix}` : updateOffer : contextPrefix;
|
|
@@ -45433,7 +45508,32 @@ async function startDirectorBridge() {
|
|
|
45433
45508
|
sessionSinks.unbind(directorSessionId, send);
|
|
45434
45509
|
},
|
|
45435
45510
|
getSessionSink: (directorSessionId) => sessionSinks.get(directorSessionId),
|
|
45436
|
-
maybeUpdateOffer: () => selfUpdateHandle.maybeUpdateOffer()
|
|
45511
|
+
maybeUpdateOffer: () => selfUpdateHandle.maybeUpdateOffer(),
|
|
45512
|
+
fileUpdateInboxItem: async (projectSlug) => {
|
|
45513
|
+
const body = selfUpdateHandle.updateInboxBody();
|
|
45514
|
+
if (!body) return;
|
|
45515
|
+
const id = `II-MCPUPD-${(0, import_node_crypto5.randomUUID)()}`;
|
|
45516
|
+
try {
|
|
45517
|
+
const row = await client.createInboxItem(projectSlug, { id, kind: "question", body });
|
|
45518
|
+
process.stderr.write(
|
|
45519
|
+
`[mrrlin-mcp director-bridge] info bridge.self_update.inbox_filed ${JSON.stringify({
|
|
45520
|
+
projectSlug,
|
|
45521
|
+
itemId: row.id,
|
|
45522
|
+
deduped: row.id !== id
|
|
45523
|
+
})}
|
|
45524
|
+
`
|
|
45525
|
+
);
|
|
45526
|
+
} catch (err) {
|
|
45527
|
+
process.stderr.write(
|
|
45528
|
+
`[mrrlin-mcp director-bridge] warn bridge.self_update.inbox_file_failed ${JSON.stringify({
|
|
45529
|
+
projectSlug,
|
|
45530
|
+
errorClass: "inbox_write_failed",
|
|
45531
|
+
message: String(err?.message ?? err)
|
|
45532
|
+
})}
|
|
45533
|
+
`
|
|
45534
|
+
);
|
|
45535
|
+
}
|
|
45536
|
+
}
|
|
45437
45537
|
};
|
|
45438
45538
|
const bridgeLogger = createBridgeLogger(config2.stateDir, process.env);
|
|
45439
45539
|
let bridgeBinShaShort = "unknown00000";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"@types/ws": "^8.18.1",
|
|
23
23
|
"esbuild": "^0.24.0",
|
|
24
24
|
"tsx": "^4.22.3",
|
|
25
|
+
"@mrrlin/client": "0.0.0",
|
|
25
26
|
"@mrrlin/codex-client": "0.0.0",
|
|
26
27
|
"@mrrlin/director-e2e": "0.0.0",
|
|
27
|
-
"@mrrlin/client": "0.0.0",
|
|
28
28
|
"@mrrlin/schemas": "0.0.0",
|
|
29
29
|
"@mrrlin/tsconfig": "0.0.0",
|
|
30
30
|
"@mrrlin/wiki": "0.0.0"
|