opencode-sesh 2.1.3 → 2.1.4

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/AGENTS.md CHANGED
@@ -181,7 +181,10 @@ when opencode exits, the picker reopens with the query preserved.
181
181
  scrolling inside; the picker remains the place for transcript search.
182
182
  Sessions waiting on the user (unanswered questions, stuck runs) pin a
183
183
  **Needs input** group above the directories, with the count in the heading —
184
- same `NEEDS_INPUT_SQL` heuristic as `bin/sesh-waiting.sh`.
184
+ same `NEEDS_INPUT_SQL` heuristic as `bin/sesh-waiting.sh`. The query is
185
+ synchronous on the TUI thread, so the shared poll runs it only while the
186
+ sidebar is subscribed (`poll(listener, { waiting: true })`); the home list
187
+ never triggers it.
185
188
  - **Picker:** a custom dialog (not `DialogSelect`) grouped by
186
189
  project/directory, recency-ordered, resume via
187
190
  `api.route.navigate("session", …)`. `option+o` and the command palette open it;
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.4] - 2026-11-02
9
+
10
+ ### Fixed
11
+
12
+ - **tui:** Skip the needs-input query when no sidebar consumes it (#33)
13
+
8
14
  ## [2.1.3] - 2026-11-02
9
15
 
10
16
  ### Fixed
@@ -530,6 +536,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
530
536
  - Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
531
537
  CI.
532
538
 
539
+ [2.1.4]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.3...v2.1.4
533
540
  [2.1.3]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.2...v2.1.3
534
541
  [2.1.2]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.1...v2.1.2
535
542
  [2.1.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.0...v2.1.1
package/dist/tui.js CHANGED
@@ -637,7 +637,9 @@ async function openTranscriptDb() {
637
637
  }
638
638
  }
639
639
  function createSessionPoll(api, refreshPins) {
640
- const listeners = new Set();
640
+ // Listener -> whether it reads `snapshot.waiting`. The needs-input query is
641
+ // synchronous on the TUI thread, so it only runs while such a listener exists.
642
+ const listeners = new Map();
641
643
  let snapshot = {
642
644
  entries: [],
643
645
  waiting: new Map(),
@@ -645,7 +647,11 @@ function createSessionPoll(api, refreshPins) {
645
647
  };
646
648
  let clock;
647
649
  let inFlight;
650
+ // Whether `snapshot.waiting` came from a poll that ran the query; a poll
651
+ // without waiting listeners clears it so a later subscriber never sees a stale set.
652
+ let waitingLoaded = false;
648
653
  const active = () => listeners.size > 0;
654
+ const wantsWaiting = () => [...listeners.values()].some(Boolean);
649
655
  const load = () => {
650
656
  if (inFlight || !active()) return;
651
657
  inFlight = (async () => {
@@ -671,30 +677,42 @@ function createSessionPoll(api, refreshPins) {
671
677
  };
672
678
  }
673
679
  if (!active()) return;
674
- const db = await openTranscriptDb();
680
+ const db = wantsWaiting() ? await openTranscriptDb() : undefined;
675
681
  if (db) {
676
682
  try {
677
- if (active()) snapshot = {
678
- ...snapshot,
679
- waiting: await queryWaitingIds(db)
680
- };
683
+ if (wantsWaiting()) {
684
+ snapshot = {
685
+ ...snapshot,
686
+ waiting: await queryWaitingIds(db)
687
+ };
688
+ waitingLoaded = true;
689
+ }
681
690
  } catch {
682
691
  // Keep the previous waiting set when the local store is unavailable.
683
692
  } finally {
684
693
  db.close();
685
694
  }
695
+ } else if (!wantsWaiting()) {
696
+ snapshot = {
697
+ ...snapshot,
698
+ waiting: new Map()
699
+ };
700
+ waitingLoaded = false;
686
701
  }
687
- for (const listener of listeners) listener(snapshot);
702
+ for (const listener of [...listeners.keys()]) listener(snapshot);
688
703
  })().finally(() => {
689
704
  inFlight = undefined;
690
705
  });
691
706
  };
692
- return listener => {
693
- listeners.add(listener);
707
+ return (listener, options = {}) => {
708
+ const needsWaiting = options.waiting === true;
709
+ listeners.set(listener, needsWaiting);
694
710
  listener(snapshot);
695
711
  if (!clock) {
696
712
  load();
697
713
  clock = setInterval(load, POLL_MS);
714
+ } else if (needsWaiting && !waitingLoaded) {
715
+ load();
698
716
  }
699
717
  return () => {
700
718
  listeners.delete(listener);
@@ -1212,6 +1230,8 @@ function SidebarSessions(props) {
1212
1230
  setEntries(snapshot.entries);
1213
1231
  setLoadFailed(snapshot.failed);
1214
1232
  setWaiting(snapshot.waiting);
1233
+ }, {
1234
+ waiting: true
1215
1235
  })));
1216
1236
  const filteredEntries = createMemo(() => {
1217
1237
  const q = query().trim().toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sesh",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "OpenCode session browser: search, preview, and resume every session across every project from the terminal or TUI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -540,11 +540,17 @@ async function openTranscriptDb(): Promise<any | undefined> {
540
540
  type SessionSnapshot = { entries: Entry[]; waiting: Map<string, string>; failed: boolean }
541
541
 
542
542
  function createSessionPoll(api: TuiPluginApi, refreshPins: () => void) {
543
- const listeners = new Set<(snapshot: SessionSnapshot) => void>()
543
+ // Listener -> whether it reads `snapshot.waiting`. The needs-input query is
544
+ // synchronous on the TUI thread, so it only runs while such a listener exists.
545
+ const listeners = new Map<(snapshot: SessionSnapshot) => void, boolean>()
544
546
  let snapshot: SessionSnapshot = { entries: [], waiting: new Map(), failed: false }
545
547
  let clock: ReturnType<typeof setInterval> | undefined
546
548
  let inFlight: Promise<void> | undefined
549
+ // Whether `snapshot.waiting` came from a poll that ran the query; a poll
550
+ // without waiting listeners clears it so a later subscriber never sees a stale set.
551
+ let waitingLoaded = false
547
552
  const active = () => listeners.size > 0
553
+ const wantsWaiting = () => [...listeners.values()].some(Boolean)
548
554
  const load = () => {
549
555
  if (inFlight || !active()) return
550
556
  inFlight = (async () => {
@@ -563,25 +569,34 @@ function createSessionPoll(api: TuiPluginApi, refreshPins: () => void) {
563
569
  snapshot = { ...snapshot, failed: true }
564
570
  }
565
571
  if (!active()) return
566
- const db = await openTranscriptDb()
572
+ const db = wantsWaiting() ? await openTranscriptDb() : undefined
567
573
  if (db) {
568
574
  try {
569
- if (active()) snapshot = { ...snapshot, waiting: await queryWaitingIds(db) }
575
+ if (wantsWaiting()) {
576
+ snapshot = { ...snapshot, waiting: await queryWaitingIds(db) }
577
+ waitingLoaded = true
578
+ }
570
579
  } catch {
571
580
  // Keep the previous waiting set when the local store is unavailable.
572
581
  } finally {
573
582
  db.close()
574
583
  }
584
+ } else if (!wantsWaiting()) {
585
+ snapshot = { ...snapshot, waiting: new Map() }
586
+ waitingLoaded = false
575
587
  }
576
- for (const listener of listeners) listener(snapshot)
588
+ for (const listener of [...listeners.keys()]) listener(snapshot)
577
589
  })().finally(() => { inFlight = undefined })
578
590
  }
579
- return (listener: (snapshot: SessionSnapshot) => void) => {
580
- listeners.add(listener)
591
+ return (listener: (snapshot: SessionSnapshot) => void, options: { waiting?: boolean } = {}) => {
592
+ const needsWaiting = options.waiting === true
593
+ listeners.set(listener, needsWaiting)
581
594
  listener(snapshot)
582
595
  if (!clock) {
583
596
  load()
584
597
  clock = setInterval(load, POLL_MS)
598
+ } else if (needsWaiting && !waitingLoaded) {
599
+ load()
585
600
  }
586
601
  return () => {
587
602
  listeners.delete(listener)
@@ -1098,7 +1113,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
1098
1113
  setEntries(snapshot.entries)
1099
1114
  setLoadFailed(snapshot.failed)
1100
1115
  setWaiting(snapshot.waiting)
1101
- })))
1116
+ }, { waiting: true })))
1102
1117
 
1103
1118
  const filteredEntries = createMemo(() => {
1104
1119
  const q = query().trim().toLowerCase()