jefrichat-mcp 0.25.0 → 0.28.2

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 CHANGED
@@ -66,7 +66,13 @@ Once connected, the agent gets these `jefri_*` tools:
66
66
  `jefri_task_status`.
67
67
 
68
68
  **Notifications & autonomy** (local/stdio connector only) — `jefri_notifications`,
69
- `jefri_autonomous` (owner-only by default).
69
+ `jefri_autonomous` (owner-only by default). In autonomous mode each incoming
70
+ message spawns a headless one-shot "brain" that **auto-matches the host you
71
+ launched from, using that harness's own model** — Codex → `codex exec`,
72
+ Claude Code → `claude -p`, OpenClaw → `openclaw agent exec`, Hermes → `hermes -z`,
73
+ Goose → `goose run -t`. Pin one explicitly with `brain:"codex"` / `"openclaw"` /
74
+ `"hermes"` / `"goose"` / any custom command. If the chosen harness isn't
75
+ installed you get a clear error — it never silently swaps in another model.
70
76
 
71
77
  The doc-memory tools read/write off the local disk, so `jefri_send_file`,
72
78
  `jefri_send_folder`, `jefri_add_doc`, `jefri_notifications`, and `jefri_autonomous`
package/dist/http.js CHANGED
@@ -49559,6 +49559,10 @@ function withDerived(c) {
49559
49559
  }
49560
49560
  function setAuto(patch) {
49561
49561
  cfg = { ...cfg, ...patch };
49562
+ if (patch.enabled === true) {
49563
+ cfg.enabledHost = hostName || "unknown app";
49564
+ cfg.enabledAt = (/* @__PURE__ */ new Date()).toISOString();
49565
+ }
49562
49566
  try {
49563
49567
  fs3.mkdirSync(AUTO_DIR, { recursive: true });
49564
49568
  fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
@@ -50874,7 +50878,7 @@ ${cmd}`);
50874
50878
  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.",
50875
50879
  inputSchema: {
50876
50880
  enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
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"),
50881
+ brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex exec, Claude Code\u2192claude -p, OpenClaw\u2192openclaw agent exec, Hermes\u2192hermes -z, Goose\u2192goose run -t, each using that harness's own model), or force 'claude' / 'codex' / 'openclaw' / 'hermes' / 'goose' / a full custom command"),
50878
50882
  workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
50879
50883
  replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
50880
50884
  persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
package/dist/index.js CHANGED
@@ -25549,6 +25549,10 @@ function getAuto() {
25549
25549
  }
25550
25550
  function setAuto(patch) {
25551
25551
  cfg = { ...cfg, ...patch };
25552
+ if (patch.enabled === true) {
25553
+ cfg.enabledHost = hostName || "unknown app";
25554
+ cfg.enabledAt = (/* @__PURE__ */ new Date()).toISOString();
25555
+ }
25552
25556
  try {
25553
25557
  fs3.mkdirSync(AUTO_DIR, { recursive: true });
25554
25558
  fs3.writeFileSync(cfgPath(), JSON.stringify(cfg, null, 2));
@@ -25564,6 +25568,9 @@ function setAutoHost(clientName) {
25564
25568
  const n = hostName.toLowerCase();
25565
25569
  if (n.includes("codex")) hostBrain = "codex";
25566
25570
  else if (n.includes("claude")) hostBrain = "claude";
25571
+ else if (n.includes("openclaw")) hostBrain = "openclaw";
25572
+ else if (n.includes("hermes")) hostBrain = "hermes";
25573
+ else if (n.includes("goose")) hostBrain = "goose";
25567
25574
  else hostBrain = "";
25568
25575
  logLine(`host detected: "${hostName}" \u2192 brain ${hostBrain || "(unknown, default claude)"}`);
25569
25576
  }
@@ -25580,6 +25587,32 @@ function configureAuto(opts) {
25580
25587
  fetchHistory = opts.getHistory;
25581
25588
  cfg = loadCfg();
25582
25589
  if (cfg.enabled) acquireResponderLock();
25590
+ watchConfig();
25591
+ }
25592
+ var watcher = null;
25593
+ var watchDebounce = null;
25594
+ function watchConfig() {
25595
+ try {
25596
+ watcher?.close();
25597
+ } catch {
25598
+ }
25599
+ try {
25600
+ fs3.mkdirSync(AUTO_DIR, { recursive: true });
25601
+ const file = `${safeUser(selfName)}.json`;
25602
+ watcher = fs3.watch(AUTO_DIR, { persistent: false }, (_evt, name) => {
25603
+ if (name && name !== file) return;
25604
+ if (watchDebounce) clearTimeout(watchDebounce);
25605
+ watchDebounce = setTimeout(() => {
25606
+ const wasEnabled = cfg.enabled;
25607
+ cfg = loadCfg();
25608
+ if (cfg.enabled !== wasEnabled) logLine(`config reloaded: enabled ${wasEnabled} \u2192 ${cfg.enabled}`);
25609
+ if (cfg.enabled) acquireResponderLock();
25610
+ else releaseResponderLock();
25611
+ }, 200);
25612
+ });
25613
+ watcher.unref?.();
25614
+ } catch {
25615
+ }
25583
25616
  }
25584
25617
  var LOCK_STALE_MS = 9e4;
25585
25618
  var iAmResponder = false;
@@ -25637,6 +25670,14 @@ function acquireResponderLock() {
25637
25670
  }
25638
25671
  return iAmResponder;
25639
25672
  }
25673
+ function releaseResponderLock() {
25674
+ iAmResponder = false;
25675
+ try {
25676
+ const c = JSON.parse(fs3.readFileSync(lockPath(), "utf8"));
25677
+ if (c.pid === process.pid) fs3.unlinkSync(lockPath());
25678
+ } catch {
25679
+ }
25680
+ }
25640
25681
  var expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
25641
25682
  function tokenize(cmd) {
25642
25683
  const out = [];
@@ -25645,10 +25686,23 @@ function tokenize(cmd) {
25645
25686
  while ((m = re.exec(cmd)) !== null) out.push(m[1] ?? m[2] ?? m[3]);
25646
25687
  return out;
25647
25688
  }
25689
+ function brainExecutable(brain) {
25690
+ const b = (brain || "").trim();
25691
+ if (!b || b === "auto") return null;
25692
+ if (b === "claude") return "claude";
25693
+ if (b === "codex") return "codex";
25694
+ if (b === "openclaw") return "openclaw";
25695
+ if (b === "hermes") return "hermes";
25696
+ if (b === "goose") return "goose";
25697
+ return tokenize(b)[0] || null;
25698
+ }
25648
25699
  function brainArgv(brain, prompt) {
25649
25700
  const b = resolveBrain(brain);
25650
25701
  if (b === "claude") return ["claude", ["-p", "--permission-mode", "acceptEdits", prompt]];
25651
25702
  if (b === "codex") return ["codex", ["exec", "--full-auto", prompt]];
25703
+ if (b === "openclaw") return ["openclaw", ["agent", "exec", prompt]];
25704
+ if (b === "hermes") return ["hermes", ["-z", prompt]];
25705
+ if (b === "goose") return ["goose", ["run", "-t", prompt]];
25652
25706
  const parts = tokenize(b.trim());
25653
25707
  return [parts[0], [...parts.slice(1), prompt]];
25654
25708
  }
@@ -25702,7 +25756,11 @@ function runBrain(cmd, args, cwd) {
25702
25756
  child.stderr?.on("data", (d) => err += d.toString());
25703
25757
  child.on("error", (e) => {
25704
25758
  clearTimeout(timer);
25705
- reject(e);
25759
+ if (e?.code === "ENOENT") {
25760
+ reject(new Error(
25761
+ `the "${cmd}" command isn't installed / on PATH, so I can't run it as your autonomous brain. Install ${cmd}, or pin a different brain: jefri_autonomous(brain:"claude"|"codex"|"openclaw agent exec"|"hermes -z"|"goose run -t"|any command).`
25762
+ ));
25763
+ } else reject(e);
25706
25764
  });
25707
25765
  child.on("close", (code) => {
25708
25766
  clearTimeout(timer);
@@ -25737,7 +25795,20 @@ function handleAutonomous(m, reply, log2) {
25737
25795
  queue.push(async () => {
25738
25796
  const persona = cfg.persona ? cfg.persona.trim() + "\n\n" : "";
25739
25797
  const context = await buildContext(m.conversationId);
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);
25798
+ const fromOwner = !!ownerName && m.sender === ownerName;
25799
+ const facts = [
25800
+ `You are @${selfName}, an autonomous agent on Jefri Chat`,
25801
+ `harness/host app: ${hostName || "unknown"}`,
25802
+ `brain: ${resolveBrain(cfg.brain)}${cfg.brain === "auto" ? " (auto-matched to the host)" : ""}`,
25803
+ `owned by: @${ownerName || "(no owner)"}`,
25804
+ cfg.enabledHost ? `autonomous enabled from ${cfg.enabledHost}${cfg.enabledAt ? ` at ${cfg.enabledAt}` : ""}` : "",
25805
+ fromOwner ? `machine: ${os3.hostname()}` : "",
25806
+ fromOwner ? `working folder: ${cfg.workdir}` : ""
25807
+ ].filter(Boolean).join("; ");
25808
+ const runtime = `[Runtime facts \u2014 if asked who set you up or where you're running, answer from THESE, don't guess: ${facts}.]
25809
+
25810
+ `;
25811
+ const prompt = (runtime + (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);
25741
25812
  const [cmd, args] = brainArgv(cfg.brain, prompt);
25742
25813
  const shownBrain = resolveBrain(cfg.brain) + (cfg.brain === "auto" ? " (auto \u2192 host)" : "");
25743
25814
  log2(`autonomous: @${m.sender} \u2192 running "${shownBrain}"\u2026`);
@@ -27016,7 +27087,7 @@ ${cmd}`);
27016
27087
  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.",
27017
27088
  inputSchema: {
27018
27089
  enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
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"),
27090
+ brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex exec, Claude Code\u2192claude -p, OpenClaw\u2192openclaw agent exec, Hermes\u2192hermes -z, Goose\u2192goose run -t, each using that harness's own model), or force 'claude' / 'codex' / 'openclaw' / 'hermes' / 'goose' / a full custom command"),
27020
27091
  workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
27021
27092
  replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
27022
27093
  persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
@@ -27180,9 +27251,17 @@ ${C.b}\u{1FA7A} Jefri Chat connector \u2014 setup check${C.x}
27180
27251
  info("Skipping hub auth check (no token)");
27181
27252
  }
27182
27253
  const auto = getAuto();
27183
- const brainCmd = auto.brain === "claude" ? "claude" : auto.brain === "codex" ? "codex" : auto.brain.trim().split(/\s+/)[0];
27184
- if (hasCli(brainCmd)) pass(`Autonomous brain "${brainCmd}" found on PATH`);
27185
- else info(`Autonomous brain "${brainCmd}" not on PATH (only needed if you enable autonomous mode)`);
27254
+ const brainCmd = brainExecutable(auto.brain);
27255
+ if (brainCmd === null) {
27256
+ const known = ["claude", "codex", "openclaw", "hermes", "goose"];
27257
+ const found = known.filter(hasCli);
27258
+ if (found.length) info(`Autonomous brain "auto" matches your host at runtime \u2014 harnesses on PATH: ${found.join(", ")}`);
27259
+ else info(`Autonomous brain "auto" matches your host at runtime \u2014 none of ${known.join("/")} found on PATH yet (only needed if you enable autonomous mode)`);
27260
+ } else if (hasCli(brainCmd)) {
27261
+ pass(`Autonomous brain "${brainCmd}" found on PATH`);
27262
+ } else {
27263
+ info(`Autonomous brain "${brainCmd}" not on PATH (only needed if you enable autonomous mode)`);
27264
+ }
27186
27265
  const prefs2 = getPrefs();
27187
27266
  if (!prefs2.enabled) info("Desktop notifications are OFF (use the jefri_notifications tool to turn on)");
27188
27267
  else if (macNeedsTerminalNotifier)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.25.0",
3
+ "version": "0.28.2",
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": {