jefrichat-mcp 0.31.0 → 0.32.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 +218 -45
- package/dist/index.js +112 -29
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -60681,7 +60681,7 @@ var JefriClient = class _JefriClient {
|
|
|
60681
60681
|
isAlive = true;
|
|
60682
60682
|
// heartbeat: did we get a pong since the last ping?
|
|
60683
60683
|
hasConnected = false;
|
|
60684
|
-
// armed for auto-reconnect only after the
|
|
60684
|
+
// armed for auto-reconnect only after the hub `registered` us
|
|
60685
60685
|
lastPresence = "online";
|
|
60686
60686
|
// re-announce this on reconnect
|
|
60687
60687
|
clientInfo;
|
|
@@ -60713,31 +60713,46 @@ var JefriClient = class _JefriClient {
|
|
|
60713
60713
|
}
|
|
60714
60714
|
client.token = token;
|
|
60715
60715
|
client.clientInfo = opts.clientInfo;
|
|
60716
|
-
|
|
60717
|
-
|
|
60718
|
-
|
|
60719
|
-
|
|
60720
|
-
|
|
60721
|
-
|
|
60722
|
-
|
|
60723
|
-
|
|
60724
|
-
|
|
60725
|
-
|
|
60726
|
-
|
|
60727
|
-
|
|
60728
|
-
|
|
60729
|
-
|
|
60730
|
-
|
|
60731
|
-
|
|
60732
|
-
|
|
60733
|
-
)
|
|
60734
|
-
|
|
60735
|
-
|
|
60736
|
-
|
|
60737
|
-
|
|
60738
|
-
|
|
60739
|
-
|
|
60740
|
-
|
|
60716
|
+
let onReg = () => {
|
|
60717
|
+
};
|
|
60718
|
+
let onErr = () => {
|
|
60719
|
+
};
|
|
60720
|
+
let timer;
|
|
60721
|
+
const registered = new Promise((resolve, reject) => {
|
|
60722
|
+
let done = false;
|
|
60723
|
+
const finish = (fn) => {
|
|
60724
|
+
if (done) return;
|
|
60725
|
+
done = true;
|
|
60726
|
+
client.off("registered", onReg);
|
|
60727
|
+
client.off("error", onErr);
|
|
60728
|
+
clearTimeout(timer);
|
|
60729
|
+
fn();
|
|
60730
|
+
};
|
|
60731
|
+
onReg = () => finish(resolve);
|
|
60732
|
+
onErr = (ev) => finish(
|
|
60733
|
+
() => reject(
|
|
60734
|
+
/auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
|
|
60735
|
+
)
|
|
60736
|
+
);
|
|
60737
|
+
const regTimeoutMs = typeof process !== "undefined" && Number(process.env?.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
|
|
60738
|
+
timer = setTimeout(
|
|
60739
|
+
() => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
|
|
60740
|
+
regTimeoutMs
|
|
60741
|
+
);
|
|
60742
|
+
client.on("registered", onReg);
|
|
60743
|
+
client.on("error", onErr);
|
|
60744
|
+
});
|
|
60745
|
+
try {
|
|
60746
|
+
await Promise.all([client.openSocket(), registered]);
|
|
60747
|
+
} catch (e) {
|
|
60748
|
+
client.off("registered", onReg);
|
|
60749
|
+
client.off("error", onErr);
|
|
60750
|
+
clearTimeout(timer);
|
|
60751
|
+
try {
|
|
60752
|
+
client.close();
|
|
60753
|
+
} catch {
|
|
60754
|
+
}
|
|
60755
|
+
throw e;
|
|
60741
60756
|
}
|
|
60742
60757
|
if (opts.status) client.presence(opts.status);
|
|
60743
60758
|
await client.ensureE2EKey().catch(() => {
|
|
@@ -60750,8 +60765,6 @@ var JefriClient = class _JefriClient {
|
|
|
60750
60765
|
return new Promise((resolve, reject) => {
|
|
60751
60766
|
let settled = false;
|
|
60752
60767
|
this.ws.on("open", () => {
|
|
60753
|
-
this.reconnectAttempts = 0;
|
|
60754
|
-
this.hasConnected = true;
|
|
60755
60768
|
this.startPing();
|
|
60756
60769
|
try {
|
|
60757
60770
|
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
@@ -60781,7 +60794,11 @@ var JefriClient = class _JefriClient {
|
|
|
60781
60794
|
} catch {
|
|
60782
60795
|
return;
|
|
60783
60796
|
}
|
|
60784
|
-
if (ev.event === "registered")
|
|
60797
|
+
if (ev.event === "registered") {
|
|
60798
|
+
this.identity = ev.identity;
|
|
60799
|
+
this.hasConnected = true;
|
|
60800
|
+
this.reconnectAttempts = 0;
|
|
60801
|
+
}
|
|
60785
60802
|
if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
|
|
60786
60803
|
this.authFailed = true;
|
|
60787
60804
|
}
|
|
@@ -61181,6 +61198,48 @@ var JefriClient = class _JefriClient {
|
|
|
61181
61198
|
};
|
|
61182
61199
|
var serveDir = path2.join(os.homedir(), ".acp", "serve");
|
|
61183
61200
|
|
|
61201
|
+
// src/conn-pool.ts
|
|
61202
|
+
function attachRef(c, id) {
|
|
61203
|
+
if (c.closed) return false;
|
|
61204
|
+
c.refs.add(id);
|
|
61205
|
+
return true;
|
|
61206
|
+
}
|
|
61207
|
+
function detachRef(c, id) {
|
|
61208
|
+
c.refs.delete(id);
|
|
61209
|
+
return c.refs.size === 0;
|
|
61210
|
+
}
|
|
61211
|
+
function createKeyedPool() {
|
|
61212
|
+
const ready = /* @__PURE__ */ new Map();
|
|
61213
|
+
const pending = /* @__PURE__ */ new Map();
|
|
61214
|
+
return {
|
|
61215
|
+
async acquire(key, create) {
|
|
61216
|
+
const existing = ready.get(key);
|
|
61217
|
+
if (existing !== void 0) return existing;
|
|
61218
|
+
const inflight = pending.get(key);
|
|
61219
|
+
if (inflight) return inflight;
|
|
61220
|
+
const p = (async () => {
|
|
61221
|
+
const value = await create();
|
|
61222
|
+
ready.set(key, value);
|
|
61223
|
+
return value;
|
|
61224
|
+
})();
|
|
61225
|
+
pending.set(key, p);
|
|
61226
|
+
try {
|
|
61227
|
+
return await p;
|
|
61228
|
+
} finally {
|
|
61229
|
+
pending.delete(key);
|
|
61230
|
+
}
|
|
61231
|
+
},
|
|
61232
|
+
get: (key) => ready.get(key),
|
|
61233
|
+
drop: (key) => {
|
|
61234
|
+
const v = ready.get(key);
|
|
61235
|
+
ready.delete(key);
|
|
61236
|
+
return v;
|
|
61237
|
+
},
|
|
61238
|
+
has: (key) => ready.has(key),
|
|
61239
|
+
size: () => ready.size
|
|
61240
|
+
};
|
|
61241
|
+
}
|
|
61242
|
+
|
|
61184
61243
|
// src/tools.ts
|
|
61185
61244
|
import fs4 from "node:fs";
|
|
61186
61245
|
import np3 from "node:path";
|
|
@@ -61756,6 +61815,72 @@ ${e?.message ?? e}`);
|
|
|
61756
61815
|
return ok3(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
|
|
61757
61816
|
})
|
|
61758
61817
|
);
|
|
61818
|
+
const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
|
|
61819
|
+
const ownerApi = async (c, path3, method) => {
|
|
61820
|
+
const res = await fetch(`${ctx.serverUrl}${path3}`, {
|
|
61821
|
+
method,
|
|
61822
|
+
headers: { Authorization: `Bearer ${c.token}` }
|
|
61823
|
+
});
|
|
61824
|
+
let body = {};
|
|
61825
|
+
try {
|
|
61826
|
+
body = await res.json();
|
|
61827
|
+
} catch {
|
|
61828
|
+
}
|
|
61829
|
+
return { status: res.status, body };
|
|
61830
|
+
};
|
|
61831
|
+
server.registerTool(
|
|
61832
|
+
"jefri_agent_apps",
|
|
61833
|
+
{
|
|
61834
|
+
title: "See where your agents are running",
|
|
61835
|
+
description: `For an agent you OWN, show which app currently holds its live connection (the label you gave that key, e.g. "Claude Code" or "Codex"), whether it's online, and whether it's active or deactivated. Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
|
|
61836
|
+
inputSchema: {
|
|
61837
|
+
agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
|
|
61838
|
+
}
|
|
61839
|
+
},
|
|
61840
|
+
async ({ agent }) => withClient(async (c) => {
|
|
61841
|
+
if (agent) {
|
|
61842
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
|
|
61843
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not read @${agent} (HTTP ${status}).`);
|
|
61844
|
+
const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
|
|
61845
|
+
const state = body.active ? "active" : "\u{1F6D1} deactivated";
|
|
61846
|
+
const app2 = body.online ? ` \xB7 app: ${body.app || "connected app"}` : "";
|
|
61847
|
+
return ok3(`@${agent} \u2014 ${dot}${app2} \xB7 ${state}`);
|
|
61848
|
+
}
|
|
61849
|
+
const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
|
|
61850
|
+
if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
|
|
61851
|
+
const lines = mine.map((i) => `${i.status && i.status !== "offline" ? "\u{1F7E2}" : "\u26AA"} @${i.username} \u2014 ${i.displayName}`);
|
|
61852
|
+
return ok3(`Your agents:
|
|
61853
|
+
${lines.join("\n")}
|
|
61854
|
+
|
|
61855
|
+
Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
61856
|
+
})
|
|
61857
|
+
);
|
|
61858
|
+
server.registerTool(
|
|
61859
|
+
"jefri_deactivate",
|
|
61860
|
+
{
|
|
61861
|
+
title: "Deactivate an agent",
|
|
61862
|
+
description: "Deactivate an agent you OWN: it is immediately disconnected and CANNOT reconnect from any app (Claude, Codex, SDK, \u2026) until you reactivate it. Reversible via jefri_activate. Requires running this connector as the agent's human OWNER \u2014 an agent session cannot deactivate agents.",
|
|
61863
|
+
inputSchema: { agent: external_exports.string().describe("username of YOUR agent to deactivate") }
|
|
61864
|
+
},
|
|
61865
|
+
async ({ agent }) => withClient(async (c) => {
|
|
61866
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/deactivate`, "POST");
|
|
61867
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not deactivate @${agent} (HTTP ${status}).`);
|
|
61868
|
+
return ok3(`\u{1F6D1} Deactivated @${agent}. It's disconnected and can't reconnect from any app until you reactivate it.`);
|
|
61869
|
+
})
|
|
61870
|
+
);
|
|
61871
|
+
server.registerTool(
|
|
61872
|
+
"jefri_activate",
|
|
61873
|
+
{
|
|
61874
|
+
title: "Reactivate an agent",
|
|
61875
|
+
description: "Reactivate an agent you OWN that was previously deactivated, so it can connect again. Requires running this connector as the agent's human OWNER.",
|
|
61876
|
+
inputSchema: { agent: external_exports.string().describe("username of YOUR agent to reactivate") }
|
|
61877
|
+
},
|
|
61878
|
+
async ({ agent }) => withClient(async (c) => {
|
|
61879
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/activate`, "POST");
|
|
61880
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not reactivate @${agent} (HTTP ${status}).`);
|
|
61881
|
+
return ok3(`\u2705 Reactivated @${agent}. It can connect again now.`);
|
|
61882
|
+
})
|
|
61883
|
+
);
|
|
61759
61884
|
server.registerTool(
|
|
61760
61885
|
"jefri_send",
|
|
61761
61886
|
{
|
|
@@ -62818,6 +62943,7 @@ var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
|
|
|
62818
62943
|
var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
|
|
62819
62944
|
var log = (...a) => console.error("[jefrichat-mcp-http]", ...a);
|
|
62820
62945
|
var sessions = /* @__PURE__ */ new Map();
|
|
62946
|
+
var hubPool = createKeyedPool();
|
|
62821
62947
|
var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ?? 4 * 60 * 60 * 1e3);
|
|
62822
62948
|
var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
|
|
62823
62949
|
var MAX_SESSIONS_PER_IDENTITY = Number(process.env.MCP_MAX_SESSIONS_PER_IDENTITY ?? 20);
|
|
@@ -62841,14 +62967,41 @@ function closeSession(sid, reason) {
|
|
|
62841
62967
|
if (!s) return;
|
|
62842
62968
|
sessions.delete(sid);
|
|
62843
62969
|
try {
|
|
62844
|
-
s.
|
|
62970
|
+
s.transport.close?.();
|
|
62845
62971
|
} catch {
|
|
62846
62972
|
}
|
|
62973
|
+
const pooled = hubPool.get(s.poolKey);
|
|
62974
|
+
if (pooled && detachRef(pooled, sid)) teardownPool(s.poolKey, pooled, "last session gone");
|
|
62975
|
+
log(`session ${sid.slice(0, 8)}\u2026 closed (${reason}) (${sessions.size} active)`);
|
|
62976
|
+
}
|
|
62977
|
+
function teardownPool(poolKey, pooled, reason) {
|
|
62978
|
+
pooled.closed = true;
|
|
62979
|
+
if (hubPool.get(poolKey) === pooled) hubPool.drop(poolKey);
|
|
62847
62980
|
try {
|
|
62848
|
-
|
|
62981
|
+
pooled.acp.close();
|
|
62849
62982
|
} catch {
|
|
62850
62983
|
}
|
|
62851
|
-
log(`
|
|
62984
|
+
log(`hub connection ${poolKey.slice(0, 8)}\u2026 closed (${reason})`);
|
|
62985
|
+
}
|
|
62986
|
+
function closePool(poolKey, reason) {
|
|
62987
|
+
const pooled = hubPool.get(poolKey);
|
|
62988
|
+
if (!pooled) return;
|
|
62989
|
+
pooled.closed = true;
|
|
62990
|
+
for (const sid of [...pooled.refs]) closeSession(sid, reason);
|
|
62991
|
+
if (hubPool.get(poolKey) === pooled) teardownPool(poolKey, pooled, reason);
|
|
62992
|
+
}
|
|
62993
|
+
async function acquirePooledConn(token, poolKey) {
|
|
62994
|
+
return hubPool.acquire(poolKey, async () => {
|
|
62995
|
+
const acp = await JefriClient.connect({ server: HUB, token, status: "online" });
|
|
62996
|
+
const inbox = [];
|
|
62997
|
+
if (acp.identity) attachInbox(acp, inbox, acp.identity.username);
|
|
62998
|
+
const pooled = { acp, inbox, refs: /* @__PURE__ */ new Set(), closed: false };
|
|
62999
|
+
acp.on("force_disconnect", () => closePool(poolKey, "credential revoked"));
|
|
63000
|
+
acp.on("error", (ev) => {
|
|
63001
|
+
if (/auth|revoked|unauthor|disabled/i.test(String(ev?.message ?? ""))) closePool(poolKey, "auth revoked");
|
|
63002
|
+
});
|
|
63003
|
+
return pooled;
|
|
63004
|
+
});
|
|
62852
63005
|
}
|
|
62853
63006
|
function touch(sid) {
|
|
62854
63007
|
if (!sid) return;
|
|
@@ -62967,9 +63120,10 @@ app.post("/mcp", async (req, res) => {
|
|
|
62967
63120
|
});
|
|
62968
63121
|
return;
|
|
62969
63122
|
}
|
|
62970
|
-
|
|
63123
|
+
const poolKey = sha256(token).toString("hex");
|
|
63124
|
+
let pooled;
|
|
62971
63125
|
try {
|
|
62972
|
-
|
|
63126
|
+
pooled = await acquirePooledConn(token, poolKey);
|
|
62973
63127
|
} catch (e) {
|
|
62974
63128
|
if (e instanceof JefriAuthError || e?.code === "ACP_AUTH_FAILED") {
|
|
62975
63129
|
res.status(401).json({
|
|
@@ -62985,7 +63139,9 @@ app.post("/mcp", async (req, res) => {
|
|
|
62985
63139
|
res.status(502).json({ jsonrpc: "2.0", error: { code: -32002, message: `Hub unreachable: ${e?.message ?? e}` }, id: null });
|
|
62986
63140
|
return;
|
|
62987
63141
|
}
|
|
63142
|
+
const acp = pooled.acp;
|
|
62988
63143
|
if (!acp.identity) {
|
|
63144
|
+
closePool(poolKey, "no identity");
|
|
62989
63145
|
res.status(401).json({
|
|
62990
63146
|
jsonrpc: "2.0",
|
|
62991
63147
|
error: { code: -32001, message: "Invalid or expired Jefri Chat token \u2014 rotate it in the Jefri Chat web app and reconnect." },
|
|
@@ -62993,13 +63149,19 @@ app.post("/mcp", async (req, res) => {
|
|
|
62993
63149
|
});
|
|
62994
63150
|
return;
|
|
62995
63151
|
}
|
|
62996
|
-
const inbox = [];
|
|
62997
|
-
attachInbox(acp, inbox, acp.identity.username);
|
|
62998
63152
|
const mcp = new McpServer({ name: "jefrichat", version: "0.1.0" });
|
|
62999
|
-
registerAcpTools(mcp, { ensureClient: async () => acp, inbox, serverUrl: HUB, local: false });
|
|
63153
|
+
registerAcpTools(mcp, { ensureClient: async () => acp, inbox: pooled.inbox, serverUrl: HUB, local: false });
|
|
63000
63154
|
const transport = new StreamableHTTPServerTransport({
|
|
63001
63155
|
sessionIdGenerator: () => randomUUID(),
|
|
63002
63156
|
onsessioninitialized: (sid) => {
|
|
63157
|
+
if (pooled.closed) {
|
|
63158
|
+
try {
|
|
63159
|
+
transport.close?.();
|
|
63160
|
+
} catch {
|
|
63161
|
+
}
|
|
63162
|
+
log(`session ${sid.slice(0, 8)}\u2026 refused (connection torn down during init)`);
|
|
63163
|
+
return;
|
|
63164
|
+
}
|
|
63003
63165
|
const meName = acp.identity.username;
|
|
63004
63166
|
const mine = [...sessions.entries()].filter(([, s]) => s.acp.identity?.username === meName);
|
|
63005
63167
|
if (mine.length >= MAX_SESSIONS_PER_IDENTITY) {
|
|
@@ -63017,20 +63179,31 @@ app.post("/mcp", async (req, res) => {
|
|
|
63017
63179
|
}
|
|
63018
63180
|
if (oldest) closeSession(oldest, "evicted (cap)");
|
|
63019
63181
|
}
|
|
63020
|
-
|
|
63021
|
-
|
|
63022
|
-
|
|
63023
|
-
|
|
63024
|
-
|
|
63025
|
-
|
|
63182
|
+
if (!attachRef(pooled, sid)) {
|
|
63183
|
+
try {
|
|
63184
|
+
transport.close?.();
|
|
63185
|
+
} catch {
|
|
63186
|
+
}
|
|
63187
|
+
log(`session ${sid.slice(0, 8)}\u2026 refused (connection torn down during init)`);
|
|
63188
|
+
return;
|
|
63189
|
+
}
|
|
63190
|
+
sessions.set(sid, { transport, acp, lastActive: Date.now(), tokenHash: sha256(token), inflight: 0, poolKey });
|
|
63191
|
+
log(`session ${sid.slice(0, 8)}\u2026 open as @${acp.identity.username} (${sessions.size} active, ${pooled.refs.size} on this connection)`);
|
|
63026
63192
|
}
|
|
63027
63193
|
});
|
|
63028
63194
|
transport.onclose = () => {
|
|
63029
63195
|
const sid = transport.sessionId;
|
|
63030
63196
|
if (sid) closeSession(sid, "client closed");
|
|
63031
63197
|
};
|
|
63032
|
-
|
|
63033
|
-
|
|
63198
|
+
const reservation = "init:" + randomUUID();
|
|
63199
|
+
pooled.refs.add(reservation);
|
|
63200
|
+
try {
|
|
63201
|
+
await mcp.connect(transport);
|
|
63202
|
+
await transport.handleRequest(req, res, req.body);
|
|
63203
|
+
} finally {
|
|
63204
|
+
pooled.refs.delete(reservation);
|
|
63205
|
+
if (pooled.refs.size === 0 && hubPool.get(poolKey) === pooled) teardownPool(poolKey, pooled, "init failed, no session");
|
|
63206
|
+
}
|
|
63034
63207
|
return;
|
|
63035
63208
|
}
|
|
63036
63209
|
res.status(404).json({
|
package/dist/index.js
CHANGED
|
@@ -36668,7 +36668,7 @@ var JefriClient = class _JefriClient {
|
|
|
36668
36668
|
isAlive = true;
|
|
36669
36669
|
// heartbeat: did we get a pong since the last ping?
|
|
36670
36670
|
hasConnected = false;
|
|
36671
|
-
// armed for auto-reconnect only after the
|
|
36671
|
+
// armed for auto-reconnect only after the hub `registered` us
|
|
36672
36672
|
lastPresence = "online";
|
|
36673
36673
|
// re-announce this on reconnect
|
|
36674
36674
|
clientInfo;
|
|
@@ -36700,31 +36700,46 @@ var JefriClient = class _JefriClient {
|
|
|
36700
36700
|
}
|
|
36701
36701
|
client.token = token;
|
|
36702
36702
|
client.clientInfo = opts.clientInfo;
|
|
36703
|
-
|
|
36704
|
-
|
|
36705
|
-
|
|
36706
|
-
|
|
36707
|
-
|
|
36708
|
-
|
|
36709
|
-
|
|
36710
|
-
|
|
36711
|
-
|
|
36712
|
-
|
|
36713
|
-
|
|
36714
|
-
|
|
36715
|
-
|
|
36716
|
-
|
|
36717
|
-
|
|
36718
|
-
|
|
36719
|
-
|
|
36720
|
-
)
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36724
|
-
|
|
36725
|
-
|
|
36726
|
-
|
|
36727
|
-
|
|
36703
|
+
let onReg = () => {
|
|
36704
|
+
};
|
|
36705
|
+
let onErr = () => {
|
|
36706
|
+
};
|
|
36707
|
+
let timer;
|
|
36708
|
+
const registered = new Promise((resolve, reject) => {
|
|
36709
|
+
let done = false;
|
|
36710
|
+
const finish = (fn) => {
|
|
36711
|
+
if (done) return;
|
|
36712
|
+
done = true;
|
|
36713
|
+
client.off("registered", onReg);
|
|
36714
|
+
client.off("error", onErr);
|
|
36715
|
+
clearTimeout(timer);
|
|
36716
|
+
fn();
|
|
36717
|
+
};
|
|
36718
|
+
onReg = () => finish(resolve);
|
|
36719
|
+
onErr = (ev) => finish(
|
|
36720
|
+
() => reject(
|
|
36721
|
+
/auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
|
|
36722
|
+
)
|
|
36723
|
+
);
|
|
36724
|
+
const regTimeoutMs = typeof process !== "undefined" && Number(process.env?.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
|
|
36725
|
+
timer = setTimeout(
|
|
36726
|
+
() => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
|
|
36727
|
+
regTimeoutMs
|
|
36728
|
+
);
|
|
36729
|
+
client.on("registered", onReg);
|
|
36730
|
+
client.on("error", onErr);
|
|
36731
|
+
});
|
|
36732
|
+
try {
|
|
36733
|
+
await Promise.all([client.openSocket(), registered]);
|
|
36734
|
+
} catch (e) {
|
|
36735
|
+
client.off("registered", onReg);
|
|
36736
|
+
client.off("error", onErr);
|
|
36737
|
+
clearTimeout(timer);
|
|
36738
|
+
try {
|
|
36739
|
+
client.close();
|
|
36740
|
+
} catch {
|
|
36741
|
+
}
|
|
36742
|
+
throw e;
|
|
36728
36743
|
}
|
|
36729
36744
|
if (opts.status) client.presence(opts.status);
|
|
36730
36745
|
await client.ensureE2EKey().catch(() => {
|
|
@@ -36737,8 +36752,6 @@ var JefriClient = class _JefriClient {
|
|
|
36737
36752
|
return new Promise((resolve, reject) => {
|
|
36738
36753
|
let settled = false;
|
|
36739
36754
|
this.ws.on("open", () => {
|
|
36740
|
-
this.reconnectAttempts = 0;
|
|
36741
|
-
this.hasConnected = true;
|
|
36742
36755
|
this.startPing();
|
|
36743
36756
|
try {
|
|
36744
36757
|
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
@@ -36768,7 +36781,11 @@ var JefriClient = class _JefriClient {
|
|
|
36768
36781
|
} catch {
|
|
36769
36782
|
return;
|
|
36770
36783
|
}
|
|
36771
|
-
if (ev.event === "registered")
|
|
36784
|
+
if (ev.event === "registered") {
|
|
36785
|
+
this.identity = ev.identity;
|
|
36786
|
+
this.hasConnected = true;
|
|
36787
|
+
this.reconnectAttempts = 0;
|
|
36788
|
+
}
|
|
36772
36789
|
if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
|
|
36773
36790
|
this.authFailed = true;
|
|
36774
36791
|
}
|
|
@@ -38420,6 +38437,72 @@ ${e?.message ?? e}`);
|
|
|
38420
38437
|
return ok3(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
|
|
38421
38438
|
})
|
|
38422
38439
|
);
|
|
38440
|
+
const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
|
|
38441
|
+
const ownerApi = async (c, path4, method) => {
|
|
38442
|
+
const res = await fetch(`${ctx.serverUrl}${path4}`, {
|
|
38443
|
+
method,
|
|
38444
|
+
headers: { Authorization: `Bearer ${c.token}` }
|
|
38445
|
+
});
|
|
38446
|
+
let body = {};
|
|
38447
|
+
try {
|
|
38448
|
+
body = await res.json();
|
|
38449
|
+
} catch {
|
|
38450
|
+
}
|
|
38451
|
+
return { status: res.status, body };
|
|
38452
|
+
};
|
|
38453
|
+
server2.registerTool(
|
|
38454
|
+
"jefri_agent_apps",
|
|
38455
|
+
{
|
|
38456
|
+
title: "See where your agents are running",
|
|
38457
|
+
description: `For an agent you OWN, show which app currently holds its live connection (the label you gave that key, e.g. "Claude Code" or "Codex"), whether it's online, and whether it's active or deactivated. Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
|
|
38458
|
+
inputSchema: {
|
|
38459
|
+
agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
|
|
38460
|
+
}
|
|
38461
|
+
},
|
|
38462
|
+
async ({ agent }) => withClient(async (c) => {
|
|
38463
|
+
if (agent) {
|
|
38464
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
|
|
38465
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not read @${agent} (HTTP ${status}).`);
|
|
38466
|
+
const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
|
|
38467
|
+
const state = body.active ? "active" : "\u{1F6D1} deactivated";
|
|
38468
|
+
const app = body.online ? ` \xB7 app: ${body.app || "connected app"}` : "";
|
|
38469
|
+
return ok3(`@${agent} \u2014 ${dot}${app} \xB7 ${state}`);
|
|
38470
|
+
}
|
|
38471
|
+
const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
|
|
38472
|
+
if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
|
|
38473
|
+
const lines = mine.map((i) => `${i.status && i.status !== "offline" ? "\u{1F7E2}" : "\u26AA"} @${i.username} \u2014 ${i.displayName}`);
|
|
38474
|
+
return ok3(`Your agents:
|
|
38475
|
+
${lines.join("\n")}
|
|
38476
|
+
|
|
38477
|
+
Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
|
|
38478
|
+
})
|
|
38479
|
+
);
|
|
38480
|
+
server2.registerTool(
|
|
38481
|
+
"jefri_deactivate",
|
|
38482
|
+
{
|
|
38483
|
+
title: "Deactivate an agent",
|
|
38484
|
+
description: "Deactivate an agent you OWN: it is immediately disconnected and CANNOT reconnect from any app (Claude, Codex, SDK, \u2026) until you reactivate it. Reversible via jefri_activate. Requires running this connector as the agent's human OWNER \u2014 an agent session cannot deactivate agents.",
|
|
38485
|
+
inputSchema: { agent: external_exports.string().describe("username of YOUR agent to deactivate") }
|
|
38486
|
+
},
|
|
38487
|
+
async ({ agent }) => withClient(async (c) => {
|
|
38488
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/deactivate`, "POST");
|
|
38489
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not deactivate @${agent} (HTTP ${status}).`);
|
|
38490
|
+
return ok3(`\u{1F6D1} Deactivated @${agent}. It's disconnected and can't reconnect from any app until you reactivate it.`);
|
|
38491
|
+
})
|
|
38492
|
+
);
|
|
38493
|
+
server2.registerTool(
|
|
38494
|
+
"jefri_activate",
|
|
38495
|
+
{
|
|
38496
|
+
title: "Reactivate an agent",
|
|
38497
|
+
description: "Reactivate an agent you OWN that was previously deactivated, so it can connect again. Requires running this connector as the agent's human OWNER.",
|
|
38498
|
+
inputSchema: { agent: external_exports.string().describe("username of YOUR agent to reactivate") }
|
|
38499
|
+
},
|
|
38500
|
+
async ({ agent }) => withClient(async (c) => {
|
|
38501
|
+
const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/activate`, "POST");
|
|
38502
|
+
if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not reactivate @${agent} (HTTP ${status}).`);
|
|
38503
|
+
return ok3(`\u2705 Reactivated @${agent}. It can connect again now.`);
|
|
38504
|
+
})
|
|
38505
|
+
);
|
|
38423
38506
|
server2.registerTool(
|
|
38424
38507
|
"jefri_send",
|
|
38425
38508
|
{
|
package/package.json
CHANGED