opencode-sesh 2.1.5 → 2.1.6
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 +6 -2
- package/CHANGELOG.md +7 -0
- package/dist/tui.js +46 -36
- package/package.json +1 -1
- package/tui-plugins/sesh-panel/tui.tsx +44 -39
package/AGENTS.md
CHANGED
|
@@ -198,7 +198,10 @@ when opencode exits, the picker reopens with the query preserved.
|
|
|
198
198
|
at `SESSION_MAX` and reported when truncated) rather than a fixed-size
|
|
199
199
|
window, then indexes transcript text for **every** session in batches
|
|
200
200
|
(`TRANSCRIPT_BATCH`) with bounded remote concurrency (`REMOTE_CONCURRENCY`).
|
|
201
|
-
|
|
201
|
+
Local extraction is one SQL statement per batch in the DB worker
|
|
202
|
+
(`transcriptTextQuery`, JSON-guarded) that returns only `{sid, text}` rows,
|
|
203
|
+
so tool payloads never reach the UI thread; if the worker rejects, sessions
|
|
204
|
+
not yet indexed fall back to the remote path. The header shows indexing progress/coverage. Visible transcript hits get
|
|
202
205
|
short excerpts from the text-only index (never tool or reasoning payloads).
|
|
203
206
|
Project, needs-input (Option-W), and pinned-session (Option-S) filters compose
|
|
204
207
|
with the query; the picker remembers query/scope/filters until plugin restart.
|
|
@@ -273,7 +276,8 @@ when opencode exits, the picker reopens with the query preserved.
|
|
|
273
276
|
(also run by `bun run test`).
|
|
274
277
|
- `bun run test:tui-worker` — starts the real blob Worker against a bun:sqlite
|
|
275
278
|
fixture; asserts results round-trip and the main thread keeps ticking during
|
|
276
|
-
a slow query
|
|
279
|
+
a slow query and during transcript indexing of large tool payloads (also run
|
|
280
|
+
by `bun run test`).
|
|
277
281
|
- `bun run typecheck` — `tsc` over `tui-plugins/` and `plugins/`.
|
|
278
282
|
- `bun run lint:sh` — `bash -n` on every script.
|
|
279
283
|
- `HOME=/tmp/fakehome bash install.sh` — installer smoke test.
|
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.6] - 2026-11-02
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **tui:** Extract transcript text in the DB worker (#35)
|
|
13
|
+
|
|
8
14
|
## [2.1.5] - 2026-11-02
|
|
9
15
|
|
|
10
16
|
### Fixed
|
|
@@ -542,6 +548,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
542
548
|
- Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
|
|
543
549
|
CI.
|
|
544
550
|
|
|
551
|
+
[2.1.6]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.5...v2.1.6
|
|
545
552
|
[2.1.5]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.4...v2.1.5
|
|
546
553
|
[2.1.4]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.3...v2.1.4
|
|
547
554
|
[2.1.3]: https://github.com/ozandogrultan/opencode-sesh/compare/v2.1.2...v2.1.3
|
package/dist/tui.js
CHANGED
|
@@ -920,29 +920,39 @@ GROUP BY live.session_id`, batch);
|
|
|
920
920
|
}
|
|
921
921
|
return details;
|
|
922
922
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
923
|
+
|
|
924
|
+
// Text parts only, extracted inside SQLite so tool and reasoning payloads never
|
|
925
|
+
// reach the caller. The CASE guards keep json_extract away from malformed rows
|
|
926
|
+
// and non-object parts, which SQL AND ordering alone would not.
|
|
927
|
+
function transcriptTextQuery(count) {
|
|
928
|
+
const placeholders = Array.from({
|
|
929
|
+
length: count
|
|
930
|
+
}, () => "?").join(",");
|
|
931
|
+
return `WITH msg AS (
|
|
932
|
+
SELECT session_id AS sid, seq, rowid AS rid, CASE WHEN json_valid(data) THEN data END AS doc
|
|
933
|
+
FROM session_message
|
|
934
|
+
WHERE session_id IN (${placeholders}) AND type IN ('user', 'assistant')
|
|
935
|
+
), parts AS (
|
|
936
|
+
SELECT sid, seq, rid, 0 AS part,
|
|
937
|
+
CASE WHEN json_type(doc, '$.text') = 'text' THEN json_extract(doc, '$.text') END AS text
|
|
938
|
+
FROM msg
|
|
939
|
+
UNION ALL
|
|
940
|
+
SELECT msg.sid, msg.seq, msg.rid, 1 + item.key AS part,
|
|
941
|
+
CASE WHEN item.type = 'object' THEN
|
|
942
|
+
CASE WHEN json_extract(item.value, '$.type') = 'text' AND json_type(item.value, '$.text') = 'text'
|
|
943
|
+
THEN json_extract(item.value, '$.text') END
|
|
944
|
+
END AS text
|
|
945
|
+
FROM msg, json_each(msg.doc, '$.content') AS item
|
|
946
|
+
WHERE json_type(msg.doc, '$.content') = 'array'
|
|
947
|
+
)
|
|
948
|
+
SELECT sid, text FROM parts WHERE text IS NOT NULL AND text <> '' ORDER BY sid, seq, rid, part`;
|
|
940
949
|
}
|
|
941
950
|
|
|
942
951
|
// Index titles/directories immediately, then add transcript text progressively
|
|
943
952
|
// in batches so the render thread can breathe. Covers every entry, not a fixed
|
|
944
|
-
// newest window; `onProgress` lets the picker show coverage.
|
|
945
|
-
|
|
953
|
+
// newest window; `onProgress` lets the picker show coverage. Local extraction
|
|
954
|
+
// runs through `run` (the worker client) and returns text only.
|
|
955
|
+
async function buildSearchIndex(api, entries, onProgress, active = () => true, run = dbClient.run) {
|
|
946
956
|
const index = new Map();
|
|
947
957
|
for (const entry of entries) {
|
|
948
958
|
index.set(entry.id, `${entry.title} ${entry.dir}`);
|
|
@@ -960,25 +970,25 @@ async function buildSearchIndex(api, entries, onProgress, active = () => true) {
|
|
|
960
970
|
};
|
|
961
971
|
report(false);
|
|
962
972
|
if (!active()) return index;
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
indexed += batch.length;
|
|
974
|
-
report(false);
|
|
975
|
-
await nextTick();
|
|
973
|
+
try {
|
|
974
|
+
for (let i = 0; i < ids.length; i += TRANSCRIPT_BATCH) {
|
|
975
|
+
if (!active()) break;
|
|
976
|
+
const batch = ids.slice(i, i + TRANSCRIPT_BATCH);
|
|
977
|
+
const rows = await run(transcriptTextQuery(batch.length), batch);
|
|
978
|
+
if (!active()) break;
|
|
979
|
+
const texts = new Map();
|
|
980
|
+
for (const row of rows) {
|
|
981
|
+
const list = texts.get(row.sid);
|
|
982
|
+
if (list) list.push(row.text);else texts.set(row.sid, [row.text]);
|
|
976
983
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
984
|
+
for (const [sid, list] of texts) index.set(sid, [index.get(sid) ?? "", ...list].join(" ").trim());
|
|
985
|
+
for (const id of batch) remaining.delete(id);
|
|
986
|
+
indexed += batch.length;
|
|
987
|
+
report(false);
|
|
988
|
+
await nextTick();
|
|
981
989
|
}
|
|
990
|
+
} catch {
|
|
991
|
+
// local store unavailable mid-walk; fall back to the remote path below
|
|
982
992
|
}
|
|
983
993
|
if (active() && remaining.size > 0) {
|
|
984
994
|
await buildSearchIndexRemote(api, [...remaining], index, () => {
|
package/package.json
CHANGED
|
@@ -805,33 +805,41 @@ GROUP BY live.session_id`, batch)) as { id: string; question_since: number | nul
|
|
|
805
805
|
return details
|
|
806
806
|
}
|
|
807
807
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
808
|
+
// Text parts only, extracted inside SQLite so tool and reasoning payloads never
|
|
809
|
+
// reach the caller. The CASE guards keep json_extract away from malformed rows
|
|
810
|
+
// and non-object parts, which SQL AND ordering alone would not.
|
|
811
|
+
function transcriptTextQuery(count: number): string {
|
|
812
|
+
const placeholders = Array.from({ length: count }, () => "?").join(",")
|
|
813
|
+
return `WITH msg AS (
|
|
814
|
+
SELECT session_id AS sid, seq, rowid AS rid, CASE WHEN json_valid(data) THEN data END AS doc
|
|
815
|
+
FROM session_message
|
|
816
|
+
WHERE session_id IN (${placeholders}) AND type IN ('user', 'assistant')
|
|
817
|
+
), parts AS (
|
|
818
|
+
SELECT sid, seq, rid, 0 AS part,
|
|
819
|
+
CASE WHEN json_type(doc, '$.text') = 'text' THEN json_extract(doc, '$.text') END AS text
|
|
820
|
+
FROM msg
|
|
821
|
+
UNION ALL
|
|
822
|
+
SELECT msg.sid, msg.seq, msg.rid, 1 + item.key AS part,
|
|
823
|
+
CASE WHEN item.type = 'object' THEN
|
|
824
|
+
CASE WHEN json_extract(item.value, '$.type') = 'text' AND json_type(item.value, '$.text') = 'text'
|
|
825
|
+
THEN json_extract(item.value, '$.text') END
|
|
826
|
+
END AS text
|
|
827
|
+
FROM msg, json_each(msg.doc, '$.content') AS item
|
|
828
|
+
WHERE json_type(msg.doc, '$.content') = 'array'
|
|
829
|
+
)
|
|
830
|
+
SELECT sid, text FROM parts WHERE text IS NOT NULL AND text <> '' ORDER BY sid, seq, rid, part`
|
|
825
831
|
}
|
|
826
832
|
|
|
827
833
|
// Index titles/directories immediately, then add transcript text progressively
|
|
828
834
|
// in batches so the render thread can breathe. Covers every entry, not a fixed
|
|
829
|
-
// newest window; `onProgress` lets the picker show coverage.
|
|
835
|
+
// newest window; `onProgress` lets the picker show coverage. Local extraction
|
|
836
|
+
// runs through `run` (the worker client) and returns text only.
|
|
830
837
|
async function buildSearchIndex(
|
|
831
838
|
api: TuiPluginApi,
|
|
832
839
|
entries: Entry[],
|
|
833
840
|
onProgress?: (progress: IndexProgress, index: Map<string, string>) => void,
|
|
834
841
|
active = () => true,
|
|
842
|
+
run: DbRun = dbClient.run,
|
|
835
843
|
): Promise<Map<string, string>> {
|
|
836
844
|
const index = new Map<string, string>()
|
|
837
845
|
for (const entry of entries) {
|
|
@@ -845,29 +853,26 @@ async function buildSearchIndex(
|
|
|
845
853
|
report(false)
|
|
846
854
|
|
|
847
855
|
if (!active()) return index
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
.all(...batch) ?? []) as { sid: string; data: string }[]
|
|
860
|
-
for (const row of rows) addTranscript(index, row.sid, row.data)
|
|
861
|
-
for (const id of batch) remaining.delete(id)
|
|
862
|
-
indexed += batch.length
|
|
863
|
-
report(false)
|
|
864
|
-
await nextTick()
|
|
856
|
+
try {
|
|
857
|
+
for (let i = 0; i < ids.length; i += TRANSCRIPT_BATCH) {
|
|
858
|
+
if (!active()) break
|
|
859
|
+
const batch = ids.slice(i, i + TRANSCRIPT_BATCH)
|
|
860
|
+
const rows = (await run(transcriptTextQuery(batch.length), batch)) as { sid: string; text: string }[]
|
|
861
|
+
if (!active()) break
|
|
862
|
+
const texts = new Map<string, string[]>()
|
|
863
|
+
for (const row of rows) {
|
|
864
|
+
const list = texts.get(row.sid)
|
|
865
|
+
if (list) list.push(row.text)
|
|
866
|
+
else texts.set(row.sid, [row.text])
|
|
865
867
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
868
|
+
for (const [sid, list] of texts) index.set(sid, [index.get(sid) ?? "", ...list].join(" ").trim())
|
|
869
|
+
for (const id of batch) remaining.delete(id)
|
|
870
|
+
indexed += batch.length
|
|
871
|
+
report(false)
|
|
872
|
+
await nextTick()
|
|
870
873
|
}
|
|
874
|
+
} catch {
|
|
875
|
+
// local store unavailable mid-walk; fall back to the remote path below
|
|
871
876
|
}
|
|
872
877
|
|
|
873
878
|
if (active() && remaining.size > 0) {
|