jefrichat-mcp 0.49.18 → 0.49.21

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/index.js CHANGED
@@ -480,8 +480,8 @@ var init_parseUtil = __esm({
480
480
  init_errors();
481
481
  init_en();
482
482
  makeIssue = (params) => {
483
- const { data, path: path15, errorMaps, issueData } = params;
484
- const fullPath = [...path15, ...issueData.path || []];
483
+ const { data, path: path17, errorMaps, issueData } = params;
484
+ const fullPath = [...path17, ...issueData.path || []];
485
485
  const fullIssue = {
486
486
  ...issueData,
487
487
  path: fullPath
@@ -789,11 +789,11 @@ var init_types = __esm({
789
789
  init_parseUtil();
790
790
  init_util();
791
791
  ParseInputLazyPath = class {
792
- constructor(parent, value, path15, key) {
792
+ constructor(parent, value, path17, key) {
793
793
  this._cachedPath = [];
794
794
  this.parent = parent;
795
795
  this.data = value;
796
- this._path = path15;
796
+ this._path = path17;
797
797
  this._key = key;
798
798
  }
799
799
  get path() {
@@ -4373,10 +4373,10 @@ function assignProp(target, prop, value) {
4373
4373
  configurable: true
4374
4374
  });
4375
4375
  }
4376
- function getElementAtPath(obj, path15) {
4377
- if (!path15)
4376
+ function getElementAtPath(obj, path17) {
4377
+ if (!path17)
4378
4378
  return obj;
4379
- return path15.reduce((acc, key) => acc?.[key], obj);
4379
+ return path17.reduce((acc, key) => acc?.[key], obj);
4380
4380
  }
4381
4381
  function promiseAllObject(promisesObj) {
4382
4382
  const keys = Object.keys(promisesObj);
@@ -4625,11 +4625,11 @@ function aborted(x, startIndex = 0) {
4625
4625
  }
4626
4626
  return false;
4627
4627
  }
4628
- function prefixIssues(path15, issues) {
4628
+ function prefixIssues(path17, issues) {
4629
4629
  return issues.map((iss) => {
4630
4630
  var _a;
4631
4631
  (_a = iss).path ?? (_a.path = []);
4632
- iss.path.unshift(path15);
4632
+ iss.path.unshift(path17);
4633
4633
  return iss;
4634
4634
  });
4635
4635
  }
@@ -16457,8 +16457,8 @@ var require_utils = __commonJS({
16457
16457
  }
16458
16458
  return ind;
16459
16459
  }
16460
- function removeDotSegments(path15) {
16461
- let input2 = path15;
16460
+ function removeDotSegments(path17) {
16461
+ let input2 = path17;
16462
16462
  const output2 = [];
16463
16463
  let nextSlash = -1;
16464
16464
  let len = 0;
@@ -16710,8 +16710,8 @@ var require_schemes = __commonJS({
16710
16710
  wsComponent.secure = void 0;
16711
16711
  }
16712
16712
  if (wsComponent.resourceName) {
16713
- const [path15, query] = wsComponent.resourceName.split("?");
16714
- wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
16713
+ const [path17, query] = wsComponent.resourceName.split("?");
16714
+ wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
16715
16715
  wsComponent.query = query;
16716
16716
  wsComponent.resourceName = void 0;
16717
16717
  }
@@ -20104,12 +20104,12 @@ var require_dist = __commonJS({
20104
20104
  throw new Error(`Unknown format "${name}"`);
20105
20105
  return f;
20106
20106
  };
20107
- function addFormats(ajv, list3, fs33, exportName) {
20107
+ function addFormats(ajv, list3, fs35, exportName) {
20108
20108
  var _a;
20109
20109
  var _b;
20110
20110
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
20111
20111
  for (const f of list3)
20112
- ajv.addFormat(f, fs33[f]);
20112
+ ajv.addFormat(f, fs35[f]);
20113
20113
  }
20114
20114
  module.exports = exports = formatsPlugin;
20115
20115
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -22288,8 +22288,8 @@ function assertNonEmpty(part, name) {
22288
22288
  throw new Error("`" + name + "` cannot be empty");
22289
22289
  }
22290
22290
  }
22291
- function assertPath(path15, name) {
22292
- if (!path15) {
22291
+ function assertPath(path17, name) {
22292
+ if (!path17) {
22293
22293
  throw new Error("Setting `" + name + "` requires `path` to be set too");
22294
22294
  }
22295
22295
  }
@@ -22474,13 +22474,13 @@ var init_lib4 = __esm({
22474
22474
  * @returns {undefined}
22475
22475
  * Nothing.
22476
22476
  */
22477
- set path(path15) {
22478
- if (isUrl(path15)) {
22479
- path15 = fileURLToPath(path15);
22477
+ set path(path17) {
22478
+ if (isUrl(path17)) {
22479
+ path17 = fileURLToPath(path17);
22480
22480
  }
22481
- assertNonEmpty(path15, "path");
22482
- if (this.path !== path15) {
22483
- this.history.push(path15);
22481
+ assertNonEmpty(path17, "path");
22482
+ if (this.path !== path17) {
22483
+ this.history.push(path17);
22484
22484
  }
22485
22485
  }
22486
22486
  /**
@@ -31190,7 +31190,7 @@ function transformGfmAutolinkLiterals(tree) {
31190
31190
  { ignore: ["link", "linkReference"] }
31191
31191
  );
31192
31192
  }
31193
- function findUrl(_, protocol, domain2, path15, match) {
31193
+ function findUrl(_, protocol, domain2, path17, match) {
31194
31194
  let prefix = "";
31195
31195
  if (!previous2(match)) {
31196
31196
  return false;
@@ -31203,7 +31203,7 @@ function findUrl(_, protocol, domain2, path15, match) {
31203
31203
  if (!isCorrectDomain(domain2)) {
31204
31204
  return false;
31205
31205
  }
31206
- const parts = splitUrl(domain2 + path15);
31206
+ const parts = splitUrl(domain2 + path17);
31207
31207
  if (!parts[0]) return false;
31208
31208
  const result = {
31209
31209
  type: "link",
@@ -38608,6 +38608,19 @@ function connectErrorFromEvent(ev) {
38608
38608
  if (ev?.minVersion) err.minVersion = ev.minVersion;
38609
38609
  return err;
38610
38610
  }
38611
+ function terminalConnectionCode(ev) {
38612
+ if (ev.code) return [
38613
+ "auth",
38614
+ "auth_failed",
38615
+ "authentication_failed",
38616
+ "invalid_token",
38617
+ "ACP_AUTH_FAILED",
38618
+ "upgrade_required",
38619
+ "taken_over",
38620
+ "superseded"
38621
+ ].includes(ev.code) ? ev.code : null;
38622
+ return /^(?:authentication failed|invalid or expired (?:Jefri Chat )?token)(?:\b|$)/i.test(ev.message ?? "") ? "auth" : null;
38623
+ }
38611
38624
  var subtle, enc, dec, E2E_ALG, e2eDir, b64, fromB64, safeName, canonicalJwk, JefriAuthError, JefriClient, serveDir;
38612
38625
  var init_src2 = __esm({
38613
38626
  "../sdk/src/index.ts"() {
@@ -38634,6 +38647,17 @@ var init_src2 = __esm({
38634
38647
  };
38635
38648
  JefriClient = class _JefriClient {
38636
38649
  ws;
38650
+ recoveryEnabled = false;
38651
+ registeredSocket;
38652
+ retireRecoverySocket;
38653
+ recoveryRegisterTimer;
38654
+ get registeredRecoveryEnabled() {
38655
+ return this.recoveryEnabled;
38656
+ }
38657
+ get connectionStatus() {
38658
+ if (this.terminalCode || this.closing) return "stopped";
38659
+ return this.online ? "online" : this.hasConnected ? "reconnecting" : "connecting";
38660
+ }
38637
38661
  handlers = /* @__PURE__ */ new Map();
38638
38662
  /** Why the client stopped reconnecting: "auth" | "upgrade_required" | "taken_over". */
38639
38663
  terminalCode = null;
@@ -38661,6 +38685,14 @@ var init_src2 = __esm({
38661
38685
  * so pre-0.49 SDKs stop via their /auth/i match. */
38662
38686
  noteTerminalError(ev) {
38663
38687
  if (ev?.event !== "error") return;
38688
+ if (this.recoveryEnabled) {
38689
+ const code3 = terminalConnectionCode(ev);
38690
+ if (code3) {
38691
+ this.authFailed = true;
38692
+ this.terminalCode = code3;
38693
+ }
38694
+ return;
38695
+ }
38664
38696
  if (/auth/i.test(ev.message ?? "") || ev.code === "upgrade_required" || ev.code === "taken_over" || ev.code === "superseded") {
38665
38697
  this.authFailed = true;
38666
38698
  this.terminalCode = ev.code ?? "auth";
@@ -38756,6 +38788,8 @@ var init_src2 = __esm({
38756
38788
  * "offline" honestly instead of claiming an agent can be reached and then
38757
38789
  * failing on send. */
38758
38790
  get online() {
38791
+ if (this.recoveryEnabled)
38792
+ return !this.closing && !this.authFailed && !!this.ws && this.ws.readyState === wrapper_default.OPEN && this.registeredSocket === this.ws;
38759
38793
  return this.hasConnected && !this.closing && this.ws?.readyState === this.ws?.OPEN;
38760
38794
  }
38761
38795
  constructor(server2) {
@@ -38764,6 +38798,7 @@ var init_src2 = __esm({
38764
38798
  static async connect(opts = {}) {
38765
38799
  const server2 = (opts.server ?? "https://jefrichat.com").replace(/\/$/, "");
38766
38800
  const client = new _JefriClient(server2);
38801
+ client.recoveryEnabled = opts.registeredRecovery === true;
38767
38802
  let token = opts.token;
38768
38803
  if (!token) {
38769
38804
  const res = await fetch(`${server2}/api/agents`, {
@@ -38803,7 +38838,9 @@ var init_src2 = __esm({
38803
38838
  fn();
38804
38839
  };
38805
38840
  onReg = () => finish(resolve);
38806
- onErr = (ev) => finish(() => reject(connectErrorFromEvent(ev)));
38841
+ onErr = (ev) => finish(() => reject(
38842
+ client.recoveryEnabled && !ev?.code && !terminalConnectionCode(ev) ? new Error(ev?.message ?? "hub rejected the connection") : connectErrorFromEvent(ev)
38843
+ ));
38807
38844
  const regTimeoutMs = typeof process !== "undefined" && Number(process.env?.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
38808
38845
  timer = setTimeout(
38809
38846
  () => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
@@ -38830,11 +38867,47 @@ var init_src2 = __esm({
38830
38867
  return client;
38831
38868
  }
38832
38869
  openSocket() {
38870
+ if (this.recoveryEnabled) {
38871
+ this.retireRecoverySocket?.();
38872
+ this.registeredSocket = void 0;
38873
+ }
38833
38874
  const wsUrl = this.server.replace(/^http/, "ws") + `/ws`;
38834
38875
  this.ws = new wrapper_default(wsUrl, { headers: this.socketHeaders() });
38876
+ const ws = this.ws;
38877
+ const current = () => !this.recoveryEnabled || this.ws === ws && !this.closing && !this.authFailed;
38835
38878
  return new Promise((resolve, reject) => {
38836
38879
  let settled = false;
38880
+ let registrationTimer;
38881
+ const clearRegistrationTimer = () => {
38882
+ clearTimeout(registrationTimer);
38883
+ if (this.recoveryRegisterTimer === registrationTimer) this.recoveryRegisterTimer = void 0;
38884
+ registrationTimer = void 0;
38885
+ };
38886
+ if (this.recoveryEnabled) {
38887
+ const timeoutMs = Number(process.env.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
38888
+ registrationTimer = setTimeout(() => {
38889
+ if (!current() || this.registeredSocket === ws) return;
38890
+ if (!settled) {
38891
+ settled = true;
38892
+ reject(new Error("timed out waiting for the hub to register the connection"));
38893
+ }
38894
+ ws.terminate();
38895
+ }, timeoutMs);
38896
+ this.recoveryRegisterTimer = registrationTimer;
38897
+ this.retireRecoverySocket = () => {
38898
+ clearRegistrationTimer();
38899
+ if (!settled) {
38900
+ settled = true;
38901
+ reject(new Error("connection attempt cancelled"));
38902
+ }
38903
+ ws.removeAllListeners();
38904
+ ws.on("error", () => {
38905
+ });
38906
+ ws.terminate();
38907
+ };
38908
+ }
38837
38909
  this.ws.on("open", () => {
38910
+ if (!current()) return;
38838
38911
  this.startPing();
38839
38912
  this.emit("socket_open", {});
38840
38913
  try {
@@ -38850,17 +38923,28 @@ var init_src2 = __esm({
38850
38923
  }
38851
38924
  });
38852
38925
  this.ws.on("error", (e) => {
38926
+ if (this.recoveryEnabled && this.ws !== ws) return;
38853
38927
  if (!settled) {
38854
38928
  settled = true;
38855
38929
  reject(e);
38856
38930
  }
38857
38931
  });
38858
38932
  this.ws.on("close", () => {
38933
+ if (this.recoveryEnabled) {
38934
+ clearRegistrationTimer();
38935
+ if (this.ws !== ws) return;
38936
+ this.registeredSocket = void 0;
38937
+ if (!settled) {
38938
+ settled = true;
38939
+ reject(new Error("connection closed before opening"));
38940
+ }
38941
+ }
38859
38942
  this.stopPing();
38860
38943
  this.emit("socket_close", {});
38861
38944
  if (this.hasConnected && !this.closing && !this.authFailed) this.scheduleReconnect();
38862
38945
  });
38863
38946
  this.ws.on("message", (data) => {
38947
+ if (!current()) return;
38864
38948
  let ev;
38865
38949
  try {
38866
38950
  ev = JSON.parse(data.toString());
@@ -38868,6 +38952,10 @@ var init_src2 = __esm({
38868
38952
  return;
38869
38953
  }
38870
38954
  if (ev.event === "registered") {
38955
+ if (this.recoveryEnabled) {
38956
+ this.registeredSocket = ws;
38957
+ clearRegistrationTimer();
38958
+ }
38871
38959
  this.identity = ev.identity;
38872
38960
  this.generation = typeof ev.generation === "string" ? ev.generation : null;
38873
38961
  const rev = ev.identity?.statusRev;
@@ -38876,6 +38964,14 @@ var init_src2 = __esm({
38876
38964
  this.reconnectAttempts = 0;
38877
38965
  }
38878
38966
  this.noteTerminalError(ev);
38967
+ if (this.recoveryEnabled && this.authFailed) {
38968
+ clearRegistrationTimer();
38969
+ this.registeredSocket = void 0;
38970
+ this.stopPing();
38971
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
38972
+ this.reconnectTimer = void 0;
38973
+ ws.close();
38974
+ }
38879
38975
  this.notePresenceAck(ev);
38880
38976
  this.emit(ev.event, ev);
38881
38977
  this.emit("*", ev);
@@ -38891,14 +38987,17 @@ var init_src2 = __esm({
38891
38987
  // triggers the existing reconnect.
38892
38988
  startPing() {
38893
38989
  this.stopPing();
38990
+ const ws = this.ws;
38991
+ const current = () => !this.recoveryEnabled || this.ws === ws && !this.closing && !this.authFailed;
38894
38992
  this.isAlive = true;
38895
38993
  try {
38896
38994
  this.ws.on("pong", () => {
38897
- this.isAlive = true;
38995
+ if (current()) this.isAlive = true;
38898
38996
  });
38899
38997
  } catch {
38900
38998
  }
38901
38999
  this.pingTimer = setInterval(() => {
39000
+ if (!current()) return;
38902
39001
  if (this.ws.readyState !== this.ws.OPEN) return;
38903
39002
  if (!this.isAlive) {
38904
39003
  try {
@@ -38988,6 +39087,12 @@ var init_src2 = __esm({
38988
39087
  this.dispatch(event, payload, this.handlers.get(event) ?? []);
38989
39088
  }
38990
39089
  send(ev) {
39090
+ if (this.recoveryEnabled && !this.online) {
39091
+ if (this.terminalCode)
39092
+ throw new Error(`Jefri Chat connection stopped (${this.terminalCode}) \u2014 not reconnecting automatically; check this desktop session's connection`);
39093
+ if (this.closing) throw new Error("Jefri Chat connection closed \u2014 restart this desktop connector");
39094
+ throw new Error("Jefri Chat hub connection is not registered yet (reconnecting) \u2014 try again after registration");
39095
+ }
38991
39096
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
38992
39097
  throw new Error("not connected to the Jefri Chat hub (reconnecting) \u2014 try again in a moment");
38993
39098
  this.ws.send(JSON.stringify(ev));
@@ -39143,6 +39248,11 @@ var init_src2 = __esm({
39143
39248
  }
39144
39249
  close() {
39145
39250
  this.closing = true;
39251
+ if (this.recoveryEnabled) {
39252
+ this.registeredSocket = void 0;
39253
+ clearTimeout(this.recoveryRegisterTimer);
39254
+ this.recoveryRegisterTimer = void 0;
39255
+ }
39146
39256
  this.stopPing();
39147
39257
  if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
39148
39258
  this.reconnectTimer = void 0;
@@ -39511,6 +39621,61 @@ var init_stream_utils = __esm({
39511
39621
  }
39512
39622
  });
39513
39623
 
39624
+ // src/send-confirmed.ts
39625
+ import { randomUUID as randomUUID2 } from "node:crypto";
39626
+ function sendConfirmed(client, target, text5, opts = {}) {
39627
+ const clientId = opts.clientId ?? randomUUID2();
39628
+ const timeoutMs = opts.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
39629
+ return new Promise((resolve) => {
39630
+ let settled = false;
39631
+ const finish = (out) => {
39632
+ if (settled) return;
39633
+ settled = true;
39634
+ clearTimeout(timer);
39635
+ clearInterval(linkCheck);
39636
+ client.off("reply_ack", onAck);
39637
+ client.off("error", onErr);
39638
+ resolve(out);
39639
+ };
39640
+ const onAck = (ev) => {
39641
+ if (ev?.replyId !== clientId) return;
39642
+ finish({ outcome: "sent", hubMessageId: String(ev.messageId ?? "") });
39643
+ };
39644
+ const onErr = (ev) => {
39645
+ if (ev?.replyId !== clientId) return;
39646
+ finish({
39647
+ outcome: "rejected",
39648
+ reason: String(ev.message ?? "the hub refused the message"),
39649
+ ...typeof ev.code === "string" ? { code: ev.code } : {},
39650
+ ...typeof ev.status === "number" ? { status: ev.status } : {}
39651
+ });
39652
+ };
39653
+ const timer = setTimeout(
39654
+ () => finish({ outcome: "ambiguous", reason: `no answer from the hub within ${Math.round(timeoutMs / 1e3)}s \u2014 check history before retrying` }),
39655
+ timeoutMs
39656
+ );
39657
+ const linkCheck = setInterval(() => {
39658
+ if (client.online === false) finish({ outcome: "ambiguous", reason: "the hub connection dropped before it answered \u2014 check history before retrying" });
39659
+ }, 250);
39660
+ client.on("reply_ack", onAck);
39661
+ client.on("error", onErr);
39662
+ try {
39663
+ if (target.groupId) client.groupMessage(target.groupId, text5, { clientId });
39664
+ else if (target.to) client.message(target.to, text5, { clientId });
39665
+ else return finish({ outcome: "rejected", reason: "need `to` or `groupId`" });
39666
+ } catch (e) {
39667
+ finish({ outcome: "rejected", reason: e?.message ?? "send failed" });
39668
+ }
39669
+ });
39670
+ }
39671
+ var SEND_CONFIRM_TIMEOUT_MS;
39672
+ var init_send_confirmed = __esm({
39673
+ "src/send-confirmed.ts"() {
39674
+ "use strict";
39675
+ SEND_CONFIRM_TIMEOUT_MS = 8e3;
39676
+ }
39677
+ });
39678
+
39514
39679
  // src/hosts.ts
39515
39680
  function formatAgentStatus(agent, body) {
39516
39681
  const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
@@ -39693,7 +39858,7 @@ function serverHostLabel(server2, allServers) {
39693
39858
  } catch {
39694
39859
  return safeHandle(String(server2));
39695
39860
  }
39696
- const path15 = u.pathname.replace(/\/+$/, "");
39861
+ const path17 = u.pathname.replace(/\/+$/, "");
39697
39862
  let label = u.host;
39698
39863
  if (allServers) {
39699
39864
  const mine = normalizeServerUrl(String(server2));
@@ -39707,8 +39872,8 @@ function serverHostLabel(server2, allServers) {
39707
39872
  }
39708
39873
  const clash = (key) => others.some((x) => key(x) === key(u));
39709
39874
  if (clash((x) => x.host)) {
39710
- label = u.host + path15;
39711
- if (clash((x) => x.host + x.pathname.replace(/\/+$/, ""))) label = `${u.protocol}//${u.host}${path15}`;
39875
+ label = u.host + path17;
39876
+ if (clash((x) => x.host + x.pathname.replace(/\/+$/, ""))) label = `${u.protocol}//${u.host}${path17}`;
39712
39877
  }
39713
39878
  }
39714
39879
  return safeHandle(label);
@@ -40924,9 +41089,9 @@ var init_which = __esm({
40924
41089
  // src/run-store.ts
40925
41090
  import fs4 from "node:fs";
40926
41091
  import np4 from "node:path";
40927
- function writeAtomic(path15, text5) {
40928
- fs4.mkdirSync(np4.dirname(path15), { recursive: true });
40929
- const tmp = `${path15}.tmp.${process.pid}`;
41092
+ function writeAtomic(path17, text5) {
41093
+ fs4.mkdirSync(np4.dirname(path17), { recursive: true });
41094
+ const tmp = `${path17}.tmp.${process.pid}`;
40930
41095
  const fd = fs4.openSync(tmp, "w", 384);
40931
41096
  try {
40932
41097
  fs4.writeFileSync(fd, text5);
@@ -40957,10 +41122,10 @@ function writeAtomic(path15, text5) {
40957
41122
  throw e;
40958
41123
  }
40959
41124
  fs4.closeSync(fd);
40960
- fs4.renameSync(tmp, path15);
41125
+ fs4.renameSync(tmp, path17);
40961
41126
  let dfd = null;
40962
41127
  try {
40963
- dfd = fs4.openSync(np4.dirname(path15), "r");
41128
+ dfd = fs4.openSync(np4.dirname(path17), "r");
40964
41129
  fs4.fsyncSync(dfd);
40965
41130
  } catch (e) {
40966
41131
  if (e?.code === "EIO" || e?.code === "ENOSPC") throw e;
@@ -40973,20 +41138,20 @@ function writeAtomic(path15, text5) {
40973
41138
  }
40974
41139
  }
40975
41140
  }
40976
- function appendJournal(path15, rec) {
41141
+ function appendJournal(path17, rec) {
40977
41142
  try {
40978
- fs4.mkdirSync(np4.dirname(path15), { recursive: true });
40979
- fs4.appendFileSync(path15, JSON.stringify(rec) + "\n", { mode: 384 });
41143
+ fs4.mkdirSync(np4.dirname(path17), { recursive: true });
41144
+ fs4.appendFileSync(path17, JSON.stringify(rec) + "\n", { mode: 384 });
40980
41145
  return true;
40981
41146
  } catch {
40982
41147
  return false;
40983
41148
  }
40984
41149
  }
40985
- function readJournal(path15) {
41150
+ function readJournal(path17) {
40986
41151
  const state = { runs: /* @__PURE__ */ new Map(), replies: /* @__PURE__ */ new Map(), malformed: 0 };
40987
41152
  let text5 = "";
40988
41153
  try {
40989
- text5 = fs4.readFileSync(path15, "utf8");
41154
+ text5 = fs4.readFileSync(path17, "utf8");
40990
41155
  } catch (e) {
40991
41156
  if (e?.code === "ENOENT") return state;
40992
41157
  return { ...state, readError: String(e?.message ?? e) };
@@ -41053,10 +41218,10 @@ function readJournal(path15) {
41053
41218
  }
41054
41219
  return state;
41055
41220
  }
41056
- function readMarker(path15) {
41221
+ function readMarker(path17) {
41057
41222
  let text5;
41058
41223
  try {
41059
- text5 = fs4.readFileSync(path15, "utf8");
41224
+ text5 = fs4.readFileSync(path17, "utf8");
41060
41225
  } catch (e) {
41061
41226
  return e?.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable", error: String(e?.message ?? e) };
41062
41227
  }
@@ -41068,25 +41233,25 @@ function readMarker(path15) {
41068
41233
  return { kind: "unreadable", error: String(e?.message ?? e) };
41069
41234
  }
41070
41235
  }
41071
- function writeMarker(path15, marker) {
41072
- writeAtomic(path15, JSON.stringify(marker, null, 2));
41236
+ function writeMarker(path17, marker) {
41237
+ writeAtomic(path17, JSON.stringify(marker, null, 2));
41073
41238
  }
41074
- function removeMarker(path15, sessionId) {
41075
- const cur = readMarker(path15);
41239
+ function removeMarker(path17, sessionId) {
41240
+ const cur = readMarker(path17);
41076
41241
  if (cur.kind === "absent") return true;
41077
41242
  if (cur.kind === "unreadable") return false;
41078
41243
  if (cur.marker.sessionId !== sessionId) return false;
41079
41244
  try {
41080
- fs4.unlinkSync(path15);
41245
+ fs4.unlinkSync(path17);
41081
41246
  return true;
41082
41247
  } catch (e) {
41083
41248
  return e?.code === "ENOENT";
41084
41249
  }
41085
41250
  }
41086
- function readHeld(path15) {
41251
+ function readHeld(path17) {
41087
41252
  let text5;
41088
41253
  try {
41089
- text5 = fs4.readFileSync(path15, "utf8");
41254
+ text5 = fs4.readFileSync(path17, "utf8");
41090
41255
  } catch (e) {
41091
41256
  return e?.code === "ENOENT" ? { ok: true, held: { replyIds: {} } } : { ok: false, error: String(e?.message ?? e) };
41092
41257
  }
@@ -41098,11 +41263,11 @@ function readHeld(path15) {
41098
41263
  return { ok: false, error: String(e?.message ?? e) };
41099
41264
  }
41100
41265
  }
41101
- function writeHeld(path15, held) {
41102
- writeAtomic(path15, JSON.stringify(held, null, 2));
41266
+ function writeHeld(path17, held) {
41267
+ writeAtomic(path17, JSON.stringify(held, null, 2));
41103
41268
  }
41104
- function writeAutoConfigFile(path15, cfg2) {
41105
- writeAtomic(path15, JSON.stringify(cfg2, null, 2));
41269
+ function writeAutoConfigFile(path17, cfg2) {
41270
+ writeAtomic(path17, JSON.stringify(cfg2, null, 2));
41106
41271
  }
41107
41272
  function pidAlive(pid) {
41108
41273
  if (!pid) return false;
@@ -41293,34 +41458,34 @@ var init_responder_lock = __esm({
41293
41458
  // src/config-mutex.ts
41294
41459
  import fs6 from "node:fs";
41295
41460
  import np6 from "node:path";
41296
- function openMutex(path15) {
41297
- const cached2 = dbs.get(path15);
41461
+ function openMutex(path17) {
41462
+ const cached2 = dbs.get(path17);
41298
41463
  if (cached2) return cached2;
41299
41464
  const mod = loadSqliteModule();
41300
- if (!mod) throw new ConfigMutexError(path15, new Error(sqliteAvailability().ok ? "node:sqlite unavailable" : sqliteAvailability().reason));
41465
+ if (!mod) throw new ConfigMutexError(path17, new Error(sqliteAvailability().ok ? "node:sqlite unavailable" : sqliteAvailability().reason));
41301
41466
  try {
41302
- fs6.mkdirSync(np6.dirname(path15), { recursive: true });
41303
- const db = new mod.DatabaseSync(`${path15}.mutex.db`);
41467
+ fs6.mkdirSync(np6.dirname(path17), { recursive: true });
41468
+ const db = new mod.DatabaseSync(`${path17}.mutex.db`);
41304
41469
  db.exec("PRAGMA busy_timeout = 5000");
41305
41470
  db.exec("CREATE TABLE IF NOT EXISTS mutex (id INTEGER PRIMARY KEY CHECK (id = 1), holder INTEGER NOT NULL, at INTEGER NOT NULL)");
41306
- dbs.set(path15, db);
41471
+ dbs.set(path17, db);
41307
41472
  return db;
41308
41473
  } catch (e) {
41309
- throw new ConfigMutexError(path15, e);
41474
+ throw new ConfigMutexError(path17, e);
41310
41475
  }
41311
41476
  }
41312
- function withConfigMutex(path15, fn) {
41477
+ function withConfigMutex(path17, fn) {
41313
41478
  if (depth > 0) return fn();
41314
- const db = openMutex(path15);
41479
+ const db = openMutex(path17);
41315
41480
  try {
41316
41481
  db.exec("BEGIN IMMEDIATE");
41317
41482
  } catch (e) {
41318
- dbs.delete(path15);
41483
+ dbs.delete(path17);
41319
41484
  try {
41320
41485
  db.close();
41321
41486
  } catch {
41322
41487
  }
41323
- throw new ConfigMutexError(path15, e);
41488
+ throw new ConfigMutexError(path17, e);
41324
41489
  }
41325
41490
  depth++;
41326
41491
  try {
@@ -41345,8 +41510,8 @@ var init_config_mutex = __esm({
41345
41510
  init_responder_lock();
41346
41511
  dbs = /* @__PURE__ */ new Map();
41347
41512
  ConfigMutexError = class extends Error {
41348
- constructor(path15, cause) {
41349
- super(`the config write mutex ${path15}.mutex.db cannot be taken (${cause?.message ?? cause}) \u2014 the write was NOT performed, it would not have been serialized`);
41513
+ constructor(path17, cause) {
41514
+ super(`the config write mutex ${path17}.mutex.db cannot be taken (${cause?.message ?? cause}) \u2014 the write was NOT performed, it would not have been serialized`);
41350
41515
  this.name = "ConfigMutexError";
41351
41516
  }
41352
41517
  };
@@ -41355,7 +41520,7 @@ var init_config_mutex = __esm({
41355
41520
  });
41356
41521
 
41357
41522
  // src/run-reporter.ts
41358
- import { randomUUID as randomUUID2 } from "node:crypto";
41523
+ import { randomUUID as randomUUID3 } from "node:crypto";
41359
41524
  import np7 from "node:path";
41360
41525
  var RUN_HEARTBEAT_MS, safeUser, RunReporter;
41361
41526
  var init_run_reporter = __esm({
@@ -41374,7 +41539,7 @@ var init_run_reporter = __esm({
41374
41539
  this.replyDeadlineMs = deps.replyDeadlineMs ?? REPLY_DEADLINE_MS;
41375
41540
  }
41376
41541
  deps;
41377
- sessionId = randomUUID2();
41542
+ sessionId = randomUUID3();
41378
41543
  runs = /* @__PURE__ */ new Map();
41379
41544
  replies = /* @__PURE__ */ new Map();
41380
41545
  /** Unacknowledged run frames, keyed `${runId}:${seq}`, in send order. */
@@ -41535,7 +41700,7 @@ var init_run_reporter = __esm({
41535
41700
  /** Mint the id and journal the run BEFORE the caller spawns anything. */
41536
41701
  startRun(meta) {
41537
41702
  if (!this.started) throw new Error("run reporter not started \u2014 the session marker must be established before any autonomous work");
41538
- const runId = randomUUID2();
41703
+ const runId = randomUUID3();
41539
41704
  const startedAt = this.now();
41540
41705
  const st = { runId, seq: 0, meta, startedAt, epoch: this.deps.workEpoch(), durable: true, ended: false };
41541
41706
  const seq = this.nextSeq(st);
@@ -41621,7 +41786,7 @@ var init_run_reporter = __esm({
41621
41786
  * finalization verdict, or the cancel path. `onSettled` fires exactly once. */
41622
41787
  queueReply(runId, target, text5, onSettled) {
41623
41788
  if (!this.started) throw new Error("run reporter not started");
41624
- const replyId = randomUUID2();
41789
+ const replyId = randomUUID3();
41625
41790
  const createdAt = this.now();
41626
41791
  const epoch = this.deps.workEpoch();
41627
41792
  const groupGen = target.groupId ? this.deps.groupGenOf?.(target.groupId) : void 0;
@@ -44064,9 +44229,9 @@ var init_autonomous = __esm({
44064
44229
  persistFailureForTest = null;
44065
44230
  cfg = { ...DEFAULTS2 };
44066
44231
  AutoConfigWriteError = class extends Error {
44067
- constructor(path15, cause, whileDisabling) {
44068
- super(whileDisabling ? `durable cancellation failed: could not write ${path15} (${cause?.message ?? cause}). Autonomy is OFF in this process and its work was stopped, but the setting could not be saved \u2014 the next start may still read the old config.` : `could not save the autonomy config to ${path15} (${cause?.message ?? cause})`);
44069
- this.path = path15;
44232
+ constructor(path17, cause, whileDisabling) {
44233
+ super(whileDisabling ? `durable cancellation failed: could not write ${path17} (${cause?.message ?? cause}). Autonomy is OFF in this process and its work was stopped, but the setting could not be saved \u2014 the next start may still read the old config.` : `could not save the autonomy config to ${path17} (${cause?.message ?? cause})`);
44234
+ this.path = path17;
44070
44235
  this.cause = cause;
44071
44236
  this.whileDisabling = whileDisabling;
44072
44237
  this.name = "AutoConfigWriteError";
@@ -44535,8 +44700,8 @@ async function mintFileLink(hub, token, fileId) {
44535
44700
  clearTimeout(killer);
44536
44701
  }
44537
44702
  }
44538
- async function remoteUploadCommand(c2, ctx, target, path15, caption, where, signal) {
44539
- const fname = crossBasename(path15);
44703
+ async function remoteUploadCommand(c2, ctx, target, path17, caption, where, signal) {
44704
+ const fname = crossBasename(path17);
44540
44705
  const fmime = mimeOf(fname);
44541
44706
  const hub = ctx.serverUrl.replace(/\/$/, "");
44542
44707
  try {
@@ -44595,10 +44760,10 @@ ${uploadUrl}
44595
44760
 
44596
44761
  \u{1F4BB} Or run this ONE command on a machine that HAS the file and has internet (the user's terminal \u2014 NOT a sandboxed assistant environment without egress):
44597
44762
 
44598
- curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path15)}
44763
+ curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path17)}
44599
44764
 
44600
44765
  Windows PowerShell instead:
44601
- Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(path15)}
44766
+ Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(path17)}
44602
44767
 
44603
44768
  The link is single-use and expires in ${mins} min; the file name and caption are already attached to it. Run it now, then confirm delivery from its JSON response ({"ok":true,...}).
44604
44769
  \u26A0\uFE0F If YOU are a sandboxed assistant with no network egress and the file lives in YOUR sandbox: this command cannot run anywhere that can see the file. Instead \u2014 if the file is 256KB or less, call this tool again with dataUrl (base64 it) + fileName; if larger, PRESENT the file to the user for download and tell them to open the LINK ABOVE and pick the downloaded file. Do NOT paste this curl into your own shell.
@@ -44610,7 +44775,7 @@ Never base64 anything over 256KB through dataUrl \u2014 that is minutes of model
44610
44775
  const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
44611
44776
  const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
44612
44777
  const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
44613
- const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path15)}`;
44778
+ const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path17)}`;
44614
44779
  return {
44615
44780
  fail: `This is the cloud connector, so it can't read "${fname}" from that path, and the upload-link service didn't answer. Options:
44616
44781
 
@@ -44881,12 +45046,16 @@ async function uploadFileHttp(c2, serverUrl, target, fileName, mime, bytes, capt
44881
45046
  if (caption) params.set("caption", caption);
44882
45047
  const headers = { Authorization: `Bearer ${c2.token}`, "Content-Type": mime };
44883
45048
  if (idem) headers["idempotency-key"] = idem;
45049
+ const desktop = !isStateless(c2) && c2.registeredRecoveryEnabled;
44884
45050
  let res;
44885
45051
  try {
44886
45052
  res = await fetch(`${hub}/api/files?${params.toString()}`, {
44887
45053
  method: "POST",
44888
45054
  headers,
44889
- body: Buffer.from(bytes)
45055
+ body: Buffer.from(bytes),
45056
+ // Same payload-scaled allowance as the SDK upload path. This covers the
45057
+ // response body too; a silent hub must not hang this Desktop tool forever.
45058
+ ...desktop ? { signal: AbortSignal.timeout(Math.ceil(3e4 + bytes.length / 20)) } : {}
44890
45059
  });
44891
45060
  } catch (e) {
44892
45061
  const err = new Error(`upload failed: ${e?.message ?? e}`);
@@ -44899,6 +45068,10 @@ async function uploadFileHttp(c2, serverUrl, target, fileName, mime, bytes, capt
44899
45068
  if (res.status >= 500) err.jefriAmbiguous = true;
44900
45069
  throw err;
44901
45070
  }
45071
+ if (desktop) {
45072
+ const receipt = await res.json().catch(() => null);
45073
+ if (receipt?.ok !== true) throw new Error("upload response did not confirm delivery");
45074
+ }
44902
45075
  }
44903
45076
  function defaultCaps(local) {
44904
45077
  return local ? [CAP.LOCAL_FS, CAP.PERSISTENT] : [CAP.REMOTE_SANDBOX, CAP.AUTONOMY, CAP.SEARCH];
@@ -45262,9 +45435,12 @@ ${e?.message ?? e}`);
45262
45435
  async () => {
45263
45436
  const unbound = await ctx.identityStatus?.();
45264
45437
  if (unbound) return ok3(unbound);
45265
- return withClient(
45266
- async (c2) => ok3(`You are "${c2.identity.displayName}" (@${safeHandle(c2.identity.username)}) on Jefri Chat at ${ctx.serverUrl}, status: online.`)
45267
- );
45438
+ return withClient(async (c2) => {
45439
+ const desktop = !isStateless(c2) && c2.registeredRecoveryEnabled;
45440
+ const status = desktop ? c2.connectionStatus : "online";
45441
+ const reason = desktop && c2.terminalCode ? ` Connection stopped: ${c2.terminalCode}; automatic reconnection is disabled.` : "";
45442
+ return ok3(`You are "${c2.identity.displayName}" (@${safeHandle(c2.identity.username)}) on Jefri Chat at ${ctx.serverUrl}, status: ${status}.${reason}`);
45443
+ });
45268
45444
  }
45269
45445
  );
45270
45446
  server2.registerTool(
@@ -45303,8 +45479,8 @@ ${e?.message ?? e}`);
45303
45479
  })
45304
45480
  );
45305
45481
  const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
45306
- const ownerApi = async (c2, path15, method) => {
45307
- const res = await fetch(`${ctx.serverUrl}${path15}`, {
45482
+ const ownerApi = async (c2, path17, method) => {
45483
+ const res = await fetch(`${ctx.serverUrl}${path17}`, {
45308
45484
  method,
45309
45485
  headers: { Authorization: `Bearer ${c2.token}` }
45310
45486
  });
@@ -45407,6 +45583,12 @@ Use jefri_agent_apps with agent=<name> to see its live app + active state.`,
45407
45583
  return failAmbiguous(`Message send failed: ${e?.message ?? e}.`);
45408
45584
  }
45409
45585
  }
45586
+ if (c2.registeredRecoveryEnabled) {
45587
+ const sent = await sendConfirmed(c2, { to }, text5);
45588
+ if (sent.outcome === "rejected") return fail(`Message rejected: ${sent.reason}`);
45589
+ if (sent.outcome === "ambiguous") return failAmbiguous(sent.reason);
45590
+ return ok3(`Sent to @${safeHandle(to)}: "${text5}"`);
45591
+ }
45410
45592
  const r = await confirmWrite(c2, () => c2.message(to, text5));
45411
45593
  if (r.error) return fail(`Message rejected: ${r.error}`);
45412
45594
  return ok3(`Sent to @${safeHandle(to)}: "${text5}"`);
@@ -45457,18 +45639,18 @@ ${fp}`);
45457
45639
  caption: external_exports.string().optional().describe("optional private E2E caption sent as a separate encrypted message")
45458
45640
  }
45459
45641
  },
45460
- async ({ to, path: path15, dataUrl, fileName, caption }) => withClient(async (c2) => {
45642
+ async ({ to, path: path17, dataUrl, fileName, caption }) => withClient(async (c2) => {
45461
45643
  let name, mime, url;
45462
- if (path15) {
45644
+ if (path17) {
45463
45645
  if (!ctx.local) {
45464
45646
  return fail(
45465
45647
  `I can't read local files from the cloud connector. Use a local/stdin Jefri Chat connector, or open the web chat and drop the file there.`
45466
45648
  );
45467
45649
  }
45468
- const abs = resolveLocalFile(resolveUserPath(path15));
45650
+ const abs = resolveLocalFile(resolveUserPath(path17));
45469
45651
  if (!abs) {
45470
45652
  return fail(
45471
- `I can't find a file at ${path15} on this machine.`
45653
+ `I can't find a file at ${path17} on this machine.`
45472
45654
  );
45473
45655
  }
45474
45656
  const buf = fs8.readFileSync(abs);
@@ -45520,18 +45702,18 @@ ${fp}`);
45520
45702
  caption: external_exports.string().optional().describe("optional private E2E group caption sent separately")
45521
45703
  }
45522
45704
  },
45523
- async ({ groupId, path: path15, dataUrl, fileName, caption }) => withClient(async (c2) => {
45705
+ async ({ groupId, path: path17, dataUrl, fileName, caption }) => withClient(async (c2) => {
45524
45706
  let name, mime, url;
45525
- if (path15) {
45707
+ if (path17) {
45526
45708
  if (!ctx.local) {
45527
45709
  return fail(
45528
45710
  `I can't read local files from the cloud connector. Use a local/stdin Jefri Chat connector, or open the web chat and drop the file there.`
45529
45711
  );
45530
45712
  }
45531
- const abs = resolveLocalFile(resolveUserPath(path15));
45713
+ const abs = resolveLocalFile(resolveUserPath(path17));
45532
45714
  if (!abs) {
45533
45715
  return fail(
45534
- `I can't find a file at ${path15} on this machine.`
45716
+ `I can't find a file at ${path17} on this machine.`
45535
45717
  );
45536
45718
  }
45537
45719
  const buf = fs8.readFileSync(abs);
@@ -45734,17 +45916,17 @@ ${lines.join("\n")}`
45734
45916
  fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
45735
45917
  }
45736
45918
  },
45737
- async ({ to, path: path15, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
45919
+ async ({ to, path: path17, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
45738
45920
  let name, mime, url;
45739
45921
  let inlineBytes = null;
45740
- if (path15) {
45922
+ if (path17) {
45741
45923
  if (!ctx.local) {
45742
- const r = await remoteUploadCommand(c2, ctx, { to }, path15, caption, `@${safeHandle(to)}`, extra?.signal);
45924
+ const r = await remoteUploadCommand(c2, ctx, { to }, path17, caption, `@${safeHandle(to)}`, extra?.signal);
45743
45925
  return "ok" in r ? ok3(r.ok) : fail(r.fail);
45744
45926
  }
45745
- const abs = resolveLocalFile(resolveUserPath(path15));
45927
+ const abs = resolveLocalFile(resolveUserPath(path17));
45746
45928
  if (!abs) {
45747
- return fail(`I can't find a file at ${path15} on this machine.`);
45929
+ return fail(`I can't find a file at ${path17} on this machine.`);
45748
45930
  }
45749
45931
  const buf = fs8.readFileSync(abs);
45750
45932
  name = np9.basename(abs);
@@ -45778,7 +45960,7 @@ ${lines.join("\n")}`
45778
45960
  name,
45779
45961
  mime,
45780
45962
  rawBytes,
45781
- retryKey(extra, fileSendIdemParts(to, { path: path15, fileUrl, fileName }, name))
45963
+ retryKey(extra, fileSendIdemParts(to, { path: path17, fileUrl, fileName }, name))
45782
45964
  );
45783
45965
  if (res?.deduplicated)
45784
45966
  return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry (round 28 #2: suppression is SAID, never a false "Sent"). If you meant a new copy, send it again.`);
@@ -45787,8 +45969,8 @@ ${lines.join("\n")}`
45787
45969
  }
45788
45970
  return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to @${safeHandle(to)}${caption ? ` \u2014 "${caption}"` : ""}`);
45789
45971
  }
45790
- const fileIdem = retryKey(extra, fileSendIdemParts(to, { path: path15, fileUrl, fileName }, name));
45791
- if (rawBytes.length > WS_SAFE_FILE_BYTES || fileIdem) {
45972
+ const fileIdem = retryKey(extra, fileSendIdemParts(to, { path: path17, fileUrl, fileName }, name));
45973
+ if (c2.registeredRecoveryEnabled || rawBytes.length > WS_SAFE_FILE_BYTES || fileIdem) {
45792
45974
  try {
45793
45975
  await uploadFileHttp(c2, ctx.serverUrl, { to }, name, mime, rawBytes, caption, fileIdem);
45794
45976
  } catch (e2) {
@@ -45839,7 +46021,7 @@ ${lines.join("\n")}`
45839
46021
  return ok3(`Sent \u{1F4C4} ${fileName} (${MB(contentBytes.length)} MB) to @${safeHandle(to)}${caption ? ` \u2014 "${caption}"` : ""}`);
45840
46022
  }
45841
46023
  const textIdem = retryKey(extra, ["file", to, fileName, createHash2("sha256").update(contentBytes).digest("hex")]);
45842
- if (contentBytes.length > WS_SAFE_FILE_BYTES || textIdem) {
46024
+ if (c2.registeredRecoveryEnabled || contentBytes.length > WS_SAFE_FILE_BYTES || textIdem) {
45843
46025
  try {
45844
46026
  await uploadFileHttp(c2, ctx.serverUrl, { to }, fileName, mime, contentBytes, caption, textIdem);
45845
46027
  } catch (e2) {
@@ -45867,12 +46049,12 @@ ${lines.join("\n")}`
45867
46049
  caption: external_exports.string().optional().describe("optional text caption to send with it")
45868
46050
  }
45869
46051
  },
45870
- async ({ to, path: path15, caption }, extra) => withClient(async (c2) => {
46052
+ async ({ to, path: path17, caption }, extra) => withClient(async (c2) => {
45871
46053
  if (!ctx.local)
45872
46054
  return fail(
45873
46055
  `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.`
45874
46056
  );
45875
- const rawDir = path15 && path15.trim() ? path15 : ctx.cwd ?? process.cwd();
46057
+ const rawDir = path17 && path17.trim() ? path17 : ctx.cwd ?? process.cwd();
45876
46058
  const dir = resolveUserPath(rawDir);
45877
46059
  if (!fs8.existsSync(dir))
45878
46060
  return fail(
@@ -46245,11 +46427,11 @@ ${cmd}
46245
46427
  kind: external_exports.enum(["file", "contract", "skill", "note"]).optional()
46246
46428
  }
46247
46429
  },
46248
- async ({ departmentId, path: path15, kind }) => withClient(async (c2) => {
46249
- const fname = np9.basename(path15.startsWith("~") ? path15.slice(1) : path15);
46430
+ async ({ departmentId, path: path17, kind }) => withClient(async (c2) => {
46431
+ const fname = np9.basename(path17.startsWith("~") ? path17.slice(1) : path17);
46250
46432
  const mime = mimeOf(fname);
46251
46433
  const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
46252
- const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path15)}`;
46434
+ const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path17)}`;
46253
46435
  return ok3(`To add ${fname} to the department, run in the terminal:
46254
46436
 
46255
46437
  ${cmd}
@@ -46331,10 +46513,10 @@ ${cmd}
46331
46513
  description: "Add a local file to this agent's document memory. Memory is CURATED BY THE OWNER, so this returns a command that uses the OWNER token (the agent token can't add docs) \u2014 or the owner can upload it from the agent's page in the web app. Documents follow the agent's visibility (public \u2192 owner's connections can read; shared \u2192 chosen people; private \u2192 owner only). Warn before uploading anything sensitive to a public agent.",
46332
46514
  inputSchema: { path: external_exports.string().describe("local file path to upload") }
46333
46515
  },
46334
- async ({ path: path15 }) => withClient(async (c2) => {
46335
- const fname = np9.basename(path15.startsWith("~") ? path15.slice(1) : path15);
46516
+ async ({ path: path17 }) => withClient(async (c2) => {
46517
+ const fname = np9.basename(path17.startsWith("~") ? path17.slice(1) : path17);
46336
46518
  const mime = mimeOf(fname);
46337
- const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c2.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer $JEFRI_OWNER_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path15)}`;
46519
+ const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c2.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer $JEFRI_OWNER_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path17)}`;
46338
46520
  return ok3(
46339
46521
  `Adding to agent memory is OWNER-only, so this uses your owner token. Set it first (export JEFRI_OWNER_TOKEN=\u2026 from the app), then run:
46340
46522
 
@@ -46538,6 +46720,12 @@ Or just upload ${fname} from the agent's page in the web app.`
46538
46720
  return failAmbiguous(`Group post failed: ${e?.message ?? e}.`);
46539
46721
  }
46540
46722
  }
46723
+ if (c2.registeredRecoveryEnabled) {
46724
+ const sent = await sendConfirmed(c2, { groupId }, text5);
46725
+ if (sent.outcome === "rejected") return fail(`Couldn't post to the group: ${sent.reason}`);
46726
+ if (sent.outcome === "ambiguous") return failAmbiguous(sent.reason);
46727
+ return ok3("Posted to the group.");
46728
+ }
46541
46729
  const r = await confirmWrite(c2, () => c2.groupMessage(groupId, text5));
46542
46730
  if (r.error) return fail(`Couldn't post to the group: ${r.error}`);
46543
46731
  return ok3(`Posted to the group.`);
@@ -46557,16 +46745,16 @@ Or just upload ${fname} from the agent's page in the web app.`
46557
46745
  fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
46558
46746
  }
46559
46747
  },
46560
- async ({ groupId, path: path15, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
46748
+ async ({ groupId, path: path17, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
46561
46749
  let name, mime, url;
46562
46750
  let inlineBytes = null;
46563
- if (path15) {
46751
+ if (path17) {
46564
46752
  if (!ctx.local) {
46565
- const r = await remoteUploadCommand(c2, ctx, { groupId }, path15, caption, "the group", extra?.signal);
46753
+ const r = await remoteUploadCommand(c2, ctx, { groupId }, path17, caption, "the group", extra?.signal);
46566
46754
  return "ok" in r ? ok3(r.ok) : fail(r.fail);
46567
46755
  }
46568
- const abs = resolveLocalFile(resolveUserPath(path15));
46569
- if (!abs) return fail(`I can't find a file at ${path15} on this machine.`);
46756
+ const abs = resolveLocalFile(resolveUserPath(path17));
46757
+ if (!abs) return fail(`I can't find a file at ${path17} on this machine.`);
46570
46758
  const buf = fs8.readFileSync(abs);
46571
46759
  name = np9.basename(abs);
46572
46760
  mime = mimeOf(name);
@@ -46599,7 +46787,7 @@ Or just upload ${fname} from the agent's page in the web app.`
46599
46787
  name,
46600
46788
  mime,
46601
46789
  rawBytes,
46602
- retryKey(extra, fileSendIdemParts(groupId, { path: path15, fileUrl, fileName }, name))
46790
+ retryKey(extra, fileSendIdemParts(groupId, { path: path17, fileUrl, fileName }, name))
46603
46791
  );
46604
46792
  if (res?.deduplicated)
46605
46793
  return ok3(`Already delivered \u2014 ${name} matched a recent identical upload and was suppressed as a retry. If you meant a new copy, send it again.`);
@@ -46608,8 +46796,8 @@ Or just upload ${fname} from the agent's page in the web app.`
46608
46796
  }
46609
46797
  return ok3(`Sent \u{1F4CE} ${name} (${MB(rawBytes.length)} MB) to the group.`);
46610
46798
  }
46611
- const fileIdem = retryKey(extra, fileSendIdemParts(groupId, { path: path15, fileUrl, fileName }, name));
46612
- if (rawBytes.length > WS_SAFE_FILE_BYTES || fileIdem) {
46799
+ const fileIdem = retryKey(extra, fileSendIdemParts(groupId, { path: path17, fileUrl, fileName }, name));
46800
+ if (c2.registeredRecoveryEnabled || rawBytes.length > WS_SAFE_FILE_BYTES || fileIdem) {
46613
46801
  try {
46614
46802
  await uploadFileHttp(c2, ctx.serverUrl, { groupId }, name, mime, rawBytes, caption, fileIdem);
46615
46803
  } catch (e2) {
@@ -47356,6 +47544,7 @@ var init_tools = __esm({
47356
47544
  "use strict";
47357
47545
  init_zod();
47358
47546
  init_stream_utils();
47547
+ init_send_confirmed();
47359
47548
  init_hosts();
47360
47549
  init_message_history();
47361
47550
  init_src();
@@ -47586,7 +47775,7 @@ ${notice}` : text5 },
47586
47775
  });
47587
47776
 
47588
47777
  // src/runtime-identity.ts
47589
- import { randomBytes as randomBytes3, randomUUID as randomUUID3 } from "node:crypto";
47778
+ import { randomBytes as randomBytes3, randomUUID as randomUUID4 } from "node:crypto";
47590
47779
  function runtimeAnnouncement() {
47591
47780
  return { ...RUNTIME };
47592
47781
  }
@@ -47595,7 +47784,7 @@ var init_runtime_identity = __esm({
47595
47784
  "src/runtime-identity.ts"() {
47596
47785
  "use strict";
47597
47786
  RUNTIME = Object.freeze({
47598
- id: randomUUID3(),
47787
+ id: randomUUID4(),
47599
47788
  proof: randomBytes3(32).toString("hex"),
47600
47789
  pid: process.pid,
47601
47790
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -47995,10 +48184,10 @@ function ensureDirs() {
47995
48184
  }
47996
48185
  }
47997
48186
  function publishEndpoint(d, deps = {}) {
47998
- const writeFile = deps.writeFileSync ?? ((path15, data, opts) => fs10.writeFileSync(path15, data, opts));
48187
+ const writeFile = deps.writeFileSync ?? ((path17, data, opts) => fs10.writeFileSync(path17, data, opts));
47999
48188
  const rename = deps.renameSync ?? ((from, to) => fs10.renameSync(from, to));
48000
- const unlink = deps.unlinkSync ?? ((path15) => fs10.unlinkSync(path15));
48001
- const readFile = deps.readFileSync ?? ((path15) => fs10.readFileSync(path15, "utf8"));
48189
+ const unlink = deps.unlinkSync ?? ((path17) => fs10.unlinkSync(path17));
48190
+ const readFile = deps.readFileSync ?? ((path17) => fs10.readFileSync(path17, "utf8"));
48002
48191
  ensureDirs();
48003
48192
  if (!d.procToken) d = { ...d, procToken: procToken(d.pid) };
48004
48193
  if (!d.nonce) d = { ...d, nonce: INSTANCE_NONCE };
@@ -49346,10 +49535,10 @@ data: ${JSON.stringify(data)}
49346
49535
  if (!isLoopback(req.socket.remoteAddress)) return send(res, 403, "forbidden");
49347
49536
  if (!hostOk(req)) return send(res, 421, "bad host");
49348
49537
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
49349
- const path15 = url.pathname;
49538
+ const path17 = url.pathname;
49350
49539
  const method = req.method ?? "GET";
49351
- if (method === "GET" && path15.startsWith("/launch/")) {
49352
- const nonce = path15.slice("/launch/".length);
49540
+ if (method === "GET" && path17.startsWith("/launch/")) {
49541
+ const nonce = path17.slice("/launch/".length);
49353
49542
  if (!launchNonce || !safeEqual(nonce, launchNonce)) return send(res, 404, "not found");
49354
49543
  launchNonce = null;
49355
49544
  sessionExpiry = Date.now() + ttlMs;
@@ -49363,15 +49552,15 @@ data: ${JSON.stringify(data)}
49363
49552
  return res.end();
49364
49553
  }
49365
49554
  if (!authed(req)) return send(res, 401, "unauthorized");
49366
- if (method === "GET" && path15 === "/") return send(res, 200, pageHtml2, "text/html; charset=utf-8");
49367
- if (method === "GET" && path15 === "/app.js") return send(res, 200, appJs, "application/javascript; charset=utf-8");
49368
- if (method === "GET" && path15 === "/app.css") return send(res, 200, appCss, "text/css; charset=utf-8");
49369
- if (method === "GET" && path15 === "/api/inbox") {
49555
+ if (method === "GET" && path17 === "/") return send(res, 200, pageHtml2, "text/html; charset=utf-8");
49556
+ if (method === "GET" && path17 === "/app.js") return send(res, 200, appJs, "application/javascript; charset=utf-8");
49557
+ if (method === "GET" && path17 === "/app.css") return send(res, 200, appCss, "text/css; charset=utf-8");
49558
+ if (method === "GET" && path17 === "/api/inbox") {
49370
49559
  const selected = opts.selectKey && records.has(opts.selectKey) ? opts.selectKey : null;
49371
49560
  const body = { messages: snapshot2().map(publishRow), csrf: csrfToken, snapshotSeq: seq, agents: withDisplay(agentsSnapshot), selected, expiresAt: sessionExpiry, drafts: { durable: !!drafts?.durable } };
49372
49561
  return send(res, 200, JSON.stringify(body), "application/json");
49373
49562
  }
49374
- if (method === "GET" && path15 === "/api/message") {
49563
+ if (method === "GET" && path17 === "/api/message") {
49375
49564
  const key = url.searchParams.get("key") ?? "";
49376
49565
  const rec = records.get(key);
49377
49566
  if (!rec) return send(res, 404, JSON.stringify({ error: "unknown or stale message key" }), "application/json");
@@ -49379,13 +49568,13 @@ data: ${JSON.stringify(data)}
49379
49568
  const body = { key, html: renderMessageHtml(source) };
49380
49569
  return send(res, 200, JSON.stringify(body), "application/json");
49381
49570
  }
49382
- if (method === "POST" && (path15 === "/api/draft" || path15 === "/api/draft/discard")) {
49571
+ if (method === "POST" && (path17 === "/api/draft" || path17 === "/api/draft/discard")) {
49383
49572
  if (!mutationGuards(req)) return send(res, 403, JSON.stringify({ error: "forbidden" }), "application/json");
49384
49573
  if (!drafts) return send(res, 503, JSON.stringify({ error: "drafts are not available on this runtime", code: "drafts_unavailable" }), "application/json");
49385
49574
  const body = await readJson(req).catch(() => null);
49386
49575
  const key = body && typeof body.key === "string" ? body.key : "";
49387
49576
  if (!records.has(key)) return send(res, 409, JSON.stringify({ error: "unknown or stale message key" }), "application/json");
49388
- if (path15 === "/api/draft/discard") {
49577
+ if (path17 === "/api/draft/discard") {
49389
49578
  const composeId2 = body && typeof body.composeId === "string" ? body.composeId : "";
49390
49579
  const rev = body && Number.isInteger(body.rev) ? Number(body.rev) : NaN;
49391
49580
  if (!composeId2 || !Number.isFinite(rev)) return send(res, 400, JSON.stringify({ error: "composeId and rev are required" }), "application/json");
@@ -49400,7 +49589,7 @@ data: ${JSON.stringify(data)}
49400
49589
  if (!r.ok) return send(res, 409, JSON.stringify({ error: "draft_conflict", draft: r.conflict }), "application/json");
49401
49590
  return send(res, 200, JSON.stringify({ ok: true, draft: r.draft }), "application/json");
49402
49591
  }
49403
- if (method === "POST" && (path15 === "/api/reply" || path15 === "/api/handle" || path15 === "/api/mark-read")) {
49592
+ if (method === "POST" && (path17 === "/api/reply" || path17 === "/api/handle" || path17 === "/api/mark-read")) {
49404
49593
  if (!mutationGuards(req)) return send(res, 403, JSON.stringify({ error: "forbidden" }), "application/json");
49405
49594
  const body = await readJson(req).catch(() => null);
49406
49595
  const key = body && typeof body.key === "string" ? body.key : "";
@@ -49412,7 +49601,7 @@ data: ${JSON.stringify(data)}
49412
49601
  const status = code3 === "private_unsupported" || code3 === "unknown_method" ? 422 : code3 === "unknown_agent" || code3 === "ambiguous_agent" || code3 === "offline" ? 409 : 502;
49413
49602
  return send(res, status, JSON.stringify({ error: e?.message ?? "control error", code: code3 }), "application/json");
49414
49603
  };
49415
- if (path15 === "/api/reply") {
49604
+ if (path17 === "/api/reply") {
49416
49605
  const text5 = body && typeof body.text === "string" ? body.text : "";
49417
49606
  if (!text5.trim()) return send(res, 400, JSON.stringify({ error: "empty reply" }), "application/json");
49418
49607
  const attemptId = body && typeof body.attemptId === "string" && /^[A-Za-z0-9-]{1,64}$/.test(body.attemptId) ? body.attemptId : void 0;
@@ -49443,7 +49632,7 @@ data: ${JSON.stringify(data)}
49443
49632
  emit("sent", { key: rec.key, ...attemptId ? { attemptId } : {}, outcome, ...draftRef ? { draft: draftRef } : {} });
49444
49633
  return send(res, 200, JSON.stringify({ ok: true, ...attemptId ? { attemptId } : {}, outcome }), "application/json");
49445
49634
  }
49446
- if (path15 === "/api/handle") {
49635
+ if (path17 === "/api/handle") {
49447
49636
  if (handleStarting.has(rec.key)) return send(res, 409, JSON.stringify({ error: "a handoff is already being started for this message", code: "busy" }), "application/json");
49448
49637
  handleStarting.add(rec.key);
49449
49638
  try {
@@ -49477,7 +49666,7 @@ data: ${JSON.stringify(data)}
49477
49666
  emit("updated", publishRow(rec));
49478
49667
  return send(res, 200, JSON.stringify({ ok: true }), "application/json");
49479
49668
  }
49480
- if (method === "GET" && path15 === "/api/events") {
49669
+ if (method === "GET" && path17 === "/api/events") {
49481
49670
  await ensureSubscribed();
49482
49671
  res.writeHead(200, { ...securityHeaders(), "Content-Type": "text/event-stream", Connection: "keep-alive" });
49483
49672
  res.write(": ok\n\n");
@@ -49562,7 +49751,7 @@ var init_popup_broker = __esm({
49562
49751
  // src/popup-drafts.ts
49563
49752
  import fs11 from "node:fs";
49564
49753
  import np12 from "node:path";
49565
- import { randomUUID as randomUUID4 } from "node:crypto";
49754
+ import { randomUUID as randomUUID5 } from "node:crypto";
49566
49755
  function openPopupDrafts(opts) {
49567
49756
  const now = opts.now ?? (() => Date.now());
49568
49757
  const avail = sqliteAvailability();
@@ -49604,7 +49793,7 @@ function openPopupDrafts(opts) {
49604
49793
  if (o.composeId === void 0) {
49605
49794
  const cur = get(key);
49606
49795
  if (cur) return { ok: false, conflict: cur };
49607
- const composeId = randomUUID4();
49796
+ const composeId = randomUUID5();
49608
49797
  db.prepare("INSERT INTO popup_drafts (key, compose_id, rev, text, at) VALUES (?, ?, 1, ?, ?)").run(key, composeId, text5, at);
49609
49798
  return { ok: true, draft: { composeId, rev: 1, text: text5, at } };
49610
49799
  }
@@ -49613,7 +49802,7 @@ function openPopupDrafts(opts) {
49613
49802
  if (Number(r.changes) !== 1) {
49614
49803
  const cur = get(key);
49615
49804
  if (!cur) {
49616
- const composeId = randomUUID4();
49805
+ const composeId = randomUUID5();
49617
49806
  db.prepare("INSERT INTO popup_drafts (key, compose_id, rev, text, at) VALUES (?, ?, 1, ?, ?)").run(key, composeId, text5, at);
49618
49807
  return { ok: true, draft: { composeId, rev: 1, text: text5, at } };
49619
49808
  }
@@ -49658,12 +49847,12 @@ function memoryDrafts(now) {
49658
49847
  const cur = m.get(key) ?? null;
49659
49848
  if (o.composeId === void 0) {
49660
49849
  if (cur) return { ok: false, conflict: cur };
49661
- const d2 = { composeId: randomUUID4(), rev: 1, text: text5, at };
49850
+ const d2 = { composeId: randomUUID5(), rev: 1, text: text5, at };
49662
49851
  m.set(key, d2);
49663
49852
  return { ok: true, draft: d2 };
49664
49853
  }
49665
49854
  if (!cur) {
49666
- const d2 = { composeId: randomUUID4(), rev: 1, text: text5, at };
49855
+ const d2 = { composeId: randomUUID5(), rev: 1, text: text5, at };
49667
49856
  m.set(key, d2);
49668
49857
  return { ok: true, draft: d2 };
49669
49858
  }
@@ -49803,7 +49992,7 @@ function serverHostLabel2(server2, allServers) {
49803
49992
  } catch {
49804
49993
  return safeHandle(String(server2));
49805
49994
  }
49806
- const path15 = u.pathname.replace(/\/+$/, "");
49995
+ const path17 = u.pathname.replace(/\/+$/, "");
49807
49996
  let label = u.host;
49808
49997
  if (allServers) {
49809
49998
  const mine = normalizeServerUrl(String(server2));
@@ -49817,8 +50006,8 @@ function serverHostLabel2(server2, allServers) {
49817
50006
  }
49818
50007
  const clash = (key) => others.some((o) => key(o) === key(u));
49819
50008
  if (clash((x) => x.host)) {
49820
- label = u.host + path15;
49821
- if (clash((x) => x.host + x.pathname.replace(/\/+$/, ""))) label = `${u.protocol}//${u.host}${path15}`;
50009
+ label = u.host + path17;
50010
+ if (clash((x) => x.host + x.pathname.replace(/\/+$/, ""))) label = `${u.protocol}//${u.host}${path17}`;
49822
50011
  }
49823
50012
  }
49824
50013
  return safeHandle(label);
@@ -51788,61 +51977,6 @@ var init_version = __esm({
51788
51977
  }
51789
51978
  });
51790
51979
 
51791
- // src/send-confirmed.ts
51792
- import { randomUUID as randomUUID5 } from "node:crypto";
51793
- function sendConfirmed(client, target, text5, opts = {}) {
51794
- const clientId = opts.clientId ?? randomUUID5();
51795
- const timeoutMs = opts.timeoutMs ?? SEND_CONFIRM_TIMEOUT_MS;
51796
- return new Promise((resolve) => {
51797
- let settled = false;
51798
- const finish = (out) => {
51799
- if (settled) return;
51800
- settled = true;
51801
- clearTimeout(timer);
51802
- clearInterval(linkCheck);
51803
- client.off("reply_ack", onAck);
51804
- client.off("error", onErr);
51805
- resolve(out);
51806
- };
51807
- const onAck = (ev) => {
51808
- if (ev?.replyId !== clientId) return;
51809
- finish({ outcome: "sent", hubMessageId: String(ev.messageId ?? "") });
51810
- };
51811
- const onErr = (ev) => {
51812
- if (ev?.replyId !== clientId) return;
51813
- finish({
51814
- outcome: "rejected",
51815
- reason: String(ev.message ?? "the hub refused the message"),
51816
- ...typeof ev.code === "string" ? { code: ev.code } : {},
51817
- ...typeof ev.status === "number" ? { status: ev.status } : {}
51818
- });
51819
- };
51820
- const timer = setTimeout(
51821
- () => finish({ outcome: "ambiguous", reason: `no answer from the hub within ${Math.round(timeoutMs / 1e3)}s \u2014 check history before retrying` }),
51822
- timeoutMs
51823
- );
51824
- const linkCheck = setInterval(() => {
51825
- if (client.online === false) finish({ outcome: "ambiguous", reason: "the hub connection dropped before it answered \u2014 check history before retrying" });
51826
- }, 250);
51827
- client.on("reply_ack", onAck);
51828
- client.on("error", onErr);
51829
- try {
51830
- if (target.groupId) client.groupMessage(target.groupId, text5, { clientId });
51831
- else if (target.to) client.message(target.to, text5, { clientId });
51832
- else return finish({ outcome: "rejected", reason: "need `to` or `groupId`" });
51833
- } catch (e) {
51834
- finish({ outcome: "rejected", reason: e?.message ?? "send failed" });
51835
- }
51836
- });
51837
- }
51838
- var SEND_CONFIRM_TIMEOUT_MS;
51839
- var init_send_confirmed = __esm({
51840
- "src/send-confirmed.ts"() {
51841
- "use strict";
51842
- SEND_CONFIRM_TIMEOUT_MS = 8e3;
51843
- }
51844
- });
51845
-
51846
51980
  // src/identity-lock.ts
51847
51981
  import fs17 from "node:fs";
51848
51982
  import os8 from "node:os";
@@ -55163,6 +55297,158 @@ var init_secret_input = __esm({
55163
55297
  }
55164
55298
  });
55165
55299
 
55300
+ // plugin/claude-layout.mjs
55301
+ import fs27 from "node:fs";
55302
+ import path13 from "node:path";
55303
+ function buildPluginManifest(version2) {
55304
+ return {
55305
+ name: "jefri-chat",
55306
+ displayName: "Jefri Chat",
55307
+ version: version2,
55308
+ description: "WhatsApp for AI agents \u2014 local Claude Desktop connector. Needs Node installed. Configure this desktop identity with plugin install --app claude before importing; credentials stay on this machine, outside the plugin.",
55309
+ author: { name: "Jefri Chat", url: "https://jefrichat.com" },
55310
+ homepage: "https://jefrichat.com",
55311
+ license: "UNLICENSED",
55312
+ keywords: ["mcp", "agents", "chat", "jefri"]
55313
+ };
55314
+ }
55315
+ function buildPluginMcp() {
55316
+ return {
55317
+ mcpServers: {
55318
+ jefri: {
55319
+ command: "node",
55320
+ args: ["${CLAUDE_PLUGIN_ROOT}/server/launch.mjs"],
55321
+ env: { JEFRI_CONNECTOR_PROFILE: "claude_desktop", JEFRI_CLAUDE_DESKTOP_RECOVERY: "1" }
55322
+ }
55323
+ }
55324
+ };
55325
+ }
55326
+ function writePluginFiles(out, version2) {
55327
+ fs27.mkdirSync(path13.join(out, ".claude-plugin"), { recursive: true });
55328
+ fs27.mkdirSync(path13.join(out, "server"), { recursive: true });
55329
+ fs27.writeFileSync(path13.join(out, ".claude-plugin", "plugin.json"), JSON.stringify(buildPluginManifest(version2), null, 2) + "\n");
55330
+ fs27.writeFileSync(path13.join(out, ".mcp.json"), JSON.stringify(buildPluginMcp(), null, 2) + "\n");
55331
+ fs27.writeFileSync(path13.join(out, "package.json"), JSON.stringify({ name: "jefri-chat-desktop-plugin", version: version2, private: true, type: "module" }, null, 2) + "\n");
55332
+ fs27.writeFileSync(path13.join(out, "server", "launch.mjs"), CLAUDE_LAUNCHER);
55333
+ }
55334
+ var CLAUDE_LAUNCHER;
55335
+ var init_claude_layout = __esm({
55336
+ "plugin/claude-layout.mjs"() {
55337
+ "use strict";
55338
+ CLAUDE_LAUNCHER = `import fs from "node:fs";
55339
+ import os from "node:os";
55340
+ import path from "node:path";
55341
+ const configPath = path.join(os.homedir(), ".jefri", "claude-desktop", "connection.json");
55342
+ let fd;
55343
+ try {
55344
+ fd = fs.openSync(configPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
55345
+ const st = fs.fstatSync(fd);
55346
+ if (!st.isFile() || st.size > 16384 || (process.platform !== "win32" &&
55347
+ ((st.mode & 0o077) !== 0 || (process.getuid && st.uid !== process.getuid())))) throw new Error();
55348
+ const c = JSON.parse(fs.readFileSync(fd, "utf8"));
55349
+ const hub = new URL(c.server);
55350
+ if (c.schema !== 1 || typeof c.agent !== "string" || !c.agent.trim() ||
55351
+ typeof c.token !== "string" || !c.token.trim() || /[\\r\\n]/.test(c.token) ||
55352
+ !["http:", "https:"].includes(hub.protocol) || hub.username || hub.password ||
55353
+ hub.search || hub.hash || hub.pathname !== "/") throw new Error();
55354
+ for (const k of ["JEFRI_AS", "JEFRI_PROFILE", "JEFRI_LIVE_HANDOFF", "JEFRI_DAEMON", "JEFRI_TAKEOVER_TICKET", "JEFRI_OWNER_TOKEN"]) delete process.env[k];
55355
+ process.env.JEFRI_TOKEN = c.token;
55356
+ process.env.JEFRI_SERVER = hub.origin;
55357
+ process.env.JEFRI_CONNECTOR_PROFILE = "claude_desktop";
55358
+ process.env.JEFRI_CLAUDE_DESKTOP_RECOVERY = "1";
55359
+ } catch {
55360
+ console.error("Jefri Claude Desktop setup is missing or invalid. Run this connector's plugin install --app claude --agent <username> command; enter the token at its hidden prompt. The private connection.json must be owned by you and mode 0600. No other identity was used.");
55361
+ process.exitCode = 1;
55362
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
55363
+ if (!process.exitCode) await import("./index.mjs");
55364
+ `;
55365
+ }
55366
+ });
55367
+
55368
+ // src/claude-plugin-setup.ts
55369
+ import fs28 from "node:fs";
55370
+ import path14 from "node:path";
55371
+ import { execFileSync } from "node:child_process";
55372
+ function installClaudePlugin(opts) {
55373
+ const hub = normalizeHub2(opts.server);
55374
+ if (!opts.agent || opts.agent.length > 128 || /\s/.test(opts.agent)) throw new Error("agent username is required");
55375
+ const version2 = bundleVersionOf(opts.bundlePath);
55376
+ if (!version2 || !fs28.statSync(opts.bundlePath).isFile()) throw new Error("cannot determine the pinned connector bundle and version; nothing was written");
55377
+ const dir = claudePluginDir(opts.home);
55378
+ const lock2 = acquireInstallLock(path14.join(opts.home, ".jefri", "claude-desktop-install.lock"));
55379
+ let stage;
55380
+ let previous3;
55381
+ let swapped = false;
55382
+ try {
55383
+ let old;
55384
+ if (fs28.existsSync(dir)) {
55385
+ if (fs28.lstatSync(dir).isSymbolicLink() || !fs28.existsSync(path14.join(dir, MARKER)))
55386
+ throw new Error("Claude Desktop setup directory is not managed by this installer; left untouched");
55387
+ try {
55388
+ const file = path14.join(dir, "connection.json");
55389
+ const st = fs28.lstatSync(file);
55390
+ if (!st.isFile() || st.size > 16384 || process.platform !== "win32" && ((st.mode & 63) !== 0 || st.uid !== process.getuid?.())) throw new Error();
55391
+ old = JSON.parse(fs28.readFileSync(file, "utf8"));
55392
+ } catch {
55393
+ throw new Error("existing Claude Desktop configuration is unreadable or not private; left untouched");
55394
+ }
55395
+ }
55396
+ const token = opts.token || (old?.agent === opts.agent && old?.server === hub ? old.token : void 0);
55397
+ if (typeof token !== "string" || token.length > 4096 || !token.trim() || /[\r\n]/.test(token))
55398
+ throw new Error("no token: enter this Claude Desktop agent's token at the hidden prompt; nothing was written");
55399
+ execFileSync("zip", ["-v"], { stdio: "ignore" });
55400
+ stage = fs28.mkdtempSync(path14.join(path14.dirname(dir), ".claude-desktop-stage-"));
55401
+ fs28.chmodSync(stage, 448);
55402
+ fs28.writeFileSync(path14.join(stage, MARKER), "1\n", { mode: 384 });
55403
+ fs28.writeFileSync(path14.join(stage, "connection.json"), JSON.stringify({ schema: 1, agent: opts.agent, server: hub, token }, null, 2) + "\n", { mode: 384 });
55404
+ const plugin = path14.join(stage, "plugin");
55405
+ writePluginFiles(plugin, version2);
55406
+ fs28.copyFileSync(opts.bundlePath, path14.join(plugin, "server", "index.mjs"));
55407
+ execFileSync("zip", ["-q", "-r", "-X", path14.join(stage, "jefri-chat.plugin"), ".claude-plugin", ".mcp.json", "package.json", "server"], { cwd: plugin, stdio: "ignore" });
55408
+ lock2.assertHeld("replacing Claude Desktop setup");
55409
+ if (fs28.existsSync(dir)) {
55410
+ previous3 = `${stage}.previous`;
55411
+ fs28.renameSync(dir, previous3);
55412
+ }
55413
+ try {
55414
+ fs28.renameSync(stage, dir);
55415
+ swapped = true;
55416
+ } catch (e) {
55417
+ if (previous3) {
55418
+ try {
55419
+ fs28.renameSync(previous3, dir);
55420
+ previous3 = void 0;
55421
+ } catch {
55422
+ throw new Error(`could not activate or restore Claude Desktop setup; previous setup is intact at ${previous3}`);
55423
+ }
55424
+ }
55425
+ throw e;
55426
+ }
55427
+ return {
55428
+ archive: path14.join(dir, "jefri-chat.plugin"),
55429
+ configPath: path14.join(dir, "connection.json"),
55430
+ version: version2,
55431
+ hub,
55432
+ agent: opts.agent,
55433
+ identity: opts.token ? "stored" : "kept",
55434
+ previous: previous3
55435
+ };
55436
+ } finally {
55437
+ if (stage && !swapped) fs28.rmSync(stage, { recursive: true, force: true });
55438
+ lock2.release();
55439
+ }
55440
+ }
55441
+ var claudePluginDir, MARKER;
55442
+ var init_claude_plugin_setup = __esm({
55443
+ "src/claude-plugin-setup.ts"() {
55444
+ "use strict";
55445
+ init_claude_layout();
55446
+ init_plugin_setup();
55447
+ claudePluginDir = (home) => path14.join(home, ".jefri", "claude-desktop");
55448
+ MARKER = ".jefri-claude-install";
55449
+ }
55450
+ });
55451
+
55166
55452
  // src/plugin-cli.ts
55167
55453
  var plugin_cli_exports = {};
55168
55454
  __export(plugin_cli_exports, {
@@ -55170,7 +55456,7 @@ __export(plugin_cli_exports, {
55170
55456
  runPluginCli: () => runPluginCli,
55171
55457
  selfBundlePath: () => selfBundlePath
55172
55458
  });
55173
- import fs27 from "node:fs";
55459
+ import fs29 from "node:fs";
55174
55460
  import os15 from "node:os";
55175
55461
  import { fileURLToPath as fileURLToPath3 } from "node:url";
55176
55462
  function selfBundlePath() {
@@ -55184,12 +55470,12 @@ async function runPluginCli(argv, deps = {}) {
55184
55470
  const err = deps.err ?? ((l) => console.error(l));
55185
55471
  const sub = argv[0];
55186
55472
  if (sub !== "install") {
55187
- err("usage: jefrichat-mcp plugin install --app chatgpt|codex --agent <username> [--server <hub url>] (the token is asked for hidden, or read from JEFRI_TOKEN)");
55473
+ err("usage: jefrichat-mcp plugin install --app chatgpt|codex|claude --agent <username> [--server <hub url>] (the token is asked for hidden, or read from JEFRI_TOKEN)");
55188
55474
  return 2;
55189
55475
  }
55190
55476
  const app = argValue(argv, "--app") ?? "chatgpt";
55191
- if (app !== "chatgpt" && app !== "codex") {
55192
- err(`--app must be chatgpt or codex, got ${JSON.stringify(app)}`);
55477
+ if (app !== "chatgpt" && app !== "codex" && app !== "claude") {
55478
+ err(`--app must be chatgpt, codex or claude, got ${JSON.stringify(app)}`);
55193
55479
  return 2;
55194
55480
  }
55195
55481
  const agent = argValue(argv, "--agent");
@@ -55201,12 +55487,13 @@ async function runPluginCli(argv, deps = {}) {
55201
55487
  err("refusing --token on the command line: it never belongs in shell history or a process list \u2014 run without it and paste the token at the hidden prompt (or set JEFRI_TOKEN for scripts)");
55202
55488
  return 2;
55203
55489
  }
55204
- if (env.JEFRI_CONNECTOR_PROFILE && env.JEFRI_CONNECTOR_PROFILE !== PLUGIN_CONNECTOR_PROFILE)
55205
- err(`ignoring JEFRI_CONNECTOR_PROFILE=${JSON.stringify(env.JEFRI_CONNECTOR_PROFILE)}: desktop plugins always use ${PLUGIN_CONNECTOR_PROFILE} (the agent identity is a separate profile)`);
55490
+ const connectorProfile = app === "claude" ? "claude_desktop" : PLUGIN_CONNECTOR_PROFILE;
55491
+ if (env.JEFRI_CONNECTOR_PROFILE && env.JEFRI_CONNECTOR_PROFILE !== connectorProfile)
55492
+ err(`ignoring JEFRI_CONNECTOR_PROFILE=${JSON.stringify(env.JEFRI_CONNECTOR_PROFILE)}: this desktop plugin uses ${connectorProfile} (the agent identity is separate)`);
55206
55493
  const server2 = argValue(argv, "--server") || env.JEFRI_SERVER || "https://jefrichat.com";
55207
55494
  const home = deps.home ?? os15.homedir();
55208
55495
  const bundlePath = deps.bundlePath ?? selfBundlePath();
55209
- if (!fs27.existsSync(bundlePath)) {
55496
+ if (!fs29.existsSync(bundlePath)) {
55210
55497
  err(`cannot find the connector bundle to pin (${bundlePath})`);
55211
55498
  return 1;
55212
55499
  }
@@ -55221,6 +55508,22 @@ async function runPluginCli(argv, deps = {}) {
55221
55508
  }
55222
55509
  }
55223
55510
  let r;
55511
+ if (app === "claude") {
55512
+ try {
55513
+ const c2 = installClaudePlugin({ agent, server: server2, token, home, bundlePath });
55514
+ out(`Claude Desktop package prepared (connector ${c2.version}): ${c2.archive}`);
55515
+ out(`Private desktop identity ${c2.identity}: ${c2.configPath} (0600, not inside the plugin)`);
55516
+ if (c2.previous) out(`Previous setup kept privately for recovery: ${c2.previous}`);
55517
+ out("Not activated or verified yet. Claude Desktop -> Settings -> Plugins -> Upload local plugin: import the file above, replacing the old Jefri plugin, then start a NEW chat.");
55518
+ out("Use only one Jefri plugin/extension for this identity. Do not enable an old extension alongside it. Other apps and shared saved identities were not changed.");
55519
+ out(`Expected identity: @${safeHandle(agent)} on ${c2.hub}. Capability profile: claude_desktop.`);
55520
+ out(VERIFY_PROMPT);
55521
+ return 0;
55522
+ } catch (e) {
55523
+ err(`Claude Desktop setup failed: ${String(e?.message ?? e).replaceAll(token ?? "\0", "[redacted]")}`);
55524
+ return 1;
55525
+ }
55526
+ }
55224
55527
  try {
55225
55528
  r = installLocalPlugin({ agent, server: server2, token, app, home, bundlePath });
55226
55529
  } catch (e) {
@@ -55251,6 +55554,7 @@ var init_plugin_cli = __esm({
55251
55554
  init_src();
55252
55555
  init_plugin_setup();
55253
55556
  init_secret_input();
55557
+ init_claude_plugin_setup();
55254
55558
  argValue = (argv, flag) => {
55255
55559
  const i = argv.indexOf(flag);
55256
55560
  return i >= 0 && i + 1 < argv.length ? argv[i + 1] : void 0;
@@ -55630,41 +55934,41 @@ var init_daemon_control = __esm({
55630
55934
  });
55631
55935
 
55632
55936
  // src/daemon.ts
55633
- import * as fs28 from "node:fs";
55937
+ import * as fs30 from "node:fs";
55634
55938
  import * as np18 from "node:path";
55635
55939
  import { randomBytes as randomBytes13 } from "node:crypto";
55636
55940
  import { createHash as createHash9 } from "node:crypto";
55637
- function realOpenExclusive(path15, content3) {
55638
- const tmp = `${path15}.${process.pid}.${randomBytes13(6).toString("hex")}.tmp`;
55639
- fs28.writeFileSync(tmp, content3, { mode: 384 });
55941
+ function realOpenExclusive(path17, content3) {
55942
+ const tmp = `${path17}.${process.pid}.${randomBytes13(6).toString("hex")}.tmp`;
55943
+ fs30.writeFileSync(tmp, content3, { mode: 384 });
55640
55944
  try {
55641
- fs28.linkSync(tmp, path15);
55945
+ fs30.linkSync(tmp, path17);
55642
55946
  return true;
55643
55947
  } catch (e) {
55644
55948
  if (e.code === "EEXIST") return false;
55645
55949
  throw e;
55646
55950
  } finally {
55647
55951
  try {
55648
- fs28.unlinkSync(tmp);
55952
+ fs30.unlinkSync(tmp);
55649
55953
  } catch {
55650
55954
  }
55651
55955
  }
55652
55956
  }
55653
- function realReadFile(path15) {
55957
+ function realReadFile(path17) {
55654
55958
  try {
55655
- return fs28.readFileSync(path15, "utf8");
55959
+ return fs30.readFileSync(path17, "utf8");
55656
55960
  } catch {
55657
55961
  return null;
55658
55962
  }
55659
55963
  }
55660
- function realWriteFileAtomic(path15, content3) {
55661
- const tmp = `${path15}.${process.pid}.${randomBytes13(6).toString("hex")}.tmp`;
55662
- fs28.writeFileSync(tmp, content3, { mode: 384 });
55663
- fs28.renameSync(tmp, path15);
55964
+ function realWriteFileAtomic(path17, content3) {
55965
+ const tmp = `${path17}.${process.pid}.${randomBytes13(6).toString("hex")}.tmp`;
55966
+ fs30.writeFileSync(tmp, content3, { mode: 384 });
55967
+ fs30.renameSync(tmp, path17);
55664
55968
  }
55665
- function realRemoveFile(path15) {
55969
+ function realRemoveFile(path17) {
55666
55970
  try {
55667
- fs28.unlinkSync(path15);
55971
+ fs30.unlinkSync(path17);
55668
55972
  } catch {
55669
55973
  }
55670
55974
  }
@@ -56099,7 +56403,7 @@ var init_daemon_proxy_proto = __esm({
56099
56403
  });
56100
56404
 
56101
56405
  // src/daemon-server.ts
56102
- import fs29 from "node:fs";
56406
+ import fs31 from "node:fs";
56103
56407
  import net4 from "node:net";
56104
56408
  import { randomBytes as randomBytes14 } from "node:crypto";
56105
56409
  function errToCode(e) {
@@ -56365,7 +56669,7 @@ async function startDaemonServer(opts) {
56365
56669
  });
56366
56670
  if (process.platform !== "win32") {
56367
56671
  try {
56368
- fs29.chmodSync(opts.socketPath, 384);
56672
+ fs31.chmodSync(opts.socketPath, 384);
56369
56673
  } catch {
56370
56674
  }
56371
56675
  }
@@ -56461,7 +56765,7 @@ async function startDaemonServer(opts) {
56461
56765
  server2.close(() => {
56462
56766
  if (process.platform !== "win32") {
56463
56767
  try {
56464
- fs29.unlinkSync(opts.socketPath);
56768
+ fs31.unlinkSync(opts.socketPath);
56465
56769
  } catch {
56466
56770
  }
56467
56771
  }
@@ -56913,13 +57217,13 @@ var init_proxy = __esm({
56913
57217
  if (typeof t.unref === "function") t.unref();
56914
57218
  });
56915
57219
  if (this.closed) return;
56916
- let path15 = this.opts.socketPath;
57220
+ let path17 = this.opts.socketPath;
56917
57221
  try {
56918
- if (this.opts.resolveSocket) path15 = await this.opts.resolveSocket();
57222
+ if (this.opts.resolveSocket) path17 = await this.opts.resolveSocket();
56919
57223
  } catch {
56920
57224
  }
56921
57225
  try {
56922
- const r = await this.startDial(path15);
57226
+ const r = await this.startDial(path17);
56923
57227
  try {
56924
57228
  this.opts.onReattached?.(r);
56925
57229
  } catch {
@@ -56959,7 +57263,7 @@ var init_proxy = __esm({
56959
57263
  });
56960
57264
 
56961
57265
  // src/inject-quarantine.ts
56962
- import fs30 from "node:fs";
57266
+ import fs32 from "node:fs";
56963
57267
  import cp14 from "node:child_process";
56964
57268
  import np19 from "node:path";
56965
57269
  import { createHash as createHash11, randomBytes as randomBytes16 } from "node:crypto";
@@ -57025,7 +57329,7 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57025
57329
  const read = () => {
57026
57330
  let raw;
57027
57331
  try {
57028
- raw = fs30.readFileSync(file, "utf8");
57332
+ raw = fs32.readFileSync(file, "utf8");
57029
57333
  } catch {
57030
57334
  return "absent";
57031
57335
  }
@@ -57044,7 +57348,7 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57044
57348
  const pruneDeadGenerations = () => {
57045
57349
  let names;
57046
57350
  try {
57047
- names = fs30.readdirSync(dir);
57351
+ names = fs32.readdirSync(dir);
57048
57352
  } catch {
57049
57353
  return;
57050
57354
  }
@@ -57054,14 +57358,14 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57054
57358
  const p = np19.join(dir, n);
57055
57359
  let body;
57056
57360
  try {
57057
- body = JSON.parse(fs30.readFileSync(p, "utf8"));
57361
+ body = JSON.parse(fs32.readFileSync(p, "utf8"));
57058
57362
  } catch {
57059
57363
  continue;
57060
57364
  }
57061
57365
  if (typeof body.panePid !== "number") continue;
57062
57366
  if (!paneAlive(body.panePid, body.paneToken ?? "")) {
57063
57367
  try {
57064
- fs30.unlinkSync(p);
57368
+ fs32.unlinkSync(p);
57065
57369
  } catch {
57066
57370
  }
57067
57371
  }
@@ -57080,22 +57384,22 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57080
57384
  };
57081
57385
  const tmp = `${file}.${writer.pid}.${attempt}.tmp`;
57082
57386
  try {
57083
- fs30.mkdirSync(dir, { recursive: true, mode: 448 });
57084
- fs30.writeFileSync(tmp, JSON.stringify(body), { mode: 384 });
57387
+ fs32.mkdirSync(dir, { recursive: true, mode: 448 });
57388
+ fs32.writeFileSync(tmp, JSON.stringify(body), { mode: 384 });
57085
57389
  } catch (e) {
57086
57390
  try {
57087
- fs30.unlinkSync(tmp);
57391
+ fs32.unlinkSync(tmp);
57088
57392
  } catch {
57089
57393
  }
57090
57394
  return { ok: false, reason: `could not record the write-ahead marker (${e.code ?? e}) \u2014 refusing to touch the terminal` };
57091
57395
  }
57092
57396
  try {
57093
- fs30.linkSync(tmp, file);
57094
- fs30.unlinkSync(tmp);
57397
+ fs32.linkSync(tmp, file);
57398
+ fs32.unlinkSync(tmp);
57095
57399
  return { ok: true, attempt };
57096
57400
  } catch (e) {
57097
57401
  try {
57098
- fs30.unlinkSync(tmp);
57402
+ fs32.unlinkSync(tmp);
57099
57403
  } catch {
57100
57404
  }
57101
57405
  const code3 = e.code;
@@ -57141,7 +57445,7 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57141
57445
  if (body.attempt !== attempt || body.writerPid !== writer.pid) return;
57142
57446
  if (outcome === "clean") {
57143
57447
  try {
57144
- fs30.unlinkSync(file);
57448
+ fs32.unlinkSync(file);
57145
57449
  } catch {
57146
57450
  }
57147
57451
  return;
@@ -57149,8 +57453,8 @@ function createInjectQuarantine(resolution2, dir = dirFor(), deps = {}) {
57149
57453
  const dirty = { ...body, state: "dirty", reason: outcome.dirty, at: (/* @__PURE__ */ new Date()).toISOString() };
57150
57454
  try {
57151
57455
  const tmp = `${file}.${writer.pid}.dirty.tmp`;
57152
- fs30.writeFileSync(tmp, JSON.stringify(dirty), { mode: 384 });
57153
- fs30.renameSync(tmp, file);
57456
+ fs32.writeFileSync(tmp, JSON.stringify(dirty), { mode: 384 });
57457
+ fs32.renameSync(tmp, file);
57154
57458
  } catch {
57155
57459
  }
57156
57460
  }
@@ -57185,16 +57489,16 @@ __export(daemon_mode_exports, {
57185
57489
  wireInboxEvents: () => wireInboxEvents,
57186
57490
  wireIncomingDelivery: () => wireIncomingDelivery
57187
57491
  });
57188
- import fs31 from "node:fs";
57492
+ import fs33 from "node:fs";
57189
57493
  import os17 from "node:os";
57190
- import path13 from "node:path";
57494
+ import path15 from "node:path";
57191
57495
  function resolveIdentityEnv() {
57192
57496
  const agent = process.env.JEFRI_AS;
57193
57497
  const token = process.env.JEFRI_TOKEN;
57194
57498
  if (agent && token) return { agent, token };
57195
57499
  if (agent) {
57196
57500
  try {
57197
- const cache = JSON.parse(fs31.readFileSync(jefriPath("tokens.json"), "utf8"));
57501
+ const cache = JSON.parse(fs33.readFileSync(jefriPath("tokens.json"), "utf8"));
57198
57502
  const server2 = process.env.JEFRI_SERVER ?? "http://localhost:4000";
57199
57503
  const cached2 = cache[`${server2}::${agent}`];
57200
57504
  if (cached2) return { agent, token: cached2 };
@@ -57910,7 +58214,7 @@ async function runDaemonMode() {
57910
58214
  }
57911
58215
  const transport = new StdioServerTransport();
57912
58216
  await server2.connect(transport);
57913
- log(`proxy ready \u2014 @${safeHandle(id.agent)} via ${path13.basename(socketPath)}${daemonStopRef.stop ? " (this process IS the daemon)" : ""}`);
58217
+ log(`proxy ready \u2014 @${safeHandle(id.agent)} via ${path15.basename(socketPath)}${daemonStopRef.stop ? " (this process IS the daemon)" : ""}`);
57914
58218
  let shuttingDown = false;
57915
58219
  const shutdown = () => {
57916
58220
  if (shuttingDown) return;
@@ -58036,12 +58340,19 @@ init_state_root();
58036
58340
  init_src();
58037
58341
  init_stdio2();
58038
58342
  init_src2();
58039
- init_src();
58040
- import fs32 from "node:fs";
58343
+ import fs34 from "node:fs";
58041
58344
  import os18 from "node:os";
58042
- import path14 from "node:path";
58345
+ import path16 from "node:path";
58043
58346
  import { randomBytes as randomBytes17 } from "node:crypto";
58044
58347
 
58348
+ // src/claude-desktop-recovery.ts
58349
+ function claudeDesktopRecoveryEnabled(env = process.env) {
58350
+ return env.JEFRI_CONNECTOR_PROFILE === "claude_desktop" && env.JEFRI_CLAUDE_DESKTOP_RECOVERY === "1";
58351
+ }
58352
+
58353
+ // src/index.ts
58354
+ init_src();
58355
+
58045
58356
  // src/group-cache.ts
58046
58357
  function createGroupCache(opts) {
58047
58358
  const gateTimeoutMs = opts.gateTimeoutMs ?? 1e4;
@@ -59367,20 +59678,21 @@ var USERNAME = process.env.JEFRI_AS ?? "claude_agent";
59367
59678
  var NAME = process.env.JEFRI_NAME ?? USERNAME;
59368
59679
  var TAGS = (process.env.JEFRI_TAGS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
59369
59680
  var CAPS = (process.env.JEFRI_CAPABILITIES ?? "coding").split(",").map((s) => s.trim()).filter(Boolean);
59681
+ var DESKTOP_RECOVERY = claudeDesktopRecoveryEnabled();
59370
59682
  var log2 = (...a) => console.error("[jefrichat-mcp]", ...a);
59371
59683
  var VERBOSE = process.env.JEFRI_DEBUG === "1" || process.env.JEFRI_VERBOSE === "1";
59372
59684
  var note = (...a) => {
59373
59685
  if (VERBOSE) log2(...a);
59374
59686
  };
59375
59687
  var TOKEN_CACHE = () => jefriPath("tokens.json");
59376
- var LEGACY_TOKEN_CACHE = path14.join(os18.homedir(), ".acp", "tokens.json");
59688
+ var LEGACY_TOKEN_CACHE = path16.join(os18.homedir(), ".acp", "tokens.json");
59377
59689
  var cacheKey = `${SERVER}::${USERNAME}`;
59378
59690
  function readTokenCache() {
59379
59691
  try {
59380
- return JSON.parse(fs32.readFileSync(TOKEN_CACHE(), "utf8"));
59692
+ return JSON.parse(fs34.readFileSync(TOKEN_CACHE(), "utf8"));
59381
59693
  } catch {
59382
59694
  try {
59383
- return JSON.parse(fs32.readFileSync(LEGACY_TOKEN_CACHE, "utf8"));
59695
+ return JSON.parse(fs34.readFileSync(LEGACY_TOKEN_CACHE, "utf8"));
59384
59696
  } catch {
59385
59697
  return {};
59386
59698
  }
@@ -59389,8 +59701,8 @@ function readTokenCache() {
59389
59701
  function cacheToken(tok) {
59390
59702
  const c2 = readTokenCache();
59391
59703
  c2[cacheKey] = tok;
59392
- fs32.mkdirSync(path14.dirname(TOKEN_CACHE()), { recursive: true });
59393
- fs32.writeFileSync(TOKEN_CACHE(), JSON.stringify(c2, null, 2));
59704
+ fs34.mkdirSync(path16.dirname(TOKEN_CACHE()), { recursive: true });
59705
+ fs34.writeFileSync(TOKEN_CACHE(), JSON.stringify(c2, null, 2));
59394
59706
  }
59395
59707
  var clientPromise = null;
59396
59708
  var inbox = [];
@@ -59445,6 +59757,7 @@ function ensureClient() {
59445
59757
  };
59446
59758
  const cleanup = [];
59447
59759
  const provision = () => JefriClient.connect({
59760
+ registeredRecovery: DESKTOP_RECOVERY,
59448
59761
  server: hubUrl(),
59449
59762
  username: USERNAME,
59450
59763
  name: NAME,
@@ -59457,7 +59770,7 @@ function ensureClient() {
59457
59770
  });
59458
59771
  let c2;
59459
59772
  if (known) {
59460
- c2 = await JefriClient.connect({ server: hubUrl(), token: known, status: "online", clientInfo, takeoverTicket: LIVE_DESTINATION ? void 0 : process.env.JEFRI_TAKEOVER_TICKET });
59773
+ c2 = await JefriClient.connect({ registeredRecovery: DESKTOP_RECOVERY, server: hubUrl(), token: known, status: "online", clientInfo, takeoverTicket: LIVE_DESTINATION ? void 0 : process.env.JEFRI_TAKEOVER_TICKET });
59461
59774
  if (!c2.identity) {
59462
59775
  if (TOKEN) throw new Error("JEFRI_TOKEN was rejected by the server");
59463
59776
  if (!LEGACY_MODE) throw new Error("this profile's token was rejected by the server \u2014 fix it in ~/.jefri/identities.json");
@@ -60213,6 +60526,10 @@ async function main() {
60213
60526
  const bootConnect = (attempt = 0) => {
60214
60527
  if (liveHold || liveMessage) return;
60215
60528
  ensureClient().catch((e) => {
60529
+ if (DESKTOP_RECOVERY && terminalConnectionCode(e)) {
60530
+ log2(`initial connect stopped (${terminalConnectionCode(e)}):`, e?.message ?? e);
60531
+ return;
60532
+ }
60216
60533
  const delay = Math.min(2e3 * 2 ** attempt, 3e4);
60217
60534
  const jittered = Math.floor(delay / 2 + Math.random() * (delay / 2));
60218
60535
  log2(`initial connect failed (retrying in ${Math.round(jittered / 1e3)}s):`, e?.message ?? e);