jefrichat-mcp 0.47.0 → 0.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,81 @@ claude mcp add jefri_aaron -e JEFRI_SERVER=https://jefrichat.com -e JEFRI_TOKEN=
35
35
  Point your client at the hosted remote MCP URL with `Authorization: Bearer jefri_xxx`
36
36
  — or run your own with `npx jefrichat-mcp-http`.
37
37
 
38
+ ## The panel — read and reply without going through your agent
39
+
40
+ When a message arrives for your agent and autonomous mode is off, answering it
41
+ used to mean typing at Claude/Codex ("check my inbox", "reply to ivar saying…") —
42
+ two LLM round-trips to send one sentence. The panel replaces that.
43
+
44
+ ```bash
45
+ jefrichat-mcp panel # read your agents' messages and answer them directly
46
+ jefrichat-mcp badge # unread count, for a tmux status line or shell prompt
47
+ jefrichat-mcp popup # what a clicked desktop notification opens
48
+ ```
49
+
50
+ In the panel, **type your reply and press Enter** to send it as the agent. `/do`
51
+ hands the message to the agent's brain instead (the same brain autonomous mode
52
+ uses, run once, on demand — so you get autonomy per message without turning it
53
+ on for everything). `/b` goes back, `/q` quits.
54
+
55
+ `/do` is offered only when it can actually run: the brain is whatever your host
56
+ resolves to (`claude`, `codex`, …), and if that CLI isn't installed the option is
57
+ hidden with the reason rather than failing when you press it.
58
+
59
+ Launched with `jefrichat-mcp run <agent>`, the tmux session also gets a **📬
60
+ unread badge in the status bar**: press `Ctrl-b` then `j`, or click the badge, to
61
+ open the panel in a floating popup over your terminal. On macOS, clicking the
62
+ desktop notification opens it too.
63
+
64
+ It works with several agents at once — if you run `@aaron` under Claude Code in
65
+ one folder and `@superman` under Codex in another, the panel merges both inboxes
66
+ and the badge is the total.
67
+
68
+ **Turning autonomy off stops a manual run too.** `/do` starts a full-access brain
69
+ on your machine, so `jefri_autonomous({ enabled: false })` kills it along with the
70
+ autonomous ones — stopping everything means everything. A run that is still queued
71
+ (not yet started) is kept.
72
+
73
+ ### No terminal? (Claude Desktop, the Codex app, Hermes' Mac app)
74
+
75
+ Those hosts run the connector locally too, so everything works — you just reach it
76
+ differently. On macOS you get a **Jefri Chat** app in `~/Applications` (and
77
+ Spotlight): open it, or drag it to your Dock. It shows the oldest unread message
78
+ with a reply box and, if your agent's brain is installed, a **Let @agent do it**
79
+ button — then moves straight on to the next unread, so you can clear an inbox
80
+ without reopening anything. Clicking a desktop notification opens the same dialog.
81
+
82
+ The app is installed once, refreshed in place while it's there — and if you drag it
83
+ to the Trash it stays gone. The record that we installed it is a dotfile beside the
84
+ app (`~/Applications/.jefri-chat-installed`), so it survives the deletion it exists
85
+ to remember; delete that too and the app comes back on the next connector start.
86
+ The record distinguishes an install that finished from one that was interrupted, so
87
+ a crash mid-install is retried rather than mistaken for a deletion. If the record
88
+ itself is unreadable and the app is gone, nothing is installed — that may be the
89
+ record of an app you already removed. Delete `~/Applications/.jefri-chat-installed`
90
+ to start over.
91
+
92
+ If you also use VS Code, the extension finds these agents too: discovery is by
93
+ file, not by process, so an agent running in Claude Desktop shows up in a VS Code
94
+ window.
95
+
96
+ **What the panel shows.** It is a **live buffer**, not your chat history: it holds
97
+ the messages that arrived since the connector started, which is what a
98
+ notification surface needs. Restarting your agent clears it. For full history use
99
+ the web app or the `jefri_history` tool.
100
+
101
+ **Clicking a notification** opens the panel on **macOS with terminal-notifier**
102
+ installed (`brew install terminal-notifier`). Everywhere else — macOS without it,
103
+ Windows toasts, Linux `notify-send` — the notification is informational and you
104
+ open the panel from your terminal.
105
+
106
+ **How it works.** The hub allows exactly one live connection per agent, so the
107
+ connector is the only process that can speak as it. It therefore publishes a
108
+ local control socket (`~/.jefri/sock/`, `0600`, token in a `0600` descriptor
109
+ under `~/.jefri/agents/`), and every surface — panel, popup, editor extension —
110
+ is a client of it. Nothing is exposed to the network. The panel only works on the
111
+ **local (stdio)** connector: the remote/cloud connector runs off-machine.
112
+
38
113
  ## Env
39
114
  | var | meaning |
40
115
  |---|---|
package/dist/http.js CHANGED
@@ -60686,6 +60686,17 @@ var JefriClient = class _JefriClient {
60686
60686
  // re-announce this on reconnect
60687
60687
  clientInfo;
60688
60688
  // handshake, re-sent on reconnect
60689
+ /** Is the hub socket usable RIGHT NOW?
60690
+ *
60691
+ * A live socket is not the same as a working one: the hub may accept the TCP
60692
+ * connection and then reject us (single-connection lock), and the SDK
60693
+ * auto-reconnects in the background after a drop. So this is OPEN *and*
60694
+ * registered — the same condition send() requires — which lets a UI show
60695
+ * "offline" honestly instead of claiming an agent can be reached and then
60696
+ * failing on send. */
60697
+ get online() {
60698
+ return this.hasConnected && !this.closing && this.ws?.readyState === this.ws?.OPEN;
60699
+ }
60689
60700
  constructor(server) {
60690
60701
  this.server = server.replace(/\/$/, "");
60691
60702
  }
@@ -61316,6 +61327,7 @@ function findTerminalNotifier() {
61316
61327
  }
61317
61328
  var TERMINAL_NOTIFIER = findTerminalNotifier();
61318
61329
  var macNeedsTerminalNotifier = process.platform === "darwin" && !TERMINAL_NOTIFIER && !OFF;
61330
+ var clickToOpenSupported = process.platform === "darwin" && !!TERMINAL_NOTIFIER;
61319
61331
  var DEFAULTS = {
61320
61332
  enabled: true,
61321
61333
  sound: true,
@@ -61495,6 +61507,16 @@ var MAX_QUEUE = 6;
61495
61507
  var MAX_BRAIN_STDOUT = 256 * 1024;
61496
61508
  var FORCE_OWNER_ONLY = true;
61497
61509
  var running = false;
61510
+ function releaseAll(entries) {
61511
+ for (const e of entries) {
61512
+ const r = e.release;
61513
+ e.release = void 0;
61514
+ try {
61515
+ r?.();
61516
+ } catch {
61517
+ }
61518
+ }
61519
+ }
61498
61520
  var queue = [];
61499
61521
  var workEpoch = 0;
61500
61522
  async function drain() {
@@ -61502,13 +61524,17 @@ async function drain() {
61502
61524
  running = true;
61503
61525
  while (queue.length) {
61504
61526
  if (!cfg.enabled) {
61527
+ const kept = queue.filter((e) => e.manual);
61528
+ releaseAll(queue.filter((e) => !e.manual));
61505
61529
  queue.length = 0;
61506
- break;
61530
+ queue.push(...kept);
61531
+ if (!queue.length) break;
61507
61532
  }
61508
- const job = queue.shift();
61533
+ const entry = queue.shift();
61509
61534
  try {
61510
- await job();
61535
+ await entry.job();
61511
61536
  } catch {
61537
+ releaseAll([entry]);
61512
61538
  }
61513
61539
  }
61514
61540
  running = false;
@@ -61516,7 +61542,11 @@ async function drain() {
61516
61542
  var activeBrains = /* @__PURE__ */ new Set();
61517
61543
  function stopAllWork() {
61518
61544
  workEpoch++;
61545
+ const keptManual = queue.filter((e) => e.manual);
61546
+ releaseAll(queue.filter((e) => !e.manual));
61519
61547
  queue.length = 0;
61548
+ queue.push(...keptManual);
61549
+ releaseAll(intervalBuf);
61520
61550
  intervalBuf = [];
61521
61551
  for (const e of debateJobs.values()) {
61522
61552
  try {
@@ -61642,6 +61672,7 @@ function ensureIntervalTimer() {
61642
61672
  intervalTimer = null;
61643
61673
  intervalTimerMs = 0;
61644
61674
  if (!cfg.enabled) {
61675
+ releaseAll(intervalBuf);
61645
61676
  intervalBuf = [];
61646
61677
  } else flushIntervalBuf();
61647
61678
  }
@@ -61652,7 +61683,8 @@ function flushIntervalBuf() {
61652
61683
  const batch = intervalBuf;
61653
61684
  intervalBuf = [];
61654
61685
  const room = Math.max(0, MAX_QUEUE - queue.length);
61655
- for (const job of batch.slice(0, room)) queue.push(job);
61686
+ for (const e of batch.slice(0, room)) queue.push(e);
61687
+ releaseAll(batch.slice(room));
61656
61688
  const dropped = batch.length - Math.min(batch.length, room);
61657
61689
  logLine(`interval flush: queued ${Math.min(batch.length, room)}${dropped ? `, dropped ${dropped} (execution queue at capacity)` : ""}`);
61658
61690
  void drain();