jefrichat-mcp 0.48.11 → 0.48.12

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 +75 -36
  2. package/dist/index.js +75 -36
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -62102,9 +62102,17 @@ 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
- const bucket = Math.floor(Date.now() / 6e5);
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
+ }
62108
62116
  async function fetchHistory(c, convId) {
62109
62117
  if (isStateless(c)) return c.history(convId);
62110
62118
  const p = waitFor(c, "history", (e) => e.conversationId === convId, 4e3);
@@ -62564,10 +62572,17 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
62564
62572
  }
62565
62573
  },
62566
62574
  async ({ to, text: text5 }, extra) => withClient(async (c) => {
62567
- const r = await confirmWrite(
62568
- c,
62569
- () => isStateless(c) ? c.message(to, text5, retryKey(extra, ["message", to, text5])) : c.message(to, text5)
62570
- );
62575
+ if (isStateless(c)) {
62576
+ try {
62577
+ const res = await c.message(to, text5, retryKey(extra, ["message", to, text5]));
62578
+ if (res?.deduplicated)
62579
+ 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).`);
62580
+ return ok3(`Sent to @${to}: "${text5}"`);
62581
+ } catch (e) {
62582
+ return fail(`Message rejected: ${e?.message ?? e}`);
62583
+ }
62584
+ }
62585
+ const r = await confirmWrite(c, () => c.message(to, text5));
62571
62586
  if (r.error) return fail(`Message rejected: ${r.error}`);
62572
62587
  return ok3(`Sent to @${to}: "${text5}"`);
62573
62588
  })
@@ -62780,13 +62795,15 @@ ${psCmd}
62780
62795
  return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
62781
62796
  if (isStateless(c)) {
62782
62797
  try {
62783
- await c.sendFile(
62798
+ const res = await c.sendFile(
62784
62799
  { to, caption },
62785
62800
  name,
62786
62801
  mime,
62787
62802
  rawBytes,
62788
62803
  retryKey(extra, ["file", to, name, createHash2("sha256").update(rawBytes).digest("hex")])
62789
62804
  );
62805
+ if (res?.deduplicated)
62806
+ 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
62807
  } catch (e2) {
62791
62808
  return fail(`Rejected: ${e2?.message ?? e2}`);
62792
62809
  }
@@ -62828,13 +62845,15 @@ ${psCmd}
62828
62845
  return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
62829
62846
  if (isStateless(c)) {
62830
62847
  try {
62831
- await c.sendFile(
62848
+ const res = await c.sendFile(
62832
62849
  { to, caption },
62833
62850
  fileName,
62834
62851
  mime,
62835
62852
  contentBytes,
62836
62853
  retryKey(extra, ["file", to, fileName, createHash2("sha256").update(contentBytes).digest("hex")])
62837
62854
  );
62855
+ if (res?.deduplicated)
62856
+ 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
62857
  } catch (e2) {
62839
62858
  return fail(`Rejected: ${e2?.message ?? e2}`);
62840
62859
  }
@@ -63244,25 +63263,7 @@ Or just upload ${fname} from the agent's page in the web app.`
63244
63263
  ctx.inbox.length = 0;
63245
63264
  }
63246
63265
  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
- }
63266
+ msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : 1);
63266
63267
  const groupNames = /* @__PURE__ */ new Map();
63267
63268
  if (msgs.some((m) => m.groupId)) {
63268
63269
  try {
@@ -63290,8 +63291,27 @@ Or just upload ${fname} from the agent's page in the web app.`
63290
63291
  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
63292
  return `${at}@${m.senderUsername}${where(m)}: ${body}`;
63292
63293
  }));
63293
- return ok3(`\u{1F4E8} ${lines.length} message(s):
63294
- ` + lines.join("\n"));
63294
+ const rendered = `\u{1F4E8} ${lines.length} message(s):
63295
+ ` + lines.join("\n");
63296
+ if (ctx.stateless) {
63297
+ const renderedIds = msgs.map((m) => m.id);
63298
+ const ack = async () => {
63299
+ try {
63300
+ await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
63301
+ method: "POST",
63302
+ headers: { authorization: `Bearer ${c.token}`, "content-type": "application/json" },
63303
+ body: JSON.stringify({ messageIds: renderedIds })
63304
+ });
63305
+ } catch {
63306
+ }
63307
+ };
63308
+ if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
63309
+ else await ack();
63310
+ } else {
63311
+ inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
63312
+ saveWatermark();
63313
+ }
63314
+ return ok3(rendered);
63295
63315
  })
63296
63316
  );
63297
63317
  server2.registerTool(
@@ -63321,10 +63341,17 @@ Or just upload ${fname} from the agent's page in the web app.`
63321
63341
  }
63322
63342
  },
63323
63343
  async ({ groupId, text: text5 }, extra) => withClient(async (c) => {
63324
- const r = await confirmWrite(
63325
- c,
63326
- () => isStateless(c) ? c.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5])) : c.groupMessage(groupId, text5)
63327
- );
63344
+ if (isStateless(c)) {
63345
+ try {
63346
+ const res = await c.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5]));
63347
+ if (res?.deduplicated)
63348
+ 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.`);
63349
+ return ok3(`Posted to the group.`);
63350
+ } catch (e) {
63351
+ return fail(`Couldn't post to the group: ${e?.message ?? e}`);
63352
+ }
63353
+ }
63354
+ const r = await confirmWrite(c, () => c.groupMessage(groupId, text5));
63328
63355
  if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
63329
63356
  return ok3(`Posted to the group.`);
63330
63357
  })
@@ -63389,13 +63416,15 @@ Or just upload ${fname} from the agent's page in the web app.`
63389
63416
  return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
63390
63417
  if (isStateless(c)) {
63391
63418
  try {
63392
- await c.sendFile(
63419
+ const res = await c.sendFile(
63393
63420
  { groupId, caption },
63394
63421
  name,
63395
63422
  mime,
63396
63423
  rawBytes,
63397
63424
  retryKey(extra, ["file", groupId, name, createHash2("sha256").update(rawBytes).digest("hex")])
63398
63425
  );
63426
+ if (res?.deduplicated)
63427
+ 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
63428
  } catch (e2) {
63400
63429
  return fail(`Rejected: ${e2?.message ?? e2}`);
63401
63430
  }
@@ -63487,10 +63516,20 @@ Or just upload ${fname} from the agent's page in the web app.`
63487
63516
  }
63488
63517
  },
63489
63518
  async ({ status }, extra) => withClient(async (c) => {
63490
- const declId = isStateless(c) ? retryKey(extra, ["presence", status])?.replace(/^mcp-/, "d-") : void 0;
63491
- const r = await confirmWrite(c, () => isStateless(c) ? c.presence(status, declId) : c.presence(status));
63519
+ if (isStateless(c)) {
63520
+ const declId = declKey(extra, ["presence", status]);
63521
+ try {
63522
+ const res = await c.presence(status, declId);
63523
+ ctx.onPresenceDeclared?.(res.status);
63524
+ if (res.replayed && res.status !== status)
63525
+ return ok3(`That request was an old retry \u2014 your status is "${res.status}" (a newer declaration stands).`);
63526
+ return ok3(`Status set to ${res.status}.`);
63527
+ } catch (e) {
63528
+ return fail(`Couldn't set status: ${e?.message ?? e}`);
63529
+ }
63530
+ }
63531
+ const r = await confirmWrite(c, () => c.presence(status));
63492
63532
  if (r.error) return fail(`Couldn't set status: ${r.error}`);
63493
- ctx.onPresenceDeclared?.(status);
63494
63533
  return ok3(`Status set to ${status}.`);
63495
63534
  })
63496
63535
  );
package/dist/index.js CHANGED
@@ -40562,9 +40562,17 @@ 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
- const bucket = Math.floor(Date.now() / 6e5);
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
+ }
40568
40576
  async function fetchHistory2(c2, convId) {
40569
40577
  if (isStateless(c2)) return c2.history(convId);
40570
40578
  const p = waitFor(c2, "history", (e) => e.conversationId === convId, 4e3);
@@ -41027,10 +41035,17 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
41027
41035
  }
41028
41036
  },
41029
41037
  async ({ to, text: text5 }, extra) => withClient(async (c2) => {
41030
- const r = await confirmWrite(
41031
- c2,
41032
- () => isStateless(c2) ? c2.message(to, text5, retryKey(extra, ["message", to, text5])) : c2.message(to, text5)
41033
- );
41038
+ if (isStateless(c2)) {
41039
+ try {
41040
+ const res = await c2.message(to, text5, retryKey(extra, ["message", to, text5]));
41041
+ if (res?.deduplicated)
41042
+ 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).`);
41043
+ return ok3(`Sent to @${to}: "${text5}"`);
41044
+ } catch (e) {
41045
+ return fail(`Message rejected: ${e?.message ?? e}`);
41046
+ }
41047
+ }
41048
+ const r = await confirmWrite(c2, () => c2.message(to, text5));
41034
41049
  if (r.error) return fail(`Message rejected: ${r.error}`);
41035
41050
  return ok3(`Sent to @${to}: "${text5}"`);
41036
41051
  })
@@ -41243,13 +41258,15 @@ ${psCmd}
41243
41258
  return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
41244
41259
  if (isStateless(c2)) {
41245
41260
  try {
41246
- await c2.sendFile(
41261
+ const res = await c2.sendFile(
41247
41262
  { to, caption },
41248
41263
  name,
41249
41264
  mime,
41250
41265
  rawBytes,
41251
41266
  retryKey(extra, ["file", to, name, createHash("sha256").update(rawBytes).digest("hex")])
41252
41267
  );
41268
+ if (res?.deduplicated)
41269
+ 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
41270
  } catch (e2) {
41254
41271
  return fail(`Rejected: ${e2?.message ?? e2}`);
41255
41272
  }
@@ -41291,13 +41308,15 @@ ${psCmd}
41291
41308
  return fail(`That content is ${MB(contentBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
41292
41309
  if (isStateless(c2)) {
41293
41310
  try {
41294
- await c2.sendFile(
41311
+ const res = await c2.sendFile(
41295
41312
  { to, caption },
41296
41313
  fileName,
41297
41314
  mime,
41298
41315
  contentBytes,
41299
41316
  retryKey(extra, ["file", to, fileName, createHash("sha256").update(contentBytes).digest("hex")])
41300
41317
  );
41318
+ if (res?.deduplicated)
41319
+ 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
41320
  } catch (e2) {
41302
41321
  return fail(`Rejected: ${e2?.message ?? e2}`);
41303
41322
  }
@@ -41707,25 +41726,7 @@ Or just upload ${fname} from the agent's page in the web app.`
41707
41726
  ctx.inbox.length = 0;
41708
41727
  }
41709
41728
  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
- }
41729
+ msgs.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : 1);
41729
41730
  const groupNames = /* @__PURE__ */ new Map();
41730
41731
  if (msgs.some((m) => m.groupId)) {
41731
41732
  try {
@@ -41753,8 +41754,27 @@ Or just upload ${fname} from the agent's page in the web app.`
41753
41754
  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
41755
  return `${at}@${m.senderUsername}${where(m)}: ${body}`;
41755
41756
  }));
41756
- return ok3(`\u{1F4E8} ${lines.length} message(s):
41757
- ` + lines.join("\n"));
41757
+ const rendered = `\u{1F4E8} ${lines.length} message(s):
41758
+ ` + lines.join("\n");
41759
+ if (ctx.stateless) {
41760
+ const renderedIds = msgs.map((m) => m.id);
41761
+ const ack = async () => {
41762
+ try {
41763
+ await fetch(`${ctx.serverUrl.replace(/\/$/, "")}/api/inbox/seen`, {
41764
+ method: "POST",
41765
+ headers: { authorization: `Bearer ${c2.token}`, "content-type": "application/json" },
41766
+ body: JSON.stringify({ messageIds: renderedIds })
41767
+ });
41768
+ } catch {
41769
+ }
41770
+ };
41771
+ if (ctx.deferUntilDelivered) ctx.deferUntilDelivered(ack);
41772
+ else await ack();
41773
+ } else {
41774
+ inboxWatermark.set(me, msgs[msgs.length - 1].createdAt);
41775
+ saveWatermark();
41776
+ }
41777
+ return ok3(rendered);
41758
41778
  })
41759
41779
  );
41760
41780
  server2.registerTool(
@@ -41784,10 +41804,17 @@ Or just upload ${fname} from the agent's page in the web app.`
41784
41804
  }
41785
41805
  },
41786
41806
  async ({ groupId, text: text5 }, extra) => withClient(async (c2) => {
41787
- const r = await confirmWrite(
41788
- c2,
41789
- () => isStateless(c2) ? c2.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5])) : c2.groupMessage(groupId, text5)
41790
- );
41807
+ if (isStateless(c2)) {
41808
+ try {
41809
+ const res = await c2.groupMessage(groupId, text5, retryKey(extra, ["group_message", groupId, text5]));
41810
+ if (res?.deduplicated)
41811
+ 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.`);
41812
+ return ok3(`Posted to the group.`);
41813
+ } catch (e) {
41814
+ return fail(`Couldn't post to the group: ${e?.message ?? e}`);
41815
+ }
41816
+ }
41817
+ const r = await confirmWrite(c2, () => c2.groupMessage(groupId, text5));
41791
41818
  if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
41792
41819
  return ok3(`Posted to the group.`);
41793
41820
  })
@@ -41852,13 +41879,15 @@ Or just upload ${fname} from the agent's page in the web app.`
41852
41879
  return fail(`${name} is ${MB(rawBytes.length)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit.`);
41853
41880
  if (isStateless(c2)) {
41854
41881
  try {
41855
- await c2.sendFile(
41882
+ const res = await c2.sendFile(
41856
41883
  { groupId, caption },
41857
41884
  name,
41858
41885
  mime,
41859
41886
  rawBytes,
41860
41887
  retryKey(extra, ["file", groupId, name, createHash("sha256").update(rawBytes).digest("hex")])
41861
41888
  );
41889
+ if (res?.deduplicated)
41890
+ 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
41891
  } catch (e2) {
41863
41892
  return fail(`Rejected: ${e2?.message ?? e2}`);
41864
41893
  }
@@ -41950,10 +41979,20 @@ Or just upload ${fname} from the agent's page in the web app.`
41950
41979
  }
41951
41980
  },
41952
41981
  async ({ status }, extra) => withClient(async (c2) => {
41953
- const declId = isStateless(c2) ? retryKey(extra, ["presence", status])?.replace(/^mcp-/, "d-") : void 0;
41954
- const r = await confirmWrite(c2, () => isStateless(c2) ? c2.presence(status, declId) : c2.presence(status));
41982
+ if (isStateless(c2)) {
41983
+ const declId = declKey(extra, ["presence", status]);
41984
+ try {
41985
+ const res = await c2.presence(status, declId);
41986
+ ctx.onPresenceDeclared?.(res.status);
41987
+ if (res.replayed && res.status !== status)
41988
+ return ok3(`That request was an old retry \u2014 your status is "${res.status}" (a newer declaration stands).`);
41989
+ return ok3(`Status set to ${res.status}.`);
41990
+ } catch (e) {
41991
+ return fail(`Couldn't set status: ${e?.message ?? e}`);
41992
+ }
41993
+ }
41994
+ const r = await confirmWrite(c2, () => c2.presence(status));
41955
41995
  if (r.error) return fail(`Couldn't set status: ${r.error}`);
41956
- ctx.onPresenceDeclared?.(status);
41957
41996
  return ok3(`Status set to ${status}.`);
41958
41997
  })
41959
41998
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.48.11",
3
+ "version": "0.48.12",
4
4
  "description": "Jefri Chat connector — join the Jefri Chat network (WhatsApp for AI agents) from any MCP client (Claude, Codex, Cursor, …).",
5
5
  "type": "module",
6
6
  "bin": {