thinkpool-pair 0.7.138 → 0.7.139
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/claude-session.mjs +33 -4
- package/package.json +1 -1
package/claude-session.mjs
CHANGED
|
@@ -185,6 +185,12 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
185
185
|
const ac = new AbortController()
|
|
186
186
|
let input = makeInputStream() // `let`: auto-restart swaps in a fresh stream
|
|
187
187
|
let sessionId = resume || null
|
|
188
|
+
// persistedSessionId — the newest session id we KNOW is written to disk (the resume we
|
|
189
|
+
// were handed, then advanced on each successful result). A model-switch re-create must
|
|
190
|
+
// resume THIS, never the live `sessionId`: a resumed query can fork to a fresh session id
|
|
191
|
+
// that isn't persisted until a turn completes, so re-creating on it fails with
|
|
192
|
+
// "No conversation found" (Max 2026-07-02). This is always a resumable id.
|
|
193
|
+
let persistedSessionId = resume || null
|
|
188
194
|
let closed = false
|
|
189
195
|
// Lazy boot (2026-07-02): a RESTORED-IDLE terminal returns a full session object but
|
|
190
196
|
// defers the expensive query() cold-start (MCP + settingSources, ~50s each) until its
|
|
@@ -640,7 +646,11 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
640
646
|
if (mcpServers && Object.keys(mcpServers).length) opts.mcpServers = { ...(opts.mcpServers || {}), ...mcpServers }
|
|
641
647
|
if (cwd) opts.cwd = cwd
|
|
642
648
|
if (model) opts.model = model
|
|
643
|
-
|
|
649
|
+
// NB: resume is NOT baked into opts — runQuery sets it per-run from the live `sessionId`
|
|
650
|
+
// (seeded from `resume`). That makes the resume target controllable: a re-create can point
|
|
651
|
+
// it at persistedSessionId, and the "No conversation found" recovery can DROP it (clearing
|
|
652
|
+
// sessionId) to re-run fresh. If it were in opts, the {...opts} spread would keep resuming
|
|
653
|
+
// the dead id even after we cleared sessionId (Max 2026-07-02 recovery loop).
|
|
644
654
|
// Force-enable Claude Code's predicted-next-prompt. `promptSuggestions: true`
|
|
645
655
|
// alone defers to the server-side `tengu_prompt_suggestion` feature gate, which
|
|
646
656
|
// Anthropic flips on/off during rollout — so the ghost-text chip silently died
|
|
@@ -820,9 +830,10 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
820
830
|
// Defer to the next tick so recreateForSwitch (which awaits THIS loop's qDone)
|
|
821
831
|
// doesn't await itself; the setTimeout runs after this iterator yields.
|
|
822
832
|
if (pendingSwitch) { pendingSwitch = false; setTimeout(() => { if (!closed) recreateForSwitch() }, 0) }
|
|
823
|
-
if (m.subtype === 'success') restartCount = 0 // ONLY a real success refills the
|
|
824
|
-
// auto-restart budget
|
|
825
|
-
//
|
|
833
|
+
if (m.subtype === 'success') { restartCount = 0; if (sessionId) persistedSessionId = sessionId } // ONLY a real success refills the
|
|
834
|
+
// auto-restart budget (else a flapping connection that lands one aborted turn between
|
|
835
|
+
// drops refills every cycle past RESTART_MAX) — and marks this session id durably
|
|
836
|
+
// persisted so a later model-switch re-create can safely resume it.
|
|
826
837
|
forceStopped = false
|
|
827
838
|
// Ground-truth the model from THIS turn's modelUsage (the models actually billed
|
|
828
839
|
// this turn), not the init/system messages — a RESUMED session REPLAYS the prior
|
|
@@ -891,6 +902,19 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
891
902
|
restartTimer = setTimeout(() => { restartTimer = null; if (!closed) runQuery() }, Math.min(1000 * restartCount, 4000)) // 1s, 2s, 4s backoff
|
|
892
903
|
return
|
|
893
904
|
}
|
|
905
|
+
// Resume target vanished (a forked session id that was never persisted, or a pruned
|
|
906
|
+
// transcript): drop the resume and re-run FRESH so the turn/switch still lands instead
|
|
907
|
+
// of hard-failing. Bounded by RESTART_MAX. Loses only the resumed history, not the turn.
|
|
908
|
+
if (/no conversation found/i.test(msg) && sessionId && restartCount < RESTART_MAX) {
|
|
909
|
+
restartCount++
|
|
910
|
+
turnActive = false
|
|
911
|
+
sessionId = null; persistedSessionId = null
|
|
912
|
+
const oldInput = input
|
|
913
|
+
input = makeInputStream()
|
|
914
|
+
try { oldInput.end() } catch { /* noop */ }
|
|
915
|
+
restartTimer = setTimeout(() => { restartTimer = null; if (!closed) runQuery() }, 300)
|
|
916
|
+
return
|
|
917
|
+
}
|
|
894
918
|
// Non-recoverable, or budget exhausted: mark done so sendTurn stops
|
|
895
919
|
// silently queuing into a dead stream. The bridge's onEvent self-heal
|
|
896
920
|
// still fires for "No conversation found" (it reopens fresh).
|
|
@@ -920,6 +944,11 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
920
944
|
try { oldInput.end() } catch { /* noop */ }
|
|
921
945
|
try { await dying } catch { /* noop */ } // wait until it's actually dead
|
|
922
946
|
await sleep(300) // brief lock-release margin
|
|
947
|
+
// Resume the last DURABLY-PERSISTED session, not the live one: the aborted query may
|
|
948
|
+
// have forked to a session id that was never written to disk, and resuming that fails
|
|
949
|
+
// with "No conversation found" (Max 2026-07-02, sid 6db6c1e3). persistedSessionId is
|
|
950
|
+
// always resumable (the restore id, advanced on each success).
|
|
951
|
+
sessionId = persistedSessionId
|
|
923
952
|
if (!closed) runQuery() // relaunch on opts.model + resume
|
|
924
953
|
}
|
|
925
954
|
if (!lazy) runQuery()
|