jefrichat-mcp 0.48.7 → 0.48.9
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 +198 -57
- package/dist/index.js +72 -6
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -20888,8 +20888,8 @@ var require_application = __commonJS({
|
|
|
20888
20888
|
tryRender(view, renderOptions, done);
|
|
20889
20889
|
};
|
|
20890
20890
|
app2.listen = function listen() {
|
|
20891
|
-
var
|
|
20892
|
-
return
|
|
20891
|
+
var server2 = http2.createServer(this);
|
|
20892
|
+
return server2.listen.apply(server2, arguments);
|
|
20893
20893
|
};
|
|
20894
20894
|
function logerror(err) {
|
|
20895
20895
|
if (this.get("env") !== "test") console.error(err.stack || err.toString());
|
|
@@ -33214,10 +33214,10 @@ var require_websocket_server = __commonJS({
|
|
|
33214
33214
|
process.nextTick(emitClose, this);
|
|
33215
33215
|
}
|
|
33216
33216
|
} else {
|
|
33217
|
-
const
|
|
33217
|
+
const server2 = this._server;
|
|
33218
33218
|
this._removeListeners();
|
|
33219
33219
|
this._removeListeners = this._server = null;
|
|
33220
|
-
|
|
33220
|
+
server2.close(() => {
|
|
33221
33221
|
emitClose(this);
|
|
33222
33222
|
});
|
|
33223
33223
|
}
|
|
@@ -33402,17 +33402,17 @@ var require_websocket_server = __commonJS({
|
|
|
33402
33402
|
}
|
|
33403
33403
|
};
|
|
33404
33404
|
module.exports = WebSocketServer2;
|
|
33405
|
-
function addListeners(
|
|
33406
|
-
for (const event of Object.keys(map3))
|
|
33405
|
+
function addListeners(server2, map3) {
|
|
33406
|
+
for (const event of Object.keys(map3)) server2.on(event, map3[event]);
|
|
33407
33407
|
return function removeListeners() {
|
|
33408
33408
|
for (const event of Object.keys(map3)) {
|
|
33409
|
-
|
|
33409
|
+
server2.removeListener(event, map3[event]);
|
|
33410
33410
|
}
|
|
33411
33411
|
};
|
|
33412
33412
|
}
|
|
33413
|
-
function emitClose(
|
|
33414
|
-
|
|
33415
|
-
|
|
33413
|
+
function emitClose(server2) {
|
|
33414
|
+
server2._state = CLOSED;
|
|
33415
|
+
server2.emit("close");
|
|
33416
33416
|
}
|
|
33417
33417
|
function socketOnError() {
|
|
33418
33418
|
this.destroy();
|
|
@@ -33431,11 +33431,11 @@ var require_websocket_server = __commonJS({
|
|
|
33431
33431
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
33432
33432
|
);
|
|
33433
33433
|
}
|
|
33434
|
-
function abortHandshakeOrEmitwsClientError(
|
|
33435
|
-
if (
|
|
33434
|
+
function abortHandshakeOrEmitwsClientError(server2, req, socket, code3, message, headers) {
|
|
33435
|
+
if (server2.listenerCount("wsClientError")) {
|
|
33436
33436
|
const err = new Error(message);
|
|
33437
33437
|
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
|
33438
|
-
|
|
33438
|
+
server2.emit("wsClientError", err, socket, req);
|
|
33439
33439
|
} else {
|
|
33440
33440
|
abortHandshake(socket, code3, message, headers);
|
|
33441
33441
|
}
|
|
@@ -60682,8 +60682,48 @@ var JefriClient = class _JefriClient {
|
|
|
60682
60682
|
// heartbeat: did we get a pong since the last ping?
|
|
60683
60683
|
hasConnected = false;
|
|
60684
60684
|
// armed for auto-reconnect only after the hub `registered` us
|
|
60685
|
-
|
|
60686
|
-
|
|
60685
|
+
/** UNACKNOWLEDGED presence intent, or null. Set by presence(), cleared when
|
|
60686
|
+
* the hub's own presence_update for this identity confirms the declaration
|
|
60687
|
+
* landed. Null on reconnect means: send nothing and let the hub's persisted
|
|
60688
|
+
* answer stand.
|
|
60689
|
+
*
|
|
60690
|
+
* Acknowledgment is the point, and it is what the first fix missed: a value
|
|
60691
|
+
* kept after delivery means "ever declared", not "pending" — so a client
|
|
60692
|
+
* that once declared away kept re-asserting it on every reconnect for the
|
|
60693
|
+
* life of the process, stomping whatever a newer device had chosen since.
|
|
60694
|
+
* The same stale-overwrite, one level down. */
|
|
60695
|
+
pendingPresence = null;
|
|
60696
|
+
/** The self statusRev BASELINE captured when the pending declaration was made.
|
|
60697
|
+
* An ack must be CAUSALLY newer than this — the audit reproduced a delayed
|
|
60698
|
+
* rev-9 event spending intent declared at a rev-10 baseline, after which the
|
|
60699
|
+
* server had already moved on. Identity + status matching alone cannot order
|
|
60700
|
+
* events; the revision can. */
|
|
60701
|
+
pendingBaselineRev = 0;
|
|
60702
|
+
/** Highest revision seen for our own status, from registered and from our own
|
|
60703
|
+
* presence_update events. */
|
|
60704
|
+
selfStatusRev = 0;
|
|
60705
|
+
/** Would a reconnect re-send a presence frame? True only while a declaration
|
|
60706
|
+
* is UNACKNOWLEDGED. Exposed for tests: the wire behaviour needs a live hub,
|
|
60707
|
+
* but the rule is checkable without one. */
|
|
60708
|
+
wouldReassertPresence() {
|
|
60709
|
+
return this.pendingPresence !== null;
|
|
60710
|
+
}
|
|
60711
|
+
/** The acknowledgment that turns "pending" back into nothing: the hub
|
|
60712
|
+
* broadcasts presence_update to the declaring identity too, so our own
|
|
60713
|
+
* username carrying our pending status means the declaration LANDED — and
|
|
60714
|
+
* re-asserting it on a later reconnect would stomp whatever a newer device
|
|
60715
|
+
* declares afterwards. A METHOD, not inline in the socket handler, so tests
|
|
60716
|
+
* drive the rule production runs instead of a copy of it — a test-local
|
|
60717
|
+
* re-implementation of this exact logic passed with the real clearing
|
|
60718
|
+
* deleted, which is how the previous round's test enshrined the bug. */
|
|
60719
|
+
notePresenceAck(ev) {
|
|
60720
|
+
if (ev?.event !== "presence_update" || ev.username !== this.identity?.username) return;
|
|
60721
|
+
const frontier = this.selfStatusRev;
|
|
60722
|
+
if (typeof ev.rev === "number" && ev.rev > this.selfStatusRev) this.selfStatusRev = ev.rev;
|
|
60723
|
+
if (this.pendingPresence === null || ev.status !== this.pendingPresence) return;
|
|
60724
|
+
if (typeof ev.rev !== "number" || ev.rev <= this.pendingBaselineRev || ev.rev < frontier) return;
|
|
60725
|
+
this.pendingPresence = null;
|
|
60726
|
+
}
|
|
60687
60727
|
clientInfo;
|
|
60688
60728
|
// handshake, re-sent on reconnect
|
|
60689
60729
|
/** Is the hub socket usable RIGHT NOW?
|
|
@@ -60697,15 +60737,15 @@ var JefriClient = class _JefriClient {
|
|
|
60697
60737
|
get online() {
|
|
60698
60738
|
return this.hasConnected && !this.closing && this.ws?.readyState === this.ws?.OPEN;
|
|
60699
60739
|
}
|
|
60700
|
-
constructor(
|
|
60701
|
-
this.server =
|
|
60740
|
+
constructor(server2) {
|
|
60741
|
+
this.server = server2.replace(/\/$/, "");
|
|
60702
60742
|
}
|
|
60703
60743
|
static async connect(opts = {}) {
|
|
60704
|
-
const
|
|
60705
|
-
const client = new _JefriClient(
|
|
60744
|
+
const server2 = (opts.server ?? "https://jefrichat.com").replace(/\/$/, "");
|
|
60745
|
+
const client = new _JefriClient(server2);
|
|
60706
60746
|
let token = opts.token;
|
|
60707
60747
|
if (!token) {
|
|
60708
|
-
const res = await fetch(`${
|
|
60748
|
+
const res = await fetch(`${server2}/api/agents`, {
|
|
60709
60749
|
method: "POST",
|
|
60710
60750
|
headers: { "content-type": "application/json" },
|
|
60711
60751
|
body: JSON.stringify({
|
|
@@ -60778,7 +60818,8 @@ var JefriClient = class _JefriClient {
|
|
|
60778
60818
|
this.ws.on("open", () => {
|
|
60779
60819
|
this.startPing();
|
|
60780
60820
|
try {
|
|
60781
|
-
this.
|
|
60821
|
+
if (this.pendingPresence !== null)
|
|
60822
|
+
this.ws.send(JSON.stringify({ type: "presence", status: this.pendingPresence }));
|
|
60782
60823
|
if (this.clientInfo)
|
|
60783
60824
|
this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
|
|
60784
60825
|
} catch {
|
|
@@ -60807,12 +60848,15 @@ var JefriClient = class _JefriClient {
|
|
|
60807
60848
|
}
|
|
60808
60849
|
if (ev.event === "registered") {
|
|
60809
60850
|
this.identity = ev.identity;
|
|
60851
|
+
const rev = ev.identity?.statusRev;
|
|
60852
|
+
if (typeof rev === "number" && rev > this.selfStatusRev) this.selfStatusRev = rev;
|
|
60810
60853
|
this.hasConnected = true;
|
|
60811
60854
|
this.reconnectAttempts = 0;
|
|
60812
60855
|
}
|
|
60813
60856
|
if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
|
|
60814
60857
|
this.authFailed = true;
|
|
60815
60858
|
}
|
|
60859
|
+
this.notePresenceAck(ev);
|
|
60816
60860
|
this.emit(ev.event, ev);
|
|
60817
60861
|
this.emit("*", ev);
|
|
60818
60862
|
});
|
|
@@ -60901,7 +60945,8 @@ var JefriClient = class _JefriClient {
|
|
|
60901
60945
|
}
|
|
60902
60946
|
// --- actions -------------------------------------------------------------
|
|
60903
60947
|
presence(status) {
|
|
60904
|
-
this.
|
|
60948
|
+
this.pendingPresence = status;
|
|
60949
|
+
this.pendingBaselineRev = this.selfStatusRev;
|
|
60905
60950
|
this.send({ type: "presence", status });
|
|
60906
60951
|
}
|
|
60907
60952
|
message(to, content3) {
|
|
@@ -62005,7 +62050,21 @@ function waitFor(c, event, match, timeoutMs = 4e3) {
|
|
|
62005
62050
|
}, timeoutMs);
|
|
62006
62051
|
});
|
|
62007
62052
|
}
|
|
62008
|
-
var
|
|
62053
|
+
var upgradeNotice = null;
|
|
62054
|
+
var upgradeShownAt = 0;
|
|
62055
|
+
var UPGRADE_REPEAT_MS = 24 * 60 * 60 * 1e3;
|
|
62056
|
+
function takeUpgradeNotice(now = Date.now()) {
|
|
62057
|
+
if (!upgradeNotice) return null;
|
|
62058
|
+
if (upgradeShownAt && now - upgradeShownAt < UPGRADE_REPEAT_MS) return null;
|
|
62059
|
+
upgradeShownAt = now;
|
|
62060
|
+
return upgradeNotice;
|
|
62061
|
+
}
|
|
62062
|
+
var ok3 = (text5, now = Date.now()) => {
|
|
62063
|
+
const notice = takeUpgradeNotice(now);
|
|
62064
|
+
return { content: [{ type: "text", text: notice ? `${text5}
|
|
62065
|
+
|
|
62066
|
+
${notice}` : text5 }] };
|
|
62067
|
+
};
|
|
62009
62068
|
var fail = (text5) => ({ content: [{ type: "text", text: text5 }], isError: true });
|
|
62010
62069
|
var WATERMARK_FILE = np3.join(os4.homedir(), ".jefri", "inbox.json");
|
|
62011
62070
|
var inboxWatermark = new Map(
|
|
@@ -62024,9 +62083,9 @@ function saveWatermark() {
|
|
|
62024
62083
|
} catch {
|
|
62025
62084
|
}
|
|
62026
62085
|
}
|
|
62027
|
-
function registerAcpTools(
|
|
62086
|
+
function registerAcpTools(server2, ctx) {
|
|
62028
62087
|
const ENABLE_E2E = process.env.JEFRI_ENABLE_E2E === "true";
|
|
62029
|
-
const e2eServer = ENABLE_E2E ?
|
|
62088
|
+
const e2eServer = ENABLE_E2E ? server2 : { registerTool: () => void 0 };
|
|
62030
62089
|
const withClient = async (fn) => {
|
|
62031
62090
|
try {
|
|
62032
62091
|
const c = await ctx.ensureClient();
|
|
@@ -62036,7 +62095,7 @@ function registerAcpTools(server, ctx) {
|
|
|
62036
62095
|
${e?.message ?? e}`);
|
|
62037
62096
|
}
|
|
62038
62097
|
};
|
|
62039
|
-
|
|
62098
|
+
server2.registerTool(
|
|
62040
62099
|
"jefri_whoami",
|
|
62041
62100
|
{
|
|
62042
62101
|
title: "Who am I on Jefri Chat",
|
|
@@ -62047,7 +62106,7 @@ ${e?.message ?? e}`);
|
|
|
62047
62106
|
async (c) => ok3(`You are "${c.identity.displayName}" (@${c.identity.username}) on Jefri Chat at ${ctx.serverUrl}, status: online.`)
|
|
62048
62107
|
)
|
|
62049
62108
|
);
|
|
62050
|
-
|
|
62109
|
+
server2.registerTool(
|
|
62051
62110
|
"jefri_agents",
|
|
62052
62111
|
{
|
|
62053
62112
|
title: "List your connections",
|
|
@@ -62083,7 +62142,7 @@ ${e?.message ?? e}`);
|
|
|
62083
62142
|
}
|
|
62084
62143
|
return { status: res.status, body };
|
|
62085
62144
|
};
|
|
62086
|
-
|
|
62145
|
+
server2.registerTool(
|
|
62087
62146
|
"jefri_agent_apps",
|
|
62088
62147
|
{
|
|
62089
62148
|
title: "See where your agents are running",
|
|
@@ -62107,7 +62166,7 @@ ${lines.join("\n")}
|
|
|
62107
62166
|
Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
62108
62167
|
})
|
|
62109
62168
|
);
|
|
62110
|
-
|
|
62169
|
+
server2.registerTool(
|
|
62111
62170
|
"jefri_deactivate",
|
|
62112
62171
|
{
|
|
62113
62172
|
title: "Deactivate an agent",
|
|
@@ -62120,7 +62179,7 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
62120
62179
|
return ok3(`\u{1F6D1} Deactivated @${agent}. It's disconnected and can't reconnect from any app until you reactivate it.`);
|
|
62121
62180
|
})
|
|
62122
62181
|
);
|
|
62123
|
-
|
|
62182
|
+
server2.registerTool(
|
|
62124
62183
|
"jefri_activate",
|
|
62125
62184
|
{
|
|
62126
62185
|
title: "Reactivate an agent",
|
|
@@ -62133,7 +62192,7 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
|
62133
62192
|
return ok3(`\u2705 Reactivated @${agent}. It can connect again now.`);
|
|
62134
62193
|
})
|
|
62135
62194
|
);
|
|
62136
|
-
|
|
62195
|
+
server2.registerTool(
|
|
62137
62196
|
"jefri_send",
|
|
62138
62197
|
{
|
|
62139
62198
|
title: "Send a message",
|
|
@@ -62286,7 +62345,7 @@ ${fp}`);
|
|
|
62286
62345
|
return ok3(`Sent private E2E group file.`);
|
|
62287
62346
|
})
|
|
62288
62347
|
);
|
|
62289
|
-
|
|
62348
|
+
server2.registerTool(
|
|
62290
62349
|
"jefri_send_file",
|
|
62291
62350
|
{
|
|
62292
62351
|
title: "Send a file, PDF, or image",
|
|
@@ -62370,7 +62429,7 @@ ${psCmd}
|
|
|
62370
62429
|
return ok3(`Sent \u{1F4CE} ${name} to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
62371
62430
|
})
|
|
62372
62431
|
);
|
|
62373
|
-
|
|
62432
|
+
server2.registerTool(
|
|
62374
62433
|
"jefri_send_code",
|
|
62375
62434
|
{
|
|
62376
62435
|
title: "Send code / text as a file",
|
|
@@ -62405,7 +62464,7 @@ ${psCmd}
|
|
|
62405
62464
|
return ok3(`Sent \u{1F4C4} ${fileName} (${content3.length} chars) to @${to}${caption ? ` \u2014 "${caption}"` : ""}`);
|
|
62406
62465
|
})
|
|
62407
62466
|
);
|
|
62408
|
-
|
|
62467
|
+
server2.registerTool(
|
|
62409
62468
|
"jefri_send_folder",
|
|
62410
62469
|
{
|
|
62411
62470
|
title: "Send a whole folder / project (zipped)",
|
|
@@ -62503,7 +62562,7 @@ ${psCmd}
|
|
|
62503
62562
|
);
|
|
62504
62563
|
})
|
|
62505
62564
|
);
|
|
62506
|
-
|
|
62565
|
+
server2.registerTool(
|
|
62507
62566
|
"jefri_download_file",
|
|
62508
62567
|
{
|
|
62509
62568
|
title: "Download / save a file someone sent you",
|
|
@@ -62550,7 +62609,7 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
62550
62609
|
);
|
|
62551
62610
|
})
|
|
62552
62611
|
);
|
|
62553
|
-
|
|
62612
|
+
server2.registerTool(
|
|
62554
62613
|
"jefri_connect",
|
|
62555
62614
|
{
|
|
62556
62615
|
title: "Request a connection",
|
|
@@ -62566,7 +62625,7 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
62566
62625
|
})
|
|
62567
62626
|
);
|
|
62568
62627
|
const hubBase = () => ctx.serverUrl.replace(/\/$/, "");
|
|
62569
|
-
|
|
62628
|
+
server2.registerTool(
|
|
62570
62629
|
"jefri_departments",
|
|
62571
62630
|
{
|
|
62572
62631
|
title: "List your departments",
|
|
@@ -62580,7 +62639,7 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
62580
62639
|
return ok3(d.map((x) => `\u2022 ${x.name} (${x.org}) \u2014 access: ${x.myLevel} [id: ${x.id}]`).join("\n"));
|
|
62581
62640
|
})
|
|
62582
62641
|
);
|
|
62583
|
-
|
|
62642
|
+
server2.registerTool(
|
|
62584
62643
|
"jefri_dept_files",
|
|
62585
62644
|
{
|
|
62586
62645
|
title: "List a department's files",
|
|
@@ -62595,7 +62654,7 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
62595
62654
|
return ok3(files.map((f) => `\u2022 ${f.name} [${f.kind}] \u2014 by ${f.createdBy}, ${new Date(f.updatedAt).toLocaleDateString()} (id: ${f.id})`).join("\n"));
|
|
62596
62655
|
})
|
|
62597
62656
|
);
|
|
62598
|
-
|
|
62657
|
+
server2.registerTool(
|
|
62599
62658
|
"jefri_dept_read",
|
|
62600
62659
|
{
|
|
62601
62660
|
title: "Download a department file",
|
|
@@ -62625,7 +62684,7 @@ ${cmd}
|
|
|
62625
62684
|
(Set your token first: export JEFRI_TOKEN=\u2026 from the app's Connect dialog \u2014 it isn't printed here on purpose.)`);
|
|
62626
62685
|
})
|
|
62627
62686
|
);
|
|
62628
|
-
|
|
62687
|
+
server2.registerTool(
|
|
62629
62688
|
"jefri_dept_add_file",
|
|
62630
62689
|
{
|
|
62631
62690
|
title: "Add a file to a department",
|
|
@@ -62648,7 +62707,7 @@ ${cmd}
|
|
|
62648
62707
|
(With 'write' access it's added directly; with 'propose' access it's submitted for an admin to approve; read-only is rejected.)`);
|
|
62649
62708
|
})
|
|
62650
62709
|
);
|
|
62651
|
-
|
|
62710
|
+
server2.registerTool(
|
|
62652
62711
|
"jefri_my_docs",
|
|
62653
62712
|
{
|
|
62654
62713
|
title: "List my documents (memory)",
|
|
@@ -62667,7 +62726,7 @@ ${cmd}
|
|
|
62667
62726
|
);
|
|
62668
62727
|
})
|
|
62669
62728
|
);
|
|
62670
|
-
|
|
62729
|
+
server2.registerTool(
|
|
62671
62730
|
"jefri_search_docs",
|
|
62672
62731
|
{
|
|
62673
62732
|
title: "Search my document memory",
|
|
@@ -62690,7 +62749,7 @@ ${h.text}`).join("\n\n---\n\n")
|
|
|
62690
62749
|
);
|
|
62691
62750
|
})
|
|
62692
62751
|
);
|
|
62693
|
-
|
|
62752
|
+
server2.registerTool(
|
|
62694
62753
|
"jefri_read_doc",
|
|
62695
62754
|
{
|
|
62696
62755
|
title: "Read/get one of my documents",
|
|
@@ -62715,7 +62774,7 @@ ${cmd}
|
|
|
62715
62774
|
(Set your token first: export JEFRI_TOKEN=\u2026 from the app's Connect dialog \u2014 it isn't printed here on purpose.)`);
|
|
62716
62775
|
})
|
|
62717
62776
|
);
|
|
62718
|
-
|
|
62777
|
+
server2.registerTool(
|
|
62719
62778
|
"jefri_add_doc",
|
|
62720
62779
|
{
|
|
62721
62780
|
title: "Add a document to my memory",
|
|
@@ -62735,7 +62794,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62735
62794
|
);
|
|
62736
62795
|
})
|
|
62737
62796
|
);
|
|
62738
|
-
|
|
62797
|
+
server2.registerTool(
|
|
62739
62798
|
"jefri_my_tasks",
|
|
62740
62799
|
{
|
|
62741
62800
|
title: "My department tasks",
|
|
@@ -62752,7 +62811,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62752
62811
|
);
|
|
62753
62812
|
})
|
|
62754
62813
|
);
|
|
62755
|
-
|
|
62814
|
+
server2.registerTool(
|
|
62756
62815
|
"jefri_task_status",
|
|
62757
62816
|
{
|
|
62758
62817
|
title: "Update a task's status",
|
|
@@ -62774,7 +62833,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62774
62833
|
return ok3(`Task marked ${status === "done" ? "\u2705 done" : status}.`);
|
|
62775
62834
|
})
|
|
62776
62835
|
);
|
|
62777
|
-
|
|
62836
|
+
server2.registerTool(
|
|
62778
62837
|
"jefri_inbox",
|
|
62779
62838
|
{
|
|
62780
62839
|
title: "Read unread messages",
|
|
@@ -62830,7 +62889,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62830
62889
|
` + lines.join("\n"));
|
|
62831
62890
|
})
|
|
62832
62891
|
);
|
|
62833
|
-
|
|
62892
|
+
server2.registerTool(
|
|
62834
62893
|
"jefri_groups",
|
|
62835
62894
|
{
|
|
62836
62895
|
title: "List your groups",
|
|
@@ -62846,7 +62905,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62846
62905
|
);
|
|
62847
62906
|
})
|
|
62848
62907
|
);
|
|
62849
|
-
|
|
62908
|
+
server2.registerTool(
|
|
62850
62909
|
"jefri_send_group",
|
|
62851
62910
|
{
|
|
62852
62911
|
title: "Send a group message",
|
|
@@ -62864,7 +62923,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62864
62923
|
return ok3(`Posted to the group.`);
|
|
62865
62924
|
})
|
|
62866
62925
|
);
|
|
62867
|
-
|
|
62926
|
+
server2.registerTool(
|
|
62868
62927
|
"jefri_send_group_file",
|
|
62869
62928
|
{
|
|
62870
62929
|
title: "Send a file to a group",
|
|
@@ -62938,7 +62997,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62938
62997
|
return ok3(`Sent \u{1F4CE} ${name} to the group.`);
|
|
62939
62998
|
})
|
|
62940
62999
|
);
|
|
62941
|
-
|
|
63000
|
+
server2.registerTool(
|
|
62942
63001
|
"jefri_history",
|
|
62943
63002
|
{
|
|
62944
63003
|
title: "Read conversation history",
|
|
@@ -62978,7 +63037,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62978
63037
|
return ok3(lines.join("\n"));
|
|
62979
63038
|
})
|
|
62980
63039
|
);
|
|
62981
|
-
|
|
63040
|
+
server2.registerTool(
|
|
62982
63041
|
"jefri_search",
|
|
62983
63042
|
{
|
|
62984
63043
|
title: "Discover agents",
|
|
@@ -62996,7 +63055,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
62996
63055
|
);
|
|
62997
63056
|
})
|
|
62998
63057
|
);
|
|
62999
|
-
|
|
63058
|
+
server2.registerTool(
|
|
63000
63059
|
"jefri_set_status",
|
|
63001
63060
|
{
|
|
63002
63061
|
title: "Set presence",
|
|
@@ -63011,7 +63070,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63011
63070
|
})
|
|
63012
63071
|
);
|
|
63013
63072
|
if (ctx.local) {
|
|
63014
|
-
|
|
63073
|
+
server2.registerTool(
|
|
63015
63074
|
"jefri_notifications",
|
|
63016
63075
|
{
|
|
63017
63076
|
title: "Notification settings",
|
|
@@ -63090,7 +63149,7 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
63090
63149
|
return ok3(summary);
|
|
63091
63150
|
})
|
|
63092
63151
|
);
|
|
63093
|
-
|
|
63152
|
+
server2.registerTool(
|
|
63094
63153
|
"jefri_autonomous",
|
|
63095
63154
|
{
|
|
63096
63155
|
title: "Autonomous mode",
|
|
@@ -63167,6 +63226,34 @@ function attachInbox(c, inbox, self, onIncoming) {
|
|
|
63167
63226
|
c.on("file_received", capture);
|
|
63168
63227
|
}
|
|
63169
63228
|
|
|
63229
|
+
// src/drain.ts
|
|
63230
|
+
function durationEnv(name, fallback, env = process.env) {
|
|
63231
|
+
const raw = env[name];
|
|
63232
|
+
if (raw === void 0 || raw === "") return fallback;
|
|
63233
|
+
const n = Number(raw);
|
|
63234
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
63235
|
+
console.error(`[mcp] ignoring ${name}="${raw}" \u2014 expected a positive number; using ${fallback}ms`);
|
|
63236
|
+
return fallback;
|
|
63237
|
+
}
|
|
63238
|
+
return n;
|
|
63239
|
+
}
|
|
63240
|
+
function sweepIdleSessions(map3) {
|
|
63241
|
+
let closed = 0;
|
|
63242
|
+
let busy = 0;
|
|
63243
|
+
for (const s of map3) {
|
|
63244
|
+
if (s.inflight > 0) {
|
|
63245
|
+
busy++;
|
|
63246
|
+
continue;
|
|
63247
|
+
}
|
|
63248
|
+
try {
|
|
63249
|
+
s.transport.close?.();
|
|
63250
|
+
closed++;
|
|
63251
|
+
} catch {
|
|
63252
|
+
}
|
|
63253
|
+
}
|
|
63254
|
+
return { closed, busy };
|
|
63255
|
+
}
|
|
63256
|
+
|
|
63170
63257
|
// src/http.ts
|
|
63171
63258
|
var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
|
|
63172
63259
|
var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
|
|
@@ -63310,7 +63397,14 @@ app.use((req, res, next) => {
|
|
|
63310
63397
|
next();
|
|
63311
63398
|
});
|
|
63312
63399
|
app.use(import_express.default.json({ limit: process.env.MCP_MAX_BODY ?? "4mb" }));
|
|
63313
|
-
var
|
|
63400
|
+
var draining = false;
|
|
63401
|
+
var health = (_req, res) => res.status(draining ? 503 : 200).json({
|
|
63402
|
+
ok: !draining,
|
|
63403
|
+
draining,
|
|
63404
|
+
service: "jefrichat-mcp-http",
|
|
63405
|
+
hub: HUB,
|
|
63406
|
+
sessions: sessions.size
|
|
63407
|
+
});
|
|
63314
63408
|
app.get("/health", health);
|
|
63315
63409
|
app.get("/mcp/health", health);
|
|
63316
63410
|
app.post("/mcp", async (req, res) => {
|
|
@@ -63348,6 +63442,14 @@ app.post("/mcp", async (req, res) => {
|
|
|
63348
63442
|
}
|
|
63349
63443
|
return;
|
|
63350
63444
|
}
|
|
63445
|
+
if (draining && !sessionId && isInitializeRequest(req.body)) {
|
|
63446
|
+
res.setHeader("retry-after", "1");
|
|
63447
|
+
return res.status(503).json({
|
|
63448
|
+
jsonrpc: "2.0",
|
|
63449
|
+
error: { code: -32e3, message: "server is shutting down \u2014 retry; another instance will accept this session" },
|
|
63450
|
+
id: null
|
|
63451
|
+
});
|
|
63452
|
+
}
|
|
63351
63453
|
if (!sessionId && isInitializeRequest(req.body)) {
|
|
63352
63454
|
if (initRateLimited(req)) {
|
|
63353
63455
|
res.status(429).json({
|
|
@@ -63488,13 +63590,52 @@ var bySession = async (req, res) => {
|
|
|
63488
63590
|
};
|
|
63489
63591
|
app.get("/mcp", bySession);
|
|
63490
63592
|
app.delete("/mcp", bySession);
|
|
63491
|
-
http.createServer(app).listen(PORT, () => {
|
|
63593
|
+
var server = http.createServer(app).listen(PORT, () => {
|
|
63492
63594
|
console.error(`
|
|
63493
63595
|
\u{1F310} Jefri Chat remote MCP (HTTP) on http://localhost:${PORT}/mcp`);
|
|
63494
63596
|
console.error(` hub: ${HUB}`);
|
|
63495
63597
|
console.error(` auth: Authorization: Bearer <jefri_token>
|
|
63496
63598
|
`);
|
|
63497
63599
|
});
|
|
63600
|
+
var DRAIN_MS = durationEnv("MCP_DRAIN_MS", 15e3);
|
|
63601
|
+
var UNHEALTHY_MS = durationEnv("MCP_UNHEALTHY_MS", 5e3);
|
|
63602
|
+
var shuttingDown = false;
|
|
63603
|
+
function drain2(signal) {
|
|
63604
|
+
if (shuttingDown) return;
|
|
63605
|
+
shuttingDown = true;
|
|
63606
|
+
draining = true;
|
|
63607
|
+
const deadline = Date.now() + UNHEALTHY_MS + DRAIN_MS;
|
|
63608
|
+
console.error(
|
|
63609
|
+
`[mcp] ${signal} \u2014 unhealthy for ${UNHEALTHY_MS}ms, then draining ${sessions.size} session(s) for up to ${DRAIN_MS}ms`
|
|
63610
|
+
);
|
|
63611
|
+
const closeAndFinish = () => {
|
|
63612
|
+
server.close(() => {
|
|
63613
|
+
console.error("[mcp] drained cleanly");
|
|
63614
|
+
process.exit(0);
|
|
63615
|
+
});
|
|
63616
|
+
const sweep = setInterval(() => {
|
|
63617
|
+
const { busy } = sweepIdleSessions(sessions.values());
|
|
63618
|
+
if (busy === 0) {
|
|
63619
|
+
clearInterval(sweep);
|
|
63620
|
+
console.error("[mcp] all sessions idle \u2014 closing");
|
|
63621
|
+
return;
|
|
63622
|
+
}
|
|
63623
|
+
if (Date.now() >= deadline) {
|
|
63624
|
+
clearInterval(sweep);
|
|
63625
|
+
console.error(`[mcp] drain deadline reached with ${busy} session(s) still busy \u2014 exiting`);
|
|
63626
|
+
process.exit(0);
|
|
63627
|
+
}
|
|
63628
|
+
}, 250);
|
|
63629
|
+
sweep.unref();
|
|
63630
|
+
};
|
|
63631
|
+
setTimeout(closeAndFinish, UNHEALTHY_MS).unref();
|
|
63632
|
+
setTimeout(() => {
|
|
63633
|
+
console.error("[mcp] drain deadline reached \u2014 exiting");
|
|
63634
|
+
process.exit(0);
|
|
63635
|
+
}, UNHEALTHY_MS + DRAIN_MS + 500).unref();
|
|
63636
|
+
}
|
|
63637
|
+
process.on("SIGTERM", () => drain2("SIGTERM"));
|
|
63638
|
+
process.on("SIGINT", () => drain2("SIGINT"));
|
|
63498
63639
|
/*! Bundled license information:
|
|
63499
63640
|
|
|
63500
63641
|
depd/index.js:
|
package/dist/index.js
CHANGED
|
@@ -38648,8 +38648,48 @@ var JefriClient = class _JefriClient {
|
|
|
38648
38648
|
// heartbeat: did we get a pong since the last ping?
|
|
38649
38649
|
hasConnected = false;
|
|
38650
38650
|
// armed for auto-reconnect only after the hub `registered` us
|
|
38651
|
-
|
|
38652
|
-
|
|
38651
|
+
/** UNACKNOWLEDGED presence intent, or null. Set by presence(), cleared when
|
|
38652
|
+
* the hub's own presence_update for this identity confirms the declaration
|
|
38653
|
+
* landed. Null on reconnect means: send nothing and let the hub's persisted
|
|
38654
|
+
* answer stand.
|
|
38655
|
+
*
|
|
38656
|
+
* Acknowledgment is the point, and it is what the first fix missed: a value
|
|
38657
|
+
* kept after delivery means "ever declared", not "pending" — so a client
|
|
38658
|
+
* that once declared away kept re-asserting it on every reconnect for the
|
|
38659
|
+
* life of the process, stomping whatever a newer device had chosen since.
|
|
38660
|
+
* The same stale-overwrite, one level down. */
|
|
38661
|
+
pendingPresence = null;
|
|
38662
|
+
/** The self statusRev BASELINE captured when the pending declaration was made.
|
|
38663
|
+
* An ack must be CAUSALLY newer than this — the audit reproduced a delayed
|
|
38664
|
+
* rev-9 event spending intent declared at a rev-10 baseline, after which the
|
|
38665
|
+
* server had already moved on. Identity + status matching alone cannot order
|
|
38666
|
+
* events; the revision can. */
|
|
38667
|
+
pendingBaselineRev = 0;
|
|
38668
|
+
/** Highest revision seen for our own status, from registered and from our own
|
|
38669
|
+
* presence_update events. */
|
|
38670
|
+
selfStatusRev = 0;
|
|
38671
|
+
/** Would a reconnect re-send a presence frame? True only while a declaration
|
|
38672
|
+
* is UNACKNOWLEDGED. Exposed for tests: the wire behaviour needs a live hub,
|
|
38673
|
+
* but the rule is checkable without one. */
|
|
38674
|
+
wouldReassertPresence() {
|
|
38675
|
+
return this.pendingPresence !== null;
|
|
38676
|
+
}
|
|
38677
|
+
/** The acknowledgment that turns "pending" back into nothing: the hub
|
|
38678
|
+
* broadcasts presence_update to the declaring identity too, so our own
|
|
38679
|
+
* username carrying our pending status means the declaration LANDED — and
|
|
38680
|
+
* re-asserting it on a later reconnect would stomp whatever a newer device
|
|
38681
|
+
* declares afterwards. A METHOD, not inline in the socket handler, so tests
|
|
38682
|
+
* drive the rule production runs instead of a copy of it — a test-local
|
|
38683
|
+
* re-implementation of this exact logic passed with the real clearing
|
|
38684
|
+
* deleted, which is how the previous round's test enshrined the bug. */
|
|
38685
|
+
notePresenceAck(ev) {
|
|
38686
|
+
if (ev?.event !== "presence_update" || ev.username !== this.identity?.username) return;
|
|
38687
|
+
const frontier = this.selfStatusRev;
|
|
38688
|
+
if (typeof ev.rev === "number" && ev.rev > this.selfStatusRev) this.selfStatusRev = ev.rev;
|
|
38689
|
+
if (this.pendingPresence === null || ev.status !== this.pendingPresence) return;
|
|
38690
|
+
if (typeof ev.rev !== "number" || ev.rev <= this.pendingBaselineRev || ev.rev < frontier) return;
|
|
38691
|
+
this.pendingPresence = null;
|
|
38692
|
+
}
|
|
38653
38693
|
clientInfo;
|
|
38654
38694
|
// handshake, re-sent on reconnect
|
|
38655
38695
|
/** Is the hub socket usable RIGHT NOW?
|
|
@@ -38744,7 +38784,8 @@ var JefriClient = class _JefriClient {
|
|
|
38744
38784
|
this.ws.on("open", () => {
|
|
38745
38785
|
this.startPing();
|
|
38746
38786
|
try {
|
|
38747
|
-
this.
|
|
38787
|
+
if (this.pendingPresence !== null)
|
|
38788
|
+
this.ws.send(JSON.stringify({ type: "presence", status: this.pendingPresence }));
|
|
38748
38789
|
if (this.clientInfo)
|
|
38749
38790
|
this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
|
|
38750
38791
|
} catch {
|
|
@@ -38773,12 +38814,15 @@ var JefriClient = class _JefriClient {
|
|
|
38773
38814
|
}
|
|
38774
38815
|
if (ev.event === "registered") {
|
|
38775
38816
|
this.identity = ev.identity;
|
|
38817
|
+
const rev = ev.identity?.statusRev;
|
|
38818
|
+
if (typeof rev === "number" && rev > this.selfStatusRev) this.selfStatusRev = rev;
|
|
38776
38819
|
this.hasConnected = true;
|
|
38777
38820
|
this.reconnectAttempts = 0;
|
|
38778
38821
|
}
|
|
38779
38822
|
if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
|
|
38780
38823
|
this.authFailed = true;
|
|
38781
38824
|
}
|
|
38825
|
+
this.notePresenceAck(ev);
|
|
38782
38826
|
this.emit(ev.event, ev);
|
|
38783
38827
|
this.emit("*", ev);
|
|
38784
38828
|
});
|
|
@@ -38867,7 +38911,8 @@ var JefriClient = class _JefriClient {
|
|
|
38867
38911
|
}
|
|
38868
38912
|
// --- actions -------------------------------------------------------------
|
|
38869
38913
|
presence(status) {
|
|
38870
|
-
this.
|
|
38914
|
+
this.pendingPresence = status;
|
|
38915
|
+
this.pendingBaselineRev = this.selfStatusRev;
|
|
38871
38916
|
this.send({ type: "presence", status });
|
|
38872
38917
|
}
|
|
38873
38918
|
message(to, content3) {
|
|
@@ -40788,7 +40833,24 @@ function waitFor(c2, event, match, timeoutMs = 4e3) {
|
|
|
40788
40833
|
}, timeoutMs);
|
|
40789
40834
|
});
|
|
40790
40835
|
}
|
|
40791
|
-
var
|
|
40836
|
+
var upgradeNotice = null;
|
|
40837
|
+
var upgradeShownAt = 0;
|
|
40838
|
+
var UPGRADE_REPEAT_MS = 24 * 60 * 60 * 1e3;
|
|
40839
|
+
function setUpgradeNotice(text5) {
|
|
40840
|
+
upgradeNotice = text5;
|
|
40841
|
+
}
|
|
40842
|
+
function takeUpgradeNotice(now = Date.now()) {
|
|
40843
|
+
if (!upgradeNotice) return null;
|
|
40844
|
+
if (upgradeShownAt && now - upgradeShownAt < UPGRADE_REPEAT_MS) return null;
|
|
40845
|
+
upgradeShownAt = now;
|
|
40846
|
+
return upgradeNotice;
|
|
40847
|
+
}
|
|
40848
|
+
var ok3 = (text5, now = Date.now()) => {
|
|
40849
|
+
const notice = takeUpgradeNotice(now);
|
|
40850
|
+
return { content: [{ type: "text", text: notice ? `${text5}
|
|
40851
|
+
|
|
40852
|
+
${notice}` : text5 }] };
|
|
40853
|
+
};
|
|
40792
40854
|
var fail = (text5) => ({ content: [{ type: "text", text: text5 }], isError: true });
|
|
40793
40855
|
var WATERMARK_FILE = np4.join(os4.homedir(), ".jefri", "inbox.json");
|
|
40794
40856
|
var inboxWatermark = new Map(
|
|
@@ -42936,10 +42998,14 @@ async function main() {
|
|
|
42936
42998
|
log("unhandled rejection:", reason?.stack ?? reason);
|
|
42937
42999
|
});
|
|
42938
43000
|
void checkForUpdate().then((u) => {
|
|
42939
|
-
if (u?.outdated)
|
|
43001
|
+
if (u?.outdated) {
|
|
43002
|
+
setUpgradeNotice(
|
|
43003
|
+
`\u26A0\uFE0F Jefri Chat connector ${u.current} is out of date (latest ${u.latest}). Restart this agent to upgrade \u2014 a running connector keeps the build it started with. If it was started with npx, that is enough; if installed globally, run: npm update -g jefrichat-mcp`
|
|
43004
|
+
);
|
|
42940
43005
|
note(
|
|
42941
43006
|
`\u2191 update available: jefrichat-mcp ${u.current} \u2192 ${u.latest}. Reconnect this agent (npx jefrichat-mcp@latest, or 'npm update -g jefrichat-mcp@latest' if globally installed) to get it.`
|
|
42942
43007
|
);
|
|
43008
|
+
}
|
|
42943
43009
|
}).catch(() => {
|
|
42944
43010
|
});
|
|
42945
43011
|
}
|
package/package.json
CHANGED