thinkpool-pair 0.7.140 → 0.7.142
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 +34 -3
- package/package.json +1 -1
package/claude-session.mjs
CHANGED
|
@@ -191,6 +191,7 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
191
191
|
// that isn't persisted until a turn completes, so re-creating on it fails with
|
|
192
192
|
// "No conversation found" (Max 2026-07-02). This is always a resumable id.
|
|
193
193
|
let persistedSessionId = resume || null
|
|
194
|
+
let lastTurnText = null // the most recent turn text, so a bad-resume recovery can re-deliver it
|
|
194
195
|
let closed = false
|
|
195
196
|
// Lazy boot (2026-07-02): a RESTORED-IDLE terminal returns a full session object but
|
|
196
197
|
// defers the expensive query() cold-start (MCP + settingSources, ~50s each) until its
|
|
@@ -762,8 +763,14 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
762
763
|
// m.slash_commands (init message) — the commands this session really
|
|
763
764
|
// supports: built-ins + the host's custom .claude/commands. Surfaced
|
|
764
765
|
// so the room composer's autocomplete lists what ACTUALLY exists.
|
|
765
|
-
|
|
766
|
-
|
|
766
|
+
// Prefer the INTENDED model (opts.model — set at open and on every /model switch)
|
|
767
|
+
// over the message's own `m.model`. A RESUMED session REPLAYS the transcript's old
|
|
768
|
+
// init messages, all carrying the PRE-switch model, and each one used to reset the
|
|
769
|
+
// displayed model back (Max 2026-07-02: switch to Fable, then 24 replayed
|
|
770
|
+
// "init model=claude-opus-4-8" dragged the chip back to Opus with no result yet to
|
|
771
|
+
// correct it). opts.model reflects what the session will actually run, so trust it.
|
|
772
|
+
curModel = opts.model || m.model || model || curModel
|
|
773
|
+
emit({ kind: 'system', sessionId, model: opts.model || m.model || model || null, commands: Array.isArray(m.slash_commands) ? m.slash_commands : undefined })
|
|
767
774
|
// Dynamic model list — ask the SDK for its OWN supported models and surface
|
|
768
775
|
// them to the room so the /model picker renders live options (incl. new
|
|
769
776
|
// models like Fable) instead of a hardcoded three-item list. Fire-and-forget:
|
|
@@ -819,6 +826,30 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
819
826
|
}
|
|
820
827
|
case 'result':
|
|
821
828
|
if (m.session_id) sessionId = m.session_id
|
|
829
|
+
// Bad resume target: the CLI can't find the session we tried to resume — a forked
|
|
830
|
+
// id that was never persisted, or a pruned transcript. It surfaces as an is_error
|
|
831
|
+
// RESULT (subtype error_during_execution), NOT a thrown error, so it bypasses the
|
|
832
|
+
// catch below and used to loop forever on the dead id (Max 2026-07-02, sid 6db6c1e3
|
|
833
|
+
// stuck across restarts + model switches). Drop the resume for good, re-run FRESH
|
|
834
|
+
// on the current model, and re-deliver the in-flight turn so nothing is lost.
|
|
835
|
+
if (m.is_error && Array.isArray(m.errors) && m.errors.some((x) => /no conversation found/i.test(String(x))) && (sessionId || persistedSessionId) && restartCount < RESTART_MAX) {
|
|
836
|
+
restartCount++
|
|
837
|
+
const wasActive = turnActive
|
|
838
|
+
turnActive = false
|
|
839
|
+
sessionId = null; persistedSessionId = null // never resume the dead id again
|
|
840
|
+
emit({ kind: 'note', text: 'previous session not found — continuing on a fresh one' })
|
|
841
|
+
const oldInput = input
|
|
842
|
+
input = makeInputStream()
|
|
843
|
+
try { oldInput.end() } catch { /* noop */ }
|
|
844
|
+
const replay = wasActive ? lastTurnText : null
|
|
845
|
+
restartTimer = setTimeout(() => {
|
|
846
|
+
restartTimer = null
|
|
847
|
+
if (closed) return
|
|
848
|
+
runQuery()
|
|
849
|
+
if (replay != null) { turnActive = true; lastEvtTs = Date.now(); stalledSent = false; input.push([{ type: 'text', text: replay }, { type: 'text', text: `<system-reminder>\n${TP_ROOM_REMINDER}\n</system-reminder>` }]) }
|
|
850
|
+
}, 300)
|
|
851
|
+
break
|
|
852
|
+
}
|
|
822
853
|
// Swallow the interrupt's redundant teardown results (see `interrupting` above):
|
|
823
854
|
// abort() already emitted the one canonical aborted boundary. `success` (and any
|
|
824
855
|
// non-teardown subtype) always emits; only the aborted/error_during_execution pair
|
|
@@ -956,7 +987,7 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
956
987
|
return {
|
|
957
988
|
// A lazy (restored-idle) session cold-boots the query on its first turn. input.push
|
|
958
989
|
// is queue-backed, so the pushed turn buffers and runs once the query is ready.
|
|
959
|
-
sendTurn(text) { if (!closed) { if (!started) runQuery(); turnActive = true; sawSuggestion = false; if (sugTimer) { clearTimeout(sugTimer); sugTimer = null } lastEvtTs = Date.now(); stalledSent = false; input.push([{ type: 'text', text: String(text) }, { type: 'text', text: `<system-reminder>\n${TP_ROOM_REMINDER}\n</system-reminder>` }]) } },
|
|
990
|
+
sendTurn(text) { if (!closed) { if (!started) runQuery(); turnActive = true; sawSuggestion = false; lastTurnText = String(text); if (sugTimer) { clearTimeout(sugTimer); sugTimer = null } lastEvtTs = Date.now(); stalledSent = false; input.push([{ type: 'text', text: String(text) }, { type: 'text', text: `<system-reminder>\n${TP_ROOM_REMINDER}\n</system-reminder>` }]) } },
|
|
960
991
|
// Cold-boot the query WITHOUT sending a turn — the background warmer calls this on
|
|
961
992
|
// lazily-restored idle terminals so they're ready before the user clicks them.
|
|
962
993
|
warm() { if (!started && !closed) runQuery() },
|