thinkpool-pair 0.7.173 → 0.7.174

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 +17 -6
  2. package/package.json +1 -1
package/account.mjs CHANGED
@@ -386,9 +386,21 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
386
386
  // ThinkPool nothing; there's no cost reason to refuse a free host.
387
387
 
388
388
  const DEFAULT_DIR = process.cwd() // unbound sessions auto-serve here; `bind` overrides per session
389
+ // Ground truth for "am I the managed service": ask the service manager, not the
390
+ // THINKPOOL_PAIR_AUTOUPDATE env proxy. Since pinned installs became the default,
391
+ // the plist/unit sets AUTOUPDATE only when --auto-update was passed — the env var
392
+ // now means "track @latest", NOT "I'm the service". Testing the env alone made a
393
+ // pinned managed service (a) announce service:false → dashboard says "foreground",
394
+ // and (b) skip the uninstall on a dashboard Kill → launchd respawns the "killed"
395
+ // bridge ~2s later (the 2026-06-18 zombie-toggle, reintroduced by pinning).
396
+ let isManagedService = false
397
+ try { const svc = await import('./service.mjs'); isManagedService = svc.serviceActive(null) } catch { /* not installed */ }
398
+ const runsAsService = () => process.env.THINKPOOL_PAIR_AUTOUPDATE === '1' || isManagedService
389
399
  const svcHint = process.env.THINKPOOL_PAIR_AUTOUPDATE === '1'
390
400
  ? ' (auto-updating background service — survives reboot, tracks @latest)\n'
391
- : ' (run hands-off + auto-updating: npx thinkpool-pair install-service)\n'
401
+ : isManagedService
402
+ ? ' (background service — survives reboot, restarts on crash, pinned to this version)\n'
403
+ : ' (run hands-off + auto-updating: npx thinkpool-pair install-service)\n'
392
404
  const _effDefault = loadDefaultDir() || claudeRecentProjectDir() || DEFAULT_DIR
393
405
  process.stderr.write(`\n ◆ thinkpool-pair — account mode · ${email}\n New sessions auto-serve from ${_effDefault}${loadDefaultDir() ? ' (your default)' : (claudeRecentProjectDir() ? ' (your most-recent Claude Code project)' : '')}\n (change it: npx thinkpool-pair set-default-dir <dir> · one room: npx thinkpool-pair bind <code> <dir>)\n${svcHint}`)
394
406
 
@@ -463,7 +475,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
463
475
  // restart), force the process down and release the lock so a fresh bridge
464
476
  // can take over. NOT unref'd: it must keep the loop alive until it fires.
465
477
  setTimeout(() => { try { releaseSingleton() } catch { /* noop */ } process.exit(0) }, 2500)
466
- if (process.env.THINKPOOL_PAIR_AUTOUPDATE === '1') {
478
+ if (runsAsService()) {
467
479
  try { const svc = await import('./service.mjs'); svc.uninstallService(null) } catch { /* noop */ }
468
480
  }
469
481
  process.kill(process.pid, 'SIGTERM')
@@ -493,7 +505,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
493
505
  // Unsupervised FOREGROUND bridge (no launchd/systemd) → re-spawn ourselves DETACHED
494
506
  // after releasing the lock, so "Restart" isn't a footgun that never comes back
495
507
  // (2026-06-25). A supervised bridge is respawned by its supervisor on exit(1).
496
- if (process.env.THINKPOOL_PAIR_AUTOUPDATE !== '1') {
508
+ if (!runsAsService()) {
497
509
  try { spawn(process.execPath, [process.argv[1]], { detached: true, stdio: 'ignore', env: process.env }).unref() } catch { /* noop */ }
498
510
  }
499
511
  process.exit(1)
@@ -531,8 +543,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
531
543
  const trackPresence = makeThrottledTrack(acct, { minMs: 5000, onStatus: (s) => { lastTrackStatus = s } })
532
544
  // `service` lets the dashboard show whether this bridge is the durable installed
533
545
  // service (always-on, survives reboot) vs a foreground `npx` run that dies with its
534
- // terminal. THINKPOOL_PAIR_AUTOUPDATE=1 is set ONLY by the install-service plist/unit
535
- // (service.mjs), so it's the reliable proxy for "running as the managed service".
546
+ // terminal — runsAsService() above (service-manager ground truth, not the env proxy).
536
547
  // providerPubKey: the bridge's RSA-OAEP public key (SPKI base64), generated +
537
548
  // persisted 0600 on first read. The dashboard imports it to SEAL a provider's API
538
549
  // key so it reaches the bridge without ever crossing the wire in the clear. Computed
@@ -541,7 +552,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
541
552
  // to spawn against — re-read each announce so an add/remove reflects immediately.
542
553
  // Both are additive; older clients ignore them (feature: multi-provider BYOK, slice 1).
543
554
  const PROVIDER_PUBKEY = publicKeyB64()
544
- const pushPresence = () => trackPresence({ name: machine, version: VERSION, rooms: [...children.keys()], refused: [...refused].map(([code, reason]) => ({ code, reason })), service: process.env.THINKPOOL_PAIR_AUTOUPDATE === '1', providerPubKey: PROVIDER_PUBKEY, providers: announceProviders(), ts: Date.now() })
555
+ const pushPresence = () => trackPresence({ name: machine, version: VERSION, rooms: [...children.keys()], refused: [...refused].map(([code, reason]) => ({ code, reason })), service: runsAsService(), providerPubKey: PROVIDER_PUBKEY, providers: announceProviders(), ts: Date.now() })
545
556
 
546
557
  // ── Provider registry — the multi-BYOK wire contract (slice 1). ─────────────
547
558
  // AUTH: the account channel `tpacct:<uid>` is created WITHOUT config.private:true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.173",
3
+ "version": "0.7.174",
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": {