@bojackduy/opencode-learn 1.4.1 → 1.4.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/dist/tui.js +23 -3
- package/package.json +1 -1
- package/plugins/learn-tui.tsx +24 -2
package/dist/tui.js
CHANGED
|
@@ -2677,8 +2677,27 @@ var tui = async (api) => {
|
|
|
2677
2677
|
return;
|
|
2678
2678
|
let current = currentBySession.get(curSid);
|
|
2679
2679
|
if (current) {
|
|
2680
|
-
|
|
2681
|
-
|
|
2680
|
+
const pendingStillExists = (() => {
|
|
2681
|
+
try {
|
|
2682
|
+
return fs.readdirSync(pendingDir).some((f) => f === `quiz-${current.id}.json` || f === `quiz_batch-${current.id}.json`);
|
|
2683
|
+
} catch {
|
|
2684
|
+
return true;
|
|
2685
|
+
}
|
|
2686
|
+
})();
|
|
2687
|
+
if (!pendingStillExists) {
|
|
2688
|
+
tlog("processPending stale current cleared (pending gone)", current.id);
|
|
2689
|
+
releasePopupClaim(current.id);
|
|
2690
|
+
currentBySession.delete(curSid);
|
|
2691
|
+
current = undefined;
|
|
2692
|
+
} else if (api.ui.dialog.open) {
|
|
2693
|
+
refreshPopupClaim(current.id);
|
|
2694
|
+
return;
|
|
2695
|
+
} else {
|
|
2696
|
+
tlog("processPending stale current cleared (dialog no longer open)", current.id);
|
|
2697
|
+
releasePopupClaim(current.id);
|
|
2698
|
+
currentBySession.delete(curSid);
|
|
2699
|
+
current = undefined;
|
|
2700
|
+
}
|
|
2682
2701
|
}
|
|
2683
2702
|
if (api.ui.dialog.open)
|
|
2684
2703
|
return;
|
|
@@ -2699,7 +2718,8 @@ var tui = async (api) => {
|
|
|
2699
2718
|
return null;
|
|
2700
2719
|
}
|
|
2701
2720
|
}).filter(Boolean).filter((x) => !hasAnswerArtifact(x.j.id));
|
|
2702
|
-
const
|
|
2721
|
+
const byNewest = [...matching].sort((a, b) => (b.j?.timestamp || 0) - (a.j?.timestamp || 0));
|
|
2722
|
+
const pick = byNewest.find((x) => x.j.sessionID === curSid) || byNewest.find((x) => !x.j.sessionID);
|
|
2703
2723
|
if (!pick)
|
|
2704
2724
|
return;
|
|
2705
2725
|
const file = pick.f;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@bojackduy/opencode-learn",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.2",
|
|
5
5
|
"description": "Pi learn system for OpenCode — Socratic teaching, graded quiz, Obsidian md_log, and visual makers. Port of amosblomqvist/learn (video: How I Use AI to Learn Things) to OpenCode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "AGPL-3.0-or-later",
|
package/plugins/learn-tui.tsx
CHANGED
|
@@ -812,14 +812,36 @@ export const tui: TuiPlugin = async (api) => {
|
|
|
812
812
|
const curSid = getCurrentSessionID()
|
|
813
813
|
if (!curSid) return
|
|
814
814
|
let current = currentBySession.get(curSid) as { id: string; type: string } | undefined
|
|
815
|
-
if (current) {
|
|
815
|
+
if (current) {
|
|
816
|
+
// Self-heal: the tracked dialog may have been dismissed through a path other than
|
|
817
|
+
// done()/cancel() (session/tab switch, TUI reconnect, dialog replaced externally),
|
|
818
|
+
// or its pending file may have been consumed externally. A stuck entry would block
|
|
819
|
+
// ALL future quizzes for this session forever — release it and rediscover below.
|
|
820
|
+
const pendingStillExists = (() => { try { return fs.readdirSync(pendingDir).some((f) => f === `quiz-${current!.id}.json` || f === `quiz_batch-${current!.id}.json`) } catch { return true } })()
|
|
821
|
+
if (!pendingStillExists) {
|
|
822
|
+
tlog("processPending stale current cleared (pending gone)", current.id)
|
|
823
|
+
releasePopupClaim(current.id)
|
|
824
|
+
currentBySession.delete(curSid)
|
|
825
|
+
current = undefined
|
|
826
|
+
} else if (api.ui.dialog.open) { refreshPopupClaim(current.id); return }
|
|
827
|
+
else {
|
|
828
|
+
tlog("processPending stale current cleared (dialog no longer open)", current.id)
|
|
829
|
+
releasePopupClaim(current.id)
|
|
830
|
+
currentBySession.delete(curSid)
|
|
831
|
+
current = undefined
|
|
832
|
+
}
|
|
833
|
+
}
|
|
816
834
|
if (api.ui.dialog.open) return
|
|
817
835
|
let files: string[] = []
|
|
818
836
|
try { files = fs.readdirSync(pendingDir).filter(f => f.endsWith(".json") && !f.startsWith("response-") && !f.startsWith(".") && !f.startsWith("classify")).sort() } catch { return }
|
|
819
837
|
// Session-distinct: only show pending for current session.
|
|
820
838
|
// Skip answered-pending (a response file exists, server is consuming): prevents re-popup after answer.
|
|
821
839
|
const matching = files.map(f => { try { const j = JSON.parse(fs.readFileSync(path.join(pendingDir, f), "utf8")) as any; return { f, j } } catch { return null } }).filter(Boolean).filter(x => !hasAnswerArtifact(x!.j.id)) as Array<{f: string, j: any}>
|
|
822
|
-
|
|
840
|
+
// Newest-first: when several quizzes stack up for the same session (e.g. earlier ones
|
|
841
|
+
// answered manually in chat after a popup failure), the LATEST quiz is the live one the
|
|
842
|
+
// agent is waiting on. Alphabetical order would keep re-showing the oldest stuck quiz.
|
|
843
|
+
const byNewest = [...matching].sort((a, b) => (((b as any).j?.timestamp || 0) as number) - (((a as any).j?.timestamp || 0) as number))
|
|
844
|
+
const pick = byNewest.find(x => x.j.sessionID === curSid) || byNewest.find(x => !x.j.sessionID)
|
|
823
845
|
if (!pick) return
|
|
824
846
|
const file = pick.f
|
|
825
847
|
const full = path.join(pendingDir, file)
|