jefrichat-mcp 0.49.40 → 0.49.41

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 (2) hide show
  1. package/dist/index.js +183 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -64184,6 +64184,8 @@ import { createHash as createHash7, randomUUID as randomUUID11 } from "node:cryp
64184
64184
 
64185
64185
  // src/terminal-live/hub-protocol.ts
64186
64186
  import { randomUUID as randomUUID8 } from "node:crypto";
64187
+ var NativeHubUnavailable = class extends Error {
64188
+ };
64187
64189
  function nativeHubRequest(client, operation, payload = {}, timeoutMs = 1e4) {
64188
64190
  const generation = client.generation, requestId = randomUUID8();
64189
64191
  return new Promise((resolve, reject) => {
@@ -64194,7 +64196,7 @@ function nativeHubRequest(client, operation, payload = {}, timeoutMs = 1e4) {
64194
64196
  };
64195
64197
  const closed = () => {
64196
64198
  cleanup();
64197
- reject(Error("Native hub connection ended; outcome may be unknown"));
64199
+ reject(new NativeHubUnavailable("Native hub connection ended; outcome may be unknown"));
64198
64200
  };
64199
64201
  const answer = (event) => {
64200
64202
  if (event.requestId !== requestId) return;
@@ -64210,12 +64212,12 @@ function nativeHubRequest(client, operation, payload = {}, timeoutMs = 1e4) {
64210
64212
  };
64211
64213
  const timer2 = setTimeout(() => {
64212
64214
  cleanup();
64213
- reject(Error("Native hub response timed out; do not retry uncertain work"));
64215
+ reject(new NativeHubUnavailable("Native hub response timed out; do not retry uncertain work"));
64214
64216
  }, timeoutMs);
64215
64217
  client.on("native_terminal_result", answer);
64216
64218
  client.on("socket_close", closed);
64217
64219
  try {
64218
- if (!client.online) throw Error("Native hub is not connected");
64220
+ if (!client.online) throw new NativeHubUnavailable("Native hub is not connected");
64219
64221
  client.sendNativeFrame({ type: "native_terminal", requestId, operation, payload });
64220
64222
  } catch (e) {
64221
64223
  cleanup();
@@ -64231,7 +64233,7 @@ init_state_root();
64231
64233
  import { randomUUID as randomUUID9 } from "node:crypto";
64232
64234
  var LEASE_MS = 5 * 6e4;
64233
64235
  var QUEUE_LIMIT = 64;
64234
- function createTerminalLiveBroker(now = () => Date.now()) {
64236
+ function createTerminalLiveBroker(now = () => Date.now(), onStopped) {
64235
64237
  let session;
64236
64238
  let reason = "off";
64237
64239
  let timer2;
@@ -64256,7 +64258,7 @@ function createTerminalLiveBroker(now = () => Date.now()) {
64256
64258
  e.record = r;
64257
64259
  track(r);
64258
64260
  }
64259
- function stop(why = "off") {
64261
+ function stop(why = "off", recoverable = false) {
64260
64262
  ++serial;
64261
64263
  reason = why;
64262
64264
  const s = session;
@@ -64286,6 +64288,7 @@ function createTerminalLiveBroker(now = () => Date.now()) {
64286
64288
  s.binding.release();
64287
64289
  } catch {
64288
64290
  }
64291
+ onStopped?.(why, recoverable);
64289
64292
  }
64290
64293
  async function refresh(s) {
64291
64294
  if (!current(s)) return;
@@ -64298,7 +64301,7 @@ function createTerminalLiveBroker(now = () => Date.now()) {
64298
64301
  throw Error("expired authority");
64299
64302
  s.authority = authority;
64300
64303
  } catch {
64301
- if (current(s)) stop("permission unavailable; enable again after verification");
64304
+ if (current(s)) stop("permission unavailable; fresh session verification required", true);
64302
64305
  } finally {
64303
64306
  s.refreshing = void 0;
64304
64307
  }
@@ -64443,6 +64446,7 @@ function createTerminalLiveBroker(now = () => Date.now()) {
64443
64446
  journal,
64444
64447
  // Truthful: armed is not the same as actually listening.
64445
64448
  monitorAttached: adapter2 ? attached : null,
64449
+ monitorLifetime: adapter2?.monitorLifetime?.() ?? null,
64446
64450
  ...adapter2?.reattachable && !attached && adapter2.rearmCommand ? { rearm: adapter2.rearmCommand, note: "No wake can arrive until this command is running again." } : {}
64447
64451
  };
64448
64452
  },
@@ -64704,18 +64708,34 @@ function codexQueueTransport(thread, endpoint) {
64704
64708
  // src/terminal-live/register.ts
64705
64709
  init_monitor();
64706
64710
  function registerTerminalLive(server2, deps) {
64707
- const broker = createTerminalLiveBroker();
64711
+ let requested;
64712
+ let resetInProgress = false;
64713
+ let recoveryTimer;
64714
+ let recoveryRun;
64715
+ let recoveryAgain = false;
64716
+ let recovering;
64717
+ const broker = createTerminalLiveBroker(void 0, (why, recoverable) => {
64718
+ if (resetInProgress) return;
64719
+ if (recoverable && requested) scheduleRecovery(why);
64720
+ else stop(why);
64721
+ });
64708
64722
  let generation = 0;
64709
64723
  let pending;
64710
64724
  let pendingTimer;
64711
64725
  let bound;
64712
64726
  const releases = /* @__PURE__ */ new Set();
64713
64727
  let unconfirmedRelease = false;
64714
- const stop = (why = "off") => {
64728
+ let releaseRetryAfter = 0;
64729
+ const resetBinding = (why) => {
64715
64730
  ++generation;
64716
64731
  clearTimeout(pendingTimer);
64717
64732
  pendingTimer = void 0;
64718
- broker.stop(why);
64733
+ resetInProgress = true;
64734
+ try {
64735
+ broker.stop(why);
64736
+ } finally {
64737
+ resetInProgress = false;
64738
+ }
64719
64739
  const p = pending;
64720
64740
  pending = void 0;
64721
64741
  try {
@@ -64725,6 +64745,111 @@ function registerTerminalLive(server2, deps) {
64725
64745
  }
64726
64746
  bound = void 0;
64727
64747
  };
64748
+ const stop = (why = "off") => {
64749
+ clearTimeout(recoveryTimer);
64750
+ recoveryTimer = void 0;
64751
+ recoveryRun = void 0;
64752
+ recoveryAgain = false;
64753
+ const intent = requested;
64754
+ requested = void 0;
64755
+ recovering = void 0;
64756
+ intent?.detach();
64757
+ resetBinding(why);
64758
+ };
64759
+ const sameSession = (i) => requested === i && deps.currentClient() === i.client && deps.hub() === i.hub && i.client.token === i.token && i.client.identity?.username === i.self && i.client.identity?.owner === i.owner && !getAuto().enabled && !standbyEngaged();
64760
+ function scheduleRecovery(why, delay = 1e3) {
64761
+ const intent = requested;
64762
+ if (!intent) return;
64763
+ if (!sameSession(intent)) {
64764
+ stop("monitor session or identity changed; enable explicitly in the new session");
64765
+ return;
64766
+ }
64767
+ resetBinding(why);
64768
+ recovering = "connection interrupted; monitor requested in this session, awaiting fresh native verification";
64769
+ if (recoveryRun) {
64770
+ recoveryAgain = true;
64771
+ return;
64772
+ }
64773
+ if (recoveryTimer) return;
64774
+ recoveryTimer = setTimeout(() => {
64775
+ recoveryTimer = void 0;
64776
+ void recoverSession(intent);
64777
+ }, delay);
64778
+ recoveryTimer.unref?.();
64779
+ }
64780
+ async function recoverSession(intent) {
64781
+ if (!sameSession(intent)) {
64782
+ if (requested === intent) stop("monitor session or identity changed");
64783
+ return;
64784
+ }
64785
+ if (!intent.registered || !intent.client.online) {
64786
+ scheduleRecovery("waiting for the same session to reconnect", 5e3);
64787
+ return;
64788
+ }
64789
+ const run = {};
64790
+ recoveryRun = run;
64791
+ let retry = false;
64792
+ let delay = 5e3;
64793
+ const connectionGeneration = intent.client.generation;
64794
+ try {
64795
+ await Promise.all([...releases]);
64796
+ if (!sameSession(intent)) {
64797
+ if (requested === intent) stop("monitor session or identity changed");
64798
+ return;
64799
+ }
64800
+ if (!intent.registered || !intent.client.online) throw new NativeHubUnavailable("Session is not registered yet");
64801
+ if (Date.now() < releaseRetryAfter && intent.client.generation === intent.connectionGeneration) {
64802
+ retry = true;
64803
+ delay = Math.max(1e3, releaseRetryAfter - Date.now());
64804
+ return;
64805
+ }
64806
+ await prepare(intent.args, intent);
64807
+ if (requested === intent && pending) recovering = "reconnected; waiting for the native callback in the original conversation";
64808
+ } catch (e) {
64809
+ if (requested !== intent) return;
64810
+ if (sameSession(intent) && (e instanceof NativeHubUnavailable || recoveryAgain || !intent.client.online || intent.client.generation !== connectionGeneration)) retry = true;
64811
+ else stop("monitor recovery refused; inspect status and explicitly enable after resolving the cause");
64812
+ } finally {
64813
+ if (recoveryRun === run) {
64814
+ recoveryRun = void 0;
64815
+ const again = recoveryAgain;
64816
+ recoveryAgain = false;
64817
+ if ((retry || again) && requested === intent) scheduleRecovery("temporary hub unavailability", again ? 1e3 : delay);
64818
+ }
64819
+ }
64820
+ }
64821
+ function rememberSession(intent) {
64822
+ if (requested) return;
64823
+ requested = intent;
64824
+ const lost = () => {
64825
+ intent.registered = false;
64826
+ scheduleRecovery("hub connection interrupted");
64827
+ };
64828
+ const registered = () => {
64829
+ intent.registered = true;
64830
+ if (recovering) scheduleRecovery("same session reconnected");
64831
+ };
64832
+ intent.client.on("socket_close", lost);
64833
+ intent.client.on("registered", registered);
64834
+ intent.detach = () => {
64835
+ intent.client.off("socket_close", lost);
64836
+ intent.client.off("registered", registered);
64837
+ };
64838
+ }
64839
+ const status = () => {
64840
+ const snapshot2 = broker.status();
64841
+ return {
64842
+ ...snapshot2,
64843
+ preparing: !!pending,
64844
+ monitorRequested: !!requested,
64845
+ sessionScoped: true,
64846
+ ...recovering ? { state: recovering, recovery: "same-session native probe; no delivery until verified" } : {},
64847
+ ...snapshot2.monitorLifetime === "capped" ? {
64848
+ persistenceWarning: "This attached watch is time-limited, NOT session-persistent. Do not report persistence as verified.",
64849
+ persistenceInstruction: "If this host's Monitor supports persistent: true, replace ONLY this capped reader with the same watch command plus --persistent AND persistent: true. Otherwise keep re-arming before the cap. Do not start a duplicate reader or change the agent's autonomy."
64850
+ } : {}
64851
+ };
64852
+ };
64728
64853
  const result = (data) => ({ content: [{ type: "text", text: JSON.stringify(data) }] });
64729
64854
  const wrap2 = (fn) => async (args, extra) => {
64730
64855
  const signal = args.action === "off" || args.action === "status" || args.action === "check" ? void 0 : extra?.signal;
@@ -64750,6 +64875,8 @@ function registerTerminalLive(server2, deps) {
64750
64875
  if (!supported(host)) throw Error(`Native preview hosts are ${NATIVE_PILOT_PROFILES.join(" or ")}; set JEFRI_TERMINAL_HOST to the matching local connector host.`);
64751
64876
  if (!c2.identity?.username || !c2.identity.owner || !c2.token) throw Error("A named credential and its owner are required");
64752
64877
  const answer = await credentialProfileDecision(deps.hub(), c2.token);
64878
+ if (answer.lookup.kind === "locked" && /^(network|http_(408|425|429|5\d\d))$/.test(answer.lookup.reason))
64879
+ throw new NativeHubUnavailable("Native credential verification temporarily unavailable");
64753
64880
  const profile = answer.lookup.kind === "profile" ? answer.lookup.profile : null;
64754
64881
  if (!stillCurrent() || profile !== host || !mechanismAllowed(profile, true, "terminal_live", true) || answer.verifiedAt === null || !credentialProfileDecisionCurrent(answer))
64755
64882
  throw Error("Native live refused: the current connection needs a verified credential for this terminal host. Check its identity, hub and connection; changing autonomy will not repair a credential mismatch.");
@@ -64774,27 +64901,26 @@ function registerTerminalLive(server2, deps) {
64774
64901
  };
64775
64902
  }
64776
64903
  if (args.action === "status") return {
64777
- ...broker.status(),
64778
- preparing: !!pending,
64904
+ ...status(),
64779
64905
  hub: deps.hub(),
64780
64906
  username: deps.currentClient()?.identity?.username ?? null,
64781
64907
  autonomyEnabled: getAuto().enabled,
64782
64908
  scope: "owner-side DMs and groups; experimental; current interactive session must stay open; no 10k certification"
64783
64909
  };
64784
64910
  if (args.action === "check") {
64785
- const c3 = deps.currentClient();
64786
- if (!c3?.online) throw Error("Select and connect a Jefri identity in THIS session first: use jefri_whoami to verify username and hub, and jefri_be if unassigned. Do not take over another session without the user's approval.");
64787
- const hub2 = deps.hub(), token2 = c3.token, connectionGeneration2 = c3.generation;
64788
- const self2 = c3.identity?.username, owner3 = c3.identity?.owner, attempt2 = generation;
64789
- const stillCurrent = () => attempt2 === generation && deps.currentClient() === c3 && deps.hub() === hub2 && c3.online && c3.token === token2 && c3.generation === connectionGeneration2 && c3.identity?.username === self2 && c3.identity?.owner === owner3;
64790
- const authority = await verifyAuthority(c3, stillCurrent);
64911
+ const c2 = deps.currentClient();
64912
+ if (!c2?.online) throw Error("Select and connect a Jefri identity in THIS session first: use jefri_whoami to verify username and hub, and jefri_be if unassigned. Do not take over another session without the user's approval.");
64913
+ const hub = deps.hub(), token = c2.token, connectionGeneration = c2.generation;
64914
+ const self = c2.identity?.username, owner2 = c2.identity?.owner, attempt = generation;
64915
+ const stillCurrent = () => attempt === generation && deps.currentClient() === c2 && deps.hub() === hub && c2.online && c2.token === token && c2.generation === connectionGeneration && c2.identity?.username === self && c2.identity?.owner === owner2;
64916
+ const authority = await verifyAuthority(c2, stillCurrent);
64791
64917
  if (host === "codex_local") await checkCodexQueue();
64792
64918
  if (!stillCurrent() || !credentialProfileDecisionCurrent(authority)) throw Error("Native binding superseded during preflight");
64793
64919
  const auto = getAuto(), standby = standbyEngaged(), nativeActive = broker.active() || !!pending;
64794
64920
  return {
64795
64921
  state: "preflight only; no settings changed",
64796
- hub: hub2,
64797
- username: self2,
64922
+ hub,
64923
+ username: self,
64798
64924
  host,
64799
64925
  hubEligible: true,
64800
64926
  canPrepare: !auto.enabled && !standby && !nativeActive,
@@ -64808,6 +64934,10 @@ function registerTerminalLive(server2, deps) {
64808
64934
  };
64809
64935
  }
64810
64936
  if (args.action === "recover") return broker.recover(args.cursor, args.localCursor);
64937
+ if (requested) throw Error("Monitor is already requested in this session; inspect status or turn it off before preparing again");
64938
+ return prepare(args);
64939
+ }));
64940
+ async function prepare(args, recoveryIntent) {
64811
64941
  if (args.confirmPreview !== true) throw Error("Explicit user opt-in required: confirmPreview:true");
64812
64942
  if (host === "codex_local" && !deps.env.JEFRI_TERMINAL_THREAD_ID && !args.threadId)
64813
64943
  throw Error("Codex needs its exact live thread: run `printenv CODEX_THREAD_ID` in THIS session and pass it as threadId (or set JEFRI_TERMINAL_THREAD_ID before launching). A name, --last or a stale id is refused.");
@@ -64815,6 +64945,8 @@ function registerTerminalLive(server2, deps) {
64815
64945
  if (broker.active() || pending) throw Error("Turn native live off before preparing a new binding");
64816
64946
  const attempt = ++generation;
64817
64947
  const c2 = await deps.ensureClient();
64948
+ if (recoveryIntent && (!sameSession(recoveryIntent) || c2 !== recoveryIntent.client))
64949
+ throw Error("Monitor recovery session changed");
64818
64950
  const hub = deps.hub();
64819
64951
  const self = c2.identity?.username, owner2 = c2.identity?.owner;
64820
64952
  if (!self || !owner2 || !c2.token) throw Error("A named credential and its owner are required");
@@ -64834,6 +64966,7 @@ function registerTerminalLive(server2, deps) {
64834
64966
  return true;
64835
64967
  } catch {
64836
64968
  unconfirmedRelease = true;
64969
+ releaseRetryAfter = Date.now() + 65e3;
64837
64970
  return false;
64838
64971
  }
64839
64972
  })();
@@ -64884,6 +65017,7 @@ function registerTerminalLive(server2, deps) {
64884
65017
  await activation;
64885
65018
  activated = true;
64886
65019
  unconfirmedRelease = false;
65020
+ releaseRetryAfter = 0;
64887
65021
  if (!current()) {
64888
65022
  await releaseHub();
64889
65023
  throw Error("Native binding superseded");
@@ -64899,6 +65033,7 @@ function registerTerminalLive(server2, deps) {
64899
65033
  host,
64900
65034
  target: t.target,
64901
65035
  ready: () => t.ready(),
65036
+ monitorLifetime: () => !t.ready() ? "detached" : t.monitorDescriptor && !t.sessionWatch?.() ? "capped" : "session",
64902
65037
  reattachable: t.reattachable,
64903
65038
  ...t.monitorDescriptor ? { rearmCommand: deps.monitorCommand(t.monitorDescriptor) } : {},
64904
65039
  notify: (wake) => t.emit(claimPrompt(wake, self)),
@@ -64918,10 +65053,24 @@ function registerTerminalLive(server2, deps) {
64918
65053
  }
64919
65054
  };
64920
65055
  const nonce = randomUUID11();
64921
- pending = { nonce, until: Date.now() + 12e4, binding, transport: t };
65056
+ const intent = recoveryIntent ?? {
65057
+ client: c2,
65058
+ hub,
65059
+ token,
65060
+ self,
65061
+ owner: owner2,
65062
+ connectionGeneration,
65063
+ registered: true,
65064
+ args: { confirmPreview: true, replyMode, ...host === "codex_local" ? { threadId: deps.env.JEFRI_TERMINAL_THREAD_ID || args.threadId } : {} },
65065
+ detach() {
65066
+ }
65067
+ };
65068
+ pending = { nonce, until: recoveryIntent ? Infinity : Date.now() + 12e4, binding, transport: t, intent };
64922
65069
  bound = { hub, token };
64923
- pendingTimer = setTimeout(() => stop("native probe expired; nothing armed"), 12e4);
64924
- pendingTimer.unref();
65070
+ if (!recoveryIntent) {
65071
+ pendingTimer = setTimeout(() => stop("native probe expired; nothing armed"), 12e4);
65072
+ pendingTimer.unref();
65073
+ }
64925
65074
  await t.emit(probePrompt(nonce, self));
64926
65075
  if (!current()) throw Error("Native binding superseded");
64927
65076
  return {
@@ -64966,13 +65115,16 @@ function registerTerminalLive(server2, deps) {
64966
65115
  warning: "This tests transport only, not busy/approval/reconnect safety or broad release readiness."
64967
65116
  };
64968
65117
  } catch (e) {
64969
- if (attempt === generation) stop("native preparation failed");
65118
+ if (attempt === generation) {
65119
+ if (recoveryIntent) resetBinding("native recovery preparation failed");
65120
+ else stop("native preparation failed");
65121
+ }
64970
65122
  transport?.close();
64971
65123
  lock2.release();
64972
65124
  lock2.close();
64973
65125
  throw e;
64974
65126
  }
64975
- }));
65127
+ }
64976
65128
  server2.registerTool("jefri_terminal_ready", {
64977
65129
  title: "Confirm native terminal wake",
64978
65130
  description: "Transport callback only: use the nonce delivered through the native channel/queue/monitor in THIS conversation. Do not guess it or copy it between sessions. This completes the user's explicit preview opt-in.",
@@ -64990,7 +65142,10 @@ function registerTerminalLive(server2, deps) {
64990
65142
  p.binding.release();
64991
65143
  throw e;
64992
65144
  }
64993
- return { ...broker.status(), scope: "Same-owner agents and group text with tmux sender/routing rules. Native callback verified; actual host acceptance remains unverified. Host must stay running." };
65145
+ p.intent.connectionGeneration = p.intent.client.generation;
65146
+ rememberSession(p.intent);
65147
+ recovering = void 0;
65148
+ return { ...status(), scope: "Same-owner agents and group text with tmux sender/routing rules. Native callback verified; actual host acceptance remains unverified. Host must stay running. Monitor ON lasts only in this MCP session; reconnects require a fresh native callback, never a saved global ON setting." };
64994
65149
  }));
64995
65150
  server2.registerTool("jefri_terminal_claim", {
64996
65151
  title: "Claim native terminal delivery",
@@ -65004,14 +65159,14 @@ function registerTerminalLive(server2, deps) {
65004
65159
  }, wrap2(({ wake, text: text5 }) => broker.reply(wake, text5)));
65005
65160
  return {
65006
65161
  stop,
65007
- active: () => broker.active() || !!pending,
65162
+ active: () => broker.active() || !!pending || !!requested,
65008
65163
  reject(hub, token) {
65009
- if (bound?.hub === hub && bound.token === token) stop("credential rejected");
65164
+ if (bound?.hub === hub && bound.token === token || requested?.hub === hub && requested.token === token) stop("credential rejected");
65010
65165
  },
65011
65166
  offer: broker.offer,
65012
65167
  cancelGroup: broker.cancelGroup,
65013
65168
  unresolvedGroup: broker.unresolvedGroup,
65014
- status: broker.status
65169
+ status
65015
65170
  };
65016
65171
  }
65017
65172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.40",
3
+ "version": "0.49.41",
4
4
  "engines": {
5
5
  "node": ">=22.13"
6
6
  },