thinkpool-pair 0.7.146 → 0.7.148

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 (3) hide show
  1. package/account.mjs +39 -18
  2. package/bridge.mjs +1 -1
  3. package/package.json +1 -1
package/account.mjs CHANGED
@@ -308,28 +308,49 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
308
308
  // just stops — restart is meaningful for the always-on bridge. Old bridges (pre-0.7.60)
309
309
  // don't subscribe to this event, so the dashboard button is a harmless no-op there.
310
310
  acct.on('broadcast', { event: 'restart' }, () => {
311
- const supervised = process.env.THINKPOOL_PAIR_AUTOUPDATE === '1'
312
- process.stderr.write('\n ◇ restart requested from the dashboard — bouncing bridge (sessions resume).\n')
313
311
  restarting = true
314
- setTimeout(() => {
312
+ // The dashboard "Restart bridge" button ALSO updates to the newest published version
313
+ // (Max, 2026-07-03). If an OS service manages this bridge, spawn a DETACHED updater
314
+ // that re-pins the service to @latest and reloads it (install-service resolves the
315
+ // newest version → rewrites the plist/unit → bootout+bootstrap). Detached → its own
316
+ // session, so it SURVIVES this process being booted out (the reload kills us); it
317
+ // inherits our env, whose PATH carries nvm's bin, so npx/node resolve — a bare launchd
318
+ // PATH would NOT (the 2026-07-03 gotcha that made a manual updater silently no-op).
319
+ // The reload IS the bounce; sessions resume from disk on the new version. Best-effort:
320
+ // if there's no service or npm is unreachable, we fall through to the plain bounce, so
321
+ // the button is never dead — and even a failed update degrades to today's behaviour.
322
+ const plainBounce = (delay) => setTimeout(() => {
315
323
  try { releaseSingleton() } catch { /* noop */ }
316
- // A SUPERVISED bridge (launchd KeepAlive / systemd Restart=always / the
317
- // bridge.mjs --supervise wrapper) is respawned by its supervisor on exit, so a
318
- // bare exit(1) bounces cleanly. A plain FOREGROUND bridge has NO supervisor —
319
- // there exit = death and the dashboard "Restart" becomes a footgun (2026-06-25:
320
- // Max's manual `npx thinkpool-pair` died on Restart and never came back). So when
321
- // unsupervised, re-spawn ourselves DETACHED *after* releasing the machine lock
322
- // (the child's acquireSingleton() then sees no live owner and takes it). The child
323
- // has no TTY → bridge.mjs routes a bare invocation straight to account mode. Gated
324
- // on `supervised` so we never double-spawn (supervisor + self) into two bridges.
325
- if (!supervised) {
326
- try {
327
- spawn(process.execPath, [process.argv[1]], { detached: true, stdio: 'ignore', env: process.env }).unref()
328
- } catch { /* respawn failed → exit anyway, no worse than the old behaviour */ }
324
+ // Unsupervised FOREGROUND bridge (no launchd/systemd) → re-spawn ourselves DETACHED
325
+ // after releasing the lock, so "Restart" isn't a footgun that never comes back
326
+ // (2026-06-25). A supervised bridge is respawned by its supervisor on exit(1).
327
+ if (process.env.THINKPOOL_PAIR_AUTOUPDATE !== '1') {
328
+ try { spawn(process.execPath, [process.argv[1]], { detached: true, stdio: 'ignore', env: process.env }).unref() } catch { /* noop */ }
329
329
  }
330
330
  process.exit(1)
331
- }, 2500)
332
- process.kill(process.pid, 'SIGTERM')
331
+ }, delay)
332
+ ;(async () => {
333
+ let updating = false
334
+ try {
335
+ const svc = await import('./service.mjs')
336
+ if (svc.serviceActive(null)) {
337
+ spawn('npx', ['-y', 'thinkpool-pair@latest', 'install-service'], { detached: true, stdio: 'ignore', env: process.env }).unref()
338
+ updating = true
339
+ }
340
+ } catch { /* no service / spawn failed → plain bounce below */ }
341
+ process.stderr.write(updating
342
+ ? '\n ◇ update + restart requested from the dashboard — pulling the newest bridge and reloading; sessions resume.\n'
343
+ : '\n ◇ restart requested from the dashboard — bouncing bridge (sessions resume).\n')
344
+ if (updating) {
345
+ // The updater's bootout replaces us (single clean bounce). Long backstop: if the
346
+ // update never lands (npm down / install failed), bounce the current version so the
347
+ // button isn't dead — the updater normally boots us out well before this fires.
348
+ plainBounce(60000)
349
+ return
350
+ }
351
+ plainBounce(2500)
352
+ process.kill(process.pid, 'SIGTERM')
353
+ })()
333
354
  })
334
355
  // Throttled so a realtime reconnect storm can't machine-gun track() into the
335
356
  // per-client presence rate limit (ClientPresenceRateLimitReached → channel closed
package/bridge.mjs CHANGED
@@ -534,7 +534,7 @@ function readBranch() {
534
534
  // announced `updir` without waiting on this round-trip. Spec:
535
535
  // docs/specs/2026-06-11-code-file-drop.md
536
536
  const UPDIR = path.join(os.tmpdir(), 'thinkpool-pair', room)
537
- const FILE_MAX_BYTES = 10 * 1024 * 1024
537
+ const FILE_MAX_BYTES = 25 * 1024 * 1024 // must match web FILE_MB + bucket file_size_limit (25 MB) — see src/pages/code/code-constants.js
538
538
  const safeName = (n) => String(n || 'file').replace(/[^a-zA-Z0-9._-]/g, '_').slice(-80)
539
539
 
540
540
  const supabase = createClient(SUPABASE_URL, SUPABASE_ANON, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.146",
3
+ "version": "0.7.148",
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": {