jefrichat-mcp 0.20.0 → 0.25.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 +6 -2
- package/dist/http.js +157 -42
- package/dist/index.js +185 -37
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -9,16 +9,20 @@ hub.
|
|
|
9
9
|
|
|
10
10
|
Create an agent in your Jefri Chat web app to get a **token**, then:
|
|
11
11
|
|
|
12
|
+
Name the server `jefri_<username>` (e.g. `jefri_aaron`) — connecting a second
|
|
13
|
+
agent under the same name would overwrite the first, and the per-agent name
|
|
14
|
+
shows at a glance which agent is which.
|
|
15
|
+
|
|
12
16
|
### Claude Code (local, via npx)
|
|
13
17
|
```bash
|
|
14
|
-
claude mcp add
|
|
18
|
+
claude mcp add jefri_aaron -e JEFRI_SERVER=https://jefrichat.com -e JEFRI_TOKEN=jefri_xxx -- npx -y jefrichat-mcp
|
|
15
19
|
```
|
|
16
20
|
|
|
17
21
|
### Claude Desktop / Codex / Cursor (config)
|
|
18
22
|
```json
|
|
19
23
|
{
|
|
20
24
|
"mcpServers": {
|
|
21
|
-
"
|
|
25
|
+
"jefri_aaron": {
|
|
22
26
|
"command": "npx",
|
|
23
27
|
"args": ["-y", "jefrichat-mcp"],
|
|
24
28
|
"env": { "JEFRI_SERVER": "https://jefrichat.com", "JEFRI_TOKEN": "jefri_xxx" }
|
package/dist/http.js
CHANGED
|
@@ -5250,9 +5250,9 @@ var require_on_finished = __commonJS({
|
|
|
5250
5250
|
}
|
|
5251
5251
|
});
|
|
5252
5252
|
|
|
5253
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
5253
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/read.js
|
|
5254
5254
|
var require_read = __commonJS({
|
|
5255
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
5255
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/read.js"(exports, module) {
|
|
5256
5256
|
"use strict";
|
|
5257
5257
|
var createError = require_http_errors();
|
|
5258
5258
|
var destroy = require_destroy();
|
|
@@ -14228,9 +14228,9 @@ var require_type_is = __commonJS({
|
|
|
14228
14228
|
}
|
|
14229
14229
|
});
|
|
14230
14230
|
|
|
14231
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
14231
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/json.js
|
|
14232
14232
|
var require_json = __commonJS({
|
|
14233
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
14233
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/json.js"(exports, module) {
|
|
14234
14234
|
"use strict";
|
|
14235
14235
|
var bytes = require_bytes();
|
|
14236
14236
|
var contentType = require_content_type();
|
|
@@ -14244,12 +14244,15 @@ var require_json = __commonJS({
|
|
|
14244
14244
|
var JSON_SYNTAX_REGEXP = /#+/g;
|
|
14245
14245
|
function json(options) {
|
|
14246
14246
|
var opts = options || {};
|
|
14247
|
-
var limit = typeof opts.limit
|
|
14247
|
+
var limit = typeof opts.limit === "undefined" || opts.limit === null ? 102400 : bytes.parse(opts.limit);
|
|
14248
14248
|
var inflate = opts.inflate !== false;
|
|
14249
14249
|
var reviver = opts.reviver;
|
|
14250
14250
|
var strict = opts.strict !== false;
|
|
14251
14251
|
var type = opts.type || "application/json";
|
|
14252
14252
|
var verify = opts.verify || false;
|
|
14253
|
+
if (limit === null) {
|
|
14254
|
+
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid');
|
|
14255
|
+
}
|
|
14253
14256
|
if (verify !== false && typeof verify !== "function") {
|
|
14254
14257
|
throw new TypeError("option verify must be function");
|
|
14255
14258
|
}
|
|
@@ -14359,9 +14362,9 @@ var require_json = __commonJS({
|
|
|
14359
14362
|
}
|
|
14360
14363
|
});
|
|
14361
14364
|
|
|
14362
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
14365
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/raw.js
|
|
14363
14366
|
var require_raw = __commonJS({
|
|
14364
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
14367
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/raw.js"(exports, module) {
|
|
14365
14368
|
"use strict";
|
|
14366
14369
|
var bytes = require_bytes();
|
|
14367
14370
|
var debug = require_src()("body-parser:raw");
|
|
@@ -14371,9 +14374,12 @@ var require_raw = __commonJS({
|
|
|
14371
14374
|
function raw(options) {
|
|
14372
14375
|
var opts = options || {};
|
|
14373
14376
|
var inflate = opts.inflate !== false;
|
|
14374
|
-
var limit = typeof opts.limit
|
|
14377
|
+
var limit = typeof opts.limit === "undefined" || opts.limit === null ? 102400 : bytes.parse(opts.limit);
|
|
14375
14378
|
var type = opts.type || "application/octet-stream";
|
|
14376
14379
|
var verify = opts.verify || false;
|
|
14380
|
+
if (limit === null) {
|
|
14381
|
+
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid');
|
|
14382
|
+
}
|
|
14377
14383
|
if (verify !== false && typeof verify !== "function") {
|
|
14378
14384
|
throw new TypeError("option verify must be function");
|
|
14379
14385
|
}
|
|
@@ -14415,9 +14421,9 @@ var require_raw = __commonJS({
|
|
|
14415
14421
|
}
|
|
14416
14422
|
});
|
|
14417
14423
|
|
|
14418
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
14424
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/text.js
|
|
14419
14425
|
var require_text = __commonJS({
|
|
14420
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
14426
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/text.js"(exports, module) {
|
|
14421
14427
|
"use strict";
|
|
14422
14428
|
var bytes = require_bytes();
|
|
14423
14429
|
var contentType = require_content_type();
|
|
@@ -14429,9 +14435,12 @@ var require_text = __commonJS({
|
|
|
14429
14435
|
var opts = options || {};
|
|
14430
14436
|
var defaultCharset = opts.defaultCharset || "utf-8";
|
|
14431
14437
|
var inflate = opts.inflate !== false;
|
|
14432
|
-
var limit = typeof opts.limit
|
|
14438
|
+
var limit = typeof opts.limit === "undefined" || opts.limit === null ? 102400 : bytes.parse(opts.limit);
|
|
14433
14439
|
var type = opts.type || "text/plain";
|
|
14434
14440
|
var verify = opts.verify || false;
|
|
14441
|
+
if (limit === null) {
|
|
14442
|
+
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid');
|
|
14443
|
+
}
|
|
14435
14444
|
if (verify !== false && typeof verify !== "function") {
|
|
14436
14445
|
throw new TypeError("option verify must be function");
|
|
14437
14446
|
}
|
|
@@ -17017,9 +17026,9 @@ var require_lib2 = __commonJS({
|
|
|
17017
17026
|
}
|
|
17018
17027
|
});
|
|
17019
17028
|
|
|
17020
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
17029
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/urlencoded.js
|
|
17021
17030
|
var require_urlencoded = __commonJS({
|
|
17022
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
17031
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/lib/types/urlencoded.js"(exports, module) {
|
|
17023
17032
|
"use strict";
|
|
17024
17033
|
var bytes = require_bytes();
|
|
17025
17034
|
var contentType = require_content_type();
|
|
@@ -17037,9 +17046,12 @@ var require_urlencoded = __commonJS({
|
|
|
17037
17046
|
}
|
|
17038
17047
|
var extended = opts.extended !== false;
|
|
17039
17048
|
var inflate = opts.inflate !== false;
|
|
17040
|
-
var limit = typeof opts.limit
|
|
17049
|
+
var limit = typeof opts.limit === "undefined" || opts.limit === null ? 102400 : bytes.parse(opts.limit);
|
|
17041
17050
|
var type = opts.type || "application/x-www-form-urlencoded";
|
|
17042
17051
|
var verify = opts.verify || false;
|
|
17052
|
+
if (limit === null) {
|
|
17053
|
+
throw new TypeError('option limit "' + String(opts.limit) + '" is invalid');
|
|
17054
|
+
}
|
|
17043
17055
|
if (verify !== false && typeof verify !== "function") {
|
|
17044
17056
|
throw new TypeError("option verify must be function");
|
|
17045
17057
|
}
|
|
@@ -17190,9 +17202,9 @@ var require_urlencoded = __commonJS({
|
|
|
17190
17202
|
}
|
|
17191
17203
|
});
|
|
17192
17204
|
|
|
17193
|
-
// ../../node_modules/.pnpm/body-parser@1.20.
|
|
17205
|
+
// ../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/index.js
|
|
17194
17206
|
var require_body_parser = __commonJS({
|
|
17195
|
-
"../../node_modules/.pnpm/body-parser@1.20.
|
|
17207
|
+
"../../node_modules/.pnpm/body-parser@1.20.6/node_modules/body-parser/index.js"(exports, module) {
|
|
17196
17208
|
"use strict";
|
|
17197
17209
|
var deprecate = require_depd()("body-parser");
|
|
17198
17210
|
var parsers = /* @__PURE__ */ Object.create(null);
|
|
@@ -48953,11 +48965,15 @@ var JefriClient = class _JefriClient {
|
|
|
48953
48965
|
// a bad token must NOT trigger a reconnect storm
|
|
48954
48966
|
isAlive = true;
|
|
48955
48967
|
// heartbeat: did we get a pong since the last ping?
|
|
48968
|
+
hasConnected = false;
|
|
48969
|
+
// armed for auto-reconnect only after the first open
|
|
48970
|
+
lastPresence = "online";
|
|
48971
|
+
// re-announce this on reconnect
|
|
48956
48972
|
constructor(server) {
|
|
48957
48973
|
this.server = server.replace(/\/$/, "");
|
|
48958
48974
|
}
|
|
48959
48975
|
static async connect(opts = {}) {
|
|
48960
|
-
const server = (opts.server ?? "
|
|
48976
|
+
const server = (opts.server ?? "https://jefrichat.com").replace(/\/$/, "");
|
|
48961
48977
|
const client = new _JefriClient(server);
|
|
48962
48978
|
let token = opts.token;
|
|
48963
48979
|
if (!token) {
|
|
@@ -49017,9 +49033,10 @@ var JefriClient = class _JefriClient {
|
|
|
49017
49033
|
let settled = false;
|
|
49018
49034
|
this.ws.on("open", () => {
|
|
49019
49035
|
this.reconnectAttempts = 0;
|
|
49036
|
+
this.hasConnected = true;
|
|
49020
49037
|
this.startPing();
|
|
49021
49038
|
try {
|
|
49022
|
-
this.ws.send(JSON.stringify({ type: "presence", status:
|
|
49039
|
+
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
49023
49040
|
} catch {
|
|
49024
49041
|
}
|
|
49025
49042
|
if (!settled) {
|
|
@@ -49035,7 +49052,7 @@ var JefriClient = class _JefriClient {
|
|
|
49035
49052
|
});
|
|
49036
49053
|
this.ws.on("close", () => {
|
|
49037
49054
|
this.stopPing();
|
|
49038
|
-
if (!this.closing && !this.authFailed) this.scheduleReconnect();
|
|
49055
|
+
if (this.hasConnected && !this.closing && !this.authFailed) this.scheduleReconnect();
|
|
49039
49056
|
});
|
|
49040
49057
|
this.ws.on("message", (data) => {
|
|
49041
49058
|
let ev;
|
|
@@ -49119,7 +49136,15 @@ var JefriClient = class _JefriClient {
|
|
|
49119
49136
|
return this;
|
|
49120
49137
|
}
|
|
49121
49138
|
emit(event, payload) {
|
|
49122
|
-
this.handlers.get(event)
|
|
49139
|
+
for (const h of this.handlers.get(event) ?? []) {
|
|
49140
|
+
try {
|
|
49141
|
+
const res = h(payload);
|
|
49142
|
+
if (res instanceof Promise)
|
|
49143
|
+
res.catch((e) => console.error(`[jefri-sdk] async event handler failed for "${event}":`, e));
|
|
49144
|
+
} catch (e) {
|
|
49145
|
+
console.error(`[jefri-sdk] event handler failed for "${event}":`, e);
|
|
49146
|
+
}
|
|
49147
|
+
}
|
|
49123
49148
|
}
|
|
49124
49149
|
send(ev) {
|
|
49125
49150
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
@@ -49128,6 +49153,7 @@ var JefriClient = class _JefriClient {
|
|
|
49128
49153
|
}
|
|
49129
49154
|
// --- actions -------------------------------------------------------------
|
|
49130
49155
|
presence(status) {
|
|
49156
|
+
this.lastPresence = status;
|
|
49131
49157
|
this.send({ type: "presence", status });
|
|
49132
49158
|
}
|
|
49133
49159
|
message(to, content) {
|
|
@@ -49190,8 +49216,8 @@ var JefriClient = class _JefriClient {
|
|
|
49190
49216
|
addToGroup(groupId, members) {
|
|
49191
49217
|
this.send({ type: "add_to_group", groupId, members });
|
|
49192
49218
|
}
|
|
49193
|
-
history(conversationId) {
|
|
49194
|
-
this.send({ type: "history", conversationId });
|
|
49219
|
+
history(conversationId, before) {
|
|
49220
|
+
this.send({ type: "history", conversationId, ...before ? { before } : {} });
|
|
49195
49221
|
}
|
|
49196
49222
|
close() {
|
|
49197
49223
|
this.closing = true;
|
|
@@ -49202,9 +49228,11 @@ var JefriClient = class _JefriClient {
|
|
|
49202
49228
|
}
|
|
49203
49229
|
// --- REST helpers --------------------------------------------------------
|
|
49204
49230
|
async identities() {
|
|
49205
|
-
|
|
49231
|
+
const r = await fetch(`${this.server}/api/identities`, {
|
|
49206
49232
|
headers: { authorization: `Bearer ${this.token}` }
|
|
49207
|
-
})
|
|
49233
|
+
});
|
|
49234
|
+
if (!r.ok) throw new Error(`could not load identities: ${r.status} ${await r.text()}`);
|
|
49235
|
+
return r.json();
|
|
49208
49236
|
}
|
|
49209
49237
|
async groups() {
|
|
49210
49238
|
const r = await fetch(`${this.server}/api/groups`, {
|
|
@@ -49417,6 +49445,7 @@ var JefriClient = class _JefriClient {
|
|
|
49417
49445
|
return JSON.parse(dec.decode(plaintext));
|
|
49418
49446
|
}
|
|
49419
49447
|
};
|
|
49448
|
+
var serveDir = path.join(os.homedir(), ".acp", "serve");
|
|
49420
49449
|
|
|
49421
49450
|
// src/tools.ts
|
|
49422
49451
|
import fs4 from "node:fs";
|
|
@@ -49491,7 +49520,8 @@ import os3 from "node:os";
|
|
|
49491
49520
|
var DEFAULT_TEMPLATE = '{persona}You are @{me}, an autonomous agent on Jefri Chat. A message came in{where}. It may address several agents by @name (e.g. "@alice: do X @bob: do Y") \u2014 do ONLY the part addressed to you (@{me}); ignore parts meant for other agents. If nothing is addressed to you, reply with nothing to do. You can do ANYTHING (research, writing, analysis, code, running tools) \u2014 not just code. If a CLAUDE.md or relevant files are in this folder, read them first to get oriented. If the request is unclear, reply with ONE short clarifying question and stop (only your owner will answer). Otherwise do your part and reply concisely with the result.\n\n{context}Latest message from @{sender}: {message}';
|
|
49492
49521
|
var DEFAULTS2 = {
|
|
49493
49522
|
enabled: false,
|
|
49494
|
-
brain: "
|
|
49523
|
+
brain: "auto",
|
|
49524
|
+
// "auto" = match the HOST app (Codex→codex, Claude Code→claude)
|
|
49495
49525
|
workdir: process.cwd(),
|
|
49496
49526
|
replyMode: "mentions",
|
|
49497
49527
|
persona: "",
|
|
@@ -49499,29 +49529,109 @@ var DEFAULTS2 = {
|
|
|
49499
49529
|
ownerOnly: true,
|
|
49500
49530
|
// SECURITY: default to only the owner can direct an autonomous agent
|
|
49501
49531
|
contextMessages: 12,
|
|
49532
|
+
showWorking: true,
|
|
49533
|
+
// visibility: the headless brain is invisible, so tell the chat it's working
|
|
49502
49534
|
promptTemplate: DEFAULT_TEMPLATE
|
|
49503
49535
|
};
|
|
49504
49536
|
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
49505
|
-
var
|
|
49537
|
+
var AUTO_DIR = np2.join(DIR, "autonomous");
|
|
49538
|
+
var LEGACY_FILE = np2.join(DIR, "autonomous.json");
|
|
49506
49539
|
var LOG_FILE2 = np2.join(DIR, "autonomous.log");
|
|
49507
|
-
|
|
49540
|
+
var safeUser = (u) => (u || "unknown").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
49541
|
+
function cfgPath() {
|
|
49542
|
+
return np2.join(AUTO_DIR, `${safeUser(selfName)}.json`);
|
|
49543
|
+
}
|
|
49544
|
+
function lockPath() {
|
|
49545
|
+
return np2.join(AUTO_DIR, `${safeUser(selfName)}.lock`);
|
|
49546
|
+
}
|
|
49547
|
+
function logLine(line) {
|
|
49508
49548
|
try {
|
|
49509
|
-
|
|
49549
|
+
fs3.mkdirSync(DIR, { recursive: true });
|
|
49550
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
49551
|
+
fs3.appendFileSync(LOG_FILE2, `[${ts}] ${line}
|
|
49552
|
+
`);
|
|
49510
49553
|
} catch {
|
|
49511
|
-
return { ...DEFAULTS2 };
|
|
49512
49554
|
}
|
|
49513
49555
|
}
|
|
49514
|
-
var cfg =
|
|
49556
|
+
var cfg = { ...DEFAULTS2 };
|
|
49557
|
+
function withDerived(c) {
|
|
49558
|
+
return { ...c, resolvedBrain: resolveBrain(c.brain), ownerOnlyEffective: c.ownerOnly || FORCE_OWNER_ONLY, ownerOnlyEnforced: FORCE_OWNER_ONLY };
|
|
49559
|
+
}
|
|
49515
49560
|
function setAuto(patch) {
|
|
49516
49561
|
cfg = { ...cfg, ...patch };
|
|
49517
49562
|
try {
|
|
49518
|
-
fs3.mkdirSync(
|
|
49519
|
-
fs3.writeFileSync(
|
|
49563
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
49564
|
+
fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
|
|
49520
49565
|
} catch {
|
|
49521
49566
|
}
|
|
49522
|
-
|
|
49567
|
+
if (patch.enabled) acquireResponderLock();
|
|
49568
|
+
return withDerived(cfg);
|
|
49569
|
+
}
|
|
49570
|
+
var hostBrain = "";
|
|
49571
|
+
var hostName = "";
|
|
49572
|
+
function resolveBrain(brain) {
|
|
49573
|
+
if (brain && brain !== "auto") return brain;
|
|
49574
|
+
return hostBrain || "claude";
|
|
49575
|
+
}
|
|
49576
|
+
var selfName = "";
|
|
49577
|
+
var LOCK_STALE_MS = 9e4;
|
|
49578
|
+
var iAmResponder = false;
|
|
49579
|
+
var heartbeat = null;
|
|
49580
|
+
function pidAlive(pid) {
|
|
49581
|
+
try {
|
|
49582
|
+
process.kill(pid, 0);
|
|
49583
|
+
return true;
|
|
49584
|
+
} catch (e) {
|
|
49585
|
+
return e?.code === "EPERM";
|
|
49586
|
+
}
|
|
49587
|
+
}
|
|
49588
|
+
function acquireResponderLock() {
|
|
49589
|
+
try {
|
|
49590
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
49591
|
+
const now = Date.now();
|
|
49592
|
+
let cur = null;
|
|
49593
|
+
try {
|
|
49594
|
+
cur = JSON.parse(fs3.readFileSync(lockPath(), "utf8"));
|
|
49595
|
+
} catch {
|
|
49596
|
+
}
|
|
49597
|
+
const heldByOther = cur && cur.pid !== process.pid && pidAlive(cur.pid) && now - cur.ts < LOCK_STALE_MS;
|
|
49598
|
+
if (heldByOther) {
|
|
49599
|
+
if (iAmResponder) logLine(`yielded responder lock for @${selfName} to pid ${cur.pid} (${cur.host})`);
|
|
49600
|
+
iAmResponder = false;
|
|
49601
|
+
} else {
|
|
49602
|
+
fs3.writeFileSync(lockPath(), JSON.stringify({ pid: process.pid, host: hostName || "unknown", ts: now }));
|
|
49603
|
+
if (!iAmResponder) logLine(`acquired responder lock for @${selfName} (pid ${process.pid})`);
|
|
49604
|
+
iAmResponder = true;
|
|
49605
|
+
}
|
|
49606
|
+
} catch {
|
|
49607
|
+
iAmResponder = false;
|
|
49608
|
+
}
|
|
49609
|
+
if (!heartbeat) {
|
|
49610
|
+
heartbeat = setInterval(() => {
|
|
49611
|
+
if (cfg.enabled) acquireResponderLock();
|
|
49612
|
+
}, 3e4);
|
|
49613
|
+
heartbeat.unref?.();
|
|
49614
|
+
const release = () => {
|
|
49615
|
+
try {
|
|
49616
|
+
const c = JSON.parse(fs3.readFileSync(lockPath(), "utf8"));
|
|
49617
|
+
if (c.pid === process.pid) fs3.unlinkSync(lockPath());
|
|
49618
|
+
} catch {
|
|
49619
|
+
}
|
|
49620
|
+
};
|
|
49621
|
+
process.once("exit", release);
|
|
49622
|
+
process.once("SIGINT", () => {
|
|
49623
|
+
release();
|
|
49624
|
+
process.exit(0);
|
|
49625
|
+
});
|
|
49626
|
+
process.once("SIGTERM", () => {
|
|
49627
|
+
release();
|
|
49628
|
+
process.exit(0);
|
|
49629
|
+
});
|
|
49630
|
+
}
|
|
49631
|
+
return iAmResponder;
|
|
49523
49632
|
}
|
|
49524
49633
|
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49634
|
+
var FORCE_OWNER_ONLY = true;
|
|
49525
49635
|
|
|
49526
49636
|
// src/tools.ts
|
|
49527
49637
|
var MIME = {
|
|
@@ -50361,7 +50471,7 @@ ${cmd}`);
|
|
|
50361
50471
|
"jefri_add_doc",
|
|
50362
50472
|
{
|
|
50363
50473
|
title: "Add a document to my memory",
|
|
50364
|
-
description: "Upload a local file into your own document memory. Returns a command to run (reads the file locally and uploads it).",
|
|
50474
|
+
description: "Upload a local file into your own document memory. Returns a command to run (reads the file locally and uploads it). NOTE: documents follow this agent's visibility \u2014 on a PUBLIC agent the owner's connections can read/download them; on a shared agent only the chosen people; on a private agent only the owner. Warn the owner before uploading anything sensitive to a public agent.",
|
|
50365
50475
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
50366
50476
|
},
|
|
50367
50477
|
async ({ path: path2 }) => withClient(async (c) => {
|
|
@@ -50764,14 +50874,15 @@ ${cmd}`);
|
|
|
50764
50874
|
description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
|
|
50765
50875
|
inputSchema: {
|
|
50766
50876
|
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
50767
|
-
brain: external_exports.string().optional().describe("'
|
|
50877
|
+
brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex, Claude Code\u2192claude), or force 'claude' / 'codex' / a full custom command"),
|
|
50768
50878
|
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
50769
50879
|
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
50770
50880
|
persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
|
|
50771
50881
|
promptTemplate: external_exports.string().optional().describe("advanced: fully customize how the message is framed to the brain. Placeholders: {persona} {me} {sender} {where} {context} {message}. Pass 'default' to reset."),
|
|
50772
|
-
ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER
|
|
50882
|
+
ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER. NOTE: during beta this is FORCE-ENABLED for safety (a remote message drives a tool-agent on your machine) \u2014 setting false is accepted but has no effect yet."),
|
|
50773
50883
|
contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
|
|
50774
|
-
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
50884
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)"),
|
|
50885
|
+
showWorking: external_exports.boolean().optional().describe("post a quick '\u{1F427} on it\u2026' when the brain starts, so the invisible headless run is visible in chat (default on)")
|
|
50775
50886
|
}
|
|
50776
50887
|
},
|
|
50777
50888
|
async (args) => withClient(async () => {
|
|
@@ -50780,7 +50891,8 @@ ${cmd}`);
|
|
|
50780
50891
|
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
50781
50892
|
if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
|
|
50782
50893
|
if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
|
|
50783
|
-
if (typeof args.
|
|
50894
|
+
if (typeof args.showWorking === "boolean") patch.showWorking = args.showWorking;
|
|
50895
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim().toLowerCase() === "auto" ? "auto" : args.brain.trim();
|
|
50784
50896
|
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
50785
50897
|
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
50786
50898
|
if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
|
|
@@ -50788,21 +50900,24 @@ ${cmd}`);
|
|
|
50788
50900
|
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
50789
50901
|
const c = setAuto(patch);
|
|
50790
50902
|
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
50903
|
+
const ownerLabel = c.ownerOnlyEffective ? `yes \u2014 only you direct me${c.ownerOnlyEnforced && !c.ownerOnly ? " (force-enabled during beta)" : ""}` : "no (anyone who @mentions me)";
|
|
50904
|
+
const brainLabel = c.brain === "auto" ? `auto \u2192 ${c.resolvedBrain ?? "claude"} (this host)` : c.brain;
|
|
50791
50905
|
const lines = [
|
|
50792
|
-
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
50793
|
-
` Brain: ${
|
|
50906
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"} \xB7 this agent replies from ONE place (this connector); the same agent open elsewhere stays silent`,
|
|
50907
|
+
` Brain: ${brainLabel}`,
|
|
50794
50908
|
` Work folder: ${c.workdir}`,
|
|
50795
50909
|
` Replies to: ${modeLabel}`,
|
|
50796
|
-
` Owner-only: ${
|
|
50910
|
+
` Owner-only: ${ownerLabel}`,
|
|
50797
50911
|
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
50798
50912
|
` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
|
|
50913
|
+
` Shows "on it\u2026" while working: ${c.showWorking ? "yes" : "no"}`,
|
|
50799
50914
|
` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
|
|
50800
50915
|
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
|
|
50801
50916
|
` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
|
|
50802
50917
|
];
|
|
50803
50918
|
if (c.enabled)
|
|
50804
50919
|
lines.push(`
|
|
50805
|
-
\u26A0\uFE0F Incoming messages now drive "${
|
|
50920
|
+
\u26A0\uFE0F Incoming messages now drive "${brainLabel}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
50806
50921
|
return ok(lines.join("\n"));
|
|
50807
50922
|
})
|
|
50808
50923
|
);
|
package/dist/index.js
CHANGED
|
@@ -24861,11 +24861,15 @@ var JefriClient = class _JefriClient {
|
|
|
24861
24861
|
// a bad token must NOT trigger a reconnect storm
|
|
24862
24862
|
isAlive = true;
|
|
24863
24863
|
// heartbeat: did we get a pong since the last ping?
|
|
24864
|
+
hasConnected = false;
|
|
24865
|
+
// armed for auto-reconnect only after the first open
|
|
24866
|
+
lastPresence = "online";
|
|
24867
|
+
// re-announce this on reconnect
|
|
24864
24868
|
constructor(server2) {
|
|
24865
24869
|
this.server = server2.replace(/\/$/, "");
|
|
24866
24870
|
}
|
|
24867
24871
|
static async connect(opts = {}) {
|
|
24868
|
-
const server2 = (opts.server ?? "
|
|
24872
|
+
const server2 = (opts.server ?? "https://jefrichat.com").replace(/\/$/, "");
|
|
24869
24873
|
const client = new _JefriClient(server2);
|
|
24870
24874
|
let token = opts.token;
|
|
24871
24875
|
if (!token) {
|
|
@@ -24925,9 +24929,10 @@ var JefriClient = class _JefriClient {
|
|
|
24925
24929
|
let settled = false;
|
|
24926
24930
|
this.ws.on("open", () => {
|
|
24927
24931
|
this.reconnectAttempts = 0;
|
|
24932
|
+
this.hasConnected = true;
|
|
24928
24933
|
this.startPing();
|
|
24929
24934
|
try {
|
|
24930
|
-
this.ws.send(JSON.stringify({ type: "presence", status:
|
|
24935
|
+
this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
|
|
24931
24936
|
} catch {
|
|
24932
24937
|
}
|
|
24933
24938
|
if (!settled) {
|
|
@@ -24943,7 +24948,7 @@ var JefriClient = class _JefriClient {
|
|
|
24943
24948
|
});
|
|
24944
24949
|
this.ws.on("close", () => {
|
|
24945
24950
|
this.stopPing();
|
|
24946
|
-
if (!this.closing && !this.authFailed) this.scheduleReconnect();
|
|
24951
|
+
if (this.hasConnected && !this.closing && !this.authFailed) this.scheduleReconnect();
|
|
24947
24952
|
});
|
|
24948
24953
|
this.ws.on("message", (data) => {
|
|
24949
24954
|
let ev;
|
|
@@ -25027,7 +25032,15 @@ var JefriClient = class _JefriClient {
|
|
|
25027
25032
|
return this;
|
|
25028
25033
|
}
|
|
25029
25034
|
emit(event, payload) {
|
|
25030
|
-
this.handlers.get(event)
|
|
25035
|
+
for (const h of this.handlers.get(event) ?? []) {
|
|
25036
|
+
try {
|
|
25037
|
+
const res = h(payload);
|
|
25038
|
+
if (res instanceof Promise)
|
|
25039
|
+
res.catch((e) => console.error(`[jefri-sdk] async event handler failed for "${event}":`, e));
|
|
25040
|
+
} catch (e) {
|
|
25041
|
+
console.error(`[jefri-sdk] event handler failed for "${event}":`, e);
|
|
25042
|
+
}
|
|
25043
|
+
}
|
|
25031
25044
|
}
|
|
25032
25045
|
send(ev) {
|
|
25033
25046
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
@@ -25036,6 +25049,7 @@ var JefriClient = class _JefriClient {
|
|
|
25036
25049
|
}
|
|
25037
25050
|
// --- actions -------------------------------------------------------------
|
|
25038
25051
|
presence(status) {
|
|
25052
|
+
this.lastPresence = status;
|
|
25039
25053
|
this.send({ type: "presence", status });
|
|
25040
25054
|
}
|
|
25041
25055
|
message(to, content) {
|
|
@@ -25098,8 +25112,8 @@ var JefriClient = class _JefriClient {
|
|
|
25098
25112
|
addToGroup(groupId, members) {
|
|
25099
25113
|
this.send({ type: "add_to_group", groupId, members });
|
|
25100
25114
|
}
|
|
25101
|
-
history(conversationId) {
|
|
25102
|
-
this.send({ type: "history", conversationId });
|
|
25115
|
+
history(conversationId, before) {
|
|
25116
|
+
this.send({ type: "history", conversationId, ...before ? { before } : {} });
|
|
25103
25117
|
}
|
|
25104
25118
|
close() {
|
|
25105
25119
|
this.closing = true;
|
|
@@ -25110,9 +25124,11 @@ var JefriClient = class _JefriClient {
|
|
|
25110
25124
|
}
|
|
25111
25125
|
// --- REST helpers --------------------------------------------------------
|
|
25112
25126
|
async identities() {
|
|
25113
|
-
|
|
25127
|
+
const r = await fetch(`${this.server}/api/identities`, {
|
|
25114
25128
|
headers: { authorization: `Bearer ${this.token}` }
|
|
25115
|
-
})
|
|
25129
|
+
});
|
|
25130
|
+
if (!r.ok) throw new Error(`could not load identities: ${r.status} ${await r.text()}`);
|
|
25131
|
+
return r.json();
|
|
25116
25132
|
}
|
|
25117
25133
|
async groups() {
|
|
25118
25134
|
const r = await fetch(`${this.server}/api/groups`, {
|
|
@@ -25325,6 +25341,7 @@ var JefriClient = class _JefriClient {
|
|
|
25325
25341
|
return JSON.parse(dec.decode(plaintext));
|
|
25326
25342
|
}
|
|
25327
25343
|
};
|
|
25344
|
+
var serveDir = path.join(os.homedir(), ".acp", "serve");
|
|
25328
25345
|
|
|
25329
25346
|
// src/tools.ts
|
|
25330
25347
|
import fs4 from "node:fs";
|
|
@@ -25445,9 +25462,9 @@ function notifyIncoming(m) {
|
|
|
25445
25462
|
} else if (process.platform === "win32") {
|
|
25446
25463
|
const esc2 = (x) => x.replace(/'/g, "''");
|
|
25447
25464
|
const xesc = (x) => x.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
25448
|
-
const
|
|
25449
|
-
const xml = `<toast><visual><binding template="ToastGeneric"><text>${xesc(title)}</text><text>${xesc(body)}</text></binding></visual
|
|
25450
|
-
const ps = `$ErrorActionPreference='Stop';$t='${esc2(title)}';$b='${esc2(body)}'
|
|
25465
|
+
const soundLine = sound ? `try{[System.Media.SystemSounds]::Asterisk.Play();Start-Sleep -Milliseconds 150}catch{};` : "";
|
|
25466
|
+
const xml = `<toast><visual><binding template="ToastGeneric"><text>${xesc(title)}</text><text>${xesc(body)}</text></binding></visual><audio silent="true"/></toast>`;
|
|
25467
|
+
const ps = `$ErrorActionPreference='Stop';$t='${esc2(title)}';$b='${esc2(body)}';` + soundLine + `try{[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null;$d=New-Object Windows.Data.Xml.Dom.XmlDocument;$d.LoadXml('${xml}');$toast=New-Object Windows.UI.Notifications.ToastNotification -ArgumentList $d;[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe').Show($toast);Start-Sleep -Milliseconds 400;}catch{Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.BalloonTipTitle=$t;$n.BalloonTipText=$b;$n.Visible=$true;$n.ShowBalloonTip(6000);Start-Sleep -Milliseconds 6500;$n.Dispose();[Console]::Error.WriteLine('TOASTERR-fell-back-to-balloon: '+$_.Exception.Message);}`;
|
|
25451
25468
|
const encoded = Buffer.from(ps, "utf16le").toString("base64");
|
|
25452
25469
|
const child = cp.spawn(
|
|
25453
25470
|
"powershell",
|
|
@@ -25477,7 +25494,8 @@ import os3 from "node:os";
|
|
|
25477
25494
|
var DEFAULT_TEMPLATE = '{persona}You are @{me}, an autonomous agent on Jefri Chat. A message came in{where}. It may address several agents by @name (e.g. "@alice: do X @bob: do Y") \u2014 do ONLY the part addressed to you (@{me}); ignore parts meant for other agents. If nothing is addressed to you, reply with nothing to do. You can do ANYTHING (research, writing, analysis, code, running tools) \u2014 not just code. If a CLAUDE.md or relevant files are in this folder, read them first to get oriented. If the request is unclear, reply with ONE short clarifying question and stop (only your owner will answer). Otherwise do your part and reply concisely with the result.\n\n{context}Latest message from @{sender}: {message}';
|
|
25478
25495
|
var DEFAULTS2 = {
|
|
25479
25496
|
enabled: false,
|
|
25480
|
-
brain: "
|
|
25497
|
+
brain: "auto",
|
|
25498
|
+
// "auto" = match the HOST app (Codex→codex, Claude Code→claude)
|
|
25481
25499
|
workdir: process.cwd(),
|
|
25482
25500
|
replyMode: "mentions",
|
|
25483
25501
|
persona: "",
|
|
@@ -25485,11 +25503,21 @@ var DEFAULTS2 = {
|
|
|
25485
25503
|
ownerOnly: true,
|
|
25486
25504
|
// SECURITY: default to only the owner can direct an autonomous agent
|
|
25487
25505
|
contextMessages: 12,
|
|
25506
|
+
showWorking: true,
|
|
25507
|
+
// visibility: the headless brain is invisible, so tell the chat it's working
|
|
25488
25508
|
promptTemplate: DEFAULT_TEMPLATE
|
|
25489
25509
|
};
|
|
25490
25510
|
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
25491
|
-
var
|
|
25511
|
+
var AUTO_DIR = np2.join(DIR, "autonomous");
|
|
25512
|
+
var LEGACY_FILE = np2.join(DIR, "autonomous.json");
|
|
25492
25513
|
var LOG_FILE2 = np2.join(DIR, "autonomous.log");
|
|
25514
|
+
var safeUser = (u) => (u || "unknown").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
25515
|
+
function cfgPath() {
|
|
25516
|
+
return np2.join(AUTO_DIR, `${safeUser(selfName)}.json`);
|
|
25517
|
+
}
|
|
25518
|
+
function lockPath() {
|
|
25519
|
+
return np2.join(AUTO_DIR, `${safeUser(selfName)}.lock`);
|
|
25520
|
+
}
|
|
25493
25521
|
function logLine(line) {
|
|
25494
25522
|
try {
|
|
25495
25523
|
fs3.mkdirSync(DIR, { recursive: true });
|
|
@@ -25501,23 +25529,47 @@ function logLine(line) {
|
|
|
25501
25529
|
}
|
|
25502
25530
|
function loadCfg() {
|
|
25503
25531
|
try {
|
|
25504
|
-
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(
|
|
25532
|
+
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(cfgPath(), "utf8")) };
|
|
25533
|
+
} catch {
|
|
25534
|
+
}
|
|
25535
|
+
try {
|
|
25536
|
+
const legacy = { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(LEGACY_FILE, "utf8")) };
|
|
25537
|
+
logLine(`migrated legacy autonomous.json \u2192 ${safeUser(selfName)}.json`);
|
|
25538
|
+
return legacy;
|
|
25505
25539
|
} catch {
|
|
25506
25540
|
return { ...DEFAULTS2 };
|
|
25507
25541
|
}
|
|
25508
25542
|
}
|
|
25509
|
-
var cfg =
|
|
25543
|
+
var cfg = { ...DEFAULTS2 };
|
|
25544
|
+
function withDerived(c) {
|
|
25545
|
+
return { ...c, resolvedBrain: resolveBrain(c.brain), ownerOnlyEffective: c.ownerOnly || FORCE_OWNER_ONLY, ownerOnlyEnforced: FORCE_OWNER_ONLY };
|
|
25546
|
+
}
|
|
25510
25547
|
function getAuto() {
|
|
25511
|
-
return
|
|
25548
|
+
return withDerived(cfg);
|
|
25512
25549
|
}
|
|
25513
25550
|
function setAuto(patch) {
|
|
25514
25551
|
cfg = { ...cfg, ...patch };
|
|
25515
25552
|
try {
|
|
25516
|
-
fs3.mkdirSync(
|
|
25517
|
-
fs3.writeFileSync(
|
|
25553
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
25554
|
+
fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
|
|
25518
25555
|
} catch {
|
|
25519
25556
|
}
|
|
25520
|
-
|
|
25557
|
+
if (patch.enabled) acquireResponderLock();
|
|
25558
|
+
return withDerived(cfg);
|
|
25559
|
+
}
|
|
25560
|
+
var hostBrain = "";
|
|
25561
|
+
var hostName = "";
|
|
25562
|
+
function setAutoHost(clientName) {
|
|
25563
|
+
hostName = clientName ?? "";
|
|
25564
|
+
const n = hostName.toLowerCase();
|
|
25565
|
+
if (n.includes("codex")) hostBrain = "codex";
|
|
25566
|
+
else if (n.includes("claude")) hostBrain = "claude";
|
|
25567
|
+
else hostBrain = "";
|
|
25568
|
+
logLine(`host detected: "${hostName}" \u2192 brain ${hostBrain || "(unknown, default claude)"}`);
|
|
25569
|
+
}
|
|
25570
|
+
function resolveBrain(brain) {
|
|
25571
|
+
if (brain && brain !== "auto") return brain;
|
|
25572
|
+
return hostBrain || "claude";
|
|
25521
25573
|
}
|
|
25522
25574
|
var selfName = "";
|
|
25523
25575
|
var ownerName = "";
|
|
@@ -25526,12 +25578,78 @@ function configureAuto(opts) {
|
|
|
25526
25578
|
selfName = opts.self;
|
|
25527
25579
|
ownerName = opts.owner ?? "";
|
|
25528
25580
|
fetchHistory = opts.getHistory;
|
|
25581
|
+
cfg = loadCfg();
|
|
25582
|
+
if (cfg.enabled) acquireResponderLock();
|
|
25583
|
+
}
|
|
25584
|
+
var LOCK_STALE_MS = 9e4;
|
|
25585
|
+
var iAmResponder = false;
|
|
25586
|
+
var heartbeat = null;
|
|
25587
|
+
function pidAlive(pid) {
|
|
25588
|
+
try {
|
|
25589
|
+
process.kill(pid, 0);
|
|
25590
|
+
return true;
|
|
25591
|
+
} catch (e) {
|
|
25592
|
+
return e?.code === "EPERM";
|
|
25593
|
+
}
|
|
25594
|
+
}
|
|
25595
|
+
function acquireResponderLock() {
|
|
25596
|
+
try {
|
|
25597
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
25598
|
+
const now = Date.now();
|
|
25599
|
+
let cur = null;
|
|
25600
|
+
try {
|
|
25601
|
+
cur = JSON.parse(fs3.readFileSync(lockPath(), "utf8"));
|
|
25602
|
+
} catch {
|
|
25603
|
+
}
|
|
25604
|
+
const heldByOther = cur && cur.pid !== process.pid && pidAlive(cur.pid) && now - cur.ts < LOCK_STALE_MS;
|
|
25605
|
+
if (heldByOther) {
|
|
25606
|
+
if (iAmResponder) logLine(`yielded responder lock for @${selfName} to pid ${cur.pid} (${cur.host})`);
|
|
25607
|
+
iAmResponder = false;
|
|
25608
|
+
} else {
|
|
25609
|
+
fs3.writeFileSync(lockPath(), JSON.stringify({ pid: process.pid, host: hostName || "unknown", ts: now }));
|
|
25610
|
+
if (!iAmResponder) logLine(`acquired responder lock for @${selfName} (pid ${process.pid})`);
|
|
25611
|
+
iAmResponder = true;
|
|
25612
|
+
}
|
|
25613
|
+
} catch {
|
|
25614
|
+
iAmResponder = false;
|
|
25615
|
+
}
|
|
25616
|
+
if (!heartbeat) {
|
|
25617
|
+
heartbeat = setInterval(() => {
|
|
25618
|
+
if (cfg.enabled) acquireResponderLock();
|
|
25619
|
+
}, 3e4);
|
|
25620
|
+
heartbeat.unref?.();
|
|
25621
|
+
const release = () => {
|
|
25622
|
+
try {
|
|
25623
|
+
const c = JSON.parse(fs3.readFileSync(lockPath(), "utf8"));
|
|
25624
|
+
if (c.pid === process.pid) fs3.unlinkSync(lockPath());
|
|
25625
|
+
} catch {
|
|
25626
|
+
}
|
|
25627
|
+
};
|
|
25628
|
+
process.once("exit", release);
|
|
25629
|
+
process.once("SIGINT", () => {
|
|
25630
|
+
release();
|
|
25631
|
+
process.exit(0);
|
|
25632
|
+
});
|
|
25633
|
+
process.once("SIGTERM", () => {
|
|
25634
|
+
release();
|
|
25635
|
+
process.exit(0);
|
|
25636
|
+
});
|
|
25637
|
+
}
|
|
25638
|
+
return iAmResponder;
|
|
25529
25639
|
}
|
|
25530
25640
|
var expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
|
|
25641
|
+
function tokenize(cmd) {
|
|
25642
|
+
const out = [];
|
|
25643
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
25644
|
+
let m;
|
|
25645
|
+
while ((m = re.exec(cmd)) !== null) out.push(m[1] ?? m[2] ?? m[3]);
|
|
25646
|
+
return out;
|
|
25647
|
+
}
|
|
25531
25648
|
function brainArgv(brain, prompt) {
|
|
25532
|
-
|
|
25533
|
-
if (
|
|
25534
|
-
|
|
25649
|
+
const b = resolveBrain(brain);
|
|
25650
|
+
if (b === "claude") return ["claude", ["-p", "--permission-mode", "acceptEdits", prompt]];
|
|
25651
|
+
if (b === "codex") return ["codex", ["exec", "--full-auto", prompt]];
|
|
25652
|
+
const parts = tokenize(b.trim());
|
|
25535
25653
|
return [parts[0], [...parts.slice(1), prompt]];
|
|
25536
25654
|
}
|
|
25537
25655
|
var MAX_PROMPT = 12e3;
|
|
@@ -25540,6 +25658,7 @@ var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
25540
25658
|
var FORCE_OWNER_ONLY = true;
|
|
25541
25659
|
function shouldHandle(m) {
|
|
25542
25660
|
if (!cfg.enabled) return false;
|
|
25661
|
+
if (!iAmResponder) return false;
|
|
25543
25662
|
const fromOwner = !!ownerName && m.sender === ownerName;
|
|
25544
25663
|
const ownerOnly = cfg.ownerOnly || FORCE_OWNER_ONLY;
|
|
25545
25664
|
if (ownerOnly && !fromOwner) return false;
|
|
@@ -25620,23 +25739,41 @@ function handleAutonomous(m, reply, log2) {
|
|
|
25620
25739
|
const context = await buildContext(m.conversationId);
|
|
25621
25740
|
const prompt = (cfg.promptTemplate || DEFAULT_TEMPLATE).replace(/\{persona\}/g, persona).replace(/\{me\}/g, selfName).replace(/\{sender\}/g, m.sender).replace(/\{where\}/g, where).replace(/\{context\}/g, context).replace(/\{message\}/g, m.content).slice(0, MAX_PROMPT);
|
|
25622
25741
|
const [cmd, args] = brainArgv(cfg.brain, prompt);
|
|
25623
|
-
|
|
25624
|
-
|
|
25742
|
+
const shownBrain = resolveBrain(cfg.brain) + (cfg.brain === "auto" ? " (auto \u2192 host)" : "");
|
|
25743
|
+
log2(`autonomous: @${m.sender} \u2192 running "${shownBrain}"\u2026`);
|
|
25744
|
+
logLine(` running: ${shownBrain} in ${cfg.workdir}`);
|
|
25745
|
+
if (cfg.showWorking) {
|
|
25746
|
+
try {
|
|
25747
|
+
reply(`\u{1F427} on it\u2026 (${resolveBrain(cfg.brain)})`);
|
|
25748
|
+
} catch {
|
|
25749
|
+
}
|
|
25750
|
+
}
|
|
25625
25751
|
try {
|
|
25626
25752
|
const out = await runBrain(cmd, args, cfg.workdir);
|
|
25627
25753
|
const text = (out.trim() || "(done)").slice(0, MAX_REPLY);
|
|
25628
|
-
reply
|
|
25629
|
-
log2(`autonomous: replied to @${m.sender}`);
|
|
25630
|
-
logLine(` \u2192 replied: ${text.replace(/\s+/g, " ").slice(0, 300)}`);
|
|
25754
|
+
const delivered = await deliverReply(reply, text);
|
|
25755
|
+
log2(`autonomous: ${delivered ? "replied to" : "reply FAILED (socket down) for"} @${m.sender}`);
|
|
25756
|
+
logLine(` ${delivered ? "\u2192 replied" : "\u2717 reply lost (socket down after retries)"}: ${text.replace(/\s+/g, " ").slice(0, 300)}`);
|
|
25631
25757
|
} catch (e) {
|
|
25632
25758
|
const msg = `(couldn't finish autonomously: ${e?.message ?? e})`.slice(0, MAX_REPLY);
|
|
25633
|
-
reply
|
|
25759
|
+
await deliverReply(reply, msg);
|
|
25634
25760
|
log2(`autonomous: failed for @${m.sender}: ${e?.message ?? e}`);
|
|
25635
25761
|
logLine(` \u2717 failed: ${e?.message ?? e}`);
|
|
25636
25762
|
}
|
|
25637
25763
|
});
|
|
25638
25764
|
void drain();
|
|
25639
25765
|
}
|
|
25766
|
+
async function deliverReply(reply, text) {
|
|
25767
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
25768
|
+
try {
|
|
25769
|
+
reply(text);
|
|
25770
|
+
return true;
|
|
25771
|
+
} catch {
|
|
25772
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
25773
|
+
}
|
|
25774
|
+
}
|
|
25775
|
+
return false;
|
|
25776
|
+
}
|
|
25640
25777
|
|
|
25641
25778
|
// src/tools.ts
|
|
25642
25779
|
var MIME = {
|
|
@@ -26476,7 +26613,7 @@ ${cmd}`);
|
|
|
26476
26613
|
"jefri_add_doc",
|
|
26477
26614
|
{
|
|
26478
26615
|
title: "Add a document to my memory",
|
|
26479
|
-
description: "Upload a local file into your own document memory. Returns a command to run (reads the file locally and uploads it).",
|
|
26616
|
+
description: "Upload a local file into your own document memory. Returns a command to run (reads the file locally and uploads it). NOTE: documents follow this agent's visibility \u2014 on a PUBLIC agent the owner's connections can read/download them; on a shared agent only the chosen people; on a private agent only the owner. Warn the owner before uploading anything sensitive to a public agent.",
|
|
26480
26617
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
26481
26618
|
},
|
|
26482
26619
|
async ({ path: path3 }) => withClient(async (c) => {
|
|
@@ -26879,14 +27016,15 @@ ${cmd}`);
|
|
|
26879
27016
|
description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
|
|
26880
27017
|
inputSchema: {
|
|
26881
27018
|
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
26882
|
-
brain: external_exports.string().optional().describe("'
|
|
27019
|
+
brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex, Claude Code\u2192claude), or force 'claude' / 'codex' / a full custom command"),
|
|
26883
27020
|
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
26884
27021
|
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
26885
27022
|
persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
|
|
26886
27023
|
promptTemplate: external_exports.string().optional().describe("advanced: fully customize how the message is framed to the brain. Placeholders: {persona} {me} {sender} {where} {context} {message}. Pass 'default' to reset."),
|
|
26887
|
-
ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER
|
|
27024
|
+
ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER. NOTE: during beta this is FORCE-ENABLED for safety (a remote message drives a tool-agent on your machine) \u2014 setting false is accepted but has no effect yet."),
|
|
26888
27025
|
contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
|
|
26889
|
-
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
27026
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)"),
|
|
27027
|
+
showWorking: external_exports.boolean().optional().describe("post a quick '\u{1F427} on it\u2026' when the brain starts, so the invisible headless run is visible in chat (default on)")
|
|
26890
27028
|
}
|
|
26891
27029
|
},
|
|
26892
27030
|
async (args) => withClient(async () => {
|
|
@@ -26895,7 +27033,8 @@ ${cmd}`);
|
|
|
26895
27033
|
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
26896
27034
|
if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
|
|
26897
27035
|
if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
|
|
26898
|
-
if (typeof args.
|
|
27036
|
+
if (typeof args.showWorking === "boolean") patch.showWorking = args.showWorking;
|
|
27037
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim().toLowerCase() === "auto" ? "auto" : args.brain.trim();
|
|
26899
27038
|
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
26900
27039
|
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
26901
27040
|
if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
|
|
@@ -26903,21 +27042,24 @@ ${cmd}`);
|
|
|
26903
27042
|
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
26904
27043
|
const c = setAuto(patch);
|
|
26905
27044
|
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
27045
|
+
const ownerLabel = c.ownerOnlyEffective ? `yes \u2014 only you direct me${c.ownerOnlyEnforced && !c.ownerOnly ? " (force-enabled during beta)" : ""}` : "no (anyone who @mentions me)";
|
|
27046
|
+
const brainLabel = c.brain === "auto" ? `auto \u2192 ${c.resolvedBrain ?? "claude"} (this host)` : c.brain;
|
|
26906
27047
|
const lines = [
|
|
26907
|
-
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
26908
|
-
` Brain: ${
|
|
27048
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"} \xB7 this agent replies from ONE place (this connector); the same agent open elsewhere stays silent`,
|
|
27049
|
+
` Brain: ${brainLabel}`,
|
|
26909
27050
|
` Work folder: ${c.workdir}`,
|
|
26910
27051
|
` Replies to: ${modeLabel}`,
|
|
26911
|
-
` Owner-only: ${
|
|
27052
|
+
` Owner-only: ${ownerLabel}`,
|
|
26912
27053
|
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
26913
27054
|
` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
|
|
27055
|
+
` Shows "on it\u2026" while working: ${c.showWorking ? "yes" : "no"}`,
|
|
26914
27056
|
` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
|
|
26915
27057
|
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
|
|
26916
27058
|
` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
|
|
26917
27059
|
];
|
|
26918
27060
|
if (c.enabled)
|
|
26919
27061
|
lines.push(`
|
|
26920
|
-
\u26A0\uFE0F Incoming messages now drive "${
|
|
27062
|
+
\u26A0\uFE0F Incoming messages now drive "${brainLabel}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
26921
27063
|
return ok(lines.join("\n"));
|
|
26922
27064
|
})
|
|
26923
27065
|
);
|
|
@@ -27212,6 +27354,12 @@ async function main() {
|
|
|
27212
27354
|
}
|
|
27213
27355
|
ensureClient().catch((e) => log("initial connect failed (will retry on first tool):", e?.message ?? e));
|
|
27214
27356
|
const transport = new StdioServerTransport();
|
|
27357
|
+
server.server.oninitialized = () => {
|
|
27358
|
+
try {
|
|
27359
|
+
setAutoHost(server.server.getClientVersion()?.name);
|
|
27360
|
+
} catch {
|
|
27361
|
+
}
|
|
27362
|
+
};
|
|
27215
27363
|
await server.connect(transport);
|
|
27216
27364
|
log(`MCP server ready \u2014 identity @${USERNAME}, Jefri Chat server ${SERVER}`);
|
|
27217
27365
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jefrichat-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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": {
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "tsx src/index.ts",
|
|
19
|
+
"http": "tsx src/http.ts",
|
|
20
|
+
"build": "node build.mjs",
|
|
21
|
+
"prepublishOnly": "node build.mjs"
|
|
22
|
+
},
|
|
17
23
|
"keywords": [
|
|
18
24
|
"jefrichat",
|
|
19
25
|
"jefri",
|
|
@@ -25,22 +31,17 @@
|
|
|
25
31
|
],
|
|
26
32
|
"license": "MIT",
|
|
27
33
|
"devDependencies": {
|
|
34
|
+
"@acp/shared": "workspace:*",
|
|
28
35
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
29
36
|
"@types/cors": "^2.8.17",
|
|
30
37
|
"@types/express": "^4.17.21",
|
|
31
38
|
"@types/node": "^22.10.2",
|
|
39
|
+
"jefri-sdk": "workspace:*",
|
|
32
40
|
"cors": "^2.8.5",
|
|
33
41
|
"esbuild": "^0.28.1",
|
|
34
42
|
"express": "^4.21.2",
|
|
35
43
|
"tsx": "^4.19.2",
|
|
36
44
|
"typescript": "^5.7.2",
|
|
37
|
-
"zod": "^3.23.8"
|
|
38
|
-
"@acp/shared": "0.1.0",
|
|
39
|
-
"jefri-sdk": "0.3.0"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"start": "tsx src/index.ts",
|
|
43
|
-
"http": "tsx src/http.ts",
|
|
44
|
-
"build": "node build.mjs"
|
|
45
|
+
"zod": "^3.23.8"
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|