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.
Files changed (2) hide show
  1. package/claude-session.mjs +34 -3
  2. package/package.json +1 -1
@@ -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
- curModel = m.model || model || curModel
766
- emit({ kind: 'system', sessionId, model: m.model || model || null, commands: Array.isArray(m.slash_commands) ? m.slash_commands : undefined })
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() },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.140",
3
+ "version": "0.7.142",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {