opencode-sesh 2.0.4 → 2.1.1
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 +2 -1
- package/CHANGELOG.md +14 -0
- package/README.md +16 -2
- package/bin/sesh-costs.sh +0 -1
- package/bin/sesh-delete.sh +4 -12
- package/bin/sesh-list.sh +37 -25
- package/bin/sesh-pins.sh +12 -9
- package/bin/sesh-prune.sh +8 -5
- package/bin/sesh-refresh-worker.sh +4 -1
- package/bin/sesh-waiting.sh +1 -2
- package/bin/sesh.sh +3 -3
- package/dist/tui.js +700 -683
- package/install.sh +24 -19
- package/package.json +4 -4
- package/plugins/sesh-list.ts +2 -2
- package/tui-plugins/sesh-panel/tui.tsx +142 -137
package/AGENTS.md
CHANGED
|
@@ -159,7 +159,8 @@ when opencode exits, the picker reopens with the query preserved.
|
|
|
159
159
|
## TUI panel (`tui-plugins/sesh-panel/tui.tsx`)
|
|
160
160
|
|
|
161
161
|
- **Sidebar & Home list:** `api.slots.register` on `sidebar_content` (`sidebar.content`
|
|
162
|
-
|
|
162
|
+
append mode by default — native sidebar content stays above Sessions;
|
|
163
|
+
`sidebarPlacement: "prepend"` puts Sessions first) and `home_bottom` (`home.footer`
|
|
163
164
|
prepend mode). In the sidebar, pinned directories sort first, then directory groups
|
|
164
165
|
by recency; sessions within each group sort by last update regardless of pins.
|
|
165
166
|
In the home list, pinned sessions sort first.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.1] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Harden pruning, picker concurrency, and TUI lifecycle
|
|
13
|
+
|
|
14
|
+
## [2.1.0] - 2026-10-20
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **tui:** Configure sessions sidebar placement
|
|
19
|
+
|
|
8
20
|
## [2.0.4] - 2026-10-20
|
|
9
21
|
|
|
10
22
|
### Added
|
|
@@ -506,6 +518,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
506
518
|
- Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
|
|
507
519
|
CI.
|
|
508
520
|
|
|
521
|
+
[2.1.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.0...v2.1.1
|
|
522
|
+
[2.1.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.0.4...v2.1.0
|
|
509
523
|
[2.0.4]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.0.3...v2.0.4
|
|
510
524
|
[2.0.3]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.0.2...v2.0.3
|
|
511
525
|
[2.0.2]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.0.1...v2.0.2
|
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# sesh
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml)
|
|
4
|
+
|
|
1
5
|
<h1 align="center">
|
|
2
6
|
<img src="assets/logo.svg" alt="sesh" height="96">
|
|
3
7
|
</h1>
|
|
4
8
|
|
|
5
9
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml"><img src="https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
10
|
<a href="https://www.npmjs.com/package/opencode-sesh"><img src="https://img.shields.io/npm/v/opencode-sesh" alt="npm"></a>
|
|
8
11
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
9
12
|
<a href="#contributing"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome"></a>
|
|
@@ -226,6 +229,10 @@ workspace at its directory; outside cmux it shows a toast instead.
|
|
|
226
229
|
|
|
227
230
|
### TUI sidebar and home list
|
|
228
231
|
|
|
232
|
+
`sidebarPlacement` (`"append"` or `"prepend"`, default: `"append"`) places the
|
|
233
|
+
**Sessions** section below or above OpenCode's native sidebar sections. With
|
|
234
|
+
`"append"`, native todos appear above Sessions when available.
|
|
235
|
+
|
|
229
236
|
`showSidebar` (boolean, default: `true`) controls whether the **Sessions** section
|
|
230
237
|
appears in the sidebar. Set it to `false` to hide the section while keeping the
|
|
231
238
|
home list, session picker, and other commands available. OpenCode's native sidebar
|
|
@@ -238,7 +245,7 @@ the `opencode.json` entry unchanged:
|
|
|
238
245
|
"plugins": [
|
|
239
246
|
{
|
|
240
247
|
"package": "opencode-sesh",
|
|
241
|
-
"options": { "showSidebar":
|
|
248
|
+
"options": { "showSidebar": true, "sidebarPlacement": "append" }
|
|
242
249
|
}
|
|
243
250
|
]
|
|
244
251
|
}
|
|
@@ -333,6 +340,13 @@ covers the current project) and offer to resume one.
|
|
|
333
340
|
extracted text under `${XDG_CACHE_HOME:-~/.cache}/sesh` (or `SESH_CACHE_DIR`).
|
|
334
341
|
Nothing is uploaded anywhere.
|
|
335
342
|
|
|
343
|
+
**What if a lock stays busy after a crash?** Stop all sesh pickers before
|
|
344
|
+
manually removing the lock directory named in the error; for pin locks, also
|
|
345
|
+
fully quit OpenCode. Locks are never automatically reclaimed. Restart OpenCode
|
|
346
|
+
after upgrading so older plugin instances cannot reclaim locks. Interrupted
|
|
347
|
+
extraction temporary files may remain in the cache; remove them only after all
|
|
348
|
+
pickers have stopped.
|
|
349
|
+
|
|
336
350
|
**Does it work on Windows?** It targets macOS and Linux. WSL should work; native
|
|
337
351
|
Windows is untested.
|
|
338
352
|
|
package/bin/sesh-costs.sh
CHANGED
package/bin/sesh-delete.sh
CHANGED
|
@@ -33,18 +33,10 @@ release_lock() {
|
|
|
33
33
|
rmdir "$lock" 2>/dev/null || true
|
|
34
34
|
lock_owned=0
|
|
35
35
|
}
|
|
36
|
-
#
|
|
37
|
-
# recovery. Recovery is conservative -- an unreadable or non-numeric owner,
|
|
38
|
-
# or one still alive, always loses the race.
|
|
36
|
+
# Only the owner removes a lock; stale reclamation can race a new owner.
|
|
39
37
|
acquire_lock() {
|
|
40
38
|
if mkdir "$lock" 2>/dev/null; then printf '%s\n' "$$" > "$lock/pid"; return 0; fi
|
|
41
|
-
|
|
42
|
-
case "$owner" in ''|*[!0-9]*) return 1;; esac
|
|
43
|
-
kill -0 "$owner" 2>/dev/null && return 1
|
|
44
|
-
rm -f "$lock/pid" 2>/dev/null || return 1
|
|
45
|
-
rmdir "$lock" 2>/dev/null || return 1
|
|
46
|
-
mkdir "$lock" 2>/dev/null || return 1
|
|
47
|
-
printf '%s\n' "$$" > "$lock/pid"
|
|
39
|
+
return 1
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
# fzf passes an empty second field for headers and notices. They are never
|
|
@@ -77,9 +69,9 @@ fi
|
|
|
77
69
|
# Serialize this action with the sole snapshot publisher. A short collision
|
|
78
70
|
# is safer than deleting against a moving snapshot.
|
|
79
71
|
if ! acquire_lock; then
|
|
80
|
-
|
|
72
|
+
printf 'sesh: refresh lock is busy: %s; if it persists, stop all sesh pickers before removing this lock directory\n' "$lock" >&2
|
|
81
73
|
pause
|
|
82
|
-
exit
|
|
74
|
+
exit 75
|
|
83
75
|
fi
|
|
84
76
|
lock_owned=1
|
|
85
77
|
trap 'release_lock' EXIT
|
package/bin/sesh-list.sh
CHANGED
|
@@ -23,6 +23,10 @@ SELECTED_ID=''
|
|
|
23
23
|
SCOPE_CWD=''
|
|
24
24
|
usage() { echo "usage: ${0##*/} [--refresh] [--header] [--wait-lock SECONDS] --state-dir DIR [--cwd] [--limit N] [--archived] [--toggle-scope] [--query TEXT]" >&2; }
|
|
25
25
|
while [ "$#" -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--wait-lock|--state-dir|--limit|--query|--selected-id)
|
|
28
|
+
[ "$#" -ge 2 ] || { echo "$1 requires an argument" >&2; exit 2; } ;;
|
|
29
|
+
esac
|
|
26
30
|
case "$1" in
|
|
27
31
|
--refresh) REFRESH=1 ;;
|
|
28
32
|
--header) HEADER=1 ;;
|
|
@@ -57,6 +61,12 @@ EXTRACTIONS="$CACHE_DIR/extractions"
|
|
|
57
61
|
# so metadata always covers the whole database and cache pruning is safe.
|
|
58
62
|
SCOPE_CWD_SETTING=''
|
|
59
63
|
if [ -f "$SETTINGS" ]; then
|
|
64
|
+
"$JQ_BIN" -e '
|
|
65
|
+
type == "object" and (.scope | type) == "string"
|
|
66
|
+
and (.limit | type) == "number" and .limit >= 0 and .limit == (.limit | floor)
|
|
67
|
+
and ((.archived // 0) == 0 or .archived == 1)
|
|
68
|
+
and ((.cwd // "") | type) == "string"
|
|
69
|
+
' "$SETTINGS" >/dev/null 2>&1 || { echo 'sesh: invalid settings' >&2; exit 2; }
|
|
60
70
|
[ "$LIMIT_SET" = 1 ] || LIMIT=$("$JQ_BIN" -r '.limit' "$SETTINGS")
|
|
61
71
|
[ "$SCOPE_SET" = 1 ] || SCOPE_CWD=$("$JQ_BIN" -r '.scope' "$SETTINGS")
|
|
62
72
|
[ "$ARCHIVED_SET" = 1 ] || INCLUDE_ARCHIVED=$("$JQ_BIN" -r '.archived // 0' "$SETTINGS" 2>/dev/null || printf '0')
|
|
@@ -198,19 +208,16 @@ render() {
|
|
|
198
208
|
release_lock() { rm -f "$LOCK/pid" 2>/dev/null || true; rmdir "$LOCK" 2>/dev/null || true; }
|
|
199
209
|
acquire_lock() {
|
|
200
210
|
if mkdir "$LOCK" 2>/dev/null; then printf '%s\n' "$$" > "$LOCK/pid"; return 0; fi
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
kill -0 "$owner" 2>/dev/null && return 1
|
|
204
|
-
rm -f "$LOCK/pid" 2>/dev/null || return 1
|
|
205
|
-
rmdir "$LOCK" 2>/dev/null || return 1
|
|
206
|
-
mkdir "$LOCK" 2>/dev/null || return 1
|
|
207
|
-
printf '%s\n' "$$" > "$LOCK/pid"
|
|
211
|
+
# Only the owner removes a lock; stale reclamation can race a new owner.
|
|
212
|
+
return 1
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
lock_error() { printf 'sesh: refresh lock is busy: %s; if it persists, stop all sesh pickers before removing this lock directory\n' "$LOCK" >&2; }
|
|
216
|
+
|
|
210
217
|
acquire_lock_wait() {
|
|
211
218
|
local remaining=$WAIT_LOCK
|
|
212
219
|
while ! acquire_lock; do
|
|
213
|
-
[ "$remaining" -gt 0 ] || return 75
|
|
220
|
+
[ "$remaining" -gt 0 ] || { lock_error; return 75; }
|
|
214
221
|
sleep 1
|
|
215
222
|
remaining=$((remaining - 1))
|
|
216
223
|
done
|
|
@@ -231,35 +238,39 @@ refresh() {
|
|
|
231
238
|
private_dir "$EXTRACTIONS"
|
|
232
239
|
# Migrate warm caches too, including temporary files left by older versions.
|
|
233
240
|
# Do not follow symlinks or chmod unrelated files outside our extraction store.
|
|
234
|
-
|
|
241
|
+
# SIGKILL orphans stay: the cache is shared, but refresh locks are picker-local,
|
|
242
|
+
# so removing unowned temp files could interrupt another picker's writer.
|
|
243
|
+
find "$EXTRACTIONS" -maxdepth 1 -type f -user "$(id -u)" \( -name 'ses_*.json' -o -name 'ses_*.json.tmp' -o -name 'ses_*.json.??????' \) -exec chmod 600 {} +
|
|
235
244
|
if [ "${SESH_LOCK_HELD:-}" != "$STATE_DIR" ]; then
|
|
236
|
-
if [ "$WAIT_LOCK" -gt 0 ]; then acquire_lock_wait || return $?; else acquire_lock || return
|
|
245
|
+
if [ "$WAIT_LOCK" -gt 0 ]; then acquire_lock_wait || return $?; else acquire_lock || { lock_error; return 75; }; fi
|
|
237
246
|
fi
|
|
247
|
+
refresh_scratch=''
|
|
248
|
+
refresh_cache_tmp=''
|
|
249
|
+
if [ "${SESH_LOCK_HELD:-}" = "$STATE_DIR" ]; then
|
|
250
|
+
trap '[ -z "$refresh_cache_tmp" ] || rm -f "$refresh_cache_tmp"; [ -z "$refresh_scratch" ] || rm -rf "$refresh_scratch"' EXIT
|
|
251
|
+
else
|
|
252
|
+
trap '[ -z "$refresh_cache_tmp" ] || rm -f "$refresh_cache_tmp"; [ -z "$refresh_scratch" ] || rm -rf "$refresh_scratch"; release_lock' EXIT
|
|
253
|
+
fi
|
|
254
|
+
trap 'exit 130' HUP INT TERM
|
|
238
255
|
# Persist the effective scope (after any toggle) so renders and workers agree.
|
|
239
256
|
if [ "$SCOPE_SET" = 1 ] || [ ! -f "$SETTINGS" ]; then
|
|
240
257
|
atomic_text "$SETTINGS" "$("$JQ_BIN" -cn --arg scope "$SCOPE_CWD" --argjson limit "$LIMIT" --arg cwd "$SCOPE_CWD_SETTING" --argjson archived "$INCLUDE_ARCHIVED" '{scope:$scope,limit:$limit,cwd:$cwd,archived:$archived}')"
|
|
241
258
|
fi
|
|
242
259
|
DB_PATH=$(resolve_db) || {
|
|
243
260
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: opencode database is unavailable; showing last good snapshot'; else set_status 'unavailable: opencode database is unavailable'; fi
|
|
244
|
-
return
|
|
261
|
+
return 1
|
|
245
262
|
}
|
|
246
263
|
[ -f "$DB_PATH" ] || {
|
|
247
264
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: opencode database is unavailable; showing last good snapshot'; else set_status 'unavailable: opencode database is unavailable'; fi
|
|
248
|
-
return
|
|
265
|
+
return 1
|
|
249
266
|
}
|
|
250
267
|
if [ -z "$SQLITE_BIN" ]; then
|
|
251
268
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: sqlite3 is not available; showing last good snapshot'; else set_status 'unavailable: sqlite3 is not available'; fi
|
|
252
|
-
return
|
|
269
|
+
return 1
|
|
253
270
|
fi
|
|
254
271
|
local scratch sessions parts cache_records cache_state historical needs_extract out
|
|
255
272
|
scratch=$(mktemp -d "$STATE_DIR/.refresh.XXXXXX")
|
|
256
273
|
refresh_scratch=$scratch
|
|
257
|
-
if [ "${SESH_LOCK_HELD:-}" = "$STATE_DIR" ]; then
|
|
258
|
-
trap 'rm -rf "$refresh_scratch"' EXIT
|
|
259
|
-
else
|
|
260
|
-
trap 'rm -rf "$refresh_scratch"; release_lock' EXIT
|
|
261
|
-
fi
|
|
262
|
-
trap 'exit 130' HUP INT TERM
|
|
263
274
|
sessions="$scratch/sessions.json"; parts="$scratch/parts.json"
|
|
264
275
|
cache_records="$scratch/caches.json"; cache_state="$scratch/cache-state.json"
|
|
265
276
|
historical="$scratch/historical.jsonl"; needs_extract="$scratch/needs-extract.json"
|
|
@@ -267,14 +278,14 @@ refresh() {
|
|
|
267
278
|
|
|
268
279
|
db_json "SELECT id, directory, title, time_created, time_updated, COALESCE(time_archived, 0) AS archived, COALESCE(parent_id, '') AS parent FROM session_v2 ORDER BY time_updated DESC;" > "$sessions" 2>/dev/null || {
|
|
269
280
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: opencode database query failed; showing last good snapshot'; else set_status 'unavailable: opencode database query failed'; fi
|
|
270
|
-
return
|
|
281
|
+
return 1
|
|
271
282
|
}
|
|
272
283
|
# sqlite3 emits no bytes for zero rows. Publish an empty snapshot so removal
|
|
273
284
|
# of the final session also produces the non-actionable selection notice.
|
|
274
285
|
[ -s "$sessions" ] || printf '[]\n' > "$sessions"
|
|
275
286
|
"$JQ_BIN" -e 'type == "array"' "$sessions" >/dev/null 2>&1 || {
|
|
276
287
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: opencode database query failed; showing last good snapshot'; else set_status 'unavailable: opencode database query failed'; fi
|
|
277
|
-
return
|
|
288
|
+
return 1
|
|
278
289
|
}
|
|
279
290
|
db_json "SELECT session_id, COUNT(*) AS n, MAX(time_updated) AS m FROM session_message GROUP BY session_id;" > "$parts" 2>/dev/null || printf '[]\n' > "$parts"
|
|
280
291
|
"$JQ_BIN" -e 'type == "array"' "$parts" >/dev/null 2>&1 || printf '[]\n' > "$parts"
|
|
@@ -296,7 +307,7 @@ refresh() {
|
|
|
296
307
|
| ($cached | map({key: .sessionId, value:true}) | from_entries) as $cachedIds
|
|
297
308
|
| {cached: $cached,
|
|
298
309
|
stale: [$meta[] | select($cachedIds[.id] != true) | {id, directory, title, time_created, time_updated, archived, partCount, partMax}],
|
|
299
|
-
prune: [$caches[0][]? | select(.sessionId
|
|
310
|
+
prune: [$caches[0][]? | select(if (.sessionId | type) != "string" then true else $byId[.sessionId] == null end) | .cachePath],
|
|
300
311
|
meta: $meta}
|
|
301
312
|
' "$cache_records" > "$cache_state" || {
|
|
302
313
|
set_status 'stale: snapshot construction failed; showing last good snapshot'
|
|
@@ -366,9 +377,10 @@ refresh() {
|
|
|
366
377
|
if [ "$extract_failed" = 0 ]; then
|
|
367
378
|
while IFS= read -r sid && IFS= read -r cache_path && IFS= read -r record; do
|
|
368
379
|
[ -n "$sid" ] || continue
|
|
369
|
-
|
|
370
|
-
printf '%s\n' "$record" > "$
|
|
371
|
-
mv "$
|
|
380
|
+
refresh_cache_tmp=$(mktemp "$EXTRACTIONS/$sid.json.XXXXXX")
|
|
381
|
+
printf '%s\n' "$record" > "$refresh_cache_tmp"
|
|
382
|
+
mv "$refresh_cache_tmp" "$cache_path"
|
|
383
|
+
refresh_cache_tmp=''
|
|
372
384
|
printf '%s\n' "$record" >> "$historical"
|
|
373
385
|
done < "$records"
|
|
374
386
|
fi
|
package/bin/sesh-pins.sh
CHANGED
|
@@ -12,8 +12,16 @@ value=${2:-}
|
|
|
12
12
|
read_pins() {
|
|
13
13
|
if [ -f "$PINS_FILE" ]; then
|
|
14
14
|
local pins=''
|
|
15
|
-
pins=$("$JQ_BIN" -
|
|
16
|
-
|
|
15
|
+
pins=$("$JQ_BIN" -ces '
|
|
16
|
+
if length != 1 then error("invalid pins") else .[0] end
|
|
17
|
+
| select(type == "object")
|
|
18
|
+
| {sessions: .sessions, directories: .directories}
|
|
19
|
+
| select(all(.[]; type == "array" and all(.[]; type == "string")))
|
|
20
|
+
' "$PINS_FILE" 2>/dev/null) || { echo 'sesh: cannot read pins; leaving them unchanged' >&2; return 1; }
|
|
21
|
+
printf '%s\n' "$pins"
|
|
22
|
+
elif [ -e "$PINS_FILE" ] || [ -L "$PINS_FILE" ]; then
|
|
23
|
+
echo 'sesh: cannot read pins; leaving them unchanged' >&2
|
|
24
|
+
return 1
|
|
17
25
|
else
|
|
18
26
|
printf '%s\n' "$empty"
|
|
19
27
|
fi
|
|
@@ -34,13 +42,8 @@ mkdir -p "$(dirname "$PINS_FILE")"
|
|
|
34
42
|
lock="$PINS_FILE.lock"
|
|
35
43
|
for ((attempt = 0; attempt < 50; attempt++)); do
|
|
36
44
|
if mkdir "$lock" 2>/dev/null; then printf '%s\n' "$$" > "$lock/pid"; break; fi
|
|
37
|
-
owner
|
|
38
|
-
[
|
|
39
|
-
if [[ "$owner" =~ ^[0-9]+$ ]] && ! kill -0 "$owner" 2>/dev/null; then
|
|
40
|
-
rm -f "$lock/pid" 2>/dev/null || true
|
|
41
|
-
rmdir "$lock" 2>/dev/null || true
|
|
42
|
-
fi
|
|
43
|
-
[ "$attempt" -lt 49 ] || { echo 'sesh: pins are busy; try again' >&2; exit 1; }
|
|
45
|
+
# Only the owner removes a lock; stale reclamation can race a new owner.
|
|
46
|
+
[ "$attempt" -lt 49 ] || { printf 'sesh: pins lock is busy: %s; if it persists, stop all sesh pickers and OpenCode instances before removing this lock directory\n' "$lock" >&2; exit 1; }
|
|
44
47
|
sleep 0.1
|
|
45
48
|
done
|
|
46
49
|
tmp=''
|
package/bin/sesh-prune.sh
CHANGED
|
@@ -70,11 +70,14 @@ query() { "$SQLITE_BIN" -json -readonly "$DB_PATH" "$1"; }
|
|
|
70
70
|
JQ_BIN=${SESH_JQ:-$(command -v jq || true)}
|
|
71
71
|
[ -n "$JQ_BIN" ] || { echo 'sesh: jq is required' >&2; exit 1; }
|
|
72
72
|
|
|
73
|
-
CANDIDATES=$(query "SELECT id, directory, title, time_updated AS updated FROM session_v2 WHERE time_updated < $CUTOFF AND COALESCE(time_archived, 0) = 0 AND parent_id
|
|
73
|
+
CANDIDATES=$(query "SELECT id, directory, title, time_updated AS updated FROM session_v2 WHERE time_updated < $CUTOFF AND COALESCE(time_archived, 0) = 0 AND COALESCE(parent_id, '') = '' ORDER BY time_updated ASC;")
|
|
74
74
|
# sqlite3 -json prints nothing (not []) when no rows match.
|
|
75
75
|
[ -n "$CANDIDATES" ] || CANDIDATES='[]'
|
|
76
|
-
PINS=$("$SCRIPT_DIR/sesh-pins.sh" read
|
|
77
|
-
WAITING_IDS=$(bash "$SCRIPT_DIR/sesh-waiting.sh" --json
|
|
76
|
+
PINS=$("$SCRIPT_DIR/sesh-pins.sh" read) || { echo 'sesh: refusing prune: pins protection unavailable' >&2; exit 1; }
|
|
77
|
+
WAITING_IDS=$(bash "$SCRIPT_DIR/sesh-waiting.sh" --json | "$JQ_BIN" -er '
|
|
78
|
+
if type == "array" and all(.[]; (.id | type) == "string")
|
|
79
|
+
then map(.id) | join("\n") else error("invalid waiting sessions") end
|
|
80
|
+
') || { echo 'sesh: refusing prune: waiting-session protection unavailable' >&2; exit 1; }
|
|
78
81
|
[ -n "$WAITING_IDS" ] || WAITING_IDS='__none__'
|
|
79
82
|
|
|
80
83
|
ELIGIBLE=$(printf '%s' "$CANDIDATES" | "$JQ_BIN" -c --slurpfile _pins <(printf '%s' "$PINS") --arg waiting "$WAITING_IDS" '
|
|
@@ -130,7 +133,7 @@ fi
|
|
|
130
133
|
command -v "$OPENCODE_BIN" >/dev/null 2>&1 || { echo 'sesh: opencode executable is unavailable for deletes.' >&2; exit 1; }
|
|
131
134
|
failed=0
|
|
132
135
|
done_count=0
|
|
133
|
-
|
|
136
|
+
while IFS= read -r id; do
|
|
134
137
|
[[ "$id" =~ ^ses_[A-Za-z0-9]+$ ]] || { echo "Refusing: unexpected session id shape." >&2; exit 1; }
|
|
135
138
|
if "$OPENCODE_BIN" session delete "$id" >/dev/null 2>&1; then
|
|
136
139
|
done_count=$((done_count + 1))
|
|
@@ -138,6 +141,6 @@ for id in $(printf '%s' "$ELIGIBLE" | "$JQ_BIN" -r '.[].id'); do
|
|
|
138
141
|
echo "Could not delete $id." >&2
|
|
139
142
|
failed=$((failed + 1))
|
|
140
143
|
fi
|
|
141
|
-
done
|
|
144
|
+
done < <(printf '%s' "$ELIGIBLE" | "$JQ_BIN" -r '.[].id')
|
|
142
145
|
echo "Deleted $done_count session(s) older than $older_than."
|
|
143
146
|
[ "$failed" = 0 ] || exit 1
|
|
@@ -73,7 +73,10 @@ case "$MODE" in
|
|
|
73
73
|
complete=$(cat "$STATE_DIR/worker-complete" 2>/dev/null || printf 0)
|
|
74
74
|
case "$complete" in ''|*[!0-9]*) complete=0;; esac
|
|
75
75
|
if [ "$complete" -ge "$TARGET" ]; then
|
|
76
|
-
|
|
76
|
+
case "$(cat "$STATE_DIR/status" 2>/dev/null || true)" in
|
|
77
|
+
fresh|'stale: transcript extraction incomplete; refresh to retry') ;;
|
|
78
|
+
*) exit 1 ;;
|
|
79
|
+
esac
|
|
77
80
|
exit 0
|
|
78
81
|
fi
|
|
79
82
|
worker=$(cat "$STATE_DIR/worker.pid" 2>/dev/null || true)
|
package/bin/sesh-waiting.sh
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
# Keep NEEDS_INPUT_SQL in sync with the TUI sidebar copy in tui-plugins/sesh-panel/tui.tsx.
|
|
22
22
|
set -euo pipefail
|
|
23
23
|
|
|
24
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
24
|
OPENCODE_BIN=${SESH_OPENCODE:-opencode}
|
|
26
25
|
JSON=0
|
|
27
26
|
while [ "$#" -gt 0 ]; do
|
|
@@ -80,7 +79,7 @@ SELECT s.id AS id, s.directory AS directory, s.title AS title, s.time_updated AS
|
|
|
80
79
|
) THEN 'question' ELSE 'stuck' END AS reason
|
|
81
80
|
FROM session_v2 s
|
|
82
81
|
WHERE COALESCE(s.time_archived, 0) = 0
|
|
83
|
-
AND s.parent_id
|
|
82
|
+
AND COALESCE(s.parent_id, '') = ''
|
|
84
83
|
AND (EXISTS (
|
|
85
84
|
SELECT 1 FROM live
|
|
86
85
|
WHERE live.session_id = s.id AND live.name = 'question'
|
package/bin/sesh.sh
CHANGED
|
@@ -75,12 +75,12 @@ picker_query=''
|
|
|
75
75
|
while [ "$#" -gt 0 ]; do
|
|
76
76
|
case "$1" in
|
|
77
77
|
--cwd) scope_value=$PWD ;;
|
|
78
|
-
--limit)
|
|
78
|
+
--limit) [ "$#" -ge 2 ] || { echo '--limit requires an argument' >&2; exit 2; }; picker_limit=$2; shift ;;
|
|
79
79
|
--archived) picker_archived=1 ;;
|
|
80
80
|
--print) picker_print=1 ;;
|
|
81
81
|
--fork) picker_fork=1 ;;
|
|
82
82
|
--json) picker_json=1 ;;
|
|
83
|
-
--query)
|
|
83
|
+
--query) [ "$#" -ge 2 ] || { echo '--query requires an argument' >&2; exit 2; }; picker_query=$2; shift ;;
|
|
84
84
|
*) echo "unknown option: $1" >&2; exit 2 ;;
|
|
85
85
|
esac
|
|
86
86
|
shift
|
|
@@ -129,7 +129,7 @@ cache_cmd="$q_worker render --state-dir $q_state --query {q} --selected-id {6}"
|
|
|
129
129
|
header_cmd="$q_list --header --state-dir $q_state"
|
|
130
130
|
preview_cmd="if [ -f $q_state/help ]; then $q_shortcuts; else $q_preview $q_state {2}; fi"
|
|
131
131
|
help_bind="?:transform:[ -z {q} ] && ( [ -f $q_state/help ] && rm -f $q_state/help && echo hide-preview || ( : > $q_state/help && echo show-preview+refresh-preview ) ) || echo 'put(?)'"
|
|
132
|
-
toggle_cmd="$q_list --toggle-scope --state-dir $q_state >/dev/null"
|
|
132
|
+
toggle_cmd="$q_list --toggle-scope --wait-lock 12 --state-dir $q_state >/dev/null"
|
|
133
133
|
delete_cmd="$q_delete $q_state {2}"
|
|
134
134
|
pin_session_cmd="$q_pins toggle-session {2}"
|
|
135
135
|
pin_directory_cmd="$q_pins toggle-directory {5}"
|