@martintrojer/murmur 0.2.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -412,6 +412,21 @@ declare function pidAlive(pid: number): boolean;
412
412
  * owns the pane, so this reads the same for a local and a remote pane -- a
413
413
  * reader cannot resolve a remote window id against its own tmux.
414
414
  *
415
+ * The session name is shortened to its last segment, and that is load-bearing
416
+ * rather than cosmetic. It is reached far more often than it looks: `agent_name`
417
+ * is set only by mu, `window_name` is null whenever tmux is auto-renaming (its
418
+ * default), and `pi_session` is null for the whole life of an unnamed session --
419
+ * pi's own auto-namer runs at CLOSE, so a live agent, which is exactly the one
420
+ * you are looking at, has no session name yet. So the common row for a
421
+ * hand-started pi fell through to here and printed `hacking/murmur`: a path,
422
+ * where a name belongs.
423
+ *
424
+ * The full session name is not lost. The picker's stream column shows it, and
425
+ * shows it precisely BECAUSE of this shortening: that column is blanked when it
426
+ * would repeat the name, so `hacking/murmur` in both cells collapsed to one
427
+ * path and a blank. Shortened, the row reads `murmur` + `hacking/murmur` -- the
428
+ * same width, carrying strictly more.
429
+ *
415
430
  * Falls back to the window id only when a node recorded no names at all, which
416
431
  * means a non-tmux harness.
417
432
  */
@@ -457,6 +472,36 @@ declare const ssh: Channel;
457
472
  declare function hasWarmSocket(target: string): boolean;
458
473
 
459
474
  declare const MAX_CONCURRENT_PEERS = 8;
475
+ /**
476
+ * The optional half of a collect, as a bag rather than four positional
477
+ * arguments.
478
+ *
479
+ * `collect(store, ssh, now, undefined, mux)` was already the call site before
480
+ * the floor was added, and a fifth positional -- a bare number, next to another
481
+ * bare number -- is how `now` and `floorMs` get silently swapped.
482
+ */
483
+ type CollectOptions = {
484
+ /** Bounds the whole run. Injectable so tests need not wait out real time. */
485
+ deadline?: Promise<void>;
486
+ /** The mux reconciliation asks which panes are alive. */
487
+ mux?: Mux;
488
+ /**
489
+ * Skip peers attempted within this many ms. Zero -- the default -- fetches
490
+ * every peer, which is what a deliberate `murmur collect` and the picker both
491
+ * want. Only the status bar, which repaints on a timer, passes a floor.
492
+ *
493
+ * Opt IN rather than opt out: a surface that forgets this argument keeps the
494
+ * old always-fetch behaviour, which is merely wasteful. The opposite default
495
+ * would mean a new surface silently serves stale data.
496
+ */
497
+ floorMs?: number;
498
+ /**
499
+ * Source of the floor's jitter, in [0, 1). Injected for the same reason `now`
500
+ * and `isAlive` are: a test pins both edges of the window by returning 0 and
501
+ * a value approaching 1, rather than sampling and hoping.
502
+ */
503
+ random?: () => number;
504
+ };
460
505
  type CollectResult = {
461
506
  peer: string;
462
507
  ok: boolean;
@@ -486,7 +531,7 @@ type CollectResult = {
486
531
  * One round trip per peer, and never a second: the document is complete, so what
487
532
  * arrives either replaces the cache entirely or does not touch it.
488
533
  */
489
- declare function collect(store: Store, channel: Channel, now?: number, deadline?: Promise<void>): Promise<CollectResult[]>;
534
+ declare function collect(store: Store, channel: Channel, now?: number, options?: CollectOptions): Promise<CollectResult[]>;
490
535
 
491
536
  declare function glance(store: Store, agent: PaneView, lines?: number): string | null;
492
537
 
@@ -554,8 +599,15 @@ declare function status(store: Store, identity: NodeIdentity, now?: number): Sta
554
599
  * display-popup, so one sleeping laptop would otherwise write ssh diagnostics
555
600
  * to stderr several times a minute, forever. `murmur collect`, which a human
556
601
  * runs deliberately, is the only place that prints.
602
+ *
603
+ * `floorMs` is how a caller says whether it is a TIMER or a PERSON. The status
604
+ * bar repaints on `status-interval` and passes COLLECT_FLOOR_MS, so fetch rate
605
+ * stops being tied to redraw rate. The picker passes nothing: pressing the key
606
+ * is a person asking now, and its `^r` reload comes back through here too --
607
+ * a refresh key that skipped the fetch would be a key that silently does
608
+ * nothing.
557
609
  */
558
- declare function statusWithCollect(store: Store, identity: NodeIdentity, now?: number, channel?: Channel): Promise<Status>;
610
+ declare function statusWithCollect(store: Store, identity: NodeIdentity, now?: number, channel?: Channel, options?: CollectOptions): Promise<Status>;
559
611
 
560
612
  declare const MURMUR_VERSION: string;
561
613
 
package/dist/index.js CHANGED
@@ -62,6 +62,10 @@ function runTmux(args) {
62
62
  return null;
63
63
  }
64
64
  }
65
+ function chosenWindowName(name, autoRename) {
66
+ if (autoRename === "1") return null;
67
+ return name || null;
68
+ }
65
69
  function exactSession(session) {
66
70
  return `=${session}`;
67
71
  }
@@ -81,16 +85,16 @@ var tmux = {
81
85
  "-t",
82
86
  pane,
83
87
  "-p",
84
- "#{session_id} #{window_id} #{session_name} #{window_name}"
88
+ "#{session_id} #{window_id} #{session_name} #{window_name} #{?automatic-rename,1,0}"
85
89
  ]);
86
- const [session, window, sessionName, windowName] = fields?.split(" ") ?? [];
90
+ const [session, window, sessionName, windowName, autoRename] = fields?.split(" ") ?? [];
87
91
  if (!session || !window) return null;
88
92
  return {
89
93
  session: asSessionId(session),
90
94
  window: asWindowId(window),
91
95
  pane,
92
96
  session_name: sessionName || null,
93
- window_name: windowName || null
97
+ window_name: chosenWindowName(windowName, autoRename)
94
98
  };
95
99
  },
96
100
  // Which of this host's PANES still exist. The only liveness question tmux is
@@ -181,8 +185,12 @@ function pidAlive(pid) {
181
185
  }
182
186
 
183
187
  // src/agents.ts
188
+ function sessionLeaf(session) {
189
+ const leaf = session.split("/").filter(Boolean).at(-1);
190
+ return leaf ?? session;
191
+ }
184
192
  function agentLabel(agent) {
185
- const name = agent.agent_name ?? agent.pi_session ?? agent.window_name ?? agent.session_name;
193
+ const name = agent.agent_name ?? agent.pi_session ?? agent.window_name ?? (agent.session_name === null ? null : sessionLeaf(agent.session_name));
186
194
  return terminalText(name ?? agent.window);
187
195
  }
188
196
  function agentLocation(agent) {
@@ -576,6 +584,7 @@ function viewSort(views) {
576
584
  // src/collector.ts
577
585
  var MAX_CONCURRENT_PEERS = 8;
578
586
  var COLLECT_DEADLINE_MS = 4e3;
587
+ var COLLECT_JITTER_MS = 2e4;
579
588
  async function mapSettled(items, limit, task, deadline) {
580
589
  const results = new Array(items.length);
581
590
  let cursor = 0;
@@ -597,6 +606,14 @@ async function mapSettled(items, limit, task, deadline) {
597
606
  await (stop ? Promise.race([pool, stop]) : pool);
598
607
  return results;
599
608
  }
609
+ function duePeers(peers, now, floorMs, random) {
610
+ if (floorMs <= 0) return [...peers];
611
+ return peers.filter((peer) => {
612
+ if (peer.last_attempt_at === null) return true;
613
+ const jitter = (random() - 0.5) * COLLECT_JITTER_MS;
614
+ return now - peer.last_attempt_at >= floorMs + jitter;
615
+ });
616
+ }
600
617
  function isUnreachable(message) {
601
618
  return /Host is down|No route to host|Connection refused|Connection timed out|Connection closed|Operation timed out|Network is unreachable|Name or service not known|Could not resolve hostname|timed out after/i.test(
602
619
  message
@@ -611,11 +628,12 @@ function stripInvocation(message) {
611
628
  function normalizeFailure(message) {
612
629
  return stripInvocation(message).replace(/\s+/g, " ").trim();
613
630
  }
614
- async function collect(store, channel, now = Date.now(), deadline) {
631
+ async function collect(store, channel, now = Date.now(), options = {}) {
632
+ const { deadline, mux = tmux, floorMs = 0, random = Math.random } = options;
615
633
  const results = [];
616
634
  let timer;
617
635
  try {
618
- const peers = store.peers();
636
+ const peers = duePeers(store.peers(), now, floorMs, random);
619
637
  const bounded = deadline ?? new Promise((resolve) => {
620
638
  timer = setTimeout(resolve, COLLECT_DEADLINE_MS);
621
639
  timer.unref?.();
@@ -658,7 +676,7 @@ async function collect(store, channel, now = Date.now(), deadline) {
658
676
  clearTimeout(timer);
659
677
  }
660
678
  try {
661
- store.reconcileLocal({ panes: tmux.livePanes(), now });
679
+ store.reconcileLocal({ panes: mux.livePanes(), now });
662
680
  } catch {
663
681
  }
664
682
  return results;
@@ -783,9 +801,9 @@ function status(store, identity, now = Date.now()) {
783
801
  }))
784
802
  };
785
803
  }
786
- async function statusWithCollect(store, identity, now = Date.now(), channel = ssh) {
804
+ async function statusWithCollect(store, identity, now = Date.now(), channel = ssh, options = {}) {
787
805
  try {
788
- await collect(store, channel, now);
806
+ await collect(store, channel, now, options);
789
807
  } catch {
790
808
  }
791
809
  return status(store, identity, now);