jefrichat-mcp 0.48.11 → 0.48.13
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 +134 -81
- package/dist/index.js +134 -81
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -62102,9 +62102,65 @@ function retryKey(extra, parts) {
|
|
|
62102
62102
|
if (rid === void 0 || rid === null) return void 0;
|
|
62103
62103
|
const h = extra?.requestInfo?.headers;
|
|
62104
62104
|
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
62105
|
-
|
|
62105
|
+
if (!ua) return void 0;
|
|
62106
|
+
const bucket = Math.floor(Date.now() / 75e3);
|
|
62106
62107
|
return "mcp-" + createHash2("sha256").update(JSON.stringify([bucket, ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
62107
62108
|
}
|
|
62109
|
+
function declKey(extra, parts) {
|
|
62110
|
+
const rid = extra?.requestId;
|
|
62111
|
+
if (rid === void 0 || rid === null) return void 0;
|
|
62112
|
+
const h = extra?.requestInfo?.headers;
|
|
62113
|
+
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
62114
|
+
return "d-" + createHash2("sha256").update(JSON.stringify([ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
62115
|
+
}
|
|
62116
|
+
var crossBasename = (p) => String(p).split(/[\\/]/).pop() || "file";
|
|
62117
|
+
var shPathArg = (p) => p.startsWith("~/") ? `"$HOME"${shq(p.slice(1))}` : shq(p);
|
|
62118
|
+
var psq = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
62119
|
+
var psPathArg = (p) => p.startsWith("~/") || p.startsWith("~\\") ? `($HOME + ${psq(p.slice(1))})` : psq(p);
|
|
62120
|
+
var DATAURL_INLINE_MAX = 256 * 1024;
|
|
62121
|
+
var dataUrlTooBig = (dataUrl) => dataUrl.length > DATAURL_INLINE_MAX * 1.4 ? `That dataUrl is ~${MB(dataUrl.length * 0.75)} MB decoded \u2014 far past the ${Math.round(DATAURL_INLINE_MAX / 1024)}KB inline limit. NEVER read a local file and base64 it through the model. Call this tool again with the file's PATH \u2014 you'll get a single-use upload command that sends it in seconds.` : null;
|
|
62122
|
+
async function remoteUploadCommand(c, ctx, target, path3, caption, where) {
|
|
62123
|
+
const fname = crossBasename(path3);
|
|
62124
|
+
const fmime = mimeOf(fname);
|
|
62125
|
+
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
62126
|
+
try {
|
|
62127
|
+
const g = await fetch(`${hub}/api/files/grant`, {
|
|
62128
|
+
method: "POST",
|
|
62129
|
+
headers: { authorization: `Bearer ${c.token}`, "content-type": "application/json" },
|
|
62130
|
+
body: JSON.stringify({ ...target.to ? { to: target.to } : { groupId: target.groupId }, fileName: fname, ...caption ? { caption } : {} })
|
|
62131
|
+
});
|
|
62132
|
+
if (g.ok) {
|
|
62133
|
+
const { uploadUrl, expiresInMs } = await g.json();
|
|
62134
|
+
const mins = Math.round((expiresInMs ?? 9e5) / 6e4);
|
|
62135
|
+
return {
|
|
62136
|
+
ok: `This cloud connector can't read your disk \u2014 but here is the fast path. Run this ONE command in the terminal (it uploads AND delivers the file to ${where}):
|
|
62137
|
+
|
|
62138
|
+
curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path3)}
|
|
62139
|
+
|
|
62140
|
+
Windows PowerShell instead:
|
|
62141
|
+
Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(path3)}
|
|
62142
|
+
|
|
62143
|
+
The link is single-use and expires in ${mins} min; the file name and caption are already attached to it. Run it now, then confirm delivery from its JSON response ({"ok":true,...}). Do NOT read the file and base64 it through dataUrl \u2014 that is minutes of model output and fails over ~3MB.`
|
|
62144
|
+
};
|
|
62145
|
+
}
|
|
62146
|
+
} catch {
|
|
62147
|
+
}
|
|
62148
|
+
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
62149
|
+
const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
|
|
62150
|
+
const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
62151
|
+
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path3)}`;
|
|
62152
|
+
return {
|
|
62153
|
+
fail: `This is the cloud connector, so it can't read "${fname}" off your computer, and the upload-link service didn't answer. Options:
|
|
62154
|
+
|
|
62155
|
+
\u2705 open the Jefri Chat web app and DRAG "${fname}" into the chat with ${where}:
|
|
62156
|
+
${dropLink}
|
|
62157
|
+
|
|
62158
|
+
\u2014 or from a terminal (set JEFRI_TOKEN from the app's Connect dialog first):
|
|
62159
|
+
${cmd}
|
|
62160
|
+
|
|
62161
|
+
Do NOT base64 the file through dataUrl \u2014 that streams megabytes through the model and fails over ~3MB.`
|
|
62162
|
+
};
|
|
62163
|
+
}
|
|
62108
62164
|
async function fetchHistory(c, convId) {
|
|
62109
62165
|
if (isStateless(c)) return c.history(convId);
|
|
62110
62166
|
const p = waitFor(c, "history", (e) => e.conversationId === convId, 4e3);
|
|
@@ -62564,10 +62620,17 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
62564
62620
|
}
|
|
62565
62621
|
},
|
|
62566
62622
|
async ({ to, text: text5 }, extra) => withClient(async (c) => {
|
|
62567
|
-
|
|
62568
|
-
|
|
62569
|
-
|
|
62570
|
-
|
|
62623
|
+
if (isStateless(c)) {
|
|
62624
|
+
try {
|
|
62625
|
+
const res = await c.message(to, text5, retryKey(extra, ["message", to, text5]));
|
|
62626
|
+
if (res?.deduplicated)
|
|
62627
|
+
return ok3(`Already delivered to @${to} \u2014 this request matched a recent identical send and was suppressed as a retry. If you meant it as a NEW message, send it again (it will not be suppressed twice).`);
|
|
62628
|
+
return ok3(`Sent to @${to}: "${text5}"`);
|
|
62629
|
+
} catch (e) {
|
|
62630
|
+
return fail(`Message rejected: ${e?.message ?? e}`);
|
|
62631
|
+
}
|
|
62632
|
+
}
|
|
62633
|
+
const r = await confirmWrite(c, () => c.message(to, text5));
|
|
62571
62634
|
if (r.error) return fail(`Message rejected: ${r.error}`);
|
|
62572
62635
|
return ok3(`Sent to @${to}: "${text5}"`);
|
|
62573
62636
|
})
|
|
@@ -62714,12 +62777,12 @@ ${fp}`);
|
|
|
62714
62777
|
"jefri_send_file",
|
|
62715
62778
|
{
|
|
62716
62779
|
title: "Send a file, PDF, or image",
|
|
62717
|
-
description:
|
|
62780
|
+
description: "Send a file to another Jefri Chat user/agent \u2014 PDF, image (jpg/png/gif/webp), video (mp4/mov), or any document. ALWAYS give the local file `path` (e.g. ~/Downloads/report.pdf) and an optional `caption` \u2014 even on the remote/cloud connector: there this tool answers with ONE ready-to-run terminal command (a single-use upload link) that sends the file in seconds. NEVER read the file yourself and base64 it into `dataUrl` \u2014 that streams megabytes through the model, takes minutes, and fails over ~3MB; `dataUrl` is ONLY for tiny content you already hold inline (small generated snippets). If you only have a public URL for the file, pass `fileUrl` and the connector fetches the bytes itself.",
|
|
62718
62781
|
inputSchema: {
|
|
62719
62782
|
to: external_exports.string().describe("recipient username"),
|
|
62720
62783
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
62721
62784
|
caption: external_exports.string().optional().describe("optional text caption to send with it"),
|
|
62722
|
-
dataUrl: external_exports.string().optional().describe("
|
|
62785
|
+
dataUrl: external_exports.string().optional().describe("ONLY for tiny inline content you already hold (max 256KB decoded). NEVER construct this by reading a local file \u2014 pass `path` instead."),
|
|
62723
62786
|
fileUrl: external_exports.string().optional().describe("alternatively, a public http(s) URL to fetch the file from (e.g. a generated image)"),
|
|
62724
62787
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
62725
62788
|
}
|
|
@@ -62728,31 +62791,8 @@ ${fp}`);
|
|
|
62728
62791
|
let name, mime, url;
|
|
62729
62792
|
if (path3) {
|
|
62730
62793
|
if (!ctx.local) {
|
|
62731
|
-
const
|
|
62732
|
-
|
|
62733
|
-
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
62734
|
-
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
62735
|
-
const dropLink = `${web}/?to=${encodeURIComponent(to)}`;
|
|
62736
|
-
const q = `to=${encodeURIComponent(to)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
62737
|
-
const uploadUrl = `${hub}/api/files?${q}`;
|
|
62738
|
-
const cmd = `curl -s -X POST ${shq(uploadUrl)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${fmime}" --data-binary @${shq(path3)}`;
|
|
62739
|
-
const psCmd = `Invoke-RestMethod -Uri "${uploadUrl}" -Method Post -Headers @{ Authorization = "Bearer $env:JEFRI_TOKEN" } -ContentType "${fmime}" -InFile "${path3}"`;
|
|
62740
|
-
return fail(
|
|
62741
|
-
`This is the cloud connector, so it can't read "${fname}" off your computer. Easiest fix:
|
|
62742
|
-
|
|
62743
|
-
\u2705 SIMPLE \u2014 open the Jefri Chat web app and DRAG "${fname}" into the chat with @${to}:
|
|
62744
|
-
${dropLink}
|
|
62745
|
-
|
|
62746
|
-
(If instead this is an image you GENERATED and have a public URL for, just call this tool again with \`fileUrl\` instead of \`path\` \u2014 the image sends with no drag.)
|
|
62747
|
-
|
|
62748
|
-
\u2014 Advanced (only if you prefer a terminal): run one of these to upload it directly \u2014
|
|
62749
|
-
\u2022 macOS / Linux:
|
|
62750
|
-
${cmd}
|
|
62751
|
-
\u2022 Windows (PowerShell):
|
|
62752
|
-
${psCmd}
|
|
62753
|
-
|
|
62754
|
-
(These read your token from the JEFRI_TOKEN env var \u2014 set it first from the app's Connect dialog: export JEFRI_TOKEN=\u2026 (macOS/Linux) or $env:JEFRI_TOKEN="\u2026" (PowerShell). The token isn't printed here on purpose.)`
|
|
62755
|
-
);
|
|
62794
|
+
const r = await remoteUploadCommand(c, ctx, { to }, path3, caption, `@${to}`);
|
|
62795
|
+
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
62756
62796
|
}
|
|
62757
62797
|
const abs = resolveLocalFile(path3);
|
|
62758
62798
|
if (!abs) {
|
|
@@ -62763,6 +62803,8 @@ ${psCmd}
|
|
|
62763
62803
|
mime = mimeOf(name);
|
|
62764
62804
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
62765
62805
|
} else if (dataUrl && fileName) {
|
|
62806
|
+
const tooBig = dataUrlTooBig(dataUrl);
|
|
62807
|
+
if (tooBig) return fail(tooBig);
|
|
62766
62808
|
name = fileName;
|
|
62767
62809
|
mime = dataUrl.match(/^data:([^;]+)/)?.[1] ?? mimeOf(fileName);
|
|
62768
62810
|
url = dataUrl;
|
|
@@ -62780,13 +62822,15 @@ ${psCmd}
|
|
|
62780
62822
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
62781
62823
|
if (isStateless(c)) {
|
|
62782
62824
|
try {
|
|
62783
|
-
await c.sendFile(
|
|
62825
|
+
const res = await c.sendFile(
|
|
62784
62826
|
{ to, caption },
|
|
62785
62827
|
name,
|
|
62786
62828
|
mime,
|
|
62787
62829
|
rawBytes,
|
|
62788
62830
|
retryKey(extra, ["file", to, name, createHash2("sha256").update(rawBytes).digest("hex")])
|
|
62789
62831
|
);
|
|
62832
|
+
if (res?.deduplicated)
|
|
62833
|
+
return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry (round 28 #2: suppression is SAID, never a false "Sent"). If you meant a new copy, send it again.`);
|
|
62790
62834
|
} catch (e2) {
|
|
62791
62835
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
62792
62836
|
}
|
|
@@ -62828,13 +62872,15 @@ ${psCmd}
|
|
|
62828
62872
|
return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
62829
62873
|
if (isStateless(c)) {
|
|
62830
62874
|
try {
|
|
62831
|
-
await c.sendFile(
|
|
62875
|
+
const res = await c.sendFile(
|
|
62832
62876
|
{ to, caption },
|
|
62833
62877
|
fileName,
|
|
62834
62878
|
mime,
|
|
62835
62879
|
contentBytes,
|
|
62836
62880
|
retryKey(extra, ["file", to, fileName, createHash2("sha256").update(contentBytes).digest("hex")])
|
|
62837
62881
|
);
|
|
62882
|
+
if (res?.deduplicated)
|
|
62883
|
+
return ok3(`Already delivered \u2014 ${fileName} matched a recent identical upload and was suppressed as a retry. If you meant a new copy, send it again.`);
|
|
62838
62884
|
} catch (e2) {
|
|
62839
62885
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
62840
62886
|
}
|
|
@@ -63244,25 +63290,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63244
63290
|
ctx.inbox.length = 0;
|
|
63245
63291
|
}
|
|
63246
63292
|
if (!msgs.length) return ok3("\u{1F4ED} No new messages.");
|
|
63247
|
-
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
|
|
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
|
-
}
|
|
63293
|
+
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : 1);
|
|
63266
63294
|
const groupNames = /* @__PURE__ */ new Map();
|
|
63267
63295
|
if (msgs.some((m) => m.groupId)) {
|
|
63268
63296
|
try {
|
|
@@ -63290,8 +63318,27 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63290
63318
|
const body = m.kind === "file" ? `\u{1F4CE} sent a file: ${m.fileName} \u2014 to save it call jefri_download_file(from: "${m.senderUsername}"${m.fileName ? `, fileName: "${m.fileName}"` : ""})` : m.content;
|
|
63291
63319
|
return `${at}@${m.senderUsername}${where(m)}: ${body}`;
|
|
63292
63320
|
}));
|
|
63293
|
-
|
|
63294
|
-
` + lines.join("\n")
|
|
63321
|
+
const rendered = `\u{1F4E8} ${lines.length} message(s):
|
|
63322
|
+
` + lines.join("\n");
|
|
63323
|
+
if (ctx.stateless) {
|
|
63324
|
+
const renderedIds = msgs.map((m) => m.id);
|
|
63325
|
+
const ack = async () => {
|
|
63326
|
+
try {
|
|
63327
|
+
await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
|
|
63328
|
+
method: "POST",
|
|
63329
|
+
headers: { authorization: `Bearer ${c.token}`, "content-type": "application/json" },
|
|
63330
|
+
body: JSON.stringify({ messageIds: renderedIds })
|
|
63331
|
+
});
|
|
63332
|
+
} catch {
|
|
63333
|
+
}
|
|
63334
|
+
};
|
|
63335
|
+
if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
|
|
63336
|
+
else await ack();
|
|
63337
|
+
} else {
|
|
63338
|
+
inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
|
|
63339
|
+
saveWatermark();
|
|
63340
|
+
}
|
|
63341
|
+
return ok3(rendered);
|
|
63295
63342
|
})
|
|
63296
63343
|
);
|
|
63297
63344
|
server2.registerTool(
|
|
@@ -63321,10 +63368,17 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63321
63368
|
}
|
|
63322
63369
|
},
|
|
63323
63370
|
async ({ groupId, text: text5 }, extra) => withClient(async (c) => {
|
|
63324
|
-
|
|
63325
|
-
|
|
63326
|
-
|
|
63327
|
-
|
|
63371
|
+
if (isStateless(c)) {
|
|
63372
|
+
try {
|
|
63373
|
+
const res = await c.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5]));
|
|
63374
|
+
if (res?.deduplicated)
|
|
63375
|
+
return ok3(`Already posted \u2014 this request matched a recent identical post and was suppressed as a retry. If you meant it as a NEW message, send it again.`);
|
|
63376
|
+
return ok3(`Posted to the group.`);
|
|
63377
|
+
} catch (e) {
|
|
63378
|
+
return fail(`Couldn't post to the group: ${e?.message ?? e}`);
|
|
63379
|
+
}
|
|
63380
|
+
}
|
|
63381
|
+
const r = await confirmWrite(c, () => c.groupMessage(groupId, text5));
|
|
63328
63382
|
if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
|
|
63329
63383
|
return ok3(`Posted to the group.`);
|
|
63330
63384
|
})
|
|
@@ -63338,7 +63392,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63338
63392
|
groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
|
|
63339
63393
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
63340
63394
|
caption: external_exports.string().optional().describe("optional text caption"),
|
|
63341
|
-
dataUrl: external_exports.string().optional().describe("
|
|
63395
|
+
dataUrl: external_exports.string().optional().describe("ONLY for tiny inline content you already hold (max 256KB decoded). NEVER construct this by reading a local file \u2014 pass `path` instead."),
|
|
63342
63396
|
fileUrl: external_exports.string().optional().describe("alternatively, a public http(s) URL to fetch the file from"),
|
|
63343
63397
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
63344
63398
|
}
|
|
@@ -63347,23 +63401,8 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63347
63401
|
let name, mime, url;
|
|
63348
63402
|
if (path3) {
|
|
63349
63403
|
if (!ctx.local) {
|
|
63350
|
-
const
|
|
63351
|
-
|
|
63352
|
-
const dropLink = `${web}/?group=${encodeURIComponent(groupId)}`;
|
|
63353
|
-
const fname = np3.basename(path3);
|
|
63354
|
-
const fmime = mimeOf(fname);
|
|
63355
|
-
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
63356
|
-
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${fmime}" --data-binary @${shq(path3)}`;
|
|
63357
|
-
return fail(
|
|
63358
|
-
`This is the cloud connector, so it can't read "${fname}" off your computer. Easiest fix:
|
|
63359
|
-
|
|
63360
|
-
\u2705 SIMPLE \u2014 open the Jefri Chat web app and DRAG "${fname}" into the group chat:
|
|
63361
|
-
${dropLink}
|
|
63362
|
-
|
|
63363
|
-
(If this is an image you GENERATED and have a public URL for, call this tool again with \`fileUrl\` instead of \`path\`.)
|
|
63364
|
-
|
|
63365
|
-
\u2014 Advanced (terminal): ${cmd}`
|
|
63366
|
-
);
|
|
63404
|
+
const r = await remoteUploadCommand(c, ctx, { groupId }, path3, caption, "the group");
|
|
63405
|
+
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
63367
63406
|
}
|
|
63368
63407
|
const abs = resolveLocalFile(path3);
|
|
63369
63408
|
if (!abs) return fail(`I can't find a file at ${path3} on this machine.`);
|
|
@@ -63372,6 +63411,8 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63372
63411
|
mime = mimeOf(name);
|
|
63373
63412
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
63374
63413
|
} else if (dataUrl && fileName) {
|
|
63414
|
+
const tooBig = dataUrlTooBig(dataUrl);
|
|
63415
|
+
if (tooBig) return fail(tooBig);
|
|
63375
63416
|
name = fileName;
|
|
63376
63417
|
mime = dataUrl.match(/^data:([^;]+)/)?.[1] ?? mimeOf(fileName);
|
|
63377
63418
|
url = dataUrl;
|
|
@@ -63389,13 +63430,15 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63389
63430
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
63390
63431
|
if (isStateless(c)) {
|
|
63391
63432
|
try {
|
|
63392
|
-
await c.sendFile(
|
|
63433
|
+
const res = await c.sendFile(
|
|
63393
63434
|
{ groupId, caption },
|
|
63394
63435
|
name,
|
|
63395
63436
|
mime,
|
|
63396
63437
|
rawBytes,
|
|
63397
63438
|
retryKey(extra, ["file", groupId, name, createHash2("sha256").update(rawBytes).digest("hex")])
|
|
63398
63439
|
);
|
|
63440
|
+
if (res?.deduplicated)
|
|
63441
|
+
return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry. If you meant a new copy, send it again.`);
|
|
63399
63442
|
} catch (e2) {
|
|
63400
63443
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
63401
63444
|
}
|
|
@@ -63487,10 +63530,20 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63487
63530
|
}
|
|
63488
63531
|
},
|
|
63489
63532
|
async ({ status }, extra) => withClient(async (c) => {
|
|
63490
|
-
|
|
63491
|
-
|
|
63533
|
+
if (isStateless(c)) {
|
|
63534
|
+
const declId = declKey(extra, ["presence", status]);
|
|
63535
|
+
try {
|
|
63536
|
+
const res = await c.presence(status, declId);
|
|
63537
|
+
ctx.onPresenceDeclared?.(res.status);
|
|
63538
|
+
if (res.replayed && res.status !== status)
|
|
63539
|
+
return ok3(`That request was an old retry \u2014 your status is "${res.status}" (a newer declaration stands).`);
|
|
63540
|
+
return ok3(`Status set to ${res.status}.`);
|
|
63541
|
+
} catch (e) {
|
|
63542
|
+
return fail(`Couldn't set status: ${e?.message ?? e}`);
|
|
63543
|
+
}
|
|
63544
|
+
}
|
|
63545
|
+
const r = await confirmWrite(c, () => c.presence(status));
|
|
63492
63546
|
if (r.error) return fail(`Couldn't set status: ${r.error}`);
|
|
63493
|
-
ctx.onPresenceDeclared?.(status);
|
|
63494
63547
|
return ok3(`Status set to ${status}.`);
|
|
63495
63548
|
})
|
|
63496
63549
|
);
|
package/dist/index.js
CHANGED
|
@@ -40562,9 +40562,65 @@ function retryKey(extra, parts) {
|
|
|
40562
40562
|
if (rid === void 0 || rid === null) return void 0;
|
|
40563
40563
|
const h = extra?.requestInfo?.headers;
|
|
40564
40564
|
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
40565
|
-
|
|
40565
|
+
if (!ua) return void 0;
|
|
40566
|
+
const bucket = Math.floor(Date.now() / 75e3);
|
|
40566
40567
|
return "mcp-" + createHash("sha256").update(JSON.stringify([bucket, ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
40567
40568
|
}
|
|
40569
|
+
function declKey(extra, parts) {
|
|
40570
|
+
const rid = extra?.requestId;
|
|
40571
|
+
if (rid === void 0 || rid === null) return void 0;
|
|
40572
|
+
const h = extra?.requestInfo?.headers;
|
|
40573
|
+
const ua = String((typeof h?.get === "function" ? h.get("user-agent") : h?.["user-agent"]) ?? "");
|
|
40574
|
+
return "d-" + createHash("sha256").update(JSON.stringify([ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
40575
|
+
}
|
|
40576
|
+
var crossBasename = (p) => String(p).split(/[\\/]/).pop() || "file";
|
|
40577
|
+
var shPathArg = (p) => p.startsWith("~/") ? `"$HOME"${shq(p.slice(1))}` : shq(p);
|
|
40578
|
+
var psq = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
40579
|
+
var psPathArg = (p) => p.startsWith("~/") || p.startsWith("~\\") ? `($HOME + ${psq(p.slice(1))})` : psq(p);
|
|
40580
|
+
var DATAURL_INLINE_MAX = 256 * 1024;
|
|
40581
|
+
var dataUrlTooBig = (dataUrl) => dataUrl.length > DATAURL_INLINE_MAX * 1.4 ? `That dataUrl is ~${MB(dataUrl.length * 0.75)} MB decoded \u2014 far past the ${Math.round(DATAURL_INLINE_MAX / 1024)}KB inline limit. NEVER read a local file and base64 it through the model. Call this tool again with the file's PATH \u2014 you'll get a single-use upload command that sends it in seconds.` : null;
|
|
40582
|
+
async function remoteUploadCommand(c2, ctx, target, path4, caption, where) {
|
|
40583
|
+
const fname = crossBasename(path4);
|
|
40584
|
+
const fmime = mimeOf(fname);
|
|
40585
|
+
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
40586
|
+
try {
|
|
40587
|
+
const g = await fetch(`${hub}/api/files/grant`, {
|
|
40588
|
+
method: "POST",
|
|
40589
|
+
headers: { authorization: `Bearer ${c2.token}`, "content-type": "application/json" },
|
|
40590
|
+
body: JSON.stringify({ ...target.to ? { to: target.to } : { groupId: target.groupId }, fileName: fname, ...caption ? { caption } : {} })
|
|
40591
|
+
});
|
|
40592
|
+
if (g.ok) {
|
|
40593
|
+
const { uploadUrl, expiresInMs } = await g.json();
|
|
40594
|
+
const mins = Math.round((expiresInMs ?? 9e5) / 6e4);
|
|
40595
|
+
return {
|
|
40596
|
+
ok: `This cloud connector can't read your disk \u2014 but here is the fast path. Run this ONE command in the terminal (it uploads AND delivers the file to ${where}):
|
|
40597
|
+
|
|
40598
|
+
curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path4)}
|
|
40599
|
+
|
|
40600
|
+
Windows PowerShell instead:
|
|
40601
|
+
Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(path4)}
|
|
40602
|
+
|
|
40603
|
+
The link is single-use and expires in ${mins} min; the file name and caption are already attached to it. Run it now, then confirm delivery from its JSON response ({"ok":true,...}). Do NOT read the file and base64 it through dataUrl \u2014 that is minutes of model output and fails over ~3MB.`
|
|
40604
|
+
};
|
|
40605
|
+
}
|
|
40606
|
+
} catch {
|
|
40607
|
+
}
|
|
40608
|
+
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
40609
|
+
const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
|
|
40610
|
+
const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
40611
|
+
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path4)}`;
|
|
40612
|
+
return {
|
|
40613
|
+
fail: `This is the cloud connector, so it can't read "${fname}" off your computer, and the upload-link service didn't answer. Options:
|
|
40614
|
+
|
|
40615
|
+
\u2705 open the Jefri Chat web app and DRAG "${fname}" into the chat with ${where}:
|
|
40616
|
+
${dropLink}
|
|
40617
|
+
|
|
40618
|
+
\u2014 or from a terminal (set JEFRI_TOKEN from the app's Connect dialog first):
|
|
40619
|
+
${cmd}
|
|
40620
|
+
|
|
40621
|
+
Do NOT base64 the file through dataUrl \u2014 that streams megabytes through the model and fails over ~3MB.`
|
|
40622
|
+
};
|
|
40623
|
+
}
|
|
40568
40624
|
async function fetchHistory2(c2, convId) {
|
|
40569
40625
|
if (isStateless(c2)) return c2.history(convId);
|
|
40570
40626
|
const p = waitFor(c2, "history", (e) => e.conversationId === convId, 4e3);
|
|
@@ -41027,10 +41083,17 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
41027
41083
|
}
|
|
41028
41084
|
},
|
|
41029
41085
|
async ({ to, text: text5 }, extra) => withClient(async (c2) => {
|
|
41030
|
-
|
|
41031
|
-
|
|
41032
|
-
|
|
41033
|
-
|
|
41086
|
+
if (isStateless(c2)) {
|
|
41087
|
+
try {
|
|
41088
|
+
const res = await c2.message(to, text5, retryKey(extra, ["message", to, text5]));
|
|
41089
|
+
if (res?.deduplicated)
|
|
41090
|
+
return ok3(`Already delivered to @${to} \u2014 this request matched a recent identical send and was suppressed as a retry. If you meant it as a NEW message, send it again (it will not be suppressed twice).`);
|
|
41091
|
+
return ok3(`Sent to @${to}: "${text5}"`);
|
|
41092
|
+
} catch (e) {
|
|
41093
|
+
return fail(`Message rejected: ${e?.message ?? e}`);
|
|
41094
|
+
}
|
|
41095
|
+
}
|
|
41096
|
+
const r = await confirmWrite(c2, () => c2.message(to, text5));
|
|
41034
41097
|
if (r.error) return fail(`Message rejected: ${r.error}`);
|
|
41035
41098
|
return ok3(`Sent to @${to}: "${text5}"`);
|
|
41036
41099
|
})
|
|
@@ -41177,12 +41240,12 @@ ${fp}`);
|
|
|
41177
41240
|
"jefri_send_file",
|
|
41178
41241
|
{
|
|
41179
41242
|
title: "Send a file, PDF, or image",
|
|
41180
|
-
description:
|
|
41243
|
+
description: "Send a file to another Jefri Chat user/agent \u2014 PDF, image (jpg/png/gif/webp), video (mp4/mov), or any document. ALWAYS give the local file `path` (e.g. ~/Downloads/report.pdf) and an optional `caption` \u2014 even on the remote/cloud connector: there this tool answers with ONE ready-to-run terminal command (a single-use upload link) that sends the file in seconds. NEVER read the file yourself and base64 it into `dataUrl` \u2014 that streams megabytes through the model, takes minutes, and fails over ~3MB; `dataUrl` is ONLY for tiny content you already hold inline (small generated snippets). If you only have a public URL for the file, pass `fileUrl` and the connector fetches the bytes itself.",
|
|
41181
41244
|
inputSchema: {
|
|
41182
41245
|
to: external_exports.string().describe("recipient username"),
|
|
41183
41246
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
41184
41247
|
caption: external_exports.string().optional().describe("optional text caption to send with it"),
|
|
41185
|
-
dataUrl: external_exports.string().optional().describe("
|
|
41248
|
+
dataUrl: external_exports.string().optional().describe("ONLY for tiny inline content you already hold (max 256KB decoded). NEVER construct this by reading a local file \u2014 pass `path` instead."),
|
|
41186
41249
|
fileUrl: external_exports.string().optional().describe("alternatively, a public http(s) URL to fetch the file from (e.g. a generated image)"),
|
|
41187
41250
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41188
41251
|
}
|
|
@@ -41191,31 +41254,8 @@ ${fp}`);
|
|
|
41191
41254
|
let name, mime, url;
|
|
41192
41255
|
if (path4) {
|
|
41193
41256
|
if (!ctx.local) {
|
|
41194
|
-
const
|
|
41195
|
-
|
|
41196
|
-
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
41197
|
-
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
41198
|
-
const dropLink = `${web}/?to=${encodeURIComponent(to)}`;
|
|
41199
|
-
const q = `to=${encodeURIComponent(to)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
41200
|
-
const uploadUrl = `${hub}/api/files?${q}`;
|
|
41201
|
-
const cmd = `curl -s -X POST ${shq(uploadUrl)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${fmime}" --data-binary @${shq(path4)}`;
|
|
41202
|
-
const psCmd = `Invoke-RestMethod -Uri "${uploadUrl}" -Method Post -Headers @{ Authorization = "Bearer $env:JEFRI_TOKEN" } -ContentType "${fmime}" -InFile "${path4}"`;
|
|
41203
|
-
return fail(
|
|
41204
|
-
`This is the cloud connector, so it can't read "${fname}" off your computer. Easiest fix:
|
|
41205
|
-
|
|
41206
|
-
\u2705 SIMPLE \u2014 open the Jefri Chat web app and DRAG "${fname}" into the chat with @${to}:
|
|
41207
|
-
${dropLink}
|
|
41208
|
-
|
|
41209
|
-
(If instead this is an image you GENERATED and have a public URL for, just call this tool again with \`fileUrl\` instead of \`path\` \u2014 the image sends with no drag.)
|
|
41210
|
-
|
|
41211
|
-
\u2014 Advanced (only if you prefer a terminal): run one of these to upload it directly \u2014
|
|
41212
|
-
\u2022 macOS / Linux:
|
|
41213
|
-
${cmd}
|
|
41214
|
-
\u2022 Windows (PowerShell):
|
|
41215
|
-
${psCmd}
|
|
41216
|
-
|
|
41217
|
-
(These read your token from the JEFRI_TOKEN env var \u2014 set it first from the app's Connect dialog: export JEFRI_TOKEN=\u2026 (macOS/Linux) or $env:JEFRI_TOKEN="\u2026" (PowerShell). The token isn't printed here on purpose.)`
|
|
41218
|
-
);
|
|
41257
|
+
const r = await remoteUploadCommand(c2, ctx, { to }, path4, caption, `@${to}`);
|
|
41258
|
+
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
41219
41259
|
}
|
|
41220
41260
|
const abs = resolveLocalFile(path4);
|
|
41221
41261
|
if (!abs) {
|
|
@@ -41226,6 +41266,8 @@ ${psCmd}
|
|
|
41226
41266
|
mime = mimeOf(name);
|
|
41227
41267
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
41228
41268
|
} else if (dataUrl && fileName) {
|
|
41269
|
+
const tooBig = dataUrlTooBig(dataUrl);
|
|
41270
|
+
if (tooBig) return fail(tooBig);
|
|
41229
41271
|
name = fileName;
|
|
41230
41272
|
mime = dataUrl.match(/^data:([^;]+)/)?.[1] ?? mimeOf(fileName);
|
|
41231
41273
|
url = dataUrl;
|
|
@@ -41243,13 +41285,15 @@ ${psCmd}
|
|
|
41243
41285
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41244
41286
|
if (isStateless(c2)) {
|
|
41245
41287
|
try {
|
|
41246
|
-
await c2.sendFile(
|
|
41288
|
+
const res = await c2.sendFile(
|
|
41247
41289
|
{ to, caption },
|
|
41248
41290
|
name,
|
|
41249
41291
|
mime,
|
|
41250
41292
|
rawBytes,
|
|
41251
41293
|
retryKey(extra, ["file", to, name, createHash("sha256").update(rawBytes).digest("hex")])
|
|
41252
41294
|
);
|
|
41295
|
+
if (res?.deduplicated)
|
|
41296
|
+
return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry (round 28 #2: suppression is SAID, never a false "Sent"). If you meant a new copy, send it again.`);
|
|
41253
41297
|
} catch (e2) {
|
|
41254
41298
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41255
41299
|
}
|
|
@@ -41291,13 +41335,15 @@ ${psCmd}
|
|
|
41291
41335
|
return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41292
41336
|
if (isStateless(c2)) {
|
|
41293
41337
|
try {
|
|
41294
|
-
await c2.sendFile(
|
|
41338
|
+
const res = await c2.sendFile(
|
|
41295
41339
|
{ to, caption },
|
|
41296
41340
|
fileName,
|
|
41297
41341
|
mime,
|
|
41298
41342
|
contentBytes,
|
|
41299
41343
|
retryKey(extra, ["file", to, fileName, createHash("sha256").update(contentBytes).digest("hex")])
|
|
41300
41344
|
);
|
|
41345
|
+
if (res?.deduplicated)
|
|
41346
|
+
return ok3(`Already delivered \u2014 ${fileName} matched a recent identical upload and was suppressed as a retry. If you meant a new copy, send it again.`);
|
|
41301
41347
|
} catch (e2) {
|
|
41302
41348
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41303
41349
|
}
|
|
@@ -41707,25 +41753,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41707
41753
|
ctx.inbox.length = 0;
|
|
41708
41754
|
}
|
|
41709
41755
|
if (!msgs.length) return ok3("\u{1F4ED} No new messages.");
|
|
41710
|
-
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
|
|
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
|
-
}
|
|
41756
|
+
msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : 1);
|
|
41729
41757
|
const groupNames = /* @__PURE__ */ new Map();
|
|
41730
41758
|
if (msgs.some((m) => m.groupId)) {
|
|
41731
41759
|
try {
|
|
@@ -41753,8 +41781,27 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41753
41781
|
const body = m.kind === "file" ? `\u{1F4CE} sent a file: ${m.fileName} \u2014 to save it call jefri_download_file(from: "${m.senderUsername}"${m.fileName ? `, fileName: "${m.fileName}"` : ""})` : m.content;
|
|
41754
41782
|
return `${at}@${m.senderUsername}${where(m)}: ${body}`;
|
|
41755
41783
|
}));
|
|
41756
|
-
|
|
41757
|
-
` + lines.join("\n")
|
|
41784
|
+
const rendered = `\u{1F4E8} ${lines.length} message(s):
|
|
41785
|
+
` + lines.join("\n");
|
|
41786
|
+
if (ctx.stateless) {
|
|
41787
|
+
const renderedIds = msgs.map((m) => m.id);
|
|
41788
|
+
const ack = async () => {
|
|
41789
|
+
try {
|
|
41790
|
+
await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
|
|
41791
|
+
method: "POST",
|
|
41792
|
+
headers: { authorization: `Bearer ${c2.token}`, "content-type": "application/json" },
|
|
41793
|
+
body: JSON.stringify({ messageIds: renderedIds })
|
|
41794
|
+
});
|
|
41795
|
+
} catch {
|
|
41796
|
+
}
|
|
41797
|
+
};
|
|
41798
|
+
if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
|
|
41799
|
+
else await ack();
|
|
41800
|
+
} else {
|
|
41801
|
+
inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
|
|
41802
|
+
saveWatermark();
|
|
41803
|
+
}
|
|
41804
|
+
return ok3(rendered);
|
|
41758
41805
|
})
|
|
41759
41806
|
);
|
|
41760
41807
|
server2.registerTool(
|
|
@@ -41784,10 +41831,17 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41784
41831
|
}
|
|
41785
41832
|
},
|
|
41786
41833
|
async ({ groupId, text: text5 }, extra) => withClient(async (c2) => {
|
|
41787
|
-
|
|
41788
|
-
|
|
41789
|
-
|
|
41790
|
-
|
|
41834
|
+
if (isStateless(c2)) {
|
|
41835
|
+
try {
|
|
41836
|
+
const res = await c2.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5]));
|
|
41837
|
+
if (res?.deduplicated)
|
|
41838
|
+
return ok3(`Already posted \u2014 this request matched a recent identical post and was suppressed as a retry. If you meant it as a NEW message, send it again.`);
|
|
41839
|
+
return ok3(`Posted to the group.`);
|
|
41840
|
+
} catch (e) {
|
|
41841
|
+
return fail(`Couldn't post to the group: ${e?.message ?? e}`);
|
|
41842
|
+
}
|
|
41843
|
+
}
|
|
41844
|
+
const r = await confirmWrite(c2, () => c2.groupMessage(groupId, text5));
|
|
41791
41845
|
if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
|
|
41792
41846
|
return ok3(`Posted to the group.`);
|
|
41793
41847
|
})
|
|
@@ -41801,7 +41855,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41801
41855
|
groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
|
|
41802
41856
|
path: external_exports.string().optional().describe("local file path, e.g. ~/Downloads/report.pdf"),
|
|
41803
41857
|
caption: external_exports.string().optional().describe("optional text caption"),
|
|
41804
|
-
dataUrl: external_exports.string().optional().describe("
|
|
41858
|
+
dataUrl: external_exports.string().optional().describe("ONLY for tiny inline content you already hold (max 256KB decoded). NEVER construct this by reading a local file \u2014 pass `path` instead."),
|
|
41805
41859
|
fileUrl: external_exports.string().optional().describe("alternatively, a public http(s) URL to fetch the file from"),
|
|
41806
41860
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41807
41861
|
}
|
|
@@ -41810,23 +41864,8 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41810
41864
|
let name, mime, url;
|
|
41811
41865
|
if (path4) {
|
|
41812
41866
|
if (!ctx.local) {
|
|
41813
|
-
const
|
|
41814
|
-
|
|
41815
|
-
const dropLink = `${web}/?group=${encodeURIComponent(groupId)}`;
|
|
41816
|
-
const fname = np4.basename(path4);
|
|
41817
|
-
const fmime = mimeOf(fname);
|
|
41818
|
-
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
41819
|
-
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${fmime}" --data-binary @${shq(path4)}`;
|
|
41820
|
-
return fail(
|
|
41821
|
-
`This is the cloud connector, so it can't read "${fname}" off your computer. Easiest fix:
|
|
41822
|
-
|
|
41823
|
-
\u2705 SIMPLE \u2014 open the Jefri Chat web app and DRAG "${fname}" into the group chat:
|
|
41824
|
-
${dropLink}
|
|
41825
|
-
|
|
41826
|
-
(If this is an image you GENERATED and have a public URL for, call this tool again with \`fileUrl\` instead of \`path\`.)
|
|
41827
|
-
|
|
41828
|
-
\u2014 Advanced (terminal): ${cmd}`
|
|
41829
|
-
);
|
|
41867
|
+
const r = await remoteUploadCommand(c2, ctx, { groupId }, path4, caption, "the group");
|
|
41868
|
+
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
41830
41869
|
}
|
|
41831
41870
|
const abs = resolveLocalFile(path4);
|
|
41832
41871
|
if (!abs) return fail(`I can't find a file at ${path4} on this machine.`);
|
|
@@ -41835,6 +41874,8 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41835
41874
|
mime = mimeOf(name);
|
|
41836
41875
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
41837
41876
|
} else if (dataUrl && fileName) {
|
|
41877
|
+
const tooBig = dataUrlTooBig(dataUrl);
|
|
41878
|
+
if (tooBig) return fail(tooBig);
|
|
41838
41879
|
name = fileName;
|
|
41839
41880
|
mime = dataUrl.match(/^data:([^;]+)/)?.[1] ?? mimeOf(fileName);
|
|
41840
41881
|
url = dataUrl;
|
|
@@ -41852,13 +41893,15 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41852
41893
|
return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
|
|
41853
41894
|
if (isStateless(c2)) {
|
|
41854
41895
|
try {
|
|
41855
|
-
await c2.sendFile(
|
|
41896
|
+
const res = await c2.sendFile(
|
|
41856
41897
|
{ groupId, caption },
|
|
41857
41898
|
name,
|
|
41858
41899
|
mime,
|
|
41859
41900
|
rawBytes,
|
|
41860
41901
|
retryKey(extra, ["file", groupId, name, createHash("sha256").update(rawBytes).digest("hex")])
|
|
41861
41902
|
);
|
|
41903
|
+
if (res?.deduplicated)
|
|
41904
|
+
return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry. If you meant a new copy, send it again.`);
|
|
41862
41905
|
} catch (e2) {
|
|
41863
41906
|
return fail(`Rejected: ${e2?.message ?? e2}`);
|
|
41864
41907
|
}
|
|
@@ -41950,10 +41993,20 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41950
41993
|
}
|
|
41951
41994
|
},
|
|
41952
41995
|
async ({ status }, extra) => withClient(async (c2) => {
|
|
41953
|
-
|
|
41954
|
-
|
|
41996
|
+
if (isStateless(c2)) {
|
|
41997
|
+
const declId = declKey(extra, ["presence", status]);
|
|
41998
|
+
try {
|
|
41999
|
+
const res = await c2.presence(status, declId);
|
|
42000
|
+
ctx.onPresenceDeclared?.(res.status);
|
|
42001
|
+
if (res.replayed && res.status !== status)
|
|
42002
|
+
return ok3(`That request was an old retry \u2014 your status is "${res.status}" (a newer declaration stands).`);
|
|
42003
|
+
return ok3(`Status set to ${res.status}.`);
|
|
42004
|
+
} catch (e) {
|
|
42005
|
+
return fail(`Couldn't set status: ${e?.message ?? e}`);
|
|
42006
|
+
}
|
|
42007
|
+
}
|
|
42008
|
+
const r = await confirmWrite(c2, () => c2.presence(status));
|
|
41955
42009
|
if (r.error) return fail(`Couldn't set status: ${r.error}`);
|
|
41956
|
-
ctx.onPresenceDeclared?.(status);
|
|
41957
42010
|
return ok3(`Status set to ${status}.`);
|
|
41958
42011
|
})
|
|
41959
42012
|
);
|
package/package.json
CHANGED