jefrichat-mcp 0.8.0 → 0.12.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.
Files changed (3) hide show
  1. package/dist/http.js +174 -55
  2. package/dist/index.js +578 -152
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -1300,8 +1300,8 @@ var require_node = __commonJS({
1300
1300
  }
1301
1301
  break;
1302
1302
  case "FILE":
1303
- var fs4 = __require("fs");
1304
- stream2 = new fs4.SyncWriteStream(fd2, { autoClose: false });
1303
+ var fs5 = __require("fs");
1304
+ stream2 = new fs5.SyncWriteStream(fd2, { autoClose: false });
1305
1305
  stream2._type = "fs";
1306
1306
  break;
1307
1307
  case "PIPE":
@@ -18437,7 +18437,7 @@ var require_view = __commonJS({
18437
18437
  "use strict";
18438
18438
  var debug = require_src()("express:view");
18439
18439
  var path2 = __require("path");
18440
- var fs4 = __require("fs");
18440
+ var fs5 = __require("fs");
18441
18441
  var dirname = path2.dirname;
18442
18442
  var basename = path2.basename;
18443
18443
  var extname = path2.extname;
@@ -18503,7 +18503,7 @@ var require_view = __commonJS({
18503
18503
  function tryStat(path3) {
18504
18504
  debug('stat "%s"', path3);
18505
18505
  try {
18506
- return fs4.statSync(path3);
18506
+ return fs5.statSync(path3);
18507
18507
  } catch (e) {
18508
18508
  return void 0;
18509
18509
  }
@@ -18858,7 +18858,7 @@ var require_types = __commonJS({
18858
18858
  var require_mime = __commonJS({
18859
18859
  "../../node_modules/.pnpm/mime@1.6.0/node_modules/mime/mime.js"(exports, module) {
18860
18860
  var path2 = __require("path");
18861
- var fs4 = __require("fs");
18861
+ var fs5 = __require("fs");
18862
18862
  function Mime() {
18863
18863
  this.types = /* @__PURE__ */ Object.create(null);
18864
18864
  this.extensions = /* @__PURE__ */ Object.create(null);
@@ -18879,7 +18879,7 @@ var require_mime = __commonJS({
18879
18879
  };
18880
18880
  Mime.prototype.load = function(file) {
18881
18881
  this._loading = file;
18882
- var map = {}, content = fs4.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
18882
+ var map = {}, content = fs5.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
18883
18883
  lines.forEach(function(line) {
18884
18884
  var fields = line.replace(/\s*#.*|^\s*|\s*$/g, "").split(/\s+/);
18885
18885
  map[fields.shift()] = fields;
@@ -19117,7 +19117,7 @@ var require_send = __commonJS({
19117
19117
  var escapeHtml = require_escape_html();
19118
19118
  var etag = require_etag();
19119
19119
  var fresh = require_fresh();
19120
- var fs4 = __require("fs");
19120
+ var fs5 = __require("fs");
19121
19121
  var mime = require_mime();
19122
19122
  var ms = require_ms2();
19123
19123
  var onFinished = require_on_finished();
@@ -19450,7 +19450,7 @@ var require_send = __commonJS({
19450
19450
  var i = 0;
19451
19451
  var self = this;
19452
19452
  debug('stat "%s"', path3);
19453
- fs4.stat(path3, function onstat(err, stat) {
19453
+ fs5.stat(path3, function onstat(err, stat) {
19454
19454
  if (err && err.code === "ENOENT" && !extname(path3) && path3[path3.length - 1] !== sep) {
19455
19455
  return next(err);
19456
19456
  }
@@ -19465,7 +19465,7 @@ var require_send = __commonJS({
19465
19465
  }
19466
19466
  var p = path3 + "." + self._extensions[i++];
19467
19467
  debug('stat "%s"', p);
19468
- fs4.stat(p, function(err2, stat) {
19468
+ fs5.stat(p, function(err2, stat) {
19469
19469
  if (err2) return next(err2);
19470
19470
  if (stat.isDirectory()) return next();
19471
19471
  self.emit("file", p, stat);
@@ -19483,7 +19483,7 @@ var require_send = __commonJS({
19483
19483
  }
19484
19484
  var p = join(path3, self._index[i]);
19485
19485
  debug('stat "%s"', p);
19486
- fs4.stat(p, function(err2, stat) {
19486
+ fs5.stat(p, function(err2, stat) {
19487
19487
  if (err2) return next(err2);
19488
19488
  if (stat.isDirectory()) return next();
19489
19489
  self.emit("file", p, stat);
@@ -19495,7 +19495,7 @@ var require_send = __commonJS({
19495
19495
  SendStream.prototype.stream = function stream(path3, options) {
19496
19496
  var self = this;
19497
19497
  var res = this.res;
19498
- var stream2 = fs4.createReadStream(path3, options);
19498
+ var stream2 = fs5.createReadStream(path3, options);
19499
19499
  this.emit("stream", stream2);
19500
19500
  stream2.pipe(res);
19501
19501
  function cleanup() {
@@ -29739,12 +29739,12 @@ var require_dist = __commonJS({
29739
29739
  throw new Error(`Unknown format "${name}"`);
29740
29740
  return f;
29741
29741
  };
29742
- function addFormats(ajv, list, fs4, exportName) {
29742
+ function addFormats(ajv, list, fs5, exportName) {
29743
29743
  var _a;
29744
29744
  var _b;
29745
29745
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
29746
29746
  for (const f of list)
29747
- ajv.addFormat(f, fs4[f]);
29747
+ ajv.addFormat(f, fs5[f]);
29748
29748
  }
29749
29749
  module.exports = exports = formatsPlugin;
29750
29750
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -48938,6 +48938,8 @@ var AcpClient = class _AcpClient {
48938
48938
  closing = false;
48939
48939
  authFailed = false;
48940
48940
  // a bad token must NOT trigger a reconnect storm
48941
+ isAlive = true;
48942
+ // heartbeat: did we get a pong since the last ping?
48941
48943
  constructor(server) {
48942
48944
  this.server = server.replace(/\/$/, "");
48943
48945
  }
@@ -49038,15 +49040,45 @@ var AcpClient = class _AcpClient {
49038
49040
  });
49039
49041
  });
49040
49042
  }
49041
- // Keep the hub connection alive through idle-timeout proxies.
49043
+ // Heartbeat: keep the connection warm through idle proxies AND detect a
49044
+ // half-open (silently dead) socket. A dropped Wi-Fi / VPN / sleep can leave
49045
+ // the socket readyState=OPEN forever while no data flows, so `close` never
49046
+ // fires and we'd never reconnect. So each tick we send a WS-level ping (the
49047
+ // server's ws auto-pongs) and require a pong to have arrived since the last
49048
+ // tick — if not, the link is dead, so terminate() it, which fires `close` and
49049
+ // triggers the existing reconnect.
49042
49050
  startPing() {
49043
49051
  this.stopPing();
49052
+ this.isAlive = true;
49053
+ try {
49054
+ this.ws.on("pong", () => {
49055
+ this.isAlive = true;
49056
+ });
49057
+ } catch {
49058
+ }
49044
49059
  this.pingTimer = setInterval(() => {
49060
+ if (this.ws.readyState !== this.ws.OPEN) return;
49061
+ if (!this.isAlive) {
49062
+ try {
49063
+ this.ws.terminate();
49064
+ } catch {
49065
+ try {
49066
+ this.ws.close();
49067
+ } catch {
49068
+ }
49069
+ }
49070
+ return;
49071
+ }
49072
+ this.isAlive = false;
49073
+ try {
49074
+ this.ws.ping();
49075
+ } catch {
49076
+ }
49045
49077
  try {
49046
- if (this.ws.readyState === this.ws.OPEN) this.ws.send(JSON.stringify({ type: "ping" }));
49078
+ this.ws.send(JSON.stringify({ type: "ping" }));
49047
49079
  } catch {
49048
49080
  }
49049
- }, 25e3);
49081
+ }, 15e3);
49050
49082
  }
49051
49083
  stopPing() {
49052
49084
  if (this.pingTimer) clearInterval(this.pingTimer);
@@ -49373,10 +49405,10 @@ var AcpClient = class _AcpClient {
49373
49405
  };
49374
49406
 
49375
49407
  // src/tools.ts
49376
- import fs3 from "node:fs";
49377
- import np2 from "node:path";
49408
+ import fs4 from "node:fs";
49409
+ import np3 from "node:path";
49378
49410
  import cp2 from "node:child_process";
49379
- import os3 from "node:os";
49411
+ import os4 from "node:os";
49380
49412
 
49381
49413
  // src/notify.ts
49382
49414
  import cp from "node:child_process";
@@ -49435,6 +49467,44 @@ function setPrefs(patch) {
49435
49467
  return { ...prefs };
49436
49468
  }
49437
49469
 
49470
+ // src/autonomous.ts
49471
+ import fs3 from "node:fs";
49472
+ import np2 from "node:path";
49473
+ import os3 from "node:os";
49474
+ 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}';
49475
+ var DEFAULTS2 = {
49476
+ enabled: false,
49477
+ brain: "claude",
49478
+ workdir: process.cwd(),
49479
+ replyMode: "mentions",
49480
+ persona: "",
49481
+ replyToBots: false,
49482
+ ownerOnly: false,
49483
+ contextMessages: 12,
49484
+ promptTemplate: DEFAULT_TEMPLATE
49485
+ };
49486
+ var DIR = np2.join(os3.homedir(), ".jefri");
49487
+ var FILE = np2.join(DIR, "autonomous.json");
49488
+ var LOG_FILE = np2.join(DIR, "autonomous.log");
49489
+ function loadCfg() {
49490
+ try {
49491
+ return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
49492
+ } catch {
49493
+ return { ...DEFAULTS2 };
49494
+ }
49495
+ }
49496
+ var cfg = loadCfg();
49497
+ function setAuto(patch) {
49498
+ cfg = { ...cfg, ...patch };
49499
+ try {
49500
+ fs3.mkdirSync(DIR, { recursive: true });
49501
+ fs3.writeFileSync(FILE, JSON.stringify(cfg, null, 2));
49502
+ } catch {
49503
+ }
49504
+ return { ...cfg };
49505
+ }
49506
+ var TIMEOUT_MS = 5 * 60 * 1e3;
49507
+
49438
49508
  // src/tools.ts
49439
49509
  var MIME = {
49440
49510
  ".jpg": "image/jpeg",
@@ -49455,21 +49525,21 @@ var MIME = {
49455
49525
  ".json": "application/json",
49456
49526
  ".zip": "application/zip"
49457
49527
  };
49458
- var mimeOf = (name) => MIME[np2.extname(name).toLowerCase()] ?? "application/octet-stream";
49528
+ var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
49459
49529
  function screenshotDir() {
49460
49530
  try {
49461
49531
  const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
49462
- if (out) return out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
49532
+ if (out) return out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
49463
49533
  } catch {
49464
49534
  }
49465
- const desktop = np2.join(os3.homedir(), "Desktop");
49466
- return fs3.existsSync(desktop) ? desktop : null;
49535
+ const desktop = np3.join(os4.homedir(), "Desktop");
49536
+ return fs4.existsSync(desktop) ? desktop : null;
49467
49537
  }
49468
49538
  function resolveLocalFile(rawPath) {
49469
- const expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
49539
+ const expand = (p) => p.startsWith("~") ? np3.join(os4.homedir(), p.slice(1)) : p;
49470
49540
  const exists = (p) => {
49471
49541
  try {
49472
- return fs3.existsSync(p) ? p : null;
49542
+ return fs4.existsSync(p) ? p : null;
49473
49543
  } catch {
49474
49544
  return null;
49475
49545
  }
@@ -49482,26 +49552,26 @@ function resolveLocalFile(rawPath) {
49482
49552
  hit = exists(v);
49483
49553
  if (hit) return hit;
49484
49554
  }
49485
- const base = np2.basename(abs);
49555
+ const base = np3.basename(abs);
49486
49556
  const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s  ]/i.test(base);
49487
49557
  if (looksLikeShot) {
49488
49558
  const dir = screenshotDir();
49489
49559
  if (dir) {
49490
49560
  let files = [];
49491
49561
  try {
49492
- files = fs3.readdirSync(dir);
49562
+ files = fs4.readdirSync(dir);
49493
49563
  } catch {
49494
49564
  }
49495
49565
  const norm = (s) => s.replace(/[    ⁠\s]+/g, " ").trim().toLowerCase();
49496
49566
  const want = norm(base);
49497
49567
  for (const f of files) if (norm(f) === want) {
49498
- hit = exists(np2.join(dir, f));
49568
+ hit = exists(np3.join(dir, f));
49499
49569
  if (hit) return hit;
49500
49570
  }
49501
49571
  const shots = files.filter((f) => /^Screenshot[\s  ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
49502
- const p = np2.join(dir, f);
49572
+ const p = np3.join(dir, f);
49503
49573
  try {
49504
- return { p, m: fs3.statSync(p).mtimeMs };
49574
+ return { p, m: fs4.statSync(p).mtimeMs };
49505
49575
  } catch {
49506
49576
  return { p, m: 0 };
49507
49577
  }
@@ -49661,8 +49731,8 @@ ${fp}`);
49661
49731
  `I can't find a file at ${path2} on this machine.`
49662
49732
  );
49663
49733
  }
49664
- const buf = fs3.readFileSync(abs);
49665
- name = np2.basename(abs);
49734
+ const buf = fs4.readFileSync(abs);
49735
+ name = np3.basename(abs);
49666
49736
  mime = mimeOf(name);
49667
49737
  url = `data:${mime};base64,${buf.toString("base64")}`;
49668
49738
  } else if (dataUrl && fileName) {
@@ -49722,8 +49792,8 @@ ${fp}`);
49722
49792
  `I can't find a file at ${path2} on this machine.`
49723
49793
  );
49724
49794
  }
49725
- const buf = fs3.readFileSync(abs);
49726
- name = np2.basename(abs);
49795
+ const buf = fs4.readFileSync(abs);
49796
+ name = np3.basename(abs);
49727
49797
  mime = mimeOf(name);
49728
49798
  url = `data:${mime};base64,${buf.toString("base64")}`;
49729
49799
  } else if (dataUrl && fileName) {
@@ -49755,7 +49825,7 @@ ${fp}`);
49755
49825
  let name, mime, url;
49756
49826
  if (path2) {
49757
49827
  if (!ctx.local) {
49758
- const fname = np2.basename(path2);
49828
+ const fname = np3.basename(path2);
49759
49829
  const fmime = mimeOf(fname);
49760
49830
  const hub = ctx.serverUrl.replace(/\/$/, "");
49761
49831
  const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
@@ -49780,8 +49850,8 @@ ${psCmd}
49780
49850
  if (!abs) {
49781
49851
  return fail(`I can't find a file at ${path2} on this machine.`);
49782
49852
  }
49783
- const buf = fs3.readFileSync(abs);
49784
- name = np2.basename(abs);
49853
+ const buf = fs4.readFileSync(abs);
49854
+ name = np3.basename(abs);
49785
49855
  mime = mimeOf(name);
49786
49856
  url = `data:${mime};base64,${buf.toString("base64")}`;
49787
49857
  } else if (dataUrl && fileName) {
@@ -49862,14 +49932,14 @@ ${psCmd}
49862
49932
  `Sending a folder reads files off disk, which this connector can't do \u2014 it runs in the cloud. Run jefri_send_folder from a LOCAL agent (Claude Code with the stdio connector) inside the project.`
49863
49933
  );
49864
49934
  const rawDir = path2 && path2.trim() ? path2 : process.cwd();
49865
- const dir = rawDir.startsWith("~") ? np2.join(os3.homedir(), rawDir.slice(1)) : rawDir;
49866
- if (!fs3.existsSync(dir))
49935
+ const dir = rawDir.startsWith("~") ? np3.join(os4.homedir(), rawDir.slice(1)) : rawDir;
49936
+ if (!fs4.existsSync(dir))
49867
49937
  return fail(
49868
49938
  `I can't see "${rawDir}" from here. Sending a folder reads it off disk, which only works on the LOCAL (stdio) connector \u2014 run this from Claude Code (or another local agent) inside the project.`
49869
49939
  );
49870
49940
  let stat;
49871
49941
  try {
49872
- stat = fs3.statSync(dir);
49942
+ stat = fs4.statSync(dir);
49873
49943
  } catch {
49874
49944
  return fail(`Cannot read "${rawDir}".`);
49875
49945
  }
@@ -49880,8 +49950,8 @@ ${psCmd}
49880
49950
  } catch {
49881
49951
  return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
49882
49952
  }
49883
- const base = np2.basename(np2.resolve(dir)) || "project";
49884
- const tmpZip = np2.join(os3.tmpdir(), `acp-${base}-${Date.now()}.zip`);
49953
+ const base = np3.basename(np3.resolve(dir)) || "project";
49954
+ const tmpZip = np3.join(os4.tmpdir(), `acp-${base}-${Date.now()}.zip`);
49885
49955
  let isRepo = false;
49886
49956
  try {
49887
49957
  cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
@@ -49903,16 +49973,16 @@ ${psCmd}
49903
49973
  }
49904
49974
  } catch (e) {
49905
49975
  try {
49906
- fs3.rmSync(tmpZip, { force: true });
49976
+ fs4.rmSync(tmpZip, { force: true });
49907
49977
  } catch {
49908
49978
  }
49909
49979
  return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
49910
49980
  }
49911
- if (!fs3.existsSync(tmpZip))
49981
+ if (!fs4.existsSync(tmpZip))
49912
49982
  return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
49913
- const size = fs3.statSync(tmpZip).size;
49983
+ const size = fs4.statSync(tmpZip).size;
49914
49984
  if (size > MAX_UPLOAD_BYTES) {
49915
- fs3.rmSync(tmpZip, { force: true });
49985
+ fs4.rmSync(tmpZip, { force: true });
49916
49986
  return fail(
49917
49987
  `That archive is ${MB(size)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit. Send a subfolder (e.g. path=\u2026/src) or remove large files, then try again.`
49918
49988
  );
@@ -49924,7 +49994,7 @@ ${psCmd}
49924
49994
  const res = await fetch(`${hub}/api/files?${q}`, {
49925
49995
  method: "POST",
49926
49996
  headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
49927
- body: fs3.readFileSync(tmpZip)
49997
+ body: fs4.readFileSync(tmpZip)
49928
49998
  });
49929
49999
  if (!res.ok) {
49930
50000
  const t = await res.text().catch(() => "");
@@ -49934,7 +50004,7 @@ ${psCmd}
49934
50004
  return fail(`Upload failed: ${e?.message ?? e}`);
49935
50005
  } finally {
49936
50006
  try {
49937
- fs3.rmSync(tmpZip, { force: true });
50007
+ fs4.rmSync(tmpZip, { force: true });
49938
50008
  } catch {
49939
50009
  }
49940
50010
  }
@@ -49969,12 +50039,12 @@ ${psCmd}
49969
50039
  const hub = ctx.serverUrl.replace(/\/$/, "");
49970
50040
  const out = savePath || match.fileName || "downloaded.file";
49971
50041
  if (ctx.local) {
49972
- const outPath = out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
50042
+ const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
49973
50043
  try {
49974
50044
  const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
49975
50045
  if (!r.ok) throw new Error(`download failed (${r.status})`);
49976
50046
  const buf = Buffer.from(await r.arrayBuffer());
49977
- fs3.writeFileSync(outPath, buf);
50047
+ fs4.writeFileSync(outPath, buf);
49978
50048
  return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
49979
50049
  } catch (e) {
49980
50050
  return fail(`Couldn't save the file: ${e?.message ?? e}`);
@@ -50075,7 +50145,7 @@ ${cmd}`);
50075
50145
  }
50076
50146
  },
50077
50147
  async ({ departmentId, path: path2, kind }) => withClient(async (c) => {
50078
- const fname = np2.basename(path2.startsWith("~") ? path2.slice(1) : path2);
50148
+ const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
50079
50149
  const mime = mimeOf(fname);
50080
50150
  const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
50081
50151
  const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
@@ -50159,7 +50229,7 @@ ${cmd}`);
50159
50229
  inputSchema: { path: external_exports.string().describe("local file path to upload") }
50160
50230
  },
50161
50231
  async ({ path: path2 }) => withClient(async (c) => {
50162
- const fname = np2.basename(path2.startsWith("~") ? path2.slice(1) : path2);
50232
+ const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
50163
50233
  const mime = mimeOf(fname);
50164
50234
  const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
50165
50235
  return ok(`To add ${fname} to your document memory, run in the terminal:
@@ -50311,7 +50381,7 @@ ${cmd}`);
50311
50381
  if (path2) {
50312
50382
  if (!ctx.local) {
50313
50383
  const hub = ctx.serverUrl.replace(/\/$/, "");
50314
- const fname = np2.basename(path2);
50384
+ const fname = np3.basename(path2);
50315
50385
  const fmime = mimeOf(fname);
50316
50386
  const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
50317
50387
  const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path2)}`;
@@ -50323,8 +50393,8 @@ ${cmd}`
50323
50393
  }
50324
50394
  const abs = resolveLocalFile(path2);
50325
50395
  if (!abs) return fail(`I can't find a file at ${path2} on this machine.`);
50326
- const buf = fs3.readFileSync(abs);
50327
- name = np2.basename(abs);
50396
+ const buf = fs4.readFileSync(abs);
50397
+ name = np3.basename(abs);
50328
50398
  mime = mimeOf(name);
50329
50399
  url = `data:${mime};base64,${buf.toString("base64")}`;
50330
50400
  } else if (dataUrl && fileName) {
@@ -50532,6 +50602,55 @@ ${cmd}`
50532
50602
  return ok(summary);
50533
50603
  })
50534
50604
  );
50605
+ server.registerTool(
50606
+ "jefri_autonomous",
50607
+ {
50608
+ title: "Autonomous mode",
50609
+ 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.",
50610
+ inputSchema: {
50611
+ enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
50612
+ brain: external_exports.string().optional().describe("'claude', 'codex', or a full custom command (e.g. for OpenClaw)"),
50613
+ workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
50614
+ replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
50615
+ persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
50616
+ 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."),
50617
+ ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER \u2014 ignore other people (great for a private agent team). Default off."),
50618
+ contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
50619
+ replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
50620
+ }
50621
+ },
50622
+ async (args) => withClient(async () => {
50623
+ const patch = {};
50624
+ if (typeof args.enabled === "boolean") patch.enabled = args.enabled;
50625
+ if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
50626
+ if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
50627
+ if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
50628
+ if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim();
50629
+ if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
50630
+ if (typeof args.persona === "string") patch.persona = args.persona;
50631
+ if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
50632
+ patch.promptTemplate = args.promptTemplate.trim().toLowerCase() === "default" ? DEFAULT_TEMPLATE : args.promptTemplate;
50633
+ if (args.replyMode) patch.replyMode = args.replyMode;
50634
+ const c = setAuto(patch);
50635
+ const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
50636
+ const lines = [
50637
+ `\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
50638
+ ` Brain: ${c.brain}`,
50639
+ ` Work folder: ${c.workdir}`,
50640
+ ` Replies to: ${modeLabel}`,
50641
+ ` Owner-only: ${c.ownerOnly ? "yes (only you direct me)" : "no (anyone who @mentions me)"}`,
50642
+ ` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
50643
+ ` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
50644
+ ` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
50645
+ ` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
50646
+ ` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
50647
+ ];
50648
+ if (c.enabled)
50649
+ lines.push(`
50650
+ \u26A0\uFE0F Incoming messages now drive "${c.brain}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
50651
+ return ok(lines.join("\n"));
50652
+ })
50653
+ );
50535
50654
  }
50536
50655
  }
50537
50656
  function attachInbox(c, inbox, self, onIncoming) {