jefrichat-mcp 0.49.51 → 0.49.52

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.
Files changed (3) hide show
  1. package/dist/http.js +80 -19
  2. package/dist/index.js +72 -13
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -67197,8 +67197,34 @@ function parseInlineDataUrl(dataUrl) {
67197
67197
  }
67198
67198
  var optionalFileString = external_exports.string().nullish().transform((value) => value ?? void 0);
67199
67199
  var REMOTE_FILE_GUIDANCE = "REMOTE connector (ChatGPT, Claude, or another cloud assistant): choose exactly ONE file source. (1) File in YOUR OWN sandbox: check its decoded byte size first. If readable and \u2264256KB (262144 bytes), send directly with `dataUrl` + `fileName`, no user upload step. This includes PDFs, CSVs, code, and small images. Use the actual bytes as canonical base64 in data:<mime>;base64,<base64>; omit `path` and `fileUrl`. (2) Over 256KB, unreadable, or unable to fit the tool call: use jefri_upload_link with the destination and fileName (up to 500 MB per file). If in your sandbox, present the file for download so the user can select it. Never compress, convert, resize, split, or reconstruct a file just to fit the inline limit; never stream base64 in prose. (3) File only on the USER'S DEVICE: use jefri_upload_link; do not read/base64 their local file. (4) An existing public http(s) URL \u2192 `fileUrl` (\u226425MB). Never invent a URL or pass sandbox:/file: URLs. A remote `path` (including /mnt/data) CANNOT read the assistant's sandbox or user's disk; it only creates an upload link, for compatibility. Link creation is NOT file delivery. Omit unused arguments (null is also accepted). If the host blocks a tool call for safety, respect the block; do not retry other arguments to bypass it.";
67200
- var fileSendSchema = (local) => ({
67201
- path: optionalFileString.describe(local ? "Local file path to read and send directly, e.g. ~/Downloads/report.pdf. Do not combine with dataUrl or fileUrl." : "Compatibility only: creates an upload link, NEVER reads /mnt/data or any local/sandbox path. For a readable sandbox file \u2264256KB use dataUrl + fileName WITHOUT path; otherwise use jefri_upload_link."),
67200
+ function hostFileUploadEnabled(ctx) {
67201
+ return ctx.local === false && ctx.credentialLookup?.kind === "profile" && capsForCtx(ctx).has(CAP.REMOTE_SANDBOX) && (ctx.credentialLookup.profile === "grok_bot" || ctx.credentialLookup.profile === "muse");
67202
+ }
67203
+ var HOST_FILE_GUIDANCE = "Grok Bot/Muse REMOTE connector: choose by where the original file bytes live and the tools actually available. If YOUR OWN runtime can read the file and has an allowed outbound HTTP upload tool, use jefri_upload_link with the destination and fileName, then upload the original binary body from YOUR runtime to its scoped POST URL (up to 500 MB per file). Do not ask the user to re-upload a file you can upload yourself. The connector cannot read your runtime or the user's disk; a remote path only prepares an upload grant, never sends bytes. If no permitted binary upload tool is available, readable files \u2264256KB (262144 bytes) can use dataUrl + fileName with canonical data:<mime>;base64,<base64>, omitting path and fileUrl. Otherwise present the file for download and give the user the manual upload link. Files only on the USER'S DEVICE also need that manual upload. An existing public http(s) file URL can use fileUrl (\u226425MB); never invent a URL, use sandbox:/file: URLs, or make a private file public for this. Choose exactly ONE source; omit unused arguments (null is also accepted). Never compress, convert, resize, split, or reconstruct files to fit limits; never print file bytes or large base64 in prose. Link creation is NOT file delivery: confirm the upload's HTTP/JSON receipt; after an uncertain result check the conversation before retrying. Respect host permissions and safety blocks; never switch routes to bypass a block.";
67204
+ function hostUploadUrl(raw, hub) {
67205
+ if (typeof raw !== "string") return null;
67206
+ try {
67207
+ const u = new URL(raw);
67208
+ const base = new URL(hub);
67209
+ return (u.protocol === "https:" || u.protocol === "http:") && u.origin === base.origin && !u.username && !u.password && !u.search && !u.hash && /^\/api\/files\/upload\/[a-f0-9]{48}$/.test(u.pathname) ? u.href : null;
67210
+ } catch {
67211
+ return null;
67212
+ }
67213
+ }
67214
+ function hostUploadInstructions(uploads, where, mins, manualUrl, command) {
67215
+ return `Upload prepared for ${where}. No file has been sent yet. Single-use links expire in ~${mins} min; destination, file name and caption are fixed.
67216
+ ${JSON.stringify({ uploads })}
67217
+
67218
+ First check each file's actual size (up to 500 MB) and verify that YOUR OWN runtime can read it and has an allowed outbound HTTP upload tool. If so, upload it yourself now: POST each file's raw binary body to its matching uploadUrl, with its actual Content-Type (application/octet-stream if unknown). No multipart, no base64, no Authorization header, and no redirects. For multiple files, upload sequentially, once per URL; never POST to the batch page or swap file-to-URL assignments. Keep grant URLs private; never send your Jefri token. Use a bounded request with no blind automatic retries. ` + (command ? `If your runtime has an allowed shell AND this exact file, the command is:
67219
+ ${command}
67220
+
67221
+ ` : "") + `Confirm each delivery only after HTTP success AND JSON ok === true with the delivery id; a prepared link is not success. On a timeout, connection loss, or 5xx, delivery is uncertain: check jefri_history (with for DMs, groupId for groups) before retrying; do not mint a fresh link and resend blindly. A 409/already-used response is not a new delivery. If you cannot verify the outcome, report it as unconfirmed and ask before resending.
67222
+
67223
+ If no permitted binary upload tool exists: readable files \u2264256KB can use dataUrl + fileName via jefri_send_file (jefri_send_group_file for groups); larger/unreadable files need the manual link: ${manualUrl}
67224
+ For manual upload, present a runtime file for download first so the user can pick it. A file only on the user's device requires their upload. Never claim this connector can read their disk or your runtime. Never reconstruct, compress, split, or expose a private file publicly; never inline more than 256KB. Respect all host permissions; a safety denial means stop, not try a different route to bypass it.`;
67225
+ }
67226
+ var fileSendSchema = (local, hostUpload = false) => ({
67227
+ path: optionalFileString.describe(local ? "Local file path to read and send directly, e.g. ~/Downloads/report.pdf. Do not combine with dataUrl or fileUrl." : hostUpload ? "Prepares a scoped upload grant; NEVER reads this path remotely or sends the file. If your runtime can read this file and has an allowed HTTP upload tool, follow the returned binary POST instructions. Otherwise use the manual link." : "Compatibility only: creates an upload link, NEVER reads /mnt/data or any local/sandbox path. For a readable sandbox file \u2264256KB use dataUrl + fileName WITHOUT path; otherwise use jefri_upload_link."),
67202
67228
  caption: optionalFileString.describe("optional text caption"),
67203
67229
  dataUrl: optionalFileString.describe(local ? "Small content generated in-conversation, max 256KB decoded. Use canonical data:<mime>;base64,<base64> with fileName; omit path and fileUrl. For files on disk use path directly, never read them into base64 yourself." : "Actual content you hold or can read in YOUR OWN sandbox (ChatGPT, Claude apps/Cowork, or another assistant), max 256KB decoded (262144 bytes). Use canonical data:<mime>;base64,<base64> and fileName; omit path and fileUrl. Never base64 a file on the USER machine. Never reconstruct missing bytes or inline files over the cap."),
67204
67230
  fileUrl: optionalFileString.describe("An existing public http(s) URL the connector can fetch (\u226425MB), not a sandbox path or invented URL. Do not combine with path or dataUrl."),
@@ -67211,8 +67237,8 @@ function fileSourceError(args) {
67211
67237
  return "A nonblank `fileName` is required with `dataUrl`. Nothing was sent.";
67212
67238
  return null;
67213
67239
  }
67214
- function missingFileSource(local) {
67215
- return "Provide exactly one file source: `path`, `dataUrl` + `fileName` (\u2264256KB), or a public `fileUrl`. " + (local ? "On this LOCAL connector, prefer path to send directly from disk." : REMOTE_FILE_GUIDANCE);
67240
+ function missingFileSource(local, hostUpload = false) {
67241
+ return "Provide exactly one file source: `path`, `dataUrl` + `fileName` (\u2264256KB), or a public `fileUrl`. " + (local ? "On this LOCAL connector, prefer path to send directly from disk." : hostUpload ? HOST_FILE_GUIDANCE : REMOTE_FILE_GUIDANCE);
67216
67242
  }
67217
67243
  var MINT_LINK_DEADLINE_MS = (() => {
67218
67244
  const n = Number(process.env.MCP_MINT_LINK_DEADLINE_MS);
@@ -67299,6 +67325,17 @@ async function remoteUploadCommand(c, ctx, target, path4, caption, where, signal
67299
67325
  clearTimeout(gKiller);
67300
67326
  if (signal) signal.removeEventListener("abort", onHostAbort);
67301
67327
  }
67328
+ if (hostFileUploadEnabled(ctx)) {
67329
+ const scoped = hostUploadUrl(uploadUrl, hub);
67330
+ if (!scoped) return { fail: "The upload-link service returned an unexpected upload URL. No file has been sent; do not upload to it." };
67331
+ return { ok: hostUploadInstructions(
67332
+ [{ fileName: fname, uploadUrl: scoped }],
67333
+ where,
67334
+ mins,
67335
+ scoped,
67336
+ `curl --silent --show-error --fail-with-body --connect-timeout 15 --max-time 300 -X POST ${shq(scoped)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path4)}`
67337
+ ) };
67338
+ }
67302
67339
  {
67303
67340
  return {
67304
67341
  ok: `This cloud connector can't read that path. No file has been sent.
@@ -67321,6 +67358,9 @@ Never base64 anything over 256KB through dataUrl \u2014 that is minutes of model
67321
67358
  }
67322
67359
  } catch {
67323
67360
  }
67361
+ if (hostFileUploadEnabled(ctx)) return {
67362
+ fail: "The upload-link service did not provide a usable grant. No file has been sent. Do not upload or expose your token. Retry jefri_upload_link when the service recovers; if the host denied permission, stop instead. " + HOST_FILE_GUIDANCE
67363
+ };
67324
67364
  const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
67325
67365
  const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
67326
67366
  const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
@@ -68272,6 +68312,8 @@ function ackStandbyDelivered(id4) {
68272
68312
  }
68273
68313
  }
68274
68314
  function registerAcpTools(server2, ctx) {
68315
+ const hostFileUpload = hostFileUploadEnabled(ctx);
68316
+ const remoteFileGuidance = hostFileUpload ? HOST_FILE_GUIDANCE : REMOTE_FILE_GUIDANCE;
68275
68317
  const desktopHeadlessOnly = isHeadlessOnlyDesktop(ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile);
68276
68318
  const autonomyProfile = ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile;
68277
68319
  const backgroundOnly = isBackgroundOnlyAutonomyProfile(autonomyProfile);
@@ -68709,12 +68751,12 @@ ${fp}`);
68709
68751
  "jefri_upload_link",
68710
68752
  {
68711
68753
  title: "Get an upload link (any file up to 500 MB, full quality)",
68712
- description: "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 500 MB each, FULL quality, no base64, no compression, no path needed. For ChatGPT, Claude, and other remote assistants: small files \u2264256KB (already held OR readable in YOUR OWN sandbox \u2014 PDFs, CSVs, code, small images) go directly through jefri_send_file (or jefri_send_group_file for groups) with dataUrl + fileName ONLY, no path or link needed. Use THIS tool for larger files, unreadable files, files that cannot fit the tool call, or files only on the user's device. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link. Creating the link is NOT delivery \u2014 the user must upload the file. Respect host safety blocks; do not use another route to bypass one. Omit unused fields or pass null.",
68754
+ description: hostFileUpload ? "Prepare scoped upload URLs, up to 5 files via fileNames (500 MB each). Give to OR groupId, plus fileName OR fileNames. Each URL is single-use, expires in ~15 minutes, and fixes the destination, name and caption. " + HOST_FILE_GUIDANCE : "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 500 MB each, FULL quality, no base64, no compression, no path needed. For ChatGPT, Claude, and other remote assistants: small files \u2264256KB (already held OR readable in YOUR OWN sandbox \u2014 PDFs, CSVs, code, small images) go directly through jefri_send_file (or jefri_send_group_file for groups) with dataUrl + fileName ONLY, no path or link needed. Use THIS tool for larger files, unreadable files, files that cannot fit the tool call, or files only on the user's device. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link. Creating the link is NOT delivery \u2014 the user must upload the file. Respect host safety blocks; do not use another route to bypass one. Omit unused fields or pass null.",
68713
68755
  inputSchema: {
68714
68756
  to: optionalFileString.describe("recipient username (or use groupId)"),
68715
68757
  groupId: optionalFileString.describe("group id (from jefri_groups) instead of `to`"),
68716
68758
  fileName: optionalFileString.describe("the name the delivered file should carry, e.g. photo.png (single file)"),
68717
- fileNames: external_exports.array(external_exports.string()).nullish().transform((value) => value ?? void 0).describe('for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
68759
+ fileNames: external_exports.array(external_exports.string()).nullish().transform((value) => value ?? void 0).describe(hostFileUpload ? "For 2\u20135 files: delivery names in order. Returns one binary POST URL per file plus a manual batch page." : 'for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
68718
68760
  caption: optionalFileString.describe("optional text caption delivered with the file")
68719
68761
  }
68720
68762
  },
@@ -68771,6 +68813,23 @@ ${fp}`);
68771
68813
  const mins = Math.round(body.expiresInMs / 6e4);
68772
68814
  const where = to ? `@${safeHandle(to)}` : "the group";
68773
68815
  const delivers = names.length ? `${names.length} files: ${names.map((n) => `\u{1F4CE} ${n}`).join(", ")}` : `\u{1F4CE} ${fileName}`;
68816
+ if (hostFileUpload) {
68817
+ const expectedNames = (names.length ? names : [fileName]).map((name) => (name.split(/[\\/]/).pop() ?? "file").slice(0, 300) || "file");
68818
+ const rawUploads = names.length ? body.uploads : [{ fileName: expectedNames[0], uploadUrl: body.uploadUrl }];
68819
+ if (!Array.isArray(rawUploads) || rawUploads.length !== expectedNames.length)
68820
+ return fail("The upload-link service returned incomplete file upload data. No file has been sent; do not upload to it.");
68821
+ const uploads = [];
68822
+ for (const [i, upload] of rawUploads.entries()) {
68823
+ const scoped = hostUploadUrl(upload?.uploadUrl, hub);
68824
+ if (!scoped || upload?.fileName !== expectedNames[i] || uploads.some((u) => u.uploadUrl === scoped))
68825
+ return fail("The upload-link service returned unexpected file upload data. No file has been sent; do not upload to it.");
68826
+ uploads.push({ fileName: expectedNames[i], uploadUrl: scoped });
68827
+ }
68828
+ const expectedManual = names.length ? `${hub}/api/files/upload-batch/${uploads.map((u) => u.uploadUrl.split("/").pop()).join(".")}` : uploads[0].uploadUrl;
68829
+ if (parsedUrl !== expectedManual)
68830
+ return fail("The upload-link service returned an unexpected manual upload URL. No file has been sent; do not upload to it.");
68831
+ return ok3(hostUploadInstructions(uploads, where, mins, expectedManual));
68832
+ }
68774
68833
  return ok3(
68775
68834
  `\u{1F517} One-tap upload link for ${where} (delivers ${delivers}; valid ~${mins} min, single-use):
68776
68835
  ${parsedUrl}
@@ -68883,10 +68942,10 @@ ${lines.join("\n")}`
68883
68942
  "jefri_send_file",
68884
68943
  {
68885
68944
  title: "Send a file, PDF, or image",
68886
- description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 500 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. " + REMOTE_FILE_GUIDANCE,
68945
+ description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 500 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. " + remoteFileGuidance,
68887
68946
  inputSchema: {
68888
68947
  to: external_exports.string().describe("recipient username"),
68889
- ...fileSendSchema(!!ctx.local)
68948
+ ...fileSendSchema(!!ctx.local, hostFileUpload)
68890
68949
  }
68891
68950
  },
68892
68951
  async ({ to, path: path4, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c) => {
@@ -68921,7 +68980,7 @@ ${lines.join("\n")}`
68921
68980
  return fail(`Couldn't fetch that URL: ${e2?.message ?? e2}`);
68922
68981
  }
68923
68982
  } else {
68924
- return fail(missingFileSource(!!ctx.local));
68983
+ return fail(missingFileSource(!!ctx.local, hostFileUpload));
68925
68984
  }
68926
68985
  const rawBytes = inlineBytes ?? Buffer.from(url.split(",")[1] ?? "", "base64");
68927
68986
  if (rawBytes.length > MAX_UPLOAD_BYTES)
@@ -69745,10 +69804,10 @@ Or just upload ${fname} from the agent's page in the web app.`
69745
69804
  "jefri_send_group_file",
69746
69805
  {
69747
69806
  title: "Send a file to a group",
69748
- description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 500 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). " + REMOTE_FILE_GUIDANCE,
69807
+ description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 500 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). " + remoteFileGuidance,
69749
69808
  inputSchema: {
69750
69809
  groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
69751
- ...fileSendSchema(!!ctx.local)
69810
+ ...fileSendSchema(!!ctx.local, hostFileUpload)
69752
69811
  }
69753
69812
  },
69754
69813
  async ({ groupId, path: path4, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c) => {
@@ -69781,7 +69840,7 @@ Or just upload ${fname} from the agent's page in the web app.`
69781
69840
  return fail(`Couldn't fetch that URL: ${e2?.message ?? e2}`);
69782
69841
  }
69783
69842
  } else {
69784
- return fail(missingFileSource(!!ctx.local));
69843
+ return fail(missingFileSource(!!ctx.local, hostFileUpload));
69785
69844
  }
69786
69845
  const rawBytes = inlineBytes ?? Buffer.from(url.split(",")[1] ?? "", "base64");
69787
69846
  if (rawBytes.length > MAX_UPLOAD_BYTES)
@@ -71561,15 +71620,16 @@ function registerGrokMessageTool(server2, options) {
71561
71620
  if (options.lookup.kind !== "profile" || options.lookup.profile !== "grok_bot") return;
71562
71621
  server2.registerTool("jefri_grok_read_message", {
71563
71622
  title: "Read the exact message behind a Grok webhook",
71564
- description: "Read one plain incoming DM by the webhook's from and hubMessageId (or captionMessageId). Works after inbox acknowledgement. Read-only; never marks anything seen. Verify jefri_whoami matches the event's agent and server first. For a file, use jefri_download_file with its exact messageId to retrieve bytes.",
71623
+ description: "Read one plain incoming DM or group message by the webhook's from and hubMessageId (or captionMessageId). Pass groupId only when present in the event; reply to that same group with jefri_send_group, never a DM fallback. Works after inbox acknowledgement. Read-only; never marks anything seen. Verify jefri_whoami matches the event's agent and server first. For a file, use jefri_download_file with its exact messageId and groupId when present to retrieve bytes.",
71565
71624
  inputSchema: {
71566
71625
  from: external_exports.string().min(1).max(120).describe("Exact sender username from the webhook"),
71567
- messageId: external_exports.string().min(1).max(200).describe("Exact hubMessageId or captionMessageId from the webhook")
71626
+ messageId: external_exports.string().min(1).max(200).describe("Exact hubMessageId or captionMessageId from the webhook"),
71627
+ groupId: external_exports.string().min(1).max(200).optional().describe("Exact groupId from a group webhook; omit for DMs")
71568
71628
  },
71569
71629
  annotations: { readOnlyHint: true }
71570
- }, async ({ from, messageId }, extra) => {
71630
+ }, async ({ from, messageId, groupId }, extra) => {
71571
71631
  const fail2 = (text5) => ({ isError: true, content: [{ type: "text", text: text5 }] });
71572
- if (!isRoutingKey(from) || !/^[A-Za-z0-9_-]{1,200}$/.test(messageId)) return fail2("Invalid sender or message id.");
71632
+ if (!isRoutingKey(from) || !/^[A-Za-z0-9_-]{1,200}$/.test(messageId) || groupId !== void 0 && !/^[A-Za-z0-9_-]{1,200}$/.test(groupId)) return fail2("Invalid sender, message or group id.");
71573
71633
  const me2 = options.client.identity?.username;
71574
71634
  if (!me2 || !options.client.token) return fail2("Jefri identity is not verified. Reconnect this Grok connector.");
71575
71635
  const controller = new AbortController();
@@ -71579,7 +71639,7 @@ function registerGrokMessageTool(server2, options) {
71579
71639
  const timeout = setTimeout(onAbort, 1e4);
71580
71640
  try {
71581
71641
  const url = new URL(options.hub.replace(/\/$/, "") + "/api/history");
71582
- url.searchParams.set("conversationId", dmConversationId(me2, from));
71642
+ url.searchParams.set("conversationId", groupId ? groupConversationId(groupId) : dmConversationId(me2, from));
71583
71643
  url.searchParams.set("around", messageId);
71584
71644
  url.searchParams.set("peek", "1");
71585
71645
  const response = await (options.fetch ?? fetch)(url, {
@@ -71590,13 +71650,14 @@ function registerGrokMessageTool(server2, options) {
71590
71650
  if (!response.ok || !response.body) return fail2("The exact message is unavailable or access was denied. Do not substitute another message.");
71591
71651
  const data = JSON.parse((await readCapped(response.body, 2 * 1024 * 1024)).toString("utf8"));
71592
71652
  const row = Array.isArray(data.messages) ? data.messages.find((m) => m.id === messageId) : null;
71593
- if (!row || row.senderUsername !== from || row.receiver !== me2 || row.groupId || row.encryptionMode !== "audited" || !["text", "file"].includes(row.kind) || typeof row.content !== "string")
71594
- return fail2("No matching plain incoming DM was found. Do not substitute another message.");
71653
+ if (!row || row.senderUsername !== from || row.debateId || row.encryptionMode !== "audited" || (groupId ? row.groupId !== groupId || row.receiver != null : row.receiver !== me2 || !!row.groupId) || !["text", "file"].includes(row.kind) || typeof row.content !== "string")
71654
+ return fail2("No matching plain incoming message was found. Do not substitute another message.");
71595
71655
  const message = {
71596
71656
  id: row.id,
71597
71657
  from: row.senderUsername,
71598
71658
  agent: me2,
71599
71659
  kind: row.kind,
71660
+ ...groupId ? { groupId } : {},
71600
71661
  text: row.kind === "text" ? row.content : null,
71601
71662
  fileId: row.kind === "file" ? row.id : null,
71602
71663
  fileName: row.kind === "file" ? row.fileName : null,
package/dist/index.js CHANGED
@@ -59492,6 +59492,31 @@ function parseInlineDataUrl(dataUrl) {
59492
59492
  };
59493
59493
  return { mime: m[1], bytes: bytes2 };
59494
59494
  }
59495
+ function hostFileUploadEnabled(ctx) {
59496
+ return ctx.local === false && ctx.credentialLookup?.kind === "profile" && capsForCtx(ctx).has(CAP.REMOTE_SANDBOX) && (ctx.credentialLookup.profile === "grok_bot" || ctx.credentialLookup.profile === "muse");
59497
+ }
59498
+ function hostUploadUrl(raw, hub) {
59499
+ if (typeof raw !== "string") return null;
59500
+ try {
59501
+ const u = new URL(raw);
59502
+ const base = new URL(hub);
59503
+ return (u.protocol === "https:" || u.protocol === "http:") && u.origin === base.origin && !u.username && !u.password && !u.search && !u.hash && /^\/api\/files\/upload\/[a-f0-9]{48}$/.test(u.pathname) ? u.href : null;
59504
+ } catch {
59505
+ return null;
59506
+ }
59507
+ }
59508
+ function hostUploadInstructions(uploads, where, mins, manualUrl, command) {
59509
+ return `Upload prepared for ${where}. No file has been sent yet. Single-use links expire in ~${mins} min; destination, file name and caption are fixed.
59510
+ ${JSON.stringify({ uploads })}
59511
+
59512
+ First check each file's actual size (up to 500 MB) and verify that YOUR OWN runtime can read it and has an allowed outbound HTTP upload tool. If so, upload it yourself now: POST each file's raw binary body to its matching uploadUrl, with its actual Content-Type (application/octet-stream if unknown). No multipart, no base64, no Authorization header, and no redirects. For multiple files, upload sequentially, once per URL; never POST to the batch page or swap file-to-URL assignments. Keep grant URLs private; never send your Jefri token. Use a bounded request with no blind automatic retries. ` + (command ? `If your runtime has an allowed shell AND this exact file, the command is:
59513
+ ${command}
59514
+
59515
+ ` : "") + `Confirm each delivery only after HTTP success AND JSON ok === true with the delivery id; a prepared link is not success. On a timeout, connection loss, or 5xx, delivery is uncertain: check jefri_history (with for DMs, groupId for groups) before retrying; do not mint a fresh link and resend blindly. A 409/already-used response is not a new delivery. If you cannot verify the outcome, report it as unconfirmed and ask before resending.
59516
+
59517
+ If no permitted binary upload tool exists: readable files \u2264256KB can use dataUrl + fileName via jefri_send_file (jefri_send_group_file for groups); larger/unreadable files need the manual link: ${manualUrl}
59518
+ For manual upload, present a runtime file for download first so the user can pick it. A file only on the user's device requires their upload. Never claim this connector can read their disk or your runtime. Never reconstruct, compress, split, or expose a private file publicly; never inline more than 256KB. Respect all host permissions; a safety denial means stop, not try a different route to bypass it.`;
59519
+ }
59495
59520
  function fileSourceError(args) {
59496
59521
  if ([args.path, args.dataUrl, args.fileUrl].filter(Boolean).length > 1)
59497
59522
  return "Choose exactly ONE file source: `path`, `dataUrl` + `fileName`, or `fileUrl`. Nothing was sent. For a readable sandbox file \u2264256KB, use `dataUrl` + `fileName` WITHOUT `path` or `fileUrl`.";
@@ -59499,8 +59524,8 @@ function fileSourceError(args) {
59499
59524
  return "A nonblank `fileName` is required with `dataUrl`. Nothing was sent.";
59500
59525
  return null;
59501
59526
  }
59502
- function missingFileSource(local) {
59503
- return "Provide exactly one file source: `path`, `dataUrl` + `fileName` (\u2264256KB), or a public `fileUrl`. " + (local ? "On this LOCAL connector, prefer path to send directly from disk." : REMOTE_FILE_GUIDANCE);
59527
+ function missingFileSource(local, hostUpload = false) {
59528
+ return "Provide exactly one file source: `path`, `dataUrl` + `fileName` (\u2264256KB), or a public `fileUrl`. " + (local ? "On this LOCAL connector, prefer path to send directly from disk." : hostUpload ? HOST_FILE_GUIDANCE : REMOTE_FILE_GUIDANCE);
59504
59529
  }
59505
59530
  async function mintFileLink(hub, token, fileId) {
59506
59531
  const ac = new AbortController();
@@ -59580,6 +59605,17 @@ async function remoteUploadCommand(c2, ctx, target, path39, caption, where, sign
59580
59605
  clearTimeout(gKiller);
59581
59606
  if (signal) signal.removeEventListener("abort", onHostAbort);
59582
59607
  }
59608
+ if (hostFileUploadEnabled(ctx)) {
59609
+ const scoped = hostUploadUrl(uploadUrl, hub);
59610
+ if (!scoped) return { fail: "The upload-link service returned an unexpected upload URL. No file has been sent; do not upload to it." };
59611
+ return { ok: hostUploadInstructions(
59612
+ [{ fileName: fname, uploadUrl: scoped }],
59613
+ where,
59614
+ mins,
59615
+ scoped,
59616
+ `curl --silent --show-error --fail-with-body --connect-timeout 15 --max-time 300 -X POST ${shq(scoped)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path39)}`
59617
+ ) };
59618
+ }
59583
59619
  {
59584
59620
  return {
59585
59621
  ok: `This cloud connector can't read that path. No file has been sent.
@@ -59602,6 +59638,9 @@ Never base64 anything over 256KB through dataUrl \u2014 that is minutes of model
59602
59638
  }
59603
59639
  } catch {
59604
59640
  }
59641
+ if (hostFileUploadEnabled(ctx)) return {
59642
+ fail: "The upload-link service did not provide a usable grant. No file has been sent. Do not upload or expose your token. Retry jefri_upload_link when the service recovers; if the host denied permission, stop instead. " + HOST_FILE_GUIDANCE
59643
+ };
59605
59644
  const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
59606
59645
  const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
59607
59646
  const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
@@ -60349,6 +60388,8 @@ function ackStandbyDelivered(id5) {
60349
60388
  }
60350
60389
  }
60351
60390
  function registerAcpTools(server2, ctx) {
60391
+ const hostFileUpload = hostFileUploadEnabled(ctx);
60392
+ const remoteFileGuidance = hostFileUpload ? HOST_FILE_GUIDANCE : REMOTE_FILE_GUIDANCE;
60352
60393
  const desktopHeadlessOnly = isHeadlessOnlyDesktop(ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile);
60353
60394
  const autonomyProfile = ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile;
60354
60395
  const backgroundOnly = isBackgroundOnlyAutonomyProfile(autonomyProfile);
@@ -60786,12 +60827,12 @@ ${fp}`);
60786
60827
  "jefri_upload_link",
60787
60828
  {
60788
60829
  title: "Get an upload link (any file up to 500 MB, full quality)",
60789
- description: "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 500 MB each, FULL quality, no base64, no compression, no path needed. For ChatGPT, Claude, and other remote assistants: small files \u2264256KB (already held OR readable in YOUR OWN sandbox \u2014 PDFs, CSVs, code, small images) go directly through jefri_send_file (or jefri_send_group_file for groups) with dataUrl + fileName ONLY, no path or link needed. Use THIS tool for larger files, unreadable files, files that cannot fit the tool call, or files only on the user's device. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link. Creating the link is NOT delivery \u2014 the user must upload the file. Respect host safety blocks; do not use another route to bypass one. Omit unused fields or pass null.",
60830
+ description: hostFileUpload ? "Prepare scoped upload URLs, up to 5 files via fileNames (500 MB each). Give to OR groupId, plus fileName OR fileNames. Each URL is single-use, expires in ~15 minutes, and fixes the destination, name and caption. " + HOST_FILE_GUIDANCE : "Get a ONE-TAP UPLOAD LINK to send files or images \u2014 up to 5 PER LINK via `fileNames`, up to 500 MB each, FULL quality, no base64, no compression, no path needed. For ChatGPT, Claude, and other remote assistants: small files \u2264256KB (already held OR readable in YOUR OWN sandbox \u2014 PDFs, CSVs, code, small images) go directly through jefri_send_file (or jefri_send_group_file for groups) with dataUrl + fileName ONLY, no path or link needed. Use THIS tool for larger files, unreadable files, files that cannot fit the tool call, or files only on the user's device. One cheap call, then hand the user the link \u2014 they open it (phone works), pick the file, and it delivers to the recipient automatically. Give `to` (a username) or `groupId`, plus the `fileName` the delivery should carry. The link is single-use and expires in ~15 minutes; mint a fresh one any time. If the file lives in YOUR sandbox, present it to the user for download first so they can pick it at the link. NEVER stream file bytes through plain output (base64/pixel data) \u2014 that burns the user's tokens. The ONE sanctioned carrier is a \u2264256KB `dataUrl` TOOL ARGUMENT via jefri_send_file (near the cap it may still strain output limits \u2014 then use this link); anything larger only ever travels as this link. Creating the link is NOT delivery \u2014 the user must upload the file. Respect host safety blocks; do not use another route to bypass one. Omit unused fields or pass null.",
60790
60831
  inputSchema: {
60791
60832
  to: optionalFileString.describe("recipient username (or use groupId)"),
60792
60833
  groupId: optionalFileString.describe("group id (from jefri_groups) instead of `to`"),
60793
60834
  fileName: optionalFileString.describe("the name the delivered file should carry, e.g. photo.png (single file)"),
60794
- fileNames: external_exports.array(external_exports.string()).nullish().transform((value) => value ?? void 0).describe('for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
60835
+ fileNames: external_exports.array(external_exports.string()).nullish().transform((value) => value ?? void 0).describe(hostFileUpload ? "For 2\u20135 files: delivery names in order. Returns one binary POST URL per file plus a manual batch page." : 'for a MULTI-file link (2\u20135): the delivery names in order, e.g. ["foto-1.jpg","foto-2.jpg"] \u2014 one link, the user picks them all'),
60795
60836
  caption: optionalFileString.describe("optional text caption delivered with the file")
60796
60837
  }
60797
60838
  },
@@ -60848,6 +60889,23 @@ ${fp}`);
60848
60889
  const mins = Math.round(body.expiresInMs / 6e4);
60849
60890
  const where = to ? `@${safeHandle(to)}` : "the group";
60850
60891
  const delivers = names.length ? `${names.length} files: ${names.map((n) => `\u{1F4CE} ${n}`).join(", ")}` : `\u{1F4CE} ${fileName}`;
60892
+ if (hostFileUpload) {
60893
+ const expectedNames = (names.length ? names : [fileName]).map((name) => (name.split(/[\\/]/).pop() ?? "file").slice(0, 300) || "file");
60894
+ const rawUploads = names.length ? body.uploads : [{ fileName: expectedNames[0], uploadUrl: body.uploadUrl }];
60895
+ if (!Array.isArray(rawUploads) || rawUploads.length !== expectedNames.length)
60896
+ return fail("The upload-link service returned incomplete file upload data. No file has been sent; do not upload to it.");
60897
+ const uploads = [];
60898
+ for (const [i, upload] of rawUploads.entries()) {
60899
+ const scoped = hostUploadUrl(upload?.uploadUrl, hub);
60900
+ if (!scoped || upload?.fileName !== expectedNames[i] || uploads.some((u) => u.uploadUrl === scoped))
60901
+ return fail("The upload-link service returned unexpected file upload data. No file has been sent; do not upload to it.");
60902
+ uploads.push({ fileName: expectedNames[i], uploadUrl: scoped });
60903
+ }
60904
+ const expectedManual = names.length ? `${hub}/api/files/upload-batch/${uploads.map((u) => u.uploadUrl.split("/").pop()).join(".")}` : uploads[0].uploadUrl;
60905
+ if (parsedUrl !== expectedManual)
60906
+ return fail("The upload-link service returned an unexpected manual upload URL. No file has been sent; do not upload to it.");
60907
+ return ok3(hostUploadInstructions(uploads, where, mins, expectedManual));
60908
+ }
60851
60909
  return ok3(
60852
60910
  `\u{1F517} One-tap upload link for ${where} (delivers ${delivers}; valid ~${mins} min, single-use):
60853
60911
  ${parsedUrl}
@@ -60960,10 +61018,10 @@ ${lines.join("\n")}`
60960
61018
  "jefri_send_file",
60961
61019
  {
60962
61020
  title: "Send a file, PDF, or image",
60963
- description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 500 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. " + REMOTE_FILE_GUIDANCE,
61021
+ description: ctx.local ? "Send a file to a Jefri Chat user/agent \u2014 PDF, image, video, any document. On this LOCAL connector, `path` reads the file off disk and sends it DIRECTLY (up to 500 MB) \u2014 always prefer `path`; never read a file into base64 yourself. `dataUrl` + `fileName` is only for small (\u2264256KB) content you generated in-conversation, and `fileUrl` fetches a public URL. Optional `caption` always." : "Send a file to a Jefri Chat user/agent. " + remoteFileGuidance,
60964
61022
  inputSchema: {
60965
61023
  to: external_exports.string().describe("recipient username"),
60966
- ...fileSendSchema(!!ctx.local)
61024
+ ...fileSendSchema(!!ctx.local, hostFileUpload)
60967
61025
  }
60968
61026
  },
60969
61027
  async ({ to, path: path39, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
@@ -60998,7 +61056,7 @@ ${lines.join("\n")}`
60998
61056
  return fail(`Couldn't fetch that URL: ${e2?.message ?? e2}`);
60999
61057
  }
61000
61058
  } else {
61001
- return fail(missingFileSource(!!ctx.local));
61059
+ return fail(missingFileSource(!!ctx.local, hostFileUpload));
61002
61060
  }
61003
61061
  const rawBytes = inlineBytes ?? Buffer.from(url.split(",")[1] ?? "", "base64");
61004
61062
  if (rawBytes.length > MAX_UPLOAD_BYTES)
@@ -61822,10 +61880,10 @@ Or just upload ${fname} from the agent's page in the web app.`
61822
61880
  "jefri_send_group_file",
61823
61881
  {
61824
61882
  title: "Send a file to a group",
61825
- description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 500 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). " + REMOTE_FILE_GUIDANCE,
61883
+ description: ctx.local ? "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). On this LOCAL connector, `path` sends the file DIRECTLY off disk (up to 500 MB) \u2014 always prefer `path`; `dataUrl` only for small generated content; `fileUrl` for a public URL." : "Send a file/image to a GROUP by its group id (from jefri_groups / jefri_inbox). " + remoteFileGuidance,
61826
61884
  inputSchema: {
61827
61885
  groupId: external_exports.string().describe("the group's id (from jefri_groups or jefri_inbox)"),
61828
- ...fileSendSchema(!!ctx.local)
61886
+ ...fileSendSchema(!!ctx.local, hostFileUpload)
61829
61887
  }
61830
61888
  },
61831
61889
  async ({ groupId, path: path39, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
@@ -61858,7 +61916,7 @@ Or just upload ${fname} from the agent's page in the web app.`
61858
61916
  return fail(`Couldn't fetch that URL: ${e2?.message ?? e2}`);
61859
61917
  }
61860
61918
  } else {
61861
- return fail(missingFileSource(!!ctx.local));
61919
+ return fail(missingFileSource(!!ctx.local, hostFileUpload));
61862
61920
  }
61863
61921
  const rawBytes = inlineBytes ?? Buffer.from(url.split(",")[1] ?? "", "base64");
61864
61922
  if (rawBytes.length > MAX_UPLOAD_BYTES)
@@ -62678,7 +62736,7 @@ function attachInbox(c2, inbox2, self, onIncoming) {
62678
62736
  c2.off("file_received", capture);
62679
62737
  };
62680
62738
  }
62681
- var isStateless, crossBasename, DOWNLOAD_DIR_SH, downloadDirAbs, ensureDownloadDir, safeSaveName, shPathArg, psq, psPathArg, isTextyFile, DATAURL_INLINE_MAX, optionalFileString, REMOTE_FILE_GUIDANCE, fileSendSchema, MINT_LINK_DEADLINE_MS, forwardTip, MIME, mimeOf, MAX_UPLOAD_BYTES, posIntEnv, MAX_REMOTE_URL_BYTES, MAX_CONCURRENT_REMOTE_DOWNLOADS, REMOTE_DOWNLOAD_DEADLINE_MS, activeRemoteDownloads, MB, WS_SAFE_FILE_BYTES, extFromMime, shq, CAP, PROFILE_CAPS, LOCAL_CAPS, REMOTE_CAPS, LOCAL_PROFILES, PROFILE_LOOKUP_TIMEOUT_MS, CREDENTIAL_PROFILE_TTL_MS, CREDENTIAL_PROFILE_GRACE_MS, credentialProfiles, credentialEpochs, cacheKey, nextCredentialEpoch, epochOf, onAuthorityRejected, upgradeNotice, upgradeShownAt, UPGRADE_REPEAT_MS, ok3, okWithData, fail, AMBIGUOUS_OUTCOME, OUTCOME_META_KEY, AMBIGUOUS_CAVEAT, failAmbiguous, definiteHttpReject, okConnector, INLINE_IMAGE_MIMES, IMAGE_INLINE_MAX, IMAGE_MAX_PIXELS, IMAGE_MAX_FRAMES, IMAGE_RETAINED_FACTOR, retainedCost, IMAGE_BUDGET_MAX, imageBudgetInUse, IMAGE_HOLD_MAX_MS, dropBody, looksLikeImage, okWithImage, WATERMARK_FILE, inboxWatermark, STANDBY_ACKED_FILE, standbyAcked, pendingStandbyAck, STANDBY_VERIFY_MS;
62739
+ var isStateless, crossBasename, DOWNLOAD_DIR_SH, downloadDirAbs, ensureDownloadDir, safeSaveName, shPathArg, psq, psPathArg, isTextyFile, DATAURL_INLINE_MAX, optionalFileString, REMOTE_FILE_GUIDANCE, HOST_FILE_GUIDANCE, fileSendSchema, MINT_LINK_DEADLINE_MS, forwardTip, MIME, mimeOf, MAX_UPLOAD_BYTES, posIntEnv, MAX_REMOTE_URL_BYTES, MAX_CONCURRENT_REMOTE_DOWNLOADS, REMOTE_DOWNLOAD_DEADLINE_MS, activeRemoteDownloads, MB, WS_SAFE_FILE_BYTES, extFromMime, shq, CAP, PROFILE_CAPS, LOCAL_CAPS, REMOTE_CAPS, LOCAL_PROFILES, PROFILE_LOOKUP_TIMEOUT_MS, CREDENTIAL_PROFILE_TTL_MS, CREDENTIAL_PROFILE_GRACE_MS, credentialProfiles, credentialEpochs, cacheKey, nextCredentialEpoch, epochOf, onAuthorityRejected, upgradeNotice, upgradeShownAt, UPGRADE_REPEAT_MS, ok3, okWithData, fail, AMBIGUOUS_OUTCOME, OUTCOME_META_KEY, AMBIGUOUS_CAVEAT, failAmbiguous, definiteHttpReject, okConnector, INLINE_IMAGE_MIMES, IMAGE_INLINE_MAX, IMAGE_MAX_PIXELS, IMAGE_MAX_FRAMES, IMAGE_RETAINED_FACTOR, retainedCost, IMAGE_BUDGET_MAX, imageBudgetInUse, IMAGE_HOLD_MAX_MS, dropBody, looksLikeImage, okWithImage, WATERMARK_FILE, inboxWatermark, STANDBY_ACKED_FILE, standbyAcked, pendingStandbyAck, STANDBY_VERIFY_MS;
62682
62740
  var init_tools = __esm({
62683
62741
  "src/tools.ts"() {
62684
62742
  "use strict";
@@ -62727,8 +62785,9 @@ var init_tools = __esm({
62727
62785
  DATAURL_INLINE_MAX = 256 * 1024;
62728
62786
  optionalFileString = external_exports.string().nullish().transform((value) => value ?? void 0);
62729
62787
  REMOTE_FILE_GUIDANCE = "REMOTE connector (ChatGPT, Claude, or another cloud assistant): choose exactly ONE file source. (1) File in YOUR OWN sandbox: check its decoded byte size first. If readable and \u2264256KB (262144 bytes), send directly with `dataUrl` + `fileName`, no user upload step. This includes PDFs, CSVs, code, and small images. Use the actual bytes as canonical base64 in data:<mime>;base64,<base64>; omit `path` and `fileUrl`. (2) Over 256KB, unreadable, or unable to fit the tool call: use jefri_upload_link with the destination and fileName (up to 500 MB per file). If in your sandbox, present the file for download so the user can select it. Never compress, convert, resize, split, or reconstruct a file just to fit the inline limit; never stream base64 in prose. (3) File only on the USER'S DEVICE: use jefri_upload_link; do not read/base64 their local file. (4) An existing public http(s) URL \u2192 `fileUrl` (\u226425MB). Never invent a URL or pass sandbox:/file: URLs. A remote `path` (including /mnt/data) CANNOT read the assistant's sandbox or user's disk; it only creates an upload link, for compatibility. Link creation is NOT file delivery. Omit unused arguments (null is also accepted). If the host blocks a tool call for safety, respect the block; do not retry other arguments to bypass it.";
62730
- fileSendSchema = (local) => ({
62731
- path: optionalFileString.describe(local ? "Local file path to read and send directly, e.g. ~/Downloads/report.pdf. Do not combine with dataUrl or fileUrl." : "Compatibility only: creates an upload link, NEVER reads /mnt/data or any local/sandbox path. For a readable sandbox file \u2264256KB use dataUrl + fileName WITHOUT path; otherwise use jefri_upload_link."),
62788
+ HOST_FILE_GUIDANCE = "Grok Bot/Muse REMOTE connector: choose by where the original file bytes live and the tools actually available. If YOUR OWN runtime can read the file and has an allowed outbound HTTP upload tool, use jefri_upload_link with the destination and fileName, then upload the original binary body from YOUR runtime to its scoped POST URL (up to 500 MB per file). Do not ask the user to re-upload a file you can upload yourself. The connector cannot read your runtime or the user's disk; a remote path only prepares an upload grant, never sends bytes. If no permitted binary upload tool is available, readable files \u2264256KB (262144 bytes) can use dataUrl + fileName with canonical data:<mime>;base64,<base64>, omitting path and fileUrl. Otherwise present the file for download and give the user the manual upload link. Files only on the USER'S DEVICE also need that manual upload. An existing public http(s) file URL can use fileUrl (\u226425MB); never invent a URL, use sandbox:/file: URLs, or make a private file public for this. Choose exactly ONE source; omit unused arguments (null is also accepted). Never compress, convert, resize, split, or reconstruct files to fit limits; never print file bytes or large base64 in prose. Link creation is NOT file delivery: confirm the upload's HTTP/JSON receipt; after an uncertain result check the conversation before retrying. Respect host permissions and safety blocks; never switch routes to bypass a block.";
62789
+ fileSendSchema = (local, hostUpload = false) => ({
62790
+ path: optionalFileString.describe(local ? "Local file path to read and send directly, e.g. ~/Downloads/report.pdf. Do not combine with dataUrl or fileUrl." : hostUpload ? "Prepares a scoped upload grant; NEVER reads this path remotely or sends the file. If your runtime can read this file and has an allowed HTTP upload tool, follow the returned binary POST instructions. Otherwise use the manual link." : "Compatibility only: creates an upload link, NEVER reads /mnt/data or any local/sandbox path. For a readable sandbox file \u2264256KB use dataUrl + fileName WITHOUT path; otherwise use jefri_upload_link."),
62732
62791
  caption: optionalFileString.describe("optional text caption"),
62733
62792
  dataUrl: optionalFileString.describe(local ? "Small content generated in-conversation, max 256KB decoded. Use canonical data:<mime>;base64,<base64> with fileName; omit path and fileUrl. For files on disk use path directly, never read them into base64 yourself." : "Actual content you hold or can read in YOUR OWN sandbox (ChatGPT, Claude apps/Cowork, or another assistant), max 256KB decoded (262144 bytes). Use canonical data:<mime>;base64,<base64> and fileName; omit path and fileUrl. Never base64 a file on the USER machine. Never reconstruct missing bytes or inline files over the cap."),
62734
62793
  fileUrl: optionalFileString.describe("An existing public http(s) URL the connector can fetch (\u226425MB), not a sandbox path or invented URL. Do not combine with path or dataUrl."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.51",
3
+ "version": "0.49.52",
4
4
  "engines": {
5
5
  "node": ">=22.13"
6
6
  },