jefrichat-mcp 0.21.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/dist/http.js +103 -17
- package/dist/index.js +156 -25
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -49520,7 +49520,8 @@ import os3 from "node:os";
|
|
|
49520
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}';
|
|
49521
49521
|
var DEFAULTS2 = {
|
|
49522
49522
|
enabled: false,
|
|
49523
|
-
brain: "
|
|
49523
|
+
brain: "auto",
|
|
49524
|
+
// "auto" = match the HOST app (Codex→codex, Claude Code→claude)
|
|
49524
49525
|
workdir: process.cwd(),
|
|
49525
49526
|
replyMode: "mentions",
|
|
49526
49527
|
persona: "",
|
|
@@ -49528,29 +49529,109 @@ var DEFAULTS2 = {
|
|
|
49528
49529
|
ownerOnly: true,
|
|
49529
49530
|
// SECURITY: default to only the owner can direct an autonomous agent
|
|
49530
49531
|
contextMessages: 12,
|
|
49532
|
+
showWorking: true,
|
|
49533
|
+
// visibility: the headless brain is invisible, so tell the chat it's working
|
|
49531
49534
|
promptTemplate: DEFAULT_TEMPLATE
|
|
49532
49535
|
};
|
|
49533
49536
|
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
49534
|
-
var
|
|
49537
|
+
var AUTO_DIR = np2.join(DIR, "autonomous");
|
|
49538
|
+
var LEGACY_FILE = np2.join(DIR, "autonomous.json");
|
|
49535
49539
|
var LOG_FILE2 = np2.join(DIR, "autonomous.log");
|
|
49536
|
-
|
|
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) {
|
|
49537
49548
|
try {
|
|
49538
|
-
|
|
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
|
+
`);
|
|
49539
49553
|
} catch {
|
|
49540
|
-
return { ...DEFAULTS2 };
|
|
49541
49554
|
}
|
|
49542
49555
|
}
|
|
49543
|
-
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
|
+
}
|
|
49544
49560
|
function setAuto(patch) {
|
|
49545
49561
|
cfg = { ...cfg, ...patch };
|
|
49546
49562
|
try {
|
|
49547
|
-
fs3.mkdirSync(
|
|
49548
|
-
fs3.writeFileSync(
|
|
49563
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
49564
|
+
fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
|
|
49549
49565
|
} catch {
|
|
49550
49566
|
}
|
|
49551
|
-
|
|
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;
|
|
49552
49632
|
}
|
|
49553
49633
|
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49634
|
+
var FORCE_OWNER_ONLY = true;
|
|
49554
49635
|
|
|
49555
49636
|
// src/tools.ts
|
|
49556
49637
|
var MIME = {
|
|
@@ -50793,14 +50874,15 @@ ${cmd}`);
|
|
|
50793
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.",
|
|
50794
50875
|
inputSchema: {
|
|
50795
50876
|
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
50796
|
-
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"),
|
|
50797
50878
|
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
50798
50879
|
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
50799
50880
|
persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
|
|
50800
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."),
|
|
50801
|
-
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."),
|
|
50802
50883
|
contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
|
|
50803
|
-
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)")
|
|
50804
50886
|
}
|
|
50805
50887
|
},
|
|
50806
50888
|
async (args) => withClient(async () => {
|
|
@@ -50809,7 +50891,8 @@ ${cmd}`);
|
|
|
50809
50891
|
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
50810
50892
|
if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
|
|
50811
50893
|
if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
|
|
50812
|
-
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();
|
|
50813
50896
|
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
50814
50897
|
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
50815
50898
|
if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
|
|
@@ -50817,21 +50900,24 @@ ${cmd}`);
|
|
|
50817
50900
|
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
50818
50901
|
const c = setAuto(patch);
|
|
50819
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;
|
|
50820
50905
|
const lines = [
|
|
50821
|
-
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
50822
|
-
` 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}`,
|
|
50823
50908
|
` Work folder: ${c.workdir}`,
|
|
50824
50909
|
` Replies to: ${modeLabel}`,
|
|
50825
|
-
` Owner-only: ${
|
|
50910
|
+
` Owner-only: ${ownerLabel}`,
|
|
50826
50911
|
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
50827
50912
|
` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
|
|
50913
|
+
` Shows "on it\u2026" while working: ${c.showWorking ? "yes" : "no"}`,
|
|
50828
50914
|
` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
|
|
50829
50915
|
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
|
|
50830
50916
|
` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
|
|
50831
50917
|
];
|
|
50832
50918
|
if (c.enabled)
|
|
50833
50919
|
lines.push(`
|
|
50834
|
-
\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.`);
|
|
50835
50921
|
return ok(lines.join("\n"));
|
|
50836
50922
|
})
|
|
50837
50923
|
);
|
package/dist/index.js
CHANGED
|
@@ -25494,7 +25494,8 @@ import os3 from "node:os";
|
|
|
25494
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}';
|
|
25495
25495
|
var DEFAULTS2 = {
|
|
25496
25496
|
enabled: false,
|
|
25497
|
-
brain: "
|
|
25497
|
+
brain: "auto",
|
|
25498
|
+
// "auto" = match the HOST app (Codex→codex, Claude Code→claude)
|
|
25498
25499
|
workdir: process.cwd(),
|
|
25499
25500
|
replyMode: "mentions",
|
|
25500
25501
|
persona: "",
|
|
@@ -25502,11 +25503,21 @@ var DEFAULTS2 = {
|
|
|
25502
25503
|
ownerOnly: true,
|
|
25503
25504
|
// SECURITY: default to only the owner can direct an autonomous agent
|
|
25504
25505
|
contextMessages: 12,
|
|
25506
|
+
showWorking: true,
|
|
25507
|
+
// visibility: the headless brain is invisible, so tell the chat it's working
|
|
25505
25508
|
promptTemplate: DEFAULT_TEMPLATE
|
|
25506
25509
|
};
|
|
25507
25510
|
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
25508
|
-
var
|
|
25511
|
+
var AUTO_DIR = np2.join(DIR, "autonomous");
|
|
25512
|
+
var LEGACY_FILE = np2.join(DIR, "autonomous.json");
|
|
25509
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
|
+
}
|
|
25510
25521
|
function logLine(line) {
|
|
25511
25522
|
try {
|
|
25512
25523
|
fs3.mkdirSync(DIR, { recursive: true });
|
|
@@ -25518,23 +25529,47 @@ function logLine(line) {
|
|
|
25518
25529
|
}
|
|
25519
25530
|
function loadCfg() {
|
|
25520
25531
|
try {
|
|
25521
|
-
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;
|
|
25522
25539
|
} catch {
|
|
25523
25540
|
return { ...DEFAULTS2 };
|
|
25524
25541
|
}
|
|
25525
25542
|
}
|
|
25526
|
-
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
|
+
}
|
|
25527
25547
|
function getAuto() {
|
|
25528
|
-
return
|
|
25548
|
+
return withDerived(cfg);
|
|
25529
25549
|
}
|
|
25530
25550
|
function setAuto(patch) {
|
|
25531
25551
|
cfg = { ...cfg, ...patch };
|
|
25532
25552
|
try {
|
|
25533
|
-
fs3.mkdirSync(
|
|
25534
|
-
fs3.writeFileSync(
|
|
25553
|
+
fs3.mkdirSync(AUTO_DIR, { recursive: true });
|
|
25554
|
+
fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
|
|
25535
25555
|
} catch {
|
|
25536
25556
|
}
|
|
25537
|
-
|
|
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";
|
|
25538
25573
|
}
|
|
25539
25574
|
var selfName = "";
|
|
25540
25575
|
var ownerName = "";
|
|
@@ -25543,12 +25578,78 @@ function configureAuto(opts) {
|
|
|
25543
25578
|
selfName = opts.self;
|
|
25544
25579
|
ownerName = opts.owner ?? "";
|
|
25545
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;
|
|
25546
25639
|
}
|
|
25547
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
|
+
}
|
|
25548
25648
|
function brainArgv(brain, prompt) {
|
|
25549
|
-
|
|
25550
|
-
if (
|
|
25551
|
-
|
|
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());
|
|
25552
25653
|
return [parts[0], [...parts.slice(1), prompt]];
|
|
25553
25654
|
}
|
|
25554
25655
|
var MAX_PROMPT = 12e3;
|
|
@@ -25557,6 +25658,7 @@ var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
25557
25658
|
var FORCE_OWNER_ONLY = true;
|
|
25558
25659
|
function shouldHandle(m) {
|
|
25559
25660
|
if (!cfg.enabled) return false;
|
|
25661
|
+
if (!iAmResponder) return false;
|
|
25560
25662
|
const fromOwner = !!ownerName && m.sender === ownerName;
|
|
25561
25663
|
const ownerOnly = cfg.ownerOnly || FORCE_OWNER_ONLY;
|
|
25562
25664
|
if (ownerOnly && !fromOwner) return false;
|
|
@@ -25637,23 +25739,41 @@ function handleAutonomous(m, reply, log2) {
|
|
|
25637
25739
|
const context = await buildContext(m.conversationId);
|
|
25638
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);
|
|
25639
25741
|
const [cmd, args] = brainArgv(cfg.brain, prompt);
|
|
25640
|
-
|
|
25641
|
-
|
|
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
|
+
}
|
|
25642
25751
|
try {
|
|
25643
25752
|
const out = await runBrain(cmd, args, cfg.workdir);
|
|
25644
25753
|
const text = (out.trim() || "(done)").slice(0, MAX_REPLY);
|
|
25645
|
-
reply
|
|
25646
|
-
log2(`autonomous: replied to @${m.sender}`);
|
|
25647
|
-
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)}`);
|
|
25648
25757
|
} catch (e) {
|
|
25649
25758
|
const msg = `(couldn't finish autonomously: ${e?.message ?? e})`.slice(0, MAX_REPLY);
|
|
25650
|
-
reply
|
|
25759
|
+
await deliverReply(reply, msg);
|
|
25651
25760
|
log2(`autonomous: failed for @${m.sender}: ${e?.message ?? e}`);
|
|
25652
25761
|
logLine(` \u2717 failed: ${e?.message ?? e}`);
|
|
25653
25762
|
}
|
|
25654
25763
|
});
|
|
25655
25764
|
void drain();
|
|
25656
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
|
+
}
|
|
25657
25777
|
|
|
25658
25778
|
// src/tools.ts
|
|
25659
25779
|
var MIME = {
|
|
@@ -26896,14 +27016,15 @@ ${cmd}`);
|
|
|
26896
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.",
|
|
26897
27017
|
inputSchema: {
|
|
26898
27018
|
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
26899
|
-
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"),
|
|
26900
27020
|
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
26901
27021
|
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
26902
27022
|
persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
|
|
26903
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."),
|
|
26904
|
-
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."),
|
|
26905
27025
|
contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
|
|
26906
|
-
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)")
|
|
26907
27028
|
}
|
|
26908
27029
|
},
|
|
26909
27030
|
async (args) => withClient(async () => {
|
|
@@ -26912,7 +27033,8 @@ ${cmd}`);
|
|
|
26912
27033
|
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
26913
27034
|
if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
|
|
26914
27035
|
if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
|
|
26915
|
-
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();
|
|
26916
27038
|
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
26917
27039
|
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
26918
27040
|
if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
|
|
@@ -26920,21 +27042,24 @@ ${cmd}`);
|
|
|
26920
27042
|
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
26921
27043
|
const c = setAuto(patch);
|
|
26922
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;
|
|
26923
27047
|
const lines = [
|
|
26924
|
-
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
26925
|
-
` 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}`,
|
|
26926
27050
|
` Work folder: ${c.workdir}`,
|
|
26927
27051
|
` Replies to: ${modeLabel}`,
|
|
26928
|
-
` Owner-only: ${
|
|
27052
|
+
` Owner-only: ${ownerLabel}`,
|
|
26929
27053
|
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
26930
27054
|
` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
|
|
27055
|
+
` Shows "on it\u2026" while working: ${c.showWorking ? "yes" : "no"}`,
|
|
26931
27056
|
` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
|
|
26932
27057
|
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
|
|
26933
27058
|
` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
|
|
26934
27059
|
];
|
|
26935
27060
|
if (c.enabled)
|
|
26936
27061
|
lines.push(`
|
|
26937
|
-
\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.`);
|
|
26938
27063
|
return ok(lines.join("\n"));
|
|
26939
27064
|
})
|
|
26940
27065
|
);
|
|
@@ -27229,6 +27354,12 @@ async function main() {
|
|
|
27229
27354
|
}
|
|
27230
27355
|
ensureClient().catch((e) => log("initial connect failed (will retry on first tool):", e?.message ?? e));
|
|
27231
27356
|
const transport = new StdioServerTransport();
|
|
27357
|
+
server.server.oninitialized = () => {
|
|
27358
|
+
try {
|
|
27359
|
+
setAutoHost(server.server.getClientVersion()?.name);
|
|
27360
|
+
} catch {
|
|
27361
|
+
}
|
|
27362
|
+
};
|
|
27232
27363
|
await server.connect(transport);
|
|
27233
27364
|
log(`MCP server ready \u2014 identity @${USERNAME}, Jefri Chat server ${SERVER}`);
|
|
27234
27365
|
}
|
package/package.json
CHANGED