jefrichat-mcp 0.28.2 → 0.30.0
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/README.md +2 -2
- package/dist/http.js +25 -4
- package/dist/index.js +255 -16
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ shows at a glance which agent is which.
|
|
|
15
15
|
|
|
16
16
|
### Claude Code (local, via npx)
|
|
17
17
|
```bash
|
|
18
|
-
claude mcp add jefri_aaron -e JEFRI_SERVER=https://jefrichat.com -e JEFRI_TOKEN=jefri_xxx -- npx -y jefrichat-mcp
|
|
18
|
+
claude mcp add jefri_aaron -e JEFRI_SERVER=https://jefrichat.com -e JEFRI_TOKEN=jefri_xxx -- npx -y jefrichat-mcp@latest
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### Claude Desktop / Codex / Cursor (config)
|
|
@@ -24,7 +24,7 @@ claude mcp add jefri_aaron -e JEFRI_SERVER=https://jefrichat.com -e JEFRI_TOKEN=
|
|
|
24
24
|
"mcpServers": {
|
|
25
25
|
"jefri_aaron": {
|
|
26
26
|
"command": "npx",
|
|
27
|
-
"args": ["-y", "jefrichat-mcp"],
|
|
27
|
+
"args": ["-y", "jefrichat-mcp@latest"],
|
|
28
28
|
"env": { "JEFRI_SERVER": "https://jefrichat.com", "JEFRI_TOKEN": "jefri_xxx" }
|
|
29
29
|
}
|
|
30
30
|
}
|
package/dist/http.js
CHANGED
|
@@ -48969,6 +48969,8 @@ var JefriClient = class _JefriClient {
|
|
|
48969
48969
|
// armed for auto-reconnect only after the first open
|
|
48970
48970
|
lastPresence = "online";
|
|
48971
48971
|
// re-announce this on reconnect
|
|
48972
|
+
clientInfo;
|
|
48973
|
+
// version handshake, re-sent on reconnect
|
|
48972
48974
|
constructor(server) {
|
|
48973
48975
|
this.server = server.replace(/\/$/, "");
|
|
48974
48976
|
}
|
|
@@ -48995,6 +48997,7 @@ var JefriClient = class _JefriClient {
|
|
|
48995
48997
|
client.identity = data.identity;
|
|
48996
48998
|
}
|
|
48997
48999
|
client.token = token;
|
|
49000
|
+
client.clientInfo = opts.clientInfo;
|
|
48998
49001
|
await client.openSocket();
|
|
48999
49002
|
if (!client.identity) {
|
|
49000
49003
|
await new Promise((resolve, reject) => {
|
|
@@ -49037,6 +49040,8 @@ var JefriClient = class _JefriClient {
|
|
|
49037
49040
|
this.startPing();
|
|
49038
49041
|
try {
|
|
49039
49042
|
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
49043
|
+
if (this.clientInfo)
|
|
49044
|
+
this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
|
|
49040
49045
|
} catch {
|
|
49041
49046
|
}
|
|
49042
49047
|
if (!settled) {
|
|
@@ -49171,6 +49176,20 @@ var JefriClient = class _JefriClient {
|
|
|
49171
49176
|
groupMessage(groupId, content) {
|
|
49172
49177
|
this.send({ type: "group_message", groupId, content });
|
|
49173
49178
|
}
|
|
49179
|
+
// Debates: post your argument for the turn you hold (rejected by the hub if
|
|
49180
|
+
// it isn't your turn). raiseHand/releaseFloor are the human moderator actions.
|
|
49181
|
+
debatePost(debateId, content, turnSeq) {
|
|
49182
|
+
this.send({ type: "debate_post", debateId, content, turnSeq });
|
|
49183
|
+
}
|
|
49184
|
+
debateRaiseHand(debateId) {
|
|
49185
|
+
this.send({ type: "debate_raise_hand", debateId });
|
|
49186
|
+
}
|
|
49187
|
+
debateRelease(debateId) {
|
|
49188
|
+
this.send({ type: "debate_release", debateId });
|
|
49189
|
+
}
|
|
49190
|
+
debateSummary(debateId, content) {
|
|
49191
|
+
this.send({ type: "debate_summary", debateId, content });
|
|
49192
|
+
}
|
|
49174
49193
|
sendFile(to, fileName, fileMime, fileDataUrl) {
|
|
49175
49194
|
this.send({ type: "file", to, fileName, fileMime, fileDataUrl });
|
|
49176
49195
|
}
|
|
@@ -49636,6 +49655,8 @@ function acquireResponderLock() {
|
|
|
49636
49655
|
}
|
|
49637
49656
|
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49638
49657
|
var FORCE_OWNER_ONLY = true;
|
|
49658
|
+
var DEBATE_BRAIN_TIMEOUT_MS = Number(process.env.JEFRI_DEBATE_BRAIN_TIMEOUT_MS ?? 15e4);
|
|
49659
|
+
var DEBATE_SEQ_TTL_MS = 10 * 60 * 1e3;
|
|
49639
49660
|
|
|
49640
49661
|
// src/tools.ts
|
|
49641
49662
|
var MIME = {
|
|
@@ -50947,7 +50968,7 @@ var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
|
|
|
50947
50968
|
var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
|
|
50948
50969
|
var log = (...a) => console.error("[jefrichat-mcp-http]", ...a);
|
|
50949
50970
|
var sessions = /* @__PURE__ */ new Map();
|
|
50950
|
-
var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ??
|
|
50971
|
+
var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ?? 4 * 60 * 60 * 1e3);
|
|
50951
50972
|
var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
|
|
50952
50973
|
function closeSession(sid, reason) {
|
|
50953
50974
|
const s = sessions.get(sid);
|
|
@@ -51088,16 +51109,16 @@ app.post("/mcp", async (req, res) => {
|
|
|
51088
51109
|
await transport.handleRequest(req, res, req.body);
|
|
51089
51110
|
return;
|
|
51090
51111
|
}
|
|
51091
|
-
res.status(
|
|
51112
|
+
res.status(404).json({
|
|
51092
51113
|
jsonrpc: "2.0",
|
|
51093
|
-
error: { code: -
|
|
51114
|
+
error: { code: -32001, message: "Session expired \u2014 reinitialize (your token is still valid)" },
|
|
51094
51115
|
id: null
|
|
51095
51116
|
});
|
|
51096
51117
|
});
|
|
51097
51118
|
var bySession = async (req, res) => {
|
|
51098
51119
|
const sessionId = req.headers["mcp-session-id"];
|
|
51099
51120
|
if (!sessionId || !sessions.has(sessionId)) {
|
|
51100
|
-
res.status(
|
|
51121
|
+
res.status(404).send("Session expired \u2014 reinitialize");
|
|
51101
51122
|
return;
|
|
51102
51123
|
}
|
|
51103
51124
|
touch(sessionId);
|
package/dist/index.js
CHANGED
|
@@ -24865,6 +24865,8 @@ var JefriClient = class _JefriClient {
|
|
|
24865
24865
|
// armed for auto-reconnect only after the first open
|
|
24866
24866
|
lastPresence = "online";
|
|
24867
24867
|
// re-announce this on reconnect
|
|
24868
|
+
clientInfo;
|
|
24869
|
+
// version handshake, re-sent on reconnect
|
|
24868
24870
|
constructor(server2) {
|
|
24869
24871
|
this.server = server2.replace(/\/$/, "");
|
|
24870
24872
|
}
|
|
@@ -24891,6 +24893,7 @@ var JefriClient = class _JefriClient {
|
|
|
24891
24893
|
client.identity = data.identity;
|
|
24892
24894
|
}
|
|
24893
24895
|
client.token = token;
|
|
24896
|
+
client.clientInfo = opts.clientInfo;
|
|
24894
24897
|
await client.openSocket();
|
|
24895
24898
|
if (!client.identity) {
|
|
24896
24899
|
await new Promise((resolve, reject) => {
|
|
@@ -24933,6 +24936,8 @@ var JefriClient = class _JefriClient {
|
|
|
24933
24936
|
this.startPing();
|
|
24934
24937
|
try {
|
|
24935
24938
|
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
24939
|
+
if (this.clientInfo)
|
|
24940
|
+
this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
|
|
24936
24941
|
} catch {
|
|
24937
24942
|
}
|
|
24938
24943
|
if (!settled) {
|
|
@@ -25067,6 +25072,20 @@ var JefriClient = class _JefriClient {
|
|
|
25067
25072
|
groupMessage(groupId, content) {
|
|
25068
25073
|
this.send({ type: "group_message", groupId, content });
|
|
25069
25074
|
}
|
|
25075
|
+
// Debates: post your argument for the turn you hold (rejected by the hub if
|
|
25076
|
+
// it isn't your turn). raiseHand/releaseFloor are the human moderator actions.
|
|
25077
|
+
debatePost(debateId, content, turnSeq) {
|
|
25078
|
+
this.send({ type: "debate_post", debateId, content, turnSeq });
|
|
25079
|
+
}
|
|
25080
|
+
debateRaiseHand(debateId) {
|
|
25081
|
+
this.send({ type: "debate_raise_hand", debateId });
|
|
25082
|
+
}
|
|
25083
|
+
debateRelease(debateId) {
|
|
25084
|
+
this.send({ type: "debate_release", debateId });
|
|
25085
|
+
}
|
|
25086
|
+
debateSummary(debateId, content) {
|
|
25087
|
+
this.send({ type: "debate_summary", debateId, content });
|
|
25088
|
+
}
|
|
25070
25089
|
sendFile(to, fileName, fileMime, fileDataUrl) {
|
|
25071
25090
|
this.send({ type: "file", to, fileName, fileMime, fileDataUrl });
|
|
25072
25091
|
}
|
|
@@ -25736,14 +25755,18 @@ async function drain() {
|
|
|
25736
25755
|
}
|
|
25737
25756
|
running = false;
|
|
25738
25757
|
}
|
|
25739
|
-
function runBrain(cmd, args, cwd) {
|
|
25758
|
+
function runBrain(cmd, args, cwd, opts) {
|
|
25740
25759
|
return new Promise((resolve, reject) => {
|
|
25741
25760
|
let child;
|
|
25742
25761
|
try {
|
|
25743
|
-
child = cp2.spawn(cmd, args, { cwd: expand(cwd), stdio: ["ignore", "pipe", "pipe"] });
|
|
25762
|
+
child = cp2.spawn(cmd, args, { cwd: expand(cwd), stdio: ["ignore", "pipe", "pipe"], env: opts?.env ?? process.env });
|
|
25744
25763
|
} catch (e) {
|
|
25745
25764
|
return reject(e);
|
|
25746
25765
|
}
|
|
25766
|
+
try {
|
|
25767
|
+
opts?.onSpawn?.(child);
|
|
25768
|
+
} catch {
|
|
25769
|
+
}
|
|
25747
25770
|
let out = "", err = "";
|
|
25748
25771
|
const timer = setTimeout(() => {
|
|
25749
25772
|
try {
|
|
@@ -25751,7 +25774,7 @@ function runBrain(cmd, args, cwd) {
|
|
|
25751
25774
|
} catch {
|
|
25752
25775
|
}
|
|
25753
25776
|
reject(new Error("timed out"));
|
|
25754
|
-
}, TIMEOUT_MS);
|
|
25777
|
+
}, opts?.timeoutMs ?? TIMEOUT_MS);
|
|
25755
25778
|
child.stdout?.on("data", (d) => out += d.toString());
|
|
25756
25779
|
child.stderr?.on("data", (d) => err += d.toString());
|
|
25757
25780
|
child.on("error", (e) => {
|
|
@@ -25845,6 +25868,140 @@ async function deliverReply(reply, text) {
|
|
|
25845
25868
|
}
|
|
25846
25869
|
return false;
|
|
25847
25870
|
}
|
|
25871
|
+
function debateBrainArgv(brain, prompt) {
|
|
25872
|
+
const b = resolveBrain(brain);
|
|
25873
|
+
if (b === "claude")
|
|
25874
|
+
return [
|
|
25875
|
+
"claude",
|
|
25876
|
+
[
|
|
25877
|
+
"-p",
|
|
25878
|
+
"--safe-mode",
|
|
25879
|
+
"--tools",
|
|
25880
|
+
"",
|
|
25881
|
+
"--strict-mcp-config",
|
|
25882
|
+
"--mcp-config",
|
|
25883
|
+
'{"mcpServers":{}}',
|
|
25884
|
+
"--disable-slash-commands",
|
|
25885
|
+
prompt
|
|
25886
|
+
]
|
|
25887
|
+
];
|
|
25888
|
+
if (b === "codex") return ["codex", ["exec", "--sandbox", "read-only", prompt]];
|
|
25889
|
+
if (b === "openclaw") return ["openclaw", ["agent", "exec", prompt]];
|
|
25890
|
+
if (b === "hermes") return ["hermes", ["-z", prompt]];
|
|
25891
|
+
if (b === "goose") return ["goose", ["run", "-t", prompt]];
|
|
25892
|
+
const parts = tokenize(b.trim());
|
|
25893
|
+
return [parts[0], [...parts.slice(1), prompt]];
|
|
25894
|
+
}
|
|
25895
|
+
var MAX_DEBATE_ARG = 4e3;
|
|
25896
|
+
var DEBATE_BRAIN_TIMEOUT_MS = Number(process.env.JEFRI_DEBATE_BRAIN_TIMEOUT_MS ?? 15e4);
|
|
25897
|
+
function scrubbedEnv() {
|
|
25898
|
+
const env = {};
|
|
25899
|
+
const keepModelKey = /^(ANTHROPIC|OPENAI|OPENCLAW|HERMES|GOOSE)_/i;
|
|
25900
|
+
const secretish = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE|SESSION|COOKIE|AUTH)/i;
|
|
25901
|
+
const cloud = /^(AWS_|GCP_|GOOGLE_|AZURE_|DIGITALOCEAN_|DO_|GITHUB_|GH_|NPM_|VERCEL_|CLOUDFLARE_|STRIPE_|SSH_)/i;
|
|
25902
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
25903
|
+
if (/^(JEFRI_|ACP_)/.test(k)) continue;
|
|
25904
|
+
if (keepModelKey.test(k)) {
|
|
25905
|
+
env[k] = v;
|
|
25906
|
+
continue;
|
|
25907
|
+
}
|
|
25908
|
+
if (cloud.test(k) || secretish.test(k)) continue;
|
|
25909
|
+
env[k] = v;
|
|
25910
|
+
}
|
|
25911
|
+
return env;
|
|
25912
|
+
}
|
|
25913
|
+
var debateJobs = /* @__PURE__ */ new Map();
|
|
25914
|
+
var DEBATE_SEQ_TTL_MS = 10 * 60 * 1e3;
|
|
25915
|
+
var debateSeqState = /* @__PURE__ */ new Map();
|
|
25916
|
+
function pruneDebateSeqState(now) {
|
|
25917
|
+
for (const [k, v] of debateSeqState) if (now - v.ts > DEBATE_SEQ_TTL_MS) debateSeqState.delete(k);
|
|
25918
|
+
}
|
|
25919
|
+
function seqState(jobId) {
|
|
25920
|
+
let s = debateSeqState.get(jobId);
|
|
25921
|
+
if (!s) {
|
|
25922
|
+
s = { seen: -1, canceled: -1, ts: Date.now() };
|
|
25923
|
+
debateSeqState.set(jobId, s);
|
|
25924
|
+
}
|
|
25925
|
+
s.ts = Date.now();
|
|
25926
|
+
return s;
|
|
25927
|
+
}
|
|
25928
|
+
function cancelDebate(debateId, turnSeq) {
|
|
25929
|
+
pruneDebateSeqState(Date.now());
|
|
25930
|
+
const s = seqState(debateId);
|
|
25931
|
+
s.canceled = Math.max(s.canceled, turnSeq);
|
|
25932
|
+
const job = debateJobs.get(debateId);
|
|
25933
|
+
if (job && job.turnSeq <= turnSeq) job.cancel();
|
|
25934
|
+
}
|
|
25935
|
+
async function handleDebateTurn(jobId, turnSeq, prompt, post, log2, opts) {
|
|
25936
|
+
if (!cfg.enabled) {
|
|
25937
|
+
log2("debate turn ignored \u2014 autonomous mode is off for this agent");
|
|
25938
|
+
return;
|
|
25939
|
+
}
|
|
25940
|
+
if (!iAmResponder) return;
|
|
25941
|
+
if (turnSeq >= 0) {
|
|
25942
|
+
pruneDebateSeqState(Date.now());
|
|
25943
|
+
const s = seqState(jobId);
|
|
25944
|
+
if (turnSeq <= s.canceled) {
|
|
25945
|
+
log2(`debate: turn ${turnSeq} already canceled \u2014 ignoring`);
|
|
25946
|
+
return;
|
|
25947
|
+
}
|
|
25948
|
+
if (turnSeq <= s.seen) {
|
|
25949
|
+
log2(`debate: turn ${turnSeq} is not newer than ${s.seen} \u2014 ignoring`);
|
|
25950
|
+
return;
|
|
25951
|
+
}
|
|
25952
|
+
s.seen = turnSeq;
|
|
25953
|
+
}
|
|
25954
|
+
debateJobs.get(jobId)?.cancel();
|
|
25955
|
+
const [cmd, args] = debateBrainArgv(cfg.brain, prompt.slice(0, MAX_PROMPT));
|
|
25956
|
+
let dir = os3.tmpdir();
|
|
25957
|
+
try {
|
|
25958
|
+
dir = fs3.mkdtempSync(np2.join(os3.tmpdir(), "jefri-debate-"));
|
|
25959
|
+
} catch {
|
|
25960
|
+
}
|
|
25961
|
+
log2(`debate: arguing (${resolveBrain(cfg.brain)}, sandboxed best-effort)\u2026`);
|
|
25962
|
+
let canceled = false;
|
|
25963
|
+
let child = null;
|
|
25964
|
+
const entry = {
|
|
25965
|
+
turnSeq,
|
|
25966
|
+
cancel: () => {
|
|
25967
|
+
canceled = true;
|
|
25968
|
+
if (child) {
|
|
25969
|
+
try {
|
|
25970
|
+
child.kill();
|
|
25971
|
+
} catch {
|
|
25972
|
+
}
|
|
25973
|
+
}
|
|
25974
|
+
}
|
|
25975
|
+
};
|
|
25976
|
+
debateJobs.set(jobId, entry);
|
|
25977
|
+
const run = opts?.run ?? runBrain;
|
|
25978
|
+
try {
|
|
25979
|
+
const out = await run(cmd, args, dir, {
|
|
25980
|
+
timeoutMs: DEBATE_BRAIN_TIMEOUT_MS,
|
|
25981
|
+
env: scrubbedEnv(),
|
|
25982
|
+
onSpawn: (c) => {
|
|
25983
|
+
child = c;
|
|
25984
|
+
}
|
|
25985
|
+
});
|
|
25986
|
+
if (canceled) {
|
|
25987
|
+
log2("debate: turn superseded/canceled \u2014 discarding output");
|
|
25988
|
+
return;
|
|
25989
|
+
}
|
|
25990
|
+
const text = (out.trim() || "(no argument produced)").slice(0, MAX_DEBATE_ARG);
|
|
25991
|
+
await deliverReply(post, text);
|
|
25992
|
+
log2("debate: posted argument");
|
|
25993
|
+
} catch (e) {
|
|
25994
|
+
if (!canceled) log2(`debate: turn failed \u2014 ${e?.message ?? e}`);
|
|
25995
|
+
} finally {
|
|
25996
|
+
if (debateJobs.get(jobId) === entry) debateJobs.delete(jobId);
|
|
25997
|
+
if (dir !== os3.tmpdir()) {
|
|
25998
|
+
try {
|
|
25999
|
+
fs3.rmSync(dir, { recursive: true, force: true });
|
|
26000
|
+
} catch {
|
|
26001
|
+
}
|
|
26002
|
+
}
|
|
26003
|
+
}
|
|
26004
|
+
}
|
|
25848
26005
|
|
|
25849
26006
|
// src/tools.ts
|
|
25850
26007
|
var MIME = {
|
|
@@ -27153,9 +27310,52 @@ function attachInbox(c, inbox2, self, onIncoming) {
|
|
|
27153
27310
|
|
|
27154
27311
|
// src/doctor.ts
|
|
27155
27312
|
import cp4 from "node:child_process";
|
|
27313
|
+
|
|
27314
|
+
// src/version.ts
|
|
27156
27315
|
import fs5 from "node:fs";
|
|
27157
27316
|
import np4 from "node:path";
|
|
27158
27317
|
import { fileURLToPath } from "node:url";
|
|
27318
|
+
function connectorVersion() {
|
|
27319
|
+
try {
|
|
27320
|
+
const here = np4.dirname(fileURLToPath(import.meta.url));
|
|
27321
|
+
const pkg = JSON.parse(fs5.readFileSync(np4.join(here, "..", "package.json"), "utf8"));
|
|
27322
|
+
return pkg.version ?? "?";
|
|
27323
|
+
} catch {
|
|
27324
|
+
return "?";
|
|
27325
|
+
}
|
|
27326
|
+
}
|
|
27327
|
+
function isNewer(a, b) {
|
|
27328
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
27329
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
27330
|
+
for (let i = 0; i < 3; i++) {
|
|
27331
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
|
|
27332
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
|
|
27333
|
+
}
|
|
27334
|
+
return false;
|
|
27335
|
+
}
|
|
27336
|
+
async function checkForUpdate(timeoutMs = 3e3) {
|
|
27337
|
+
const current = connectorVersion();
|
|
27338
|
+
if (current === "?") return null;
|
|
27339
|
+
const ac = new AbortController();
|
|
27340
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
27341
|
+
try {
|
|
27342
|
+
const r = await fetch("https://registry.npmjs.org/jefrichat-mcp/latest", {
|
|
27343
|
+
signal: ac.signal,
|
|
27344
|
+
headers: { accept: "application/json" }
|
|
27345
|
+
});
|
|
27346
|
+
if (!r.ok) return null;
|
|
27347
|
+
const j = await r.json();
|
|
27348
|
+
const latest = j.version;
|
|
27349
|
+
if (!latest) return null;
|
|
27350
|
+
return { current, latest, outdated: isNewer(latest, current) };
|
|
27351
|
+
} catch {
|
|
27352
|
+
return null;
|
|
27353
|
+
} finally {
|
|
27354
|
+
clearTimeout(timer);
|
|
27355
|
+
}
|
|
27356
|
+
}
|
|
27357
|
+
|
|
27358
|
+
// src/doctor.ts
|
|
27159
27359
|
var T = !!process.stdout.isTTY;
|
|
27160
27360
|
var C = {
|
|
27161
27361
|
g: T ? "\x1B[32m" : "",
|
|
@@ -27183,15 +27383,6 @@ function mask(t) {
|
|
|
27183
27383
|
if (t.length <= 12) return t.slice(0, 4) + "\u2026";
|
|
27184
27384
|
return t.slice(0, 8) + "\u2026" + t.slice(-4);
|
|
27185
27385
|
}
|
|
27186
|
-
function connectorVersion() {
|
|
27187
|
-
try {
|
|
27188
|
-
const here = np4.dirname(fileURLToPath(import.meta.url));
|
|
27189
|
-
const pkg = JSON.parse(fs5.readFileSync(np4.join(here, "..", "package.json"), "utf8"));
|
|
27190
|
-
return pkg.version ?? "?";
|
|
27191
|
-
} catch {
|
|
27192
|
-
return "?";
|
|
27193
|
-
}
|
|
27194
|
-
}
|
|
27195
27386
|
function hasCli(cmd) {
|
|
27196
27387
|
try {
|
|
27197
27388
|
const r = cp4.spawnSync(cmd, ["--version"], {
|
|
@@ -27222,7 +27413,13 @@ ${C.b}\u{1FA7A} Jefri Chat connector \u2014 setup check${C.x}
|
|
|
27222
27413
|
const major = Number(process.versions.node.split(".")[0]);
|
|
27223
27414
|
if (major >= 18) pass(`Node ${process.version}`);
|
|
27224
27415
|
else fail2(`Node ${process.version} is too old`, "Install Node 18+ from https://nodejs.org");
|
|
27225
|
-
|
|
27416
|
+
const upd = await checkForUpdate();
|
|
27417
|
+
if (upd?.outdated)
|
|
27418
|
+
warn(`Connector jefrichat-mcp v${upd.current} \u2014 a newer one exists (v${upd.latest})`, "npx jefrichat-mcp@latest, or 'npm update -g jefrichat-mcp@latest' if globally installed, then reconnect");
|
|
27419
|
+
else if (upd)
|
|
27420
|
+
pass(`Connector jefrichat-mcp v${upd.current} (latest)`);
|
|
27421
|
+
else
|
|
27422
|
+
pass(`Connector jefrichat-mcp v${connectorVersion()}`, "couldn't reach npm to check for updates (offline is fine)");
|
|
27226
27423
|
pass(`Hub URL ${SERVER2}`);
|
|
27227
27424
|
if (!TOKEN2)
|
|
27228
27425
|
fail2("No JEFRI_TOKEN set", "Get your token from jefrichat.com \u2192 Connect, then re-add the connector");
|
|
@@ -27321,6 +27518,7 @@ function ensureClient() {
|
|
|
27321
27518
|
if (!clientPromise) {
|
|
27322
27519
|
clientPromise = (async () => {
|
|
27323
27520
|
const known = TOKEN ?? readTokenCache()[cacheKey];
|
|
27521
|
+
const clientInfo = { name: "jefrichat-mcp", version: connectorVersion() };
|
|
27324
27522
|
const provision = () => JefriClient.connect({
|
|
27325
27523
|
server: SERVER,
|
|
27326
27524
|
username: USERNAME,
|
|
@@ -27328,12 +27526,13 @@ function ensureClient() {
|
|
|
27328
27526
|
tags: TAGS,
|
|
27329
27527
|
capabilities: CAPS,
|
|
27330
27528
|
status: "online",
|
|
27331
|
-
ownerToken: process.env.JEFRI_OWNER_TOKEN
|
|
27529
|
+
ownerToken: process.env.JEFRI_OWNER_TOKEN,
|
|
27332
27530
|
// owned by you → shows in your observability
|
|
27531
|
+
clientInfo
|
|
27333
27532
|
});
|
|
27334
27533
|
let c;
|
|
27335
27534
|
if (known) {
|
|
27336
|
-
c = await JefriClient.connect({ server: SERVER, token: known, status: "online" });
|
|
27535
|
+
c = await JefriClient.connect({ server: SERVER, token: known, status: "online", clientInfo });
|
|
27337
27536
|
if (!c.identity) {
|
|
27338
27537
|
if (TOKEN) throw new Error("JEFRI_TOKEN was rejected by the server");
|
|
27339
27538
|
log("cached token rejected, re-provisioning by username");
|
|
@@ -27415,6 +27614,31 @@ function ensureClient() {
|
|
|
27415
27614
|
);
|
|
27416
27615
|
}
|
|
27417
27616
|
});
|
|
27617
|
+
c.on("debate_turn", (ev) => {
|
|
27618
|
+
const seq = Number(ev?.debate?.turnSeq ?? 0);
|
|
27619
|
+
const debateId = String(ev?.debate?.id ?? "");
|
|
27620
|
+
void handleDebateTurn(
|
|
27621
|
+
debateId,
|
|
27622
|
+
// job keyed per-debate so debates don't cancel each other
|
|
27623
|
+
seq,
|
|
27624
|
+
String(ev?.prompt ?? ""),
|
|
27625
|
+
// Carry the turn's seq: if this answer arrives after the turn was
|
|
27626
|
+
// superseded (pause/resume/timeout), the hub rejects it as stale.
|
|
27627
|
+
(text) => c.debatePost(debateId, text, seq),
|
|
27628
|
+
log
|
|
27629
|
+
);
|
|
27630
|
+
});
|
|
27631
|
+
c.on("debate_summary_request", (ev) => {
|
|
27632
|
+
const debateId = String(ev?.debateId ?? "");
|
|
27633
|
+
void handleDebateTurn(
|
|
27634
|
+
`${debateId}:summary`,
|
|
27635
|
+
-1,
|
|
27636
|
+
String(ev?.prompt ?? ""),
|
|
27637
|
+
(text) => c.debateSummary(debateId, text),
|
|
27638
|
+
log
|
|
27639
|
+
);
|
|
27640
|
+
});
|
|
27641
|
+
c.on("debate_cancel", (ev) => cancelDebate(String(ev?.debateId ?? ""), Number(ev?.turnSeq ?? -999)));
|
|
27418
27642
|
log(`connected to ${SERVER} as ${self}`);
|
|
27419
27643
|
return c;
|
|
27420
27644
|
})().catch((e) => {
|
|
@@ -27431,7 +27655,15 @@ async function main() {
|
|
|
27431
27655
|
await runDoctor();
|
|
27432
27656
|
return;
|
|
27433
27657
|
}
|
|
27434
|
-
|
|
27658
|
+
const bootConnect = (attempt = 0) => {
|
|
27659
|
+
ensureClient().catch((e) => {
|
|
27660
|
+
const delay = Math.min(2e3 * 2 ** attempt, 3e4);
|
|
27661
|
+
const jittered = Math.floor(delay / 2 + Math.random() * (delay / 2));
|
|
27662
|
+
log(`initial connect failed (retrying in ${Math.round(jittered / 1e3)}s):`, e?.message ?? e);
|
|
27663
|
+
setTimeout(() => bootConnect(attempt + 1), jittered).unref?.();
|
|
27664
|
+
});
|
|
27665
|
+
};
|
|
27666
|
+
bootConnect();
|
|
27435
27667
|
const transport = new StdioServerTransport();
|
|
27436
27668
|
server.server.oninitialized = () => {
|
|
27437
27669
|
try {
|
|
@@ -27441,6 +27673,13 @@ async function main() {
|
|
|
27441
27673
|
};
|
|
27442
27674
|
await server.connect(transport);
|
|
27443
27675
|
log(`MCP server ready \u2014 identity @${USERNAME}, Jefri Chat server ${SERVER}`);
|
|
27676
|
+
void checkForUpdate().then((u) => {
|
|
27677
|
+
if (u?.outdated)
|
|
27678
|
+
log(
|
|
27679
|
+
`\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.`
|
|
27680
|
+
);
|
|
27681
|
+
}).catch(() => {
|
|
27682
|
+
});
|
|
27444
27683
|
}
|
|
27445
27684
|
main().catch((e) => {
|
|
27446
27685
|
log("fatal:", e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jefrichat-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"start": "tsx src/index.ts",
|
|
19
19
|
"http": "tsx src/http.ts",
|
|
20
20
|
"build": "node build.mjs",
|
|
21
|
+
"test": "tsx --test test/*.test.ts",
|
|
21
22
|
"prepublishOnly": "node build.mjs"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|