opencode-sesh 0.7.0 → 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/AGENTS.md +5 -3
- package/CHANGELOG.md +28 -1
- package/bin/sesh-cmux-sync.sh +4 -1
- package/package.json +1 -1
- package/tui/sesh-panel.tsx +42 -23
package/AGENTS.md
CHANGED
|
@@ -160,9 +160,11 @@ Resume happens in place (`exec` after `cd` to the session's cwd).
|
|
|
160
160
|
opens locally). Works for background agents and older panels too.
|
|
161
161
|
- **Workspace naming.** The poll renames this workspace to the current
|
|
162
162
|
session title (`cmux workspace rename <id> --title …`), skipping titles that
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
must never name a workspace: the pre-first-turn placeholder
|
|
164
|
+
(`/^New session\b/`) and plugin sentinels (`ghost-hidden`, which
|
|
165
|
+
opencode-ghost titles its internal sessions). `bin/sesh-cmux-sync.sh` does
|
|
166
|
+
the same for every workspace at once. The session title is the source of
|
|
167
|
+
truth; a manual cmux rename is overwritten on the next session-title change.
|
|
166
168
|
These are the only cmux calls in the panel; keep them behind the
|
|
167
169
|
`CMUX_WORKSPACE_ID` guard.
|
|
168
170
|
- **Picker:** a custom dialog (not `DialogSelect`) grouped by
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ 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.
|
|
26
|
+
## [0.7.1] - 2026-09-24
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Workspace naming no longer pushes the `ghost-hidden` sentinel that the
|
|
31
|
+
opencode-ghost plugin uses for its internal sessions. The placeholder check
|
|
32
|
+
was also corrected, so `New session - …` titles are skipped as intended
|
|
33
|
+
rather than slipping through.
|
|
10
34
|
## [0.7.0] - 2026-09-24
|
|
11
35
|
|
|
12
36
|
### Added
|
|
@@ -22,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
46
|
- Click-to-focus now reads cmux's per-surface resume record (`checkpoint_id`)
|
|
23
47
|
instead of heartbeat files, so it also finds background agents and panes
|
|
24
48
|
running an older panel, and has no files to sweep.
|
|
49
|
+
|
|
25
50
|
## [0.6.0] - 2026-09-24
|
|
26
51
|
|
|
27
52
|
### Added
|
|
@@ -293,7 +318,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
293
318
|
- Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
|
|
294
319
|
CI.
|
|
295
320
|
|
|
296
|
-
[Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.
|
|
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
|
|
323
|
+
[0.7.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.0...v0.7.1
|
|
297
324
|
[0.7.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.6.0...v0.7.0
|
|
298
325
|
[0.6.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.5.0...v0.6.0
|
|
299
326
|
[0.5.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.4.0...v0.5.0
|
package/bin/sesh-cmux-sync.sh
CHANGED
|
@@ -111,7 +111,10 @@ FINAL=$(printf '%s' "$PAIRS" | "$JQ_BIN" -c \
|
|
|
111
111
|
| ($current[0] | map({key: .workspace, value: .title}) | from_entries) as $shown
|
|
112
112
|
| [ .[]
|
|
113
113
|
| ($byId[.session] // "" | gsub("^ +| +$"; "")) as $t
|
|
114
|
-
| select($t != ""
|
|
114
|
+
| select($t != "")
|
|
115
|
+
# Never push a placeholder or a plugin sentinel (opencode-ghost titles its
|
|
116
|
+
# internal sessions "ghost-hidden").
|
|
117
|
+
| select(($t | test("^New session\\b|^ghost-hidden$"; "i") | not))
|
|
115
118
|
| select(($shown[.workspace] // "") != $t)
|
|
116
119
|
| . + {title: $t} ]')
|
|
117
120
|
|
package/package.json
CHANGED
package/tui/sesh-panel.tsx
CHANGED
|
@@ -177,9 +177,10 @@ function pinnedFirst(entries: Entry[], pins: Pins): Entry[] {
|
|
|
177
177
|
// dialog). Native `/sessions` and session_list (<leader>l) stay untouched.
|
|
178
178
|
const BASE_MODE = "base"
|
|
179
179
|
const POLL_MS = 15_000
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
180
|
+
// Titles that must never become a cmux workspace name: opencode's pre-first-
|
|
181
|
+
// turn placeholder, and the sentinel other plugins (opencode-ghost) use for
|
|
182
|
+
// internal sessions so their scaffolding rows stay out of the UI.
|
|
183
|
+
const PLACEHOLDER_TITLE = /^New session\b|^ghost-hidden$/i
|
|
183
184
|
|
|
184
185
|
function shortDir(dir: string, home: string): string {
|
|
185
186
|
if (!dir || dir === "/") return "other"
|
|
@@ -436,6 +437,18 @@ async function cmuxSurfaces(): Promise<CmuxSurface[]> {
|
|
|
436
437
|
return surfaces
|
|
437
438
|
}
|
|
438
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
|
+
|
|
439
452
|
// Pure selection: the workspace to focus for a session, or undefined to open
|
|
440
453
|
// locally. Prefers a live surface in a different workspace; ignores the
|
|
441
454
|
// caller's own surface and workspace (focusing where you already are is a
|
|
@@ -451,6 +464,26 @@ function pickFocusWorkspace(surfaces: CmuxSurface[], sessionID: string): string
|
|
|
451
464
|
return undefined
|
|
452
465
|
}
|
|
453
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
|
+
|
|
454
487
|
function addTranscript(index: Map<string, string>, sid: string, data: string): void {
|
|
455
488
|
try {
|
|
456
489
|
const part = JSON.parse(data) as { type?: unknown; text?: unknown }
|
|
@@ -852,23 +885,9 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
852
885
|
setSectionCollapsed((value) => !value)
|
|
853
886
|
}
|
|
854
887
|
|
|
855
|
-
//
|
|
856
|
-
// workspace
|
|
857
|
-
|
|
858
|
-
const openSession = async (entry: Entry) => {
|
|
859
|
-
if (process.env.CMUX_WORKSPACE_ID) {
|
|
860
|
-
const target = pickFocusWorkspace(await cmuxSurfaces(), entry.id)
|
|
861
|
-
if (target) {
|
|
862
|
-
try {
|
|
863
|
-
await promisify(execFile)("cmux", ["workspace", "select", target])
|
|
864
|
-
return
|
|
865
|
-
} catch {
|
|
866
|
-
// cmux unavailable; fall through to a local open
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
props.api.route.navigate("session", { sessionID: entry.id })
|
|
871
|
-
}
|
|
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)
|
|
872
891
|
|
|
873
892
|
// Flat order of the rows a cursor can land on, so keyboard navigation and
|
|
874
893
|
// mouse hover resolve to the same row.
|
|
@@ -1448,7 +1467,7 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1448
1467
|
backgroundColor={entry.id === hovered() ? theme().backgroundElement : RGBA.fromInts(0, 0, 0, 0)}
|
|
1449
1468
|
onMouseOver={() => setHovered(entry.id)}
|
|
1450
1469
|
onMouseOut={() => setHovered(undefined)}
|
|
1451
|
-
onMouseDown={() => props.api
|
|
1470
|
+
onMouseDown={() => void openSessionEntry(props.api, entry)}
|
|
1452
1471
|
>
|
|
1453
1472
|
<text flexShrink={0} style={{ fg: theme().textMuted }}>
|
|
1454
1473
|
○
|
|
@@ -1752,7 +1771,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
1752
1771
|
if (!target) return
|
|
1753
1772
|
cleanup()
|
|
1754
1773
|
api.ui.dialog.clear()
|
|
1755
|
-
api
|
|
1774
|
+
void openSessionEntry(api, target)
|
|
1756
1775
|
}
|
|
1757
1776
|
|
|
1758
1777
|
const disposeNav = api.keymap.registerLayer({
|
|
@@ -2153,7 +2172,7 @@ ORDER BY lifetime_cost DESC`).all() ?? []) as {
|
|
|
2153
2172
|
if (!target) return
|
|
2154
2173
|
disposeNav()
|
|
2155
2174
|
api.ui.dialog.clear()
|
|
2156
|
-
api
|
|
2175
|
+
void openSessionEntry(api, target.entry)
|
|
2157
2176
|
}
|
|
2158
2177
|
const disposeNav = api.keymap.registerLayer({
|
|
2159
2178
|
bindings: [
|