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 +4 -1
- package/CHANGELOG.md +7 -0
- package/dist/tui.js +29 -9
- package/package.json +1 -1
- package/tui-plugins/sesh-panel/tui.tsx +22 -7
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
|
-
|
|
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 (
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
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()
|