jefrichat-mcp 0.29.0 → 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/dist/http.js +20 -4
- package/dist/index.js +189 -4
- package/package.json +2 -1
package/dist/http.js
CHANGED
|
@@ -49176,6 +49176,20 @@ var JefriClient = class _JefriClient {
|
|
|
49176
49176
|
groupMessage(groupId, content) {
|
|
49177
49177
|
this.send({ type: "group_message", groupId, content });
|
|
49178
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
|
+
}
|
|
49179
49193
|
sendFile(to, fileName, fileMime, fileDataUrl) {
|
|
49180
49194
|
this.send({ type: "file", to, fileName, fileMime, fileDataUrl });
|
|
49181
49195
|
}
|
|
@@ -49641,6 +49655,8 @@ function acquireResponderLock() {
|
|
|
49641
49655
|
}
|
|
49642
49656
|
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49643
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;
|
|
49644
49660
|
|
|
49645
49661
|
// src/tools.ts
|
|
49646
49662
|
var MIME = {
|
|
@@ -50952,7 +50968,7 @@ var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
|
|
|
50952
50968
|
var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
|
|
50953
50969
|
var log = (...a) => console.error("[jefrichat-mcp-http]", ...a);
|
|
50954
50970
|
var sessions = /* @__PURE__ */ new Map();
|
|
50955
|
-
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);
|
|
50956
50972
|
var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
|
|
50957
50973
|
function closeSession(sid, reason) {
|
|
50958
50974
|
const s = sessions.get(sid);
|
|
@@ -51093,16 +51109,16 @@ app.post("/mcp", async (req, res) => {
|
|
|
51093
51109
|
await transport.handleRequest(req, res, req.body);
|
|
51094
51110
|
return;
|
|
51095
51111
|
}
|
|
51096
|
-
res.status(
|
|
51112
|
+
res.status(404).json({
|
|
51097
51113
|
jsonrpc: "2.0",
|
|
51098
|
-
error: { code: -
|
|
51114
|
+
error: { code: -32001, message: "Session expired \u2014 reinitialize (your token is still valid)" },
|
|
51099
51115
|
id: null
|
|
51100
51116
|
});
|
|
51101
51117
|
});
|
|
51102
51118
|
var bySession = async (req, res) => {
|
|
51103
51119
|
const sessionId = req.headers["mcp-session-id"];
|
|
51104
51120
|
if (!sessionId || !sessions.has(sessionId)) {
|
|
51105
|
-
res.status(
|
|
51121
|
+
res.status(404).send("Session expired \u2014 reinitialize");
|
|
51106
51122
|
return;
|
|
51107
51123
|
}
|
|
51108
51124
|
touch(sessionId);
|
package/dist/index.js
CHANGED
|
@@ -25072,6 +25072,20 @@ var JefriClient = class _JefriClient {
|
|
|
25072
25072
|
groupMessage(groupId, content) {
|
|
25073
25073
|
this.send({ type: "group_message", groupId, content });
|
|
25074
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
|
+
}
|
|
25075
25089
|
sendFile(to, fileName, fileMime, fileDataUrl) {
|
|
25076
25090
|
this.send({ type: "file", to, fileName, fileMime, fileDataUrl });
|
|
25077
25091
|
}
|
|
@@ -25741,14 +25755,18 @@ async function drain() {
|
|
|
25741
25755
|
}
|
|
25742
25756
|
running = false;
|
|
25743
25757
|
}
|
|
25744
|
-
function runBrain(cmd, args, cwd) {
|
|
25758
|
+
function runBrain(cmd, args, cwd, opts) {
|
|
25745
25759
|
return new Promise((resolve, reject) => {
|
|
25746
25760
|
let child;
|
|
25747
25761
|
try {
|
|
25748
|
-
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 });
|
|
25749
25763
|
} catch (e) {
|
|
25750
25764
|
return reject(e);
|
|
25751
25765
|
}
|
|
25766
|
+
try {
|
|
25767
|
+
opts?.onSpawn?.(child);
|
|
25768
|
+
} catch {
|
|
25769
|
+
}
|
|
25752
25770
|
let out = "", err = "";
|
|
25753
25771
|
const timer = setTimeout(() => {
|
|
25754
25772
|
try {
|
|
@@ -25756,7 +25774,7 @@ function runBrain(cmd, args, cwd) {
|
|
|
25756
25774
|
} catch {
|
|
25757
25775
|
}
|
|
25758
25776
|
reject(new Error("timed out"));
|
|
25759
|
-
}, TIMEOUT_MS);
|
|
25777
|
+
}, opts?.timeoutMs ?? TIMEOUT_MS);
|
|
25760
25778
|
child.stdout?.on("data", (d) => out += d.toString());
|
|
25761
25779
|
child.stderr?.on("data", (d) => err += d.toString());
|
|
25762
25780
|
child.on("error", (e) => {
|
|
@@ -25850,6 +25868,140 @@ async function deliverReply(reply, text) {
|
|
|
25850
25868
|
}
|
|
25851
25869
|
return false;
|
|
25852
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
|
+
}
|
|
25853
26005
|
|
|
25854
26006
|
// src/tools.ts
|
|
25855
26007
|
var MIME = {
|
|
@@ -27462,6 +27614,31 @@ function ensureClient() {
|
|
|
27462
27614
|
);
|
|
27463
27615
|
}
|
|
27464
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)));
|
|
27465
27642
|
log(`connected to ${SERVER} as ${self}`);
|
|
27466
27643
|
return c;
|
|
27467
27644
|
})().catch((e) => {
|
|
@@ -27478,7 +27655,15 @@ async function main() {
|
|
|
27478
27655
|
await runDoctor();
|
|
27479
27656
|
return;
|
|
27480
27657
|
}
|
|
27481
|
-
|
|
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();
|
|
27482
27667
|
const transport = new StdioServerTransport();
|
|
27483
27668
|
server.server.oninitialized = () => {
|
|
27484
27669
|
try {
|
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": [
|