thinkpool-pair 0.7.59 → 0.7.60

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/account.mjs +20 -2
  2. package/package.json +1 -1
package/account.mjs CHANGED
@@ -223,6 +223,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
223
223
  // codes + version) on a per-account channel; the dashboard subscribes read-only.
224
224
  const machine = os.hostname().replace(/\.local$/, '')
225
225
  const acct = sb.channel(`tpacct:${session.user.id}`, { config: { presence: { key: machine }, broadcast: { self: false } } })
226
+ let restarting = false // set by the "restart" broadcast → stop() exits non-zero so a supervisor respawns us
226
227
  // Dashboard "Disconnect" → broadcast shutdown → clean exit (presence leaves, bar flips live).
227
228
  // If we're the auto-restarting background service (launchd KeepAlive / systemd
228
229
  // Restart=always sets AUTOUPDATE=1), a bare exit would be respawned ~2s later —
@@ -244,6 +245,20 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
244
245
  }
245
246
  process.kill(process.pid, 'SIGTERM')
246
247
  })
248
+ // Dashboard "Restart" → broadcast restart → graceful stop with a NON-ZERO exit so
249
+ // EVERY supervisor respawns us: launchd KeepAlive / systemd Restart=always respawn
250
+ // regardless of exit code, and the bridge.mjs --supervise wrappers (cmux tier, room
251
+ // tier) respawn on non-zero but treat 0 as a clean stop. Children persist + resume
252
+ // their sessions (session-store), so the bounce is invisible in-room. Unlike shutdown
253
+ // we do NOT uninstall the service. A plain foreground bridge has no supervisor, so it
254
+ // just stops — restart is meaningful for the always-on bridge. Old bridges (pre-0.7.60)
255
+ // don't subscribe to this event, so the dashboard button is a harmless no-op there.
256
+ acct.on('broadcast', { event: 'restart' }, () => {
257
+ process.stderr.write('\n ◇ restart requested from the dashboard — bouncing bridge (sessions resume).\n')
258
+ restarting = true
259
+ setTimeout(() => { try { releaseSingleton() } catch { /* noop */ } process.exit(1) }, 2500)
260
+ process.kill(process.pid, 'SIGTERM')
261
+ })
247
262
  const pushPresence = () => { try { acct.track({ name: machine, version: VERSION, rooms: [...children.keys()], ts: Date.now() }) } catch { /* noop */ } }
248
263
  // Re-track on EVERY (re)subscribe, not just the first: a realtime reconnect
249
264
  // (network blip, or a token swap mid-flight) rejoins the channel and must
@@ -319,16 +334,19 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
319
334
  await tick()
320
335
  const iv = setInterval(tick, 15000)
321
336
  let stopping = false
337
+ // restart-vs-stop is the ONLY difference in exit code: a web "restart" sets this so
338
+ // we exit non-zero (every supervisor respawns); a normal stop/shutdown exits 0.
322
339
  const stop = (sig) => {
323
340
  if (stopping) return; stopping = true
341
+ const code = restarting ? 1 : 0
324
342
  clearInterval(iv)
325
343
  keepFresh.cancel()
326
344
  releaseSingleton()
327
345
  for (const c of children.values()) { try { c.kill(sig || 'SIGTERM') } catch { /* noop */ } }
328
346
  // Flush the presence LEAVE before exiting so the dashboard flips to
329
347
  // "not connected" in realtime (don't fire-and-forget the untrack).
330
- ;(async () => { try { await acct.untrack() } catch { /* noop */ } try { await sb.removeChannel(acct) } catch { /* noop */ } process.exit(0) })()
331
- setTimeout(() => process.exit(0), 1500) // hard backstop if the flush hangs
348
+ ;(async () => { try { await acct.untrack() } catch { /* noop */ } try { await sb.removeChannel(acct) } catch { /* noop */ } process.exit(code) })()
349
+ setTimeout(() => process.exit(code), 1500) // hard backstop if the flush hangs
332
350
  }
333
351
  for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(sig, () => stop(sig))
334
352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.59",
3
+ "version": "0.7.60",
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": {