thinkpool-pair 0.7.200 → 0.7.201

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/bridge.mjs CHANGED
@@ -844,15 +844,16 @@ const sessions = new Map()
844
844
  let attachedId = null
845
845
  let shuttingDown = false
846
846
 
847
- // Background warmer (2026-07-02): after a restart, proactively cold-boot the lazily-
848
- // restored IDLE terminals — ready-gated to ≤WARM_CONCURRENCY at once so there's no
849
- // boot-storm (the contention that made restore take minutes) — so they're warm BEFORE
850
- // the user clicks them, erasing the lazy-boot first-turn penalty. A user turn to a
851
- // still-cold terminal boots it on demand as the fallback. warmComplete frees a slot when
852
- // a terminal's init `system` event proves it live (called from onEvent).
847
+ // Background warming is OPT-IN. Even a per-room cap multiplies under the account
848
+ // supervisor: four rooms at concurrency 3 launched twelve Claude SDK cold boots at once,
849
+ // stretching measured readiness from ~15s to 40–66s and leaving every idle process
850
+ // resident. Restored transcripts already paint immediately and sendTurn boots a cold lane
851
+ // on demand, so the safe default is zero speculative runtimes. TP_BACKGROUND_WARM=1 keeps
852
+ // the old behaviour available for a deliberately single-room host.
853
853
  const warmQueue = []
854
854
  let warmingCount = 0
855
855
  const WARM_CONCURRENCY = 3
856
+ const BACKGROUND_WARM_ENABLED = process.env.TP_BACKGROUND_WARM === '1'
856
857
  function pumpWarm() {
857
858
  while (warmingCount < WARM_CONCURRENCY && warmQueue.length) {
858
859
  const e = warmQueue.shift()
@@ -2693,7 +2694,10 @@ channel
2693
2694
  // through to the SDK default when absent. Stamped on the entry so the announce
2694
2695
  // carries it at open (chip shows the model immediately, not the branch).
2695
2696
  const runtime = structuredRuntime(payload.cmd)
2696
- openStructured({ id: payload.id, runtime, mode: payload.mode, model: payload.model || undefined, provider: runtime === 'codex' ? undefined : payload.provider || undefined })
2697
+ // Opening a tab is not work. Paint and announce it immediately, then cold-boot the
2698
+ // expensive Claude/Codex runtime on its first real turn. This keeps a fresh terminal
2699
+ // instant and prevents abandoned tabs from becoming permanent resident processes.
2700
+ openStructured({ id: payload.id, runtime, mode: payload.mode, model: payload.model || undefined, provider: runtime === 'codex' ? undefined : payload.provider || undefined, defer: true })
2697
2701
  process.stderr.write(`\n ◆ web opened a structured "${payload.cmd}" session (${payload.mode || 'default'}${payload.model ? `, model ${payload.model}` : ''}).\n`)
2698
2702
  return
2699
2703
  }
@@ -2829,7 +2833,7 @@ channel
2829
2833
  // ── structured-session control (Phase 2) ──
2830
2834
  .on('broadcast', { event: 'code-open' }, ({ payload }) => {
2831
2835
  if (payload?.host && payload.host !== name) return
2832
- openStructured({ id: payload?.id || randomUUID(), runtime: payload?.runtime, model: payload?.model, provider: payload?.runtime === 'codex' ? undefined : payload?.provider, resume: payload?.resume, mode: payload?.mode })
2836
+ openStructured({ id: payload?.id || randomUUID(), runtime: payload?.runtime, model: payload?.model, provider: payload?.runtime === 'codex' ? undefined : payload?.provider, resume: payload?.resume, mode: payload?.mode, defer: true })
2833
2837
  })
2834
2838
  .on('broadcast', { event: 'code-turn' }, async ({ payload }) => {
2835
2839
  const s = payload?.term && sessions.get(payload.term)
@@ -3220,10 +3224,9 @@ channel
3220
3224
  // saved-session restore above still runs unconditionally, so a bridge
3221
3225
  // restart resumes backgrounded sessions regardless of this guard.
3222
3226
  else if (attachedCmd) openStructured({ id: randomUUID(), runtime: structuredRuntime(attachedCmd) })
3223
- // Background-warm the lazily-restored idle terminals shortly after restore, so
3224
- // they're ready before the user clicks them (no first-turn penalty). Delayed so the
3225
- // eagerly-booted (mid-turn / auto-resumed) sessions get the machine to themselves first.
3226
- setTimeout(() => {
3227
+ // Optional compatibility lever for deliberately single-room hosts. Account-mode
3228
+ // and normal standalone launches leave restored idle lanes truly lazy by default.
3229
+ if (BACKGROUND_WARM_ENABLED) setTimeout(() => {
3227
3230
  for (const [, e] of sessions) if (e.deferred && !e.warmed && !e.warming && !e.session?.started) warmQueue.push(e)
3228
3231
  if (warmQueue.length) { process.stderr.write(`\n ◆ background-warming ${warmQueue.length} idle terminal(s) (≤${WARM_CONCURRENCY} at a time)…\n`); pumpWarm() }
3229
3232
  }, 6000)
@@ -239,7 +239,9 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
239
239
  // long time"; settingSources:['user','project','local'] boots every host MCP
240
240
  // server + SessionStart hook + plugin before init. This is the A-side baseline;
241
241
  // open a terminal with TP_MCP_STRICT=1 (below) for the B-side to isolate MCP's share.
242
- const spawnT0 = Date.now()
242
+ // A lazy lane may sit unopened for hours. Start the latency clock when query() really
243
+ // launches, not when the lightweight tab/session wrapper is created.
244
+ let spawnT0 = null
243
245
  let readyLogged = false
244
246
  let modelsSent = false // one-shot: emit the SDK's supported-model list on first init
245
247
  let sdkToolsChecked = false // one-shot: run the AskUserQuestion regression guard on the first init carrying tools[]
@@ -804,6 +806,7 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
804
806
  }
805
807
 
806
808
  const runQuery = async () => {
809
+ if (spawnT0 == null) spawnT0 = Date.now()
807
810
  started = true
808
811
  // Per-query AbortController (child of the session `ac`): lets a model switch kill
809
812
  // just this query. Resolve the previous qDone in case it's still pending.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.200",
3
+ "version": "0.7.201",
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": {