jefrichat-mcp 0.49.37 → 0.49.38

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 +83 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -63967,7 +63967,9 @@ function nativeHubRequest(client, operation, payload = {}, timeoutMs = 1e4) {
63967
63967
  return;
63968
63968
  }
63969
63969
  cleanup();
63970
- if (!event.result || typeof event.result !== "object" || event.result.ok !== true) reject(Error("Native hub refused this binding or operation"));
63970
+ if (!event.result || typeof event.result !== "object" || event.result.ok !== true) reject(Error(
63971
+ event.result?.reason === "native_terminal_disabled" ? "Native terminal monitor is disabled on this hub. It needs an operator-approved hub rollout; restarting Claude Code, starting tmux or running jefrichat-mcp run will not enable it. Leave any working headless responder unchanged." : "Native hub refused this binding or operation. The hub may have native monitoring disabled, or this credential/connection is not eligible. Check the selected hub and credential before changing autonomy; do not retry uncertain work."
63972
+ ));
63971
63973
  else resolve(event.result);
63972
63974
  };
63973
63975
  const timer2 = setTimeout(() => {
@@ -64394,6 +64396,23 @@ function codexQueueArgs(thread, text5, endpoint) {
64394
64396
  throw Error("Native Codex preview accepts only private local Unix endpoints");
64395
64397
  return ["queue", "--thread", thread, "--message", text5, ...endpoint ? ["--remote", endpoint] : []];
64396
64398
  }
64399
+ var codexProcessEnv = () => Object.fromEntries(
64400
+ ["PATH", "HOME", "USER", "LOGNAME", "LANG", "LC_ALL", "TMPDIR", "SHELL", "CODEX_HOME", "XDG_CONFIG_HOME"].flatMap((k) => process.env[k] === void 0 ? [] : [[k, process.env[k]]])
64401
+ );
64402
+ async function checkCodexQueue() {
64403
+ await new Promise((resolve, reject) => {
64404
+ execFile(
64405
+ "codex",
64406
+ ["queue", "--help"],
64407
+ { env: codexProcessEnv(), timeout: 5e3, maxBuffer: 128e3, windowsHide: true },
64408
+ (error2, stdout) => {
64409
+ if (error2 || !stdout.includes("--thread") || !stdout.includes("--message"))
64410
+ reject(Error("This Codex CLI does not expose the native queue interface. Update Codex before enabling monitor; leave any working responder unchanged."));
64411
+ else resolve();
64412
+ }
64413
+ );
64414
+ });
64415
+ }
64397
64416
  function validateCodexEndpoint(endpoint) {
64398
64417
  if (!endpoint || endpoint === "unix://") return;
64399
64418
  if (!endpoint.startsWith("unix:///")) throw Error("Native Codex endpoint must be a private local socket");
@@ -64415,9 +64434,7 @@ function codexQueueTransport(thread, endpoint) {
64415
64434
  validateCodexEndpoint(endpoint);
64416
64435
  let open = true;
64417
64436
  const children = /* @__PURE__ */ new Set();
64418
- const env = {};
64419
- for (const k of ["PATH", "HOME", "USER", "LOGNAME", "LANG", "LC_ALL", "TMPDIR", "SHELL", "CODEX_HOME", "XDG_CONFIG_HOME"])
64420
- if (process.env[k] !== void 0) env[k] = process.env[k];
64437
+ const env = codexProcessEnv();
64421
64438
  return {
64422
64439
  host: "codex_local",
64423
64440
  target: thread,
@@ -64474,7 +64491,7 @@ function registerTerminalLive(server2, deps) {
64474
64491
  };
64475
64492
  const result = (data) => ({ content: [{ type: "text", text: JSON.stringify(data) }] });
64476
64493
  const wrap2 = (fn) => async (args, extra) => {
64477
- const signal = args.action === "off" || args.action === "status" ? void 0 : extra?.signal;
64494
+ const signal = args.action === "off" || args.action === "status" || args.action === "check" ? void 0 : extra?.signal;
64478
64495
  const abort = () => stop("native tool cancelled by host");
64479
64496
  try {
64480
64497
  if (signal?.aborted) throw Error("Native request already cancelled");
@@ -64483,17 +64500,32 @@ function registerTerminalLive(server2, deps) {
64483
64500
  if (signal?.aborted) throw Error("Native request cancelled; already-dispatched effects cannot be undone");
64484
64501
  return result(data);
64485
64502
  } catch (e) {
64486
- return { ...result({ error: e instanceof Error ? e.message : "Native live refused" }), isError: true };
64503
+ return { ...result({
64504
+ error: e instanceof Error ? e.message : "Native live refused",
64505
+ ...args.action ? { hub: deps.hub(), host: host ?? null, username: deps.currentClient()?.identity?.username ?? null } : {}
64506
+ }), isError: true };
64487
64507
  } finally {
64488
64508
  signal?.removeEventListener("abort", abort);
64489
64509
  }
64490
64510
  };
64491
64511
  const host = deps.env.JEFRI_TERMINAL_HOST;
64492
64512
  const supported = (h) => NATIVE_PILOT_PROFILES.includes(String(h));
64513
+ const verifyAuthority = async (c2, stillCurrent, operation = "check", payload = {}) => {
64514
+ if (!supported(host)) throw Error(`Native preview hosts are ${NATIVE_PILOT_PROFILES.join(" or ")}; set JEFRI_TERMINAL_HOST to the matching local connector host.`);
64515
+ if (!c2.identity?.username || !c2.identity.owner || !c2.token) throw Error("A named credential and its owner are required");
64516
+ const answer = await credentialProfileDecision(deps.hub(), c2.token);
64517
+ const profile = answer.lookup.kind === "profile" ? answer.lookup.profile : null;
64518
+ if (!stillCurrent() || profile !== host || !mechanismAllowed(profile, true, "terminal_live", true) || answer.verifiedAt === null || !credentialProfileDecisionCurrent(answer))
64519
+ 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.");
64520
+ const permission = await nativeHubRequest(c2, operation, payload);
64521
+ if (!stillCurrent() || !credentialProfileDecisionCurrent(answer) || operation === "check" && permission.protocol !== 1)
64522
+ throw Error("Native hub fencing is unavailable for this binding");
64523
+ return { ...answer, verifiedAt: answer.verifiedAt };
64524
+ };
64493
64525
  server2.registerTool("jefri_terminal_live", {
64494
64526
  title: "Native terminal live (preview)",
64495
- description: "Explicitly opt in to experimental no-tmux delivery. Uses the same owner-side sender, sibling-agent loop brake and group routing as tmux. Groups respect pause and current membership. Headless/tmux must be OFF first. prepare sends a native wake probe; only its callback can arm this exact MCP session. off always works and changes no saved autonomy config. recover never replays work: pass nextLocalCursor as localCursor for the next local page, and hubPending.nextCursor as cursor independently for the next hub page; null ends that scan. Requires a running host; not detached persistence.",
64496
- inputSchema: { action: external_exports.enum(["status", "prepare", "off", "recover"]), confirmPreview: external_exports.boolean().optional(), replyMode: external_exports.enum(["mentions", "all", "dms"]).optional(), cursor: external_exports.string().max(256).optional(), localCursor: external_exports.string().max(256).optional(), threadId: external_exports.string().uuid().optional() }
64527
+ description: "Native terminal monitor ON/OFF: experimental visible automatic replies in THIS conversation, without tmux. 'monitor on' explicitly requests this preview, not headless or tmux. Use THIS connection's selected agent; verify with jefri_whoami and do not ask the user to choose again when already bound. FIRST call check on the selected identity BEFORE asking to turn off headless/tmux/standby; check tests hub and credential eligibility without changing any responder or arming a watch. If refused, leave working autonomy unchanged. After a successful check, explicitly confirm any switch with the user; headless/tmux/standby must be OFF for prepare. prepare requires confirmPreview:true and sends a wake probe; only its callback can arm this exact MCP session. In Codex, run `printenv CODEX_THREAD_ID` in THIS conversation and pass it as threadId; do not ask the user for it or guess from history. Uses owner-side sender, sibling-agent loop brake and group routing; respects group pause/membership. off changes no saved autonomy config. recover never replays work: nextLocalCursor feeds localCursor, hubPending.nextCursor feeds cursor independently; null ends a scan. Requires the current interactive Claude Code/Codex session to stay open, NOT jefrichat-mcp run or tmux. A successful check is not proof of Monitor attachment or delivery.",
64528
+ inputSchema: { action: external_exports.enum(["status", "check", "prepare", "off", "recover"]), confirmPreview: external_exports.boolean().optional(), replyMode: external_exports.enum(["mentions", "all", "dms"]).optional(), cursor: external_exports.string().max(256).optional(), localCursor: external_exports.string().max(256).optional(), threadId: external_exports.string().uuid().optional() }
64497
64529
  }, wrap2(async (args) => {
64498
64530
  if (args.action === "off") {
64499
64531
  stop();
@@ -64505,7 +64537,40 @@ function registerTerminalLive(server2, deps) {
64505
64537
  rollback: "Saved headless/tmux configuration is unchanged. Re-enable it explicitly if wanted."
64506
64538
  };
64507
64539
  }
64508
- if (args.action === "status") return { ...broker.status(), preparing: !!pending, scope: "owner-side DMs and groups; experimental; running host required; no 10k certification" };
64540
+ if (args.action === "status") return {
64541
+ ...broker.status(),
64542
+ preparing: !!pending,
64543
+ hub: deps.hub(),
64544
+ username: deps.currentClient()?.identity?.username ?? null,
64545
+ autonomyEnabled: getAuto().enabled,
64546
+ scope: "owner-side DMs and groups; experimental; current interactive session must stay open; no 10k certification"
64547
+ };
64548
+ if (args.action === "check") {
64549
+ const c3 = deps.currentClient();
64550
+ 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.");
64551
+ const hub2 = deps.hub(), token2 = c3.token, connectionGeneration2 = c3.generation;
64552
+ const self2 = c3.identity?.username, owner3 = c3.identity?.owner, attempt2 = generation;
64553
+ 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;
64554
+ const authority = await verifyAuthority(c3, stillCurrent);
64555
+ if (host === "codex_local") await checkCodexQueue();
64556
+ if (!stillCurrent() || !credentialProfileDecisionCurrent(authority)) throw Error("Native binding superseded during preflight");
64557
+ const auto = getAuto(), standby = standbyEngaged(), nativeActive = broker.active() || !!pending;
64558
+ return {
64559
+ state: "preflight only; no settings changed",
64560
+ hub: hub2,
64561
+ username: self2,
64562
+ host,
64563
+ hubEligible: true,
64564
+ canPrepare: !auto.enabled && !standby && !nativeActive,
64565
+ autonomyEnabled: auto.enabled,
64566
+ autonomyMode: auto.mode,
64567
+ standbyEngaged: standby,
64568
+ nativeActive,
64569
+ ...host === "codex_local" ? { threadInstruction: "Run `printenv CODEX_THREAD_ID` in THIS Codex conversation, then pass that exact UUID as threadId to prepare. Do not ask the user for a thread ID; do not use a name, --last or history lookup." } : {},
64570
+ nextStep: nativeActive ? "A native binding is already active or preparing; inspect status, do not start a second watch." : auto.enabled || standby ? "Hub check passed. Ask the user before switching off existing headless/tmux/standby; disabling can cancel in-flight work. Then prepare with explicit preview consent." : "With explicit user preview consent, call prepare. Only the wake callback can confirm this conversation is armed.",
64571
+ warning: "Eligibility is checked again at prepare. Monitor attachment and real host delivery have not been tested by this check."
64572
+ };
64573
+ }
64509
64574
  if (args.action === "recover") return broker.recover(args.cursor, args.localCursor);
64510
64575
  if (args.confirmPreview !== true) throw Error("Explicit user opt-in required: confirmPreview:true");
64511
64576
  if (host === "codex_local" && !deps.env.JEFRI_TERMINAL_THREAD_ID && !args.threadId)
@@ -64543,14 +64608,13 @@ function registerTerminalLive(server2, deps) {
64543
64608
  };
64544
64609
  const payload = (r) => ({ sourceId: r.id, bindingId, wake: r.wake, replyId: r.reply });
64545
64610
  const replyMode = args.replyMode ?? getAuto().replyMode;
64546
- const current = () => attempt === generation && deps.currentClient() === c2 && deps.hub() === hub && c2.token === token && c2.generation === connectionGeneration && c2.identity?.username === self && c2.identity?.owner === owner2 && c2.online && !getAuto().enabled && !standbyEngaged();
64611
+ const connected = () => attempt === generation && deps.currentClient() === c2 && deps.hub() === hub && c2.token === token && c2.generation === connectionGeneration && c2.identity?.username === self && c2.identity?.owner === owner2 && c2.online;
64612
+ const current = () => connected() && !getAuto().enabled && !standbyEngaged();
64613
+ if (host === "codex_local") await checkCodexQueue();
64547
64614
  const authorize = async () => {
64548
- const answer = await credentialProfileDecision(hub, token);
64549
- const profile = answer.lookup.kind === "profile" ? answer.lookup.profile : null;
64550
- if (!current() || profile !== host || !mechanismAllowed(profile, true, "terminal_live", true) || answer.verifiedAt === null || !credentialProfileDecisionCurrent(answer))
64551
- throw Error("Native live refused: credential profile, binding or authority unavailable; existing autonomy must be off");
64552
- const hubPermission = await nativeHubRequest(c2, activated ? "renew" : "check", { bindingId });
64553
- if (!current() || !activated && hubPermission.protocol !== 1) throw Error("Native hub fencing is unavailable for this binding");
64615
+ const answer = await verifyAuthority(c2, activated ? current : connected, activated ? "renew" : "check", { bindingId });
64616
+ if (!connected()) throw Error("Native binding superseded");
64617
+ if (!current()) throw Error("Native live cannot prepare: existing headless/tmux/standby must be off. The hub check passed, but ask the user before switching: disabling can cancel in-flight work. Nothing was changed.");
64554
64618
  return { verifiedAt: answer.verifiedAt, current: () => current() && credentialProfileDecisionCurrent(answer) };
64555
64619
  };
64556
64620
  await authorize();
@@ -65592,7 +65656,6 @@ async function runDoctor() {
65592
65656
  pass(`Connector jefrichat-mcp v${upd.current} (latest)`);
65593
65657
  else
65594
65658
  pass(`Connector jefrichat-mcp v${connectorVersion()}`, "couldn't reach npm to check for updates (offline is fine)");
65595
- pass(`Hub URL ${SERVER2}`);
65596
65659
  let savedEntries = [];
65597
65660
  let savedError = null;
65598
65661
  if (!TOKEN2) {
@@ -65602,6 +65665,9 @@ async function runDoctor() {
65602
65665
  savedError = String(e?.message ?? e);
65603
65666
  }
65604
65667
  }
65668
+ if (TOKEN2) info(`Hub URL ${SERVER2}`);
65669
+ else if (savedEntries.length) info("Saved-identity mode \u2014 each identity uses its saved hub (listed below); no identity is selected by doctor.");
65670
+ else info(`Default hub URL ${SERVER2} (no identity selected)`);
65605
65671
  const lock2 = describeLock(IDENTITIES_FILE());
65606
65672
  if (lock2.present)
65607
65673
  warn(`identities.json is locked (${lock2.meta ? `pid ${lock2.meta.pid}, ${lock2.status}` : "unreadable \u2014 ownership unknown"})`, lock2.status === "dead" ? `run: ${unlockCommandHint()}` : lock2.meta ? "a save may be in progress; if that process is gone, run: " + unlockCommandHint() : "see the identity unlock message for the safe recovery step");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.37",
3
+ "version": "0.49.38",
4
4
  "engines": {
5
5
  "node": ">=22.13"
6
6
  },