jefrichat-mcp 0.48.10 → 0.48.11
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/http.js +251 -42
- package/dist/index.js +99 -24
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -32012,7 +32012,7 @@ var require_websocket = __commonJS({
|
|
|
32012
32012
|
var http2 = __require("http");
|
|
32013
32013
|
var net = __require("net");
|
|
32014
32014
|
var tls = __require("tls");
|
|
32015
|
-
var { randomBytes, createHash:
|
|
32015
|
+
var { randomBytes, createHash: createHash3 } = __require("crypto");
|
|
32016
32016
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
32017
32017
|
var { URL: URL2 } = __require("url");
|
|
32018
32018
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -32680,7 +32680,7 @@ var require_websocket = __commonJS({
|
|
|
32680
32680
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
32681
32681
|
return;
|
|
32682
32682
|
}
|
|
32683
|
-
const digest =
|
|
32683
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
32684
32684
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
32685
32685
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
32686
32686
|
return;
|
|
@@ -33049,7 +33049,7 @@ var require_websocket_server = __commonJS({
|
|
|
33049
33049
|
var EventEmitter = __require("events");
|
|
33050
33050
|
var http2 = __require("http");
|
|
33051
33051
|
var { Duplex } = __require("stream");
|
|
33052
|
-
var { createHash:
|
|
33052
|
+
var { createHash: createHash3 } = __require("crypto");
|
|
33053
33053
|
var extension3 = require_extension();
|
|
33054
33054
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
33055
33055
|
var subprotocol2 = require_subprotocol();
|
|
@@ -33356,7 +33356,7 @@ var require_websocket_server = __commonJS({
|
|
|
33356
33356
|
);
|
|
33357
33357
|
}
|
|
33358
33358
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
33359
|
-
const digest =
|
|
33359
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
33360
33360
|
const headers = [
|
|
33361
33361
|
"HTTP/1.1 101 Switching Protocols",
|
|
33362
33362
|
"Upgrade: websocket",
|
|
@@ -60785,8 +60785,12 @@ var JefriHttpClient = class {
|
|
|
60785
60785
|
/** DECLARE a status — a user-level choice that hits every connection the
|
|
60786
60786
|
* identity holds. The returned status is the SETTLED one, which may differ
|
|
60787
60787
|
* from the request if another lease outvoted it. */
|
|
60788
|
-
|
|
60789
|
-
|
|
60788
|
+
/** `declId` (optional) makes the declaration retry-safe: a re-POST of the
|
|
60789
|
+
* same id replays — answering the CURRENT settled state — instead of
|
|
60790
|
+
* reapplying, so a stale retry can never overwrite a status another device
|
|
60791
|
+
* chose in between. Reusing an id with a DIFFERENT status is a 409. */
|
|
60792
|
+
presence(status, declId) {
|
|
60793
|
+
return this.call("POST", "/api/presence", declId ? { status, declId } : { status });
|
|
60790
60794
|
}
|
|
60791
60795
|
/** KEEPALIVE — "still here", declaring nothing. This is the call for a timer.
|
|
60792
60796
|
* Heartbeating through presence("online") instead is a declaration on a
|
|
@@ -61519,8 +61523,10 @@ async function statelessPresenceBringUp(acp) {
|
|
|
61519
61523
|
try {
|
|
61520
61524
|
const hb = await acp.heartbeat();
|
|
61521
61525
|
if (hb.state === "signedOff") await acp.presence("online");
|
|
61526
|
+
return true;
|
|
61522
61527
|
} catch (e) {
|
|
61523
|
-
log("stateless presence bring-up failed (
|
|
61528
|
+
log("stateless presence bring-up failed (the sweep retries it):", e?.message ?? e);
|
|
61529
|
+
return false;
|
|
61524
61530
|
}
|
|
61525
61531
|
}
|
|
61526
61532
|
async function statelessSweepBeat(acp, pool, onAuthRevoked) {
|
|
@@ -61536,6 +61542,71 @@ async function statelessSweepBeat(acp, pool, onAuthRevoked) {
|
|
|
61536
61542
|
}
|
|
61537
61543
|
}
|
|
61538
61544
|
|
|
61545
|
+
// src/stateless-mcp.ts
|
|
61546
|
+
var rpcError = (res, status, code3, message) => res.status(status).json({ jsonrpc: "2.0", error: { code: code3, message }, id: null });
|
|
61547
|
+
function statelessMcpHandler(deps) {
|
|
61548
|
+
return async (req, res) => {
|
|
61549
|
+
if (deps.isDraining()) {
|
|
61550
|
+
res.setHeader("retry-after", "1");
|
|
61551
|
+
return rpcError(res, 503, -32e3, "server is shutting down \u2014 retry; another instance will answer");
|
|
61552
|
+
}
|
|
61553
|
+
const token = deps.bearer(req);
|
|
61554
|
+
if (!token)
|
|
61555
|
+
return rpcError(res, 401, -32001, "Missing Jefri Chat token \u2014 every request must include Authorization: Bearer <token> (or ?token=).");
|
|
61556
|
+
if (!deps.tryAcquireInflight(token))
|
|
61557
|
+
return rpcError(res, 429, -32003, "too many concurrent requests \u2014 slow down.");
|
|
61558
|
+
try {
|
|
61559
|
+
let acp;
|
|
61560
|
+
try {
|
|
61561
|
+
({ acp } = await deps.acquire(token));
|
|
61562
|
+
} catch (e) {
|
|
61563
|
+
if (deps.classifyError(e) === "auth")
|
|
61564
|
+
return rpcError(
|
|
61565
|
+
res,
|
|
61566
|
+
401,
|
|
61567
|
+
-32001,
|
|
61568
|
+
"Invalid or expired Jefri Chat token. Rotate it in the Jefri Chat web app (the agent's owner \u2192 \u{1F511} connect) and reconnect with the fresh command."
|
|
61569
|
+
);
|
|
61570
|
+
return rpcError(res, 502, -32002, `Hub unreachable: ${e?.message ?? e}`);
|
|
61571
|
+
}
|
|
61572
|
+
const mcp = new McpServer({ name: "jefrichat", version: "0.1.0" });
|
|
61573
|
+
const deferred = [];
|
|
61574
|
+
deps.registerTools(mcp, acp, { deferUntilDelivered: (fn) => deferred.push(fn) });
|
|
61575
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
61576
|
+
res.on("finish", () => {
|
|
61577
|
+
for (const fn of deferred) {
|
|
61578
|
+
try {
|
|
61579
|
+
void Promise.resolve(fn()).catch((e) => deps.log?.("deferred post-delivery work failed:", e?.message ?? e));
|
|
61580
|
+
} catch (e) {
|
|
61581
|
+
deps.log?.("deferred post-delivery work failed:", e?.message ?? e);
|
|
61582
|
+
}
|
|
61583
|
+
}
|
|
61584
|
+
deferred.length = 0;
|
|
61585
|
+
});
|
|
61586
|
+
res.on("close", () => {
|
|
61587
|
+
try {
|
|
61588
|
+
transport.close();
|
|
61589
|
+
} catch {
|
|
61590
|
+
}
|
|
61591
|
+
try {
|
|
61592
|
+
void mcp.close();
|
|
61593
|
+
} catch {
|
|
61594
|
+
}
|
|
61595
|
+
});
|
|
61596
|
+
await mcp.connect(transport);
|
|
61597
|
+
await transport.handleRequest(req, res, req.body);
|
|
61598
|
+
} catch (e) {
|
|
61599
|
+
deps.log?.("stateless request failed:", e?.message ?? e);
|
|
61600
|
+
if (!res.headersSent) rpcError(res, 500, -32603, "internal error handling the request");
|
|
61601
|
+
} finally {
|
|
61602
|
+
deps.releaseInflight(token);
|
|
61603
|
+
}
|
|
61604
|
+
};
|
|
61605
|
+
}
|
|
61606
|
+
function statelessMethodNotAllowed(_req, res) {
|
|
61607
|
+
res.status(405).json({ jsonrpc: "2.0", error: { code: -32e3, message: "stateless server: POST only (no SSE stream, no session to terminate)" }, id: null });
|
|
61608
|
+
}
|
|
61609
|
+
|
|
61539
61610
|
// src/conn-pool.ts
|
|
61540
61611
|
function attachRef(c, id) {
|
|
61541
61612
|
if (c.closed) return false;
|
|
@@ -61574,7 +61645,8 @@ function createKeyedPool() {
|
|
|
61574
61645
|
return v;
|
|
61575
61646
|
},
|
|
61576
61647
|
has: (key) => ready.has(key),
|
|
61577
|
-
size: () => ready.size
|
|
61648
|
+
size: () => ready.size,
|
|
61649
|
+
keys: () => [...ready.keys()]
|
|
61578
61650
|
};
|
|
61579
61651
|
}
|
|
61580
61652
|
|
|
@@ -61602,6 +61674,7 @@ import cp3 from "node:child_process";
|
|
|
61602
61674
|
import os4 from "node:os";
|
|
61603
61675
|
import nnet from "node:net";
|
|
61604
61676
|
import ndns from "node:dns/promises";
|
|
61677
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
61605
61678
|
import nhttp from "node:http";
|
|
61606
61679
|
import nhttps from "node:https";
|
|
61607
61680
|
|
|
@@ -62024,6 +62097,14 @@ async function confirmWrite(c, fire) {
|
|
|
62024
62097
|
const e = await err;
|
|
62025
62098
|
return e ? { error: e.message } : {};
|
|
62026
62099
|
}
|
|
62100
|
+
function retryKey(extra, parts) {
|
|
62101
|
+
const rid = extra?.requestId;
|
|
62102
|
+
if (rid === void 0 || rid === null) return void 0;
|
|
62103
|
+
const h = extra?.requestInfo?.headers;
|
|
62104
|
+
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
62105
|
+
const bucket = Math.floor(Date.now() / 6e5);
|
|
62106
|
+
return "mcp-" + createHash2("sha256").update(JSON.stringify([bucket, ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
62107
|
+
}
|
|
62027
62108
|
async function fetchHistory(c, convId) {
|
|
62028
62109
|
if (isStateless(c)) return c.history(convId);
|
|
62029
62110
|
const p = waitFor(c, "history", (e) => e.conversationId === convId, 4e3);
|
|
@@ -62482,8 +62563,11 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
62482
62563
|
text: external_exports.string().describe("the message body")
|
|
62483
62564
|
}
|
|
62484
62565
|
},
|
|
62485
|
-
async ({ to, text: text5 }) => withClient(async (c) => {
|
|
62486
|
-
const r = await confirmWrite(
|
|
62566
|
+
async ({ to, text: text5 }, extra) => withClient(async (c) => {
|
|
62567
|
+
const r = await confirmWrite(
|
|
62568
|
+
c,
|
|
62569
|
+
() => isStateless(c) ? c.message(to, text5, retryKey(extra, ["message", to, text5])) : c.message(to, text5)
|
|
62570
|
+
);
|
|
62487
62571
|
if (r.error) return fail(`Message rejected: ${r.error}`);
|
|
62488
62572
|
return ok3(`Sent to @${to}: "${text5}"`);
|
|
62489
62573
|
})
|
|
@@ -62640,7 +62724,7 @@ ${fp}`);
|
|
|
62640
62724
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
62641
62725
|
}
|
|
62642
62726
|
},
|
|
62643
|
-
async ({ to, path: path3, caption, dataUrl, fileUrl, fileName }) => withClient(async (c) => {
|
|
62727
|
+
async ({ to, path: path3, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c) => {
|
|
62644
62728
|
let name, mime, url;
|
|
62645
62729
|
if (path3) {
|
|
62646
62730
|
if (!ctx.local) {
|
|
@@ -62694,7 +62778,21 @@ ${psCmd}
|
|
|
62694
62778
|
const rawBytes = Buffer.from(url.split(",")[1] ?? "", "base64");
|
|
62695
62779
|
if (rawBytes.length > MAX_UPLOAD_BYTES)
|
|
62696
62780
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
62697
|
-
if (isStateless(c)
|
|
62781
|
+
if (isStateless(c)) {
|
|
62782
|
+
try {
|
|
62783
|
+
await c.sendFile(
|
|
62784
|
+
{ to, caption },
|
|
62785
|
+
name,
|
|
62786
|
+
mime,
|
|
62787
|
+
rawBytes,
|
|
62788
|
+
retryKey(extra, ["file", to, name, createHash2("sha256").update(rawBytes).digest("hex")])
|
|
62789
|
+
);
|
|
62790
|
+
} catch (e2) {
|
|
62791
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
62792
|
+
}
|
|
62793
|
+
return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
62794
|
+
}
|
|
62795
|
+
if (rawBytes.length > WS_SAFE_FILE_BYTES) {
|
|
62698
62796
|
try {
|
|
62699
62797
|
await uploadFileHttp(c, ctx.serverUrl, { to }, name, mime, rawBytes, caption);
|
|
62700
62798
|
} catch (e2) {
|
|
@@ -62722,13 +62820,27 @@ ${psCmd}
|
|
|
62722
62820
|
caption: external_exports.string().optional()
|
|
62723
62821
|
}
|
|
62724
62822
|
},
|
|
62725
|
-
async ({ to, fileName, content: content3, caption }) => withClient(async (c) => {
|
|
62823
|
+
async ({ to, fileName, content: content3, caption }, extra) => withClient(async (c) => {
|
|
62726
62824
|
const known = mimeOf(fileName);
|
|
62727
62825
|
const mime = known === "application/octet-stream" ? "text/plain" : known;
|
|
62728
62826
|
const contentBytes = Buffer.from(content3, "utf8");
|
|
62729
62827
|
if (contentBytes.length > MAX_UPLOAD_BYTES)
|
|
62730
62828
|
return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
62731
|
-
if (isStateless(c)
|
|
62829
|
+
if (isStateless(c)) {
|
|
62830
|
+
try {
|
|
62831
|
+
await c.sendFile(
|
|
62832
|
+
{ to, caption },
|
|
62833
|
+
fileName,
|
|
62834
|
+
mime,
|
|
62835
|
+
contentBytes,
|
|
62836
|
+
retryKey(extra, ["file", to, fileName, createHash2("sha256").update(contentBytes).digest("hex")])
|
|
62837
|
+
);
|
|
62838
|
+
} catch (e2) {
|
|
62839
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
62840
|
+
}
|
|
62841
|
+
return ok3(`Sent \u{1F4C4} ${fileName} (${MB(contentBytes.length)} MB) to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
62842
|
+
}
|
|
62843
|
+
if (contentBytes.length > WS_SAFE_FILE_BYTES) {
|
|
62732
62844
|
try {
|
|
62733
62845
|
await uploadFileHttp(c, ctx.serverUrl, { to }, fileName, mime, contentBytes, caption);
|
|
62734
62846
|
} catch (e2) {
|
|
@@ -63119,7 +63231,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63119
63231
|
},
|
|
63120
63232
|
async () => withClient(async (c) => {
|
|
63121
63233
|
const me = c.identity.username;
|
|
63122
|
-
const since = inboxWatermark.get(me);
|
|
63234
|
+
const since = ctx.stateless ? "stored" : inboxWatermark.get(me);
|
|
63123
63235
|
const url = `${ctx.serverUrl.replace(/\/$/, "")}/api/inbox` + (since ? `?since=${encodeURIComponent(since)}` : "");
|
|
63124
63236
|
let msgs = [];
|
|
63125
63237
|
try {
|
|
@@ -63133,8 +63245,24 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63133
63245
|
}
|
|
63134
63246
|
if (!msgs.length) return ok3("\u{1F4ED} No new messages.");
|
|
63135
63247
|
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
|
|
63136
|
-
|
|
63137
|
-
|
|
63248
|
+
if (ctx.stateless) {
|
|
63249
|
+
const last = msgs[msgs.length - 1];
|
|
63250
|
+
const ack = async () => {
|
|
63251
|
+
try {
|
|
63252
|
+
await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
|
|
63253
|
+
method: "POST",
|
|
63254
|
+
headers: { authorization: `Bearer ${c.token}`, "content-type": "application/json" },
|
|
63255
|
+
body: JSON.stringify({ until: last.createdAt, untilId: last.id })
|
|
63256
|
+
});
|
|
63257
|
+
} catch {
|
|
63258
|
+
}
|
|
63259
|
+
};
|
|
63260
|
+
if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
|
|
63261
|
+
else await ack();
|
|
63262
|
+
} else {
|
|
63263
|
+
inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
|
|
63264
|
+
saveWatermark();
|
|
63265
|
+
}
|
|
63138
63266
|
const groupNames = /* @__PURE__ */ new Map();
|
|
63139
63267
|
if (msgs.some((m) => m.groupId)) {
|
|
63140
63268
|
try {
|
|
@@ -63192,8 +63320,11 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63192
63320
|
text: external_exports.string().describe("the message to post to the group")
|
|
63193
63321
|
}
|
|
63194
63322
|
},
|
|
63195
|
-
async ({ groupId, text: text5 }) => withClient(async (c) => {
|
|
63196
|
-
const r = await confirmWrite(
|
|
63323
|
+
async ({ groupId, text: text5 }, extra) => withClient(async (c) => {
|
|
63324
|
+
const r = await confirmWrite(
|
|
63325
|
+
c,
|
|
63326
|
+
() => isStateless(c) ? c.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5])) : c.groupMessage(groupId, text5)
|
|
63327
|
+
);
|
|
63197
63328
|
if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
|
|
63198
63329
|
return ok3(`Posted to the group.`);
|
|
63199
63330
|
})
|
|
@@ -63212,7 +63343,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63212
63343
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
63213
63344
|
}
|
|
63214
63345
|
},
|
|
63215
|
-
async ({ groupId, path: path3, caption, dataUrl, fileUrl, fileName }) => withClient(async (c) => {
|
|
63346
|
+
async ({ groupId, path: path3, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c) => {
|
|
63216
63347
|
let name, mime, url;
|
|
63217
63348
|
if (path3) {
|
|
63218
63349
|
if (!ctx.local) {
|
|
@@ -63256,7 +63387,21 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63256
63387
|
const rawBytes = Buffer.from(url.split(",")[1] ?? "", "base64");
|
|
63257
63388
|
if (rawBytes.length > MAX_UPLOAD_BYTES)
|
|
63258
63389
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
63259
|
-
if (isStateless(c)
|
|
63390
|
+
if (isStateless(c)) {
|
|
63391
|
+
try {
|
|
63392
|
+
await c.sendFile(
|
|
63393
|
+
{ groupId, caption },
|
|
63394
|
+
name,
|
|
63395
|
+
mime,
|
|
63396
|
+
rawBytes,
|
|
63397
|
+
retryKey(extra, ["file", groupId, name, createHash2("sha256").update(rawBytes).digest("hex")])
|
|
63398
|
+
);
|
|
63399
|
+
} catch (e2) {
|
|
63400
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
63401
|
+
}
|
|
63402
|
+
return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to the group.`);
|
|
63403
|
+
}
|
|
63404
|
+
if (rawBytes.length > WS_SAFE_FILE_BYTES) {
|
|
63260
63405
|
try {
|
|
63261
63406
|
await uploadFileHttp(c, ctx.serverUrl, { groupId }, name, mime, rawBytes, caption);
|
|
63262
63407
|
} catch (e2) {
|
|
@@ -63341,9 +63486,11 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63341
63486
|
status: external_exports.enum(["online", "away", "busy", "coding", "reviewing", "planning", "offline"])
|
|
63342
63487
|
}
|
|
63343
63488
|
},
|
|
63344
|
-
async ({ status }) => withClient(async (c) => {
|
|
63345
|
-
const
|
|
63489
|
+
async ({ status }, extra) => withClient(async (c) => {
|
|
63490
|
+
const declId = isStateless(c) ? retryKey(extra, ["presence", status])?.replace(/^mcp-/, "d-") : void 0;
|
|
63491
|
+
const r = await confirmWrite(c, () => isStateless(c) ? c.presence(status, declId) : c.presence(status));
|
|
63346
63492
|
if (r.error) return fail(`Couldn't set status: ${r.error}`);
|
|
63493
|
+
ctx.onPresenceDeclared?.(status);
|
|
63347
63494
|
return ok3(`Status set to ${status}.`);
|
|
63348
63495
|
})
|
|
63349
63496
|
);
|
|
@@ -63541,6 +63688,7 @@ var sessions = /* @__PURE__ */ new Map();
|
|
|
63541
63688
|
var HOST_FRESH_MS = Number(process.env.MCP_HOST_FRESH_MS ?? 10 * 60 * 1e3);
|
|
63542
63689
|
var hubPool = createKeyedPool();
|
|
63543
63690
|
var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ?? 4 * 60 * 60 * 1e3);
|
|
63691
|
+
var STATELESS_POOL_IDLE_MS = Number(process.env.MCP_STATELESS_POOL_IDLE_MS ?? 10 * 60 * 1e3);
|
|
63544
63692
|
var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
|
|
63545
63693
|
var MAX_SESSIONS_PER_IDENTITY = Number(process.env.MCP_MAX_SESSIONS_PER_IDENTITY ?? 20);
|
|
63546
63694
|
var INIT_WINDOW_MS = 6e4;
|
|
@@ -63601,8 +63749,8 @@ async function acquirePooledConn(token, poolKey) {
|
|
|
63601
63749
|
if (STATELESS) {
|
|
63602
63750
|
const acp2 = new JefriHttpClient({ server: HUB, token });
|
|
63603
63751
|
await acp2.whoami();
|
|
63604
|
-
await statelessPresenceBringUp(acp2);
|
|
63605
|
-
return { acp: acp2, inbox: [], refs: /* @__PURE__ */ new Set(), closed: false };
|
|
63752
|
+
const broughtUp = await statelessPresenceBringUp(acp2);
|
|
63753
|
+
return { acp: acp2, inbox: [], refs: /* @__PURE__ */ new Set(), closed: false, lastSeen: Date.now(), needsBringUp: !broughtUp };
|
|
63606
63754
|
}
|
|
63607
63755
|
const acp = await JefriClient.connect({ server: HUB, token, status: "online" });
|
|
63608
63756
|
const inbox = [];
|
|
@@ -63625,15 +63773,30 @@ setInterval(() => {
|
|
|
63625
63773
|
for (const [sid, s] of sessions) {
|
|
63626
63774
|
if (now - s.lastActive > SESSION_TTL_MS) closeSession(sid, "idle");
|
|
63627
63775
|
}
|
|
63628
|
-
|
|
63629
|
-
|
|
63630
|
-
|
|
63631
|
-
|
|
63632
|
-
|
|
63633
|
-
|
|
63634
|
-
|
|
63635
|
-
|
|
63636
|
-
|
|
63776
|
+
if (STATELESS) {
|
|
63777
|
+
for (const key of hubPool.keys()) {
|
|
63778
|
+
const pooled = hubPool.get(key);
|
|
63779
|
+
if (!pooled) continue;
|
|
63780
|
+
if (now - (pooled.lastSeen ?? 0) > STATELESS_POOL_IDLE_MS) {
|
|
63781
|
+
hubPool.drop(key);
|
|
63782
|
+
continue;
|
|
63783
|
+
}
|
|
63784
|
+
if (pooled.needsBringUp) {
|
|
63785
|
+
void statelessPresenceBringUp(pooled.acp).then((ok4) => {
|
|
63786
|
+
if (ok4) pooled.needsBringUp = false;
|
|
63787
|
+
});
|
|
63788
|
+
} else {
|
|
63789
|
+
void statelessSweepBeat(pooled.acp, pooled, () => closePool(key, "auth revoked"));
|
|
63790
|
+
}
|
|
63791
|
+
}
|
|
63792
|
+
} else {
|
|
63793
|
+
const seenPools = /* @__PURE__ */ new Set();
|
|
63794
|
+
for (const s of sessions.values()) {
|
|
63795
|
+
if (seenPools.has(s.poolKey)) continue;
|
|
63796
|
+
seenPools.add(s.poolKey);
|
|
63797
|
+
const pooled = hubPool.get(s.poolKey);
|
|
63798
|
+
if (pooled) announcePoolHosts(s.poolKey, pooled);
|
|
63799
|
+
}
|
|
63637
63800
|
}
|
|
63638
63801
|
for (const [ip, e] of initHits) {
|
|
63639
63802
|
if (now > e.resetAt) initHits.delete(ip);
|
|
@@ -63697,7 +63860,7 @@ var health = (_req, res) => res.status(draining ? 503 : 200).json({
|
|
|
63697
63860
|
});
|
|
63698
63861
|
app.get("/health", health);
|
|
63699
63862
|
app.get("/mcp/health", health);
|
|
63700
|
-
|
|
63863
|
+
var statefulPost = async (req, res) => {
|
|
63701
63864
|
const sessionId = req.headers["mcp-session-id"];
|
|
63702
63865
|
if (sessionId && sessions.has(sessionId)) {
|
|
63703
63866
|
const s = sessions.get(sessionId);
|
|
@@ -63777,10 +63940,6 @@ app.post("/mcp", async (req, res) => {
|
|
|
63777
63940
|
res.status(502).json({ jsonrpc: "2.0", error: { code: -32002, message: `Hub unreachable: ${e?.message ?? e}` }, id: null });
|
|
63778
63941
|
return;
|
|
63779
63942
|
}
|
|
63780
|
-
if (STATELESS && pooled.hbSignedOff) {
|
|
63781
|
-
pooled.hbSignedOff = false;
|
|
63782
|
-
void statelessPresenceBringUp(pooled.acp);
|
|
63783
|
-
}
|
|
63784
63943
|
const acp = pooled.acp;
|
|
63785
63944
|
if (!acp.identity) {
|
|
63786
63945
|
closePool(poolKey, "no identity");
|
|
@@ -63864,7 +64023,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
63864
64023
|
error: { code: -32001, message: "Session expired \u2014 reinitialize (your token is still valid)" },
|
|
63865
64024
|
id: null
|
|
63866
64025
|
});
|
|
63867
|
-
}
|
|
64026
|
+
};
|
|
63868
64027
|
var bySession = async (req, res) => {
|
|
63869
64028
|
const sessionId = req.headers["mcp-session-id"];
|
|
63870
64029
|
const s = sessionId ? sessions.get(sessionId) : void 0;
|
|
@@ -63882,8 +64041,58 @@ var bySession = async (req, res) => {
|
|
|
63882
64041
|
touch(sessionId);
|
|
63883
64042
|
await s.transport.handleRequest(req, res);
|
|
63884
64043
|
};
|
|
63885
|
-
|
|
63886
|
-
app.
|
|
64044
|
+
if (STATELESS) {
|
|
64045
|
+
app.post(
|
|
64046
|
+
"/mcp",
|
|
64047
|
+
statelessMcpHandler({
|
|
64048
|
+
bearer,
|
|
64049
|
+
acquire: async (token) => {
|
|
64050
|
+
const poolKey = sha256(token).toString("hex");
|
|
64051
|
+
const pooled = await acquirePooledConn(token, poolKey);
|
|
64052
|
+
pooled.lastSeen = Date.now();
|
|
64053
|
+
return pooled;
|
|
64054
|
+
},
|
|
64055
|
+
// registerTools below closes over the POOLED entry via acp — the presence
|
|
64056
|
+
// resume/stop signal needs the pool, so it is looked up by identity of
|
|
64057
|
+
// the shared client (one pool owns exactly one client instance).
|
|
64058
|
+
classifyError: (e) => e instanceof JefriAuthError || e?.code === "ACP_AUTH_FAILED" || e instanceof JefriHttpError && (e.status === 401 || e.status === 403) ? "auth" : "hub",
|
|
64059
|
+
registerTools: (mcp, acp, hooks) => registerAcpTools(mcp, {
|
|
64060
|
+
ensureClient: async () => acp,
|
|
64061
|
+
inbox: [],
|
|
64062
|
+
serverUrl: HUB,
|
|
64063
|
+
local: false,
|
|
64064
|
+
stateless: true,
|
|
64065
|
+
deferUntilDelivered: hooks.deferUntilDelivered,
|
|
64066
|
+
// Round 26 #4a: a successful declaration RESUMES heartbeats (the
|
|
64067
|
+
// user came back — the hub cleared its sign-off marker, the pool
|
|
64068
|
+
// must clear its own), and a sign-off STOPS them proactively.
|
|
64069
|
+
onPresenceDeclared: (status) => {
|
|
64070
|
+
for (const key of hubPool.keys()) {
|
|
64071
|
+
const pooled = hubPool.get(key);
|
|
64072
|
+
if (pooled && pooled.acp === acp) {
|
|
64073
|
+
if (status === "offline") pooled.hbSignedOff = true;
|
|
64074
|
+
else {
|
|
64075
|
+
pooled.hbSignedOff = false;
|
|
64076
|
+
pooled.needsBringUp = false;
|
|
64077
|
+
}
|
|
64078
|
+
break;
|
|
64079
|
+
}
|
|
64080
|
+
}
|
|
64081
|
+
}
|
|
64082
|
+
}),
|
|
64083
|
+
isDraining: () => draining,
|
|
64084
|
+
tryAcquireInflight: (token) => tryAcquire(tokenInflight, sha256(token).toString("hex"), MAX_INFLIGHT_PER_TOKEN),
|
|
64085
|
+
releaseInflight: (token) => release(tokenInflight, sha256(token).toString("hex")),
|
|
64086
|
+
log: log2
|
|
64087
|
+
})
|
|
64088
|
+
);
|
|
64089
|
+
app.get("/mcp", statelessMethodNotAllowed);
|
|
64090
|
+
app.delete("/mcp", statelessMethodNotAllowed);
|
|
64091
|
+
} else {
|
|
64092
|
+
app.post("/mcp", statefulPost);
|
|
64093
|
+
app.get("/mcp", bySession);
|
|
64094
|
+
app.delete("/mcp", bySession);
|
|
64095
|
+
}
|
|
63887
64096
|
var server = http.createServer(app).listen(PORT, () => {
|
|
63888
64097
|
console.error(`
|
|
63889
64098
|
\u{1F310} Jefri Chat remote MCP (HTTP) on http://localhost:${PORT}/mcp`);
|
package/dist/index.js
CHANGED
|
@@ -9152,7 +9152,7 @@ var require_websocket = __commonJS({
|
|
|
9152
9152
|
var http = __require("http");
|
|
9153
9153
|
var net4 = __require("net");
|
|
9154
9154
|
var tls = __require("tls");
|
|
9155
|
-
var { randomBytes: randomBytes3, createHash:
|
|
9155
|
+
var { randomBytes: randomBytes3, createHash: createHash4 } = __require("crypto");
|
|
9156
9156
|
var { Duplex, Readable } = __require("stream");
|
|
9157
9157
|
var { URL: URL2 } = __require("url");
|
|
9158
9158
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9820,7 +9820,7 @@ var require_websocket = __commonJS({
|
|
|
9820
9820
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
9821
9821
|
return;
|
|
9822
9822
|
}
|
|
9823
|
-
const digest =
|
|
9823
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
9824
9824
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
9825
9825
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
9826
9826
|
return;
|
|
@@ -10189,7 +10189,7 @@ var require_websocket_server = __commonJS({
|
|
|
10189
10189
|
var EventEmitter = __require("events");
|
|
10190
10190
|
var http = __require("http");
|
|
10191
10191
|
var { Duplex } = __require("stream");
|
|
10192
|
-
var { createHash:
|
|
10192
|
+
var { createHash: createHash4 } = __require("crypto");
|
|
10193
10193
|
var extension3 = require_extension();
|
|
10194
10194
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
10195
10195
|
var subprotocol2 = require_subprotocol();
|
|
@@ -10496,7 +10496,7 @@ var require_websocket_server = __commonJS({
|
|
|
10496
10496
|
);
|
|
10497
10497
|
}
|
|
10498
10498
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
10499
|
-
const digest =
|
|
10499
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
10500
10500
|
const headers = [
|
|
10501
10501
|
"HTTP/1.1 101 Switching Protocols",
|
|
10502
10502
|
"Upgrade: websocket",
|
|
@@ -10891,7 +10891,7 @@ import fs6 from "node:fs";
|
|
|
10891
10891
|
import net from "node:net";
|
|
10892
10892
|
import os5 from "node:os";
|
|
10893
10893
|
import np5 from "node:path";
|
|
10894
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
10894
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
10895
10895
|
function parseInstanceName(name) {
|
|
10896
10896
|
if (name.startsWith(".")) return null;
|
|
10897
10897
|
const lastDot = name.lastIndexOf(".");
|
|
@@ -10922,7 +10922,7 @@ function safeUserParts(u) {
|
|
|
10922
10922
|
const raw = u || "unknown";
|
|
10923
10923
|
return {
|
|
10924
10924
|
base: raw.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 40) || "agent",
|
|
10925
|
-
hash:
|
|
10925
|
+
hash: createHash2("sha1").update(raw).digest("hex").slice(0, 8)
|
|
10926
10926
|
};
|
|
10927
10927
|
}
|
|
10928
10928
|
function safeUser2(u) {
|
|
@@ -10935,7 +10935,7 @@ function socketPathFor(username, home = os5.homedir(), platform = process.platfo
|
|
|
10935
10935
|
const preferred = np5.join(home, ".jefri", "sock", `${safe}.sock`);
|
|
10936
10936
|
if (Buffer.byteLength(preferred) <= 100) return preferred;
|
|
10937
10937
|
const uid = typeof process.getuid === "function" ? String(process.getuid()) : process.env.USERNAME ?? "";
|
|
10938
|
-
const scope =
|
|
10938
|
+
const scope = createHash2("sha1").update(`${home}\0${uid}`).digest("hex").slice(0, 8);
|
|
10939
10939
|
const { base, hash } = safeUserParts(username);
|
|
10940
10940
|
return np5.join(tmp, `jefri-${base.slice(0, 16)}-${hash}-${scope}.sock`);
|
|
10941
10941
|
}
|
|
@@ -12118,7 +12118,7 @@ __export(run_exports, {
|
|
|
12118
12118
|
tmuxRunArgv: () => tmuxRunArgv
|
|
12119
12119
|
});
|
|
12120
12120
|
import cp9 from "node:child_process";
|
|
12121
|
-
import { createHash as
|
|
12121
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
12122
12122
|
import readline2 from "node:readline";
|
|
12123
12123
|
function tmuxRunArgv(agentCmd, sessionName, cwd, envPairs = []) {
|
|
12124
12124
|
const env = envPairs.flatMap((e) => ["-e", e]);
|
|
@@ -12127,7 +12127,7 @@ function tmuxRunArgv(agentCmd, sessionName, cwd, envPairs = []) {
|
|
|
12127
12127
|
function sessionNameFor(agentCmd, cwd = process.cwd(), experimental = false) {
|
|
12128
12128
|
const exe = (agentCmd[0] || "agent").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 32);
|
|
12129
12129
|
const key = agentCmd.join("\0") + "\0" + cwd;
|
|
12130
|
-
const h =
|
|
12130
|
+
const h = createHash3("sha1").update(key + (experimental ? " x" : "")).digest("hex").slice(0, 8);
|
|
12131
12131
|
return `jefri-${exe}-${h}`;
|
|
12132
12132
|
}
|
|
12133
12133
|
function isShellExe(cmd) {
|
|
@@ -39391,6 +39391,7 @@ import cp3 from "node:child_process";
|
|
|
39391
39391
|
import os4 from "node:os";
|
|
39392
39392
|
import nnet from "node:net";
|
|
39393
39393
|
import ndns from "node:dns/promises";
|
|
39394
|
+
import { createHash } from "node:crypto";
|
|
39394
39395
|
import nhttp from "node:http";
|
|
39395
39396
|
import nhttps from "node:https";
|
|
39396
39397
|
|
|
@@ -40556,6 +40557,14 @@ async function confirmWrite(c2, fire) {
|
|
|
40556
40557
|
const e = await err;
|
|
40557
40558
|
return e ? { error: e.message } : {};
|
|
40558
40559
|
}
|
|
40560
|
+
function retryKey(extra, parts) {
|
|
40561
|
+
const rid = extra?.requestId;
|
|
40562
|
+
if (rid === void 0 || rid === null) return void 0;
|
|
40563
|
+
const h = extra?.requestInfo?.headers;
|
|
40564
|
+
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
40565
|
+
const bucket = Math.floor(Date.now() / 6e5);
|
|
40566
|
+
return "mcp-" + createHash("sha256").update(JSON.stringify([bucket, ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
40567
|
+
}
|
|
40559
40568
|
async function fetchHistory2(c2, convId) {
|
|
40560
40569
|
if (isStateless(c2)) return c2.history(convId);
|
|
40561
40570
|
const p = waitFor(c2, "history", (e) => e.conversationId === convId, 4e3);
|
|
@@ -41017,8 +41026,11 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
41017
41026
|
text: external_exports.string().describe("the message body")
|
|
41018
41027
|
}
|
|
41019
41028
|
},
|
|
41020
|
-
async ({ to, text: text5 }) => withClient(async (c2) => {
|
|
41021
|
-
const r = await confirmWrite(
|
|
41029
|
+
async ({ to, text: text5 }, extra) => withClient(async (c2) => {
|
|
41030
|
+
const r = await confirmWrite(
|
|
41031
|
+
c2,
|
|
41032
|
+
() => isStateless(c2) ? c2.message(to, text5, retryKey(extra, ["message", to, text5])) : c2.message(to, text5)
|
|
41033
|
+
);
|
|
41022
41034
|
if (r.error) return fail(`Message rejected: ${r.error}`);
|
|
41023
41035
|
return ok3(`Sent to @${to}: "${text5}"`);
|
|
41024
41036
|
})
|
|
@@ -41175,7 +41187,7 @@ ${fp}`);
|
|
|
41175
41187
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41176
41188
|
}
|
|
41177
41189
|
},
|
|
41178
|
-
async ({ to, path: path4, caption, dataUrl, fileUrl, fileName }) => withClient(async (c2) => {
|
|
41190
|
+
async ({ to, path: path4, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
|
|
41179
41191
|
let name, mime, url;
|
|
41180
41192
|
if (path4) {
|
|
41181
41193
|
if (!ctx.local) {
|
|
@@ -41229,7 +41241,21 @@ ${psCmd}
|
|
|
41229
41241
|
const rawBytes = Buffer.from(url.split(",")[1] ?? "", "base64");
|
|
41230
41242
|
if (rawBytes.length > MAX_UPLOAD_BYTES)
|
|
41231
41243
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41232
|
-
if (isStateless(c2)
|
|
41244
|
+
if (isStateless(c2)) {
|
|
41245
|
+
try {
|
|
41246
|
+
await c2.sendFile(
|
|
41247
|
+
{ to, caption },
|
|
41248
|
+
name,
|
|
41249
|
+
mime,
|
|
41250
|
+
rawBytes,
|
|
41251
|
+
retryKey(extra, ["file", to, name, createHash("sha256").update(rawBytes).digest("hex")])
|
|
41252
|
+
);
|
|
41253
|
+
} catch (e2) {
|
|
41254
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41255
|
+
}
|
|
41256
|
+
return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
41257
|
+
}
|
|
41258
|
+
if (rawBytes.length > WS_SAFE_FILE_BYTES) {
|
|
41233
41259
|
try {
|
|
41234
41260
|
await uploadFileHttp(c2, ctx.serverUrl, { to }, name, mime, rawBytes, caption);
|
|
41235
41261
|
} catch (e2) {
|
|
@@ -41257,13 +41283,27 @@ ${psCmd}
|
|
|
41257
41283
|
caption: external_exports.string().optional()
|
|
41258
41284
|
}
|
|
41259
41285
|
},
|
|
41260
|
-
async ({ to, fileName, content: content3, caption }) => withClient(async (c2) => {
|
|
41286
|
+
async ({ to, fileName, content: content3, caption }, extra) => withClient(async (c2) => {
|
|
41261
41287
|
const known = mimeOf(fileName);
|
|
41262
41288
|
const mime = known === "application/octet-stream" ? "text/plain" : known;
|
|
41263
41289
|
const contentBytes = Buffer.from(content3, "utf8");
|
|
41264
41290
|
if (contentBytes.length > MAX_UPLOAD_BYTES)
|
|
41265
41291
|
return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41266
|
-
if (isStateless(c2)
|
|
41292
|
+
if (isStateless(c2)) {
|
|
41293
|
+
try {
|
|
41294
|
+
await c2.sendFile(
|
|
41295
|
+
{ to, caption },
|
|
41296
|
+
fileName,
|
|
41297
|
+
mime,
|
|
41298
|
+
contentBytes,
|
|
41299
|
+
retryKey(extra, ["file", to, fileName, createHash("sha256").update(contentBytes).digest("hex")])
|
|
41300
|
+
);
|
|
41301
|
+
} catch (e2) {
|
|
41302
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41303
|
+
}
|
|
41304
|
+
return ok3(`Sent \u{1F4C4} ${fileName} (${MB(contentBytes.length)} MB) to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
41305
|
+
}
|
|
41306
|
+
if (contentBytes.length > WS_SAFE_FILE_BYTES) {
|
|
41267
41307
|
try {
|
|
41268
41308
|
await uploadFileHttp(c2, ctx.serverUrl, { to }, fileName, mime, contentBytes, caption);
|
|
41269
41309
|
} catch (e2) {
|
|
@@ -41654,7 +41694,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41654
41694
|
},
|
|
41655
41695
|
async () => withClient(async (c2) => {
|
|
41656
41696
|
const me = c2.identity.username;
|
|
41657
|
-
const since = inboxWatermark.get(me);
|
|
41697
|
+
const since = ctx.stateless ? "stored" : inboxWatermark.get(me);
|
|
41658
41698
|
const url = `${ctx.serverUrl.replace(/\/$/, "")}/api/inbox` + (since ? `?since=${encodeURIComponent(since)}` : "");
|
|
41659
41699
|
let msgs = [];
|
|
41660
41700
|
try {
|
|
@@ -41668,8 +41708,24 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41668
41708
|
}
|
|
41669
41709
|
if (!msgs.length) return ok3("\u{1F4ED} No new messages.");
|
|
41670
41710
|
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
|
|
41671
|
-
|
|
41672
|
-
|
|
41711
|
+
if (ctx.stateless) {
|
|
41712
|
+
const last = msgs[msgs.length - 1];
|
|
41713
|
+
const ack = async () => {
|
|
41714
|
+
try {
|
|
41715
|
+
await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
|
|
41716
|
+
method: "POST",
|
|
41717
|
+
headers: { authorization: `Bearer ${c2.token}`, "content-type": "application/json" },
|
|
41718
|
+
body: JSON.stringify({ until: last.createdAt, untilId: last.id })
|
|
41719
|
+
});
|
|
41720
|
+
} catch {
|
|
41721
|
+
}
|
|
41722
|
+
};
|
|
41723
|
+
if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
|
|
41724
|
+
else await ack();
|
|
41725
|
+
} else {
|
|
41726
|
+
inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
|
|
41727
|
+
saveWatermark();
|
|
41728
|
+
}
|
|
41673
41729
|
const groupNames = /* @__PURE__ */ new Map();
|
|
41674
41730
|
if (msgs.some((m) => m.groupId)) {
|
|
41675
41731
|
try {
|
|
@@ -41727,8 +41783,11 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41727
41783
|
text: external_exports.string().describe("the message to post to the group")
|
|
41728
41784
|
}
|
|
41729
41785
|
},
|
|
41730
|
-
async ({ groupId, text: text5 }) => withClient(async (c2) => {
|
|
41731
|
-
const r = await confirmWrite(
|
|
41786
|
+
async ({ groupId, text: text5 }, extra) => withClient(async (c2) => {
|
|
41787
|
+
const r = await confirmWrite(
|
|
41788
|
+
c2,
|
|
41789
|
+
() => isStateless(c2) ? c2.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5])) : c2.groupMessage(groupId, text5)
|
|
41790
|
+
);
|
|
41732
41791
|
if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
|
|
41733
41792
|
return ok3(`Posted to the group.`);
|
|
41734
41793
|
})
|
|
@@ -41747,7 +41806,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41747
41806
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41748
41807
|
}
|
|
41749
41808
|
},
|
|
41750
|
-
async ({ groupId, path: path4, caption, dataUrl, fileUrl, fileName }) => withClient(async (c2) => {
|
|
41809
|
+
async ({ groupId, path: path4, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
|
|
41751
41810
|
let name, mime, url;
|
|
41752
41811
|
if (path4) {
|
|
41753
41812
|
if (!ctx.local) {
|
|
@@ -41791,7 +41850,21 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41791
41850
|
const rawBytes = Buffer.from(url.split(",")[1] ?? "", "base64");
|
|
41792
41851
|
if (rawBytes.length > MAX_UPLOAD_BYTES)
|
|
41793
41852
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41794
|
-
if (isStateless(c2)
|
|
41853
|
+
if (isStateless(c2)) {
|
|
41854
|
+
try {
|
|
41855
|
+
await c2.sendFile(
|
|
41856
|
+
{ groupId, caption },
|
|
41857
|
+
name,
|
|
41858
|
+
mime,
|
|
41859
|
+
rawBytes,
|
|
41860
|
+
retryKey(extra, ["file", groupId, name, createHash("sha256").update(rawBytes).digest("hex")])
|
|
41861
|
+
);
|
|
41862
|
+
} catch (e2) {
|
|
41863
|
+
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41864
|
+
}
|
|
41865
|
+
return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to the group.`);
|
|
41866
|
+
}
|
|
41867
|
+
if (rawBytes.length > WS_SAFE_FILE_BYTES) {
|
|
41795
41868
|
try {
|
|
41796
41869
|
await uploadFileHttp(c2, ctx.serverUrl, { groupId }, name, mime, rawBytes, caption);
|
|
41797
41870
|
} catch (e2) {
|
|
@@ -41876,9 +41949,11 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41876
41949
|
status: external_exports.enum(["online", "away", "busy", "coding", "reviewing", "planning", "offline"])
|
|
41877
41950
|
}
|
|
41878
41951
|
},
|
|
41879
|
-
async ({ status }) => withClient(async (c2) => {
|
|
41880
|
-
const
|
|
41952
|
+
async ({ status }, extra) => withClient(async (c2) => {
|
|
41953
|
+
const declId = isStateless(c2) ? retryKey(extra, ["presence", status])?.replace(/^mcp-/, "d-") : void 0;
|
|
41954
|
+
const r = await confirmWrite(c2, () => isStateless(c2) ? c2.presence(status, declId) : c2.presence(status));
|
|
41881
41955
|
if (r.error) return fail(`Couldn't set status: ${r.error}`);
|
|
41956
|
+
ctx.onPresenceDeclared?.(status);
|
|
41882
41957
|
return ok3(`Status set to ${status}.`);
|
|
41883
41958
|
})
|
|
41884
41959
|
);
|
package/package.json
CHANGED