opencode-sesh 0.7.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.2] - 2026-09-24
11
+
12
+ ### Fixed
13
+
14
+ - Switching to a session from the picker (`ctrl+o` / `/sesh`), the home
15
+ "Recent sessions" list, or the needs-input picker now runs the cmux
16
+ click-to-focus bridge: a session already live in another cmux workspace
17
+ focuses that workspace instead of opening a duplicate here. Previously only
18
+ sidebar rows did this.
19
+
20
+ ### Changed
21
+
22
+ - Every open path shares one cmux-aware handler, and the cmux surface scan is
23
+ cached briefly so the additional paths add no noticeable delay. Terminals
24
+ outside cmux are unaffected — with no `CMUX_WORKSPACE_ID` no cmux call is
25
+ made, now asserted by a test.
10
26
  ## [0.7.1] - 2026-09-24
11
27
 
12
28
  ### Fixed
@@ -302,7 +318,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
302
318
  - Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
303
319
  CI.
304
320
 
305
- [Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.1...HEAD
321
+ [Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.2...HEAD
322
+ [0.7.2]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.1...v0.7.2
306
323
  [0.7.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.0...v0.7.1
307
324
  [0.7.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.6.0...v0.7.0
308
325
  [0.6.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.5.0...v0.6.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sesh",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "description": "Browse, search, preview and resume opencode sessions from the terminal and the TUI.",
6
6
  "license": "MIT",
@@ -437,6 +437,18 @@ async function cmuxSurfaces(): Promise<CmuxSurface[]> {
437
437
  return surfaces
438
438
  }
439
439
 
440
+ // The bridge walks `cmux tree` plus one `surface resume show` per surface, and
441
+ // switching sessions is frequent while the surface set changes rarely, so reuse
442
+ // a recent scan for a moment instead of re-walking cmux on every open.
443
+ let cmuxSurfacesCache: { at: number; surfaces: CmuxSurface[] } | undefined
444
+ async function cmuxSurfacesCached(): Promise<CmuxSurface[]> {
445
+ const now = Date.now()
446
+ if (cmuxSurfacesCache && now - cmuxSurfacesCache.at < 1500) return cmuxSurfacesCache.surfaces
447
+ const surfaces = await cmuxSurfaces()
448
+ cmuxSurfacesCache = { at: now, surfaces }
449
+ return surfaces
450
+ }
451
+
440
452
  // Pure selection: the workspace to focus for a session, or undefined to open
441
453
  // locally. Prefers a live surface in a different workspace; ignores the
442
454
  // caller's own surface and workspace (focusing where you already are is a
@@ -452,6 +464,26 @@ function pickFocusWorkspace(surfaces: CmuxSurface[], sessionID: string): string
452
464
  return undefined
453
465
  }
454
466
 
467
+ // Open a session from anywhere in the panel, cmux-aware: if another cmux
468
+ // workspace is already running it, focus that workspace instead of opening a
469
+ // duplicate here; otherwise open it locally. Every switch path (sidebar, home
470
+ // list, picker, needs-input picker) goes through here so none of them silently
471
+ // skips the bridge. cmux calls only run inside cmux.
472
+ async function openSessionEntry(api: TuiPluginApi, entry: Entry): Promise<void> {
473
+ if (process.env.CMUX_WORKSPACE_ID) {
474
+ const target = pickFocusWorkspace(await cmuxSurfacesCached(), entry.id)
475
+ if (target) {
476
+ try {
477
+ await promisify(execFile)("cmux", ["workspace", "select", target])
478
+ return
479
+ } catch {
480
+ // cmux unavailable; fall through to a local open
481
+ }
482
+ }
483
+ }
484
+ api.route.navigate("session", { sessionID: entry.id })
485
+ }
486
+
455
487
  function addTranscript(index: Map<string, string>, sid: string, data: string): void {
456
488
  try {
457
489
  const part = JSON.parse(data) as { type?: unknown; text?: unknown }
@@ -853,23 +885,9 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
853
885
  setSectionCollapsed((value) => !value)
854
886
  }
855
887
 
856
- // Open a session. If a cmux surface is already running it in another
857
- // workspace, focus that workspace instead of opening a duplicate here;
858
- // otherwise open it locally. `cmux` calls only run inside cmux.
859
- const openSession = async (entry: Entry) => {
860
- if (process.env.CMUX_WORKSPACE_ID) {
861
- const target = pickFocusWorkspace(await cmuxSurfaces(), entry.id)
862
- if (target) {
863
- try {
864
- await promisify(execFile)("cmux", ["workspace", "select", target])
865
- return
866
- } catch {
867
- // cmux unavailable; fall through to a local open
868
- }
869
- }
870
- }
871
- props.api.route.navigate("session", { sessionID: entry.id })
872
- }
888
+ // Sidebar rows open through the shared cmux-aware path, so they focus the
889
+ // workspace already running the session instead of opening a duplicate.
890
+ const openSession = (entry: Entry) => openSessionEntry(props.api, entry)
873
891
 
874
892
  // Flat order of the rows a cursor can land on, so keyboard navigation and
875
893
  // mouse hover resolve to the same row.
@@ -1449,7 +1467,7 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
1449
1467
  backgroundColor={entry.id === hovered() ? theme().backgroundElement : RGBA.fromInts(0, 0, 0, 0)}
1450
1468
  onMouseOver={() => setHovered(entry.id)}
1451
1469
  onMouseOut={() => setHovered(undefined)}
1452
- onMouseDown={() => props.api.route.navigate("session", { sessionID: entry.id })}
1470
+ onMouseDown={() => void openSessionEntry(props.api, entry)}
1453
1471
  >
1454
1472
  <text flexShrink={0} style={{ fg: theme().textMuted }}>
1455
1473
  ○
@@ -1753,7 +1771,7 @@ const tui: TuiPlugin = async (api) => {
1753
1771
  if (!target) return
1754
1772
  cleanup()
1755
1773
  api.ui.dialog.clear()
1756
- api.route.navigate("session", { sessionID: target.id })
1774
+ void openSessionEntry(api, target)
1757
1775
  }
1758
1776
 
1759
1777
  const disposeNav = api.keymap.registerLayer({
@@ -2154,7 +2172,7 @@ ORDER BY lifetime_cost DESC`).all() ?? []) as {
2154
2172
  if (!target) return
2155
2173
  disposeNav()
2156
2174
  api.ui.dialog.clear()
2157
- api.route.navigate("session", { sessionID: target.entry.id })
2175
+ void openSessionEntry(api, target.entry)
2158
2176
  }
2159
2177
  const disposeNav = api.keymap.registerLayer({
2160
2178
  bindings: [