thinkpool-pair 0.6.22 → 0.6.24
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 +25 -7
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -184,9 +184,10 @@ const forceContinue = ownArgs.includes('--continue')
|
|
|
184
184
|
const forceFresh = ownArgs.includes('--fresh')
|
|
185
185
|
|
|
186
186
|
// One yes/no on the bridge's stderr (same channel as the agent picker).
|
|
187
|
-
|
|
187
|
+
// defaultYes=true → bare Enter means yes; false → Enter means no (opt-in).
|
|
188
|
+
const askYesNo = (q, defaultYes = true) => new Promise((resolve) => {
|
|
188
189
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
|
|
189
|
-
rl.question(q, (ans) => { rl.close(); resolve(!/^n/i.test(
|
|
190
|
+
rl.question(q, (ans) => { rl.close(); const a = String(ans).trim(); resolve(defaultYes ? !/^n/i.test(a) : /^y/i.test(a)) })
|
|
190
191
|
})
|
|
191
192
|
|
|
192
193
|
let attachedCmd = null, attachedArgs = [], continuing = false
|
|
@@ -227,10 +228,12 @@ if (process.stdin.isTTY && !headless && process.env.THINKPOOL_PAIR_AUTOUPDATE !=
|
|
|
227
228
|
let declined = false
|
|
228
229
|
try { declined = fs.existsSync(declineFile) } catch { /* noop */ }
|
|
229
230
|
if (!declined) {
|
|
230
|
-
|
|
231
|
+
// Default NO — a bare Enter keeps it foreground (the expected "I ran it in
|
|
232
|
+
// my terminal" behaviour); installing a background daemon is deliberate opt-in.
|
|
233
|
+
const yes = await askYesNo(`\n Run as a background service instead? It survives reboot + auto-updates,\n but runs detached — you'd stop it with 'uninstall-service', not Ctrl-C.\n Install background service? [y/N] `, false)
|
|
231
234
|
if (yes) {
|
|
232
235
|
svc.installService(room, attachedCmd ? [attachedCmd, ...attachedArgs] : [])
|
|
233
|
-
process.stderr.write(
|
|
236
|
+
process.stderr.write(`\n ◆ the background service is handling room ${room} now — you can close this terminal.\n ◆ STOP IT with: npx thinkpool-pair@latest uninstall-service ${room}\n (closing the terminal does NOT stop it — it's detached under launchd/systemd.)\n\n`)
|
|
234
237
|
process.exit(0) // hand off to the supervised service (no double bridge)
|
|
235
238
|
}
|
|
236
239
|
try { fs.mkdirSync(path.dirname(declineFile), { recursive: true }); fs.writeFileSync(declineFile, new Date().toISOString()) } catch { /* noop */ }
|
|
@@ -536,6 +539,16 @@ channel
|
|
|
536
539
|
const t = terms.get(payload.term)
|
|
537
540
|
if (t && !t.attached) { try { t.term.resize(Math.max(payload.cols, 80), Math.max(payload.rows, 20)); announce() } catch { /* noop */ } }
|
|
538
541
|
})
|
|
542
|
+
.on('broadcast', { event: 'session-deleted' }, async () => {
|
|
543
|
+
// The room was deleted from the dashboard. Close the bridge — and if we're a
|
|
544
|
+
// background service, uninstall ourselves first so the supervisor doesn't
|
|
545
|
+
// restart us into a room that no longer exists.
|
|
546
|
+
process.stderr.write('\n ◆ this room was deleted — closing the bridge.\n')
|
|
547
|
+
if (process.env.THINKPOOL_PAIR_AUTOUPDATE === '1') {
|
|
548
|
+
try { const svc = await import('./service.mjs'); svc.uninstallService(room) } catch { /* noop */ }
|
|
549
|
+
}
|
|
550
|
+
shutdown()
|
|
551
|
+
})
|
|
539
552
|
.on('broadcast', { event: 'term-open' }, ({ payload }) => {
|
|
540
553
|
if (!payload?.id || !payload?.cmd) return
|
|
541
554
|
// Multi-bridge rooms: a targeted open is for ONE machine. Untargeted
|
|
@@ -781,7 +794,7 @@ if (process.env.THINKPOOL_PAIR_AUTOUPDATE === '1' && VERSION) {
|
|
|
781
794
|
}, 60000).unref()
|
|
782
795
|
}
|
|
783
796
|
|
|
784
|
-
function shutdown() {
|
|
797
|
+
async function shutdown() {
|
|
785
798
|
if (shuttingDown) return
|
|
786
799
|
shuttingDown = true
|
|
787
800
|
clearInterval(flushTimer)
|
|
@@ -789,11 +802,16 @@ function shutdown() {
|
|
|
789
802
|
const bye = Buffer.from('\r\n[ shared session ended ]\r\n', 'utf8').toString('base64')
|
|
790
803
|
for (const id of terms.keys()) bcast('pty-out', { term: id, b64: bye })
|
|
791
804
|
} catch { /* noop */ }
|
|
792
|
-
|
|
805
|
+
// Leave presence EXPLICITLY before exiting so the web room flips to dormant
|
|
806
|
+
// immediately. Previously we removeChannel()'d and process.exit()'d on the next
|
|
807
|
+
// line — the leave frame never flushed, so the web only noticed via the realtime
|
|
808
|
+
// heartbeat timeout (tens of seconds later) and looked stuck "live".
|
|
809
|
+
try { await channel.untrack() } catch { /* noop */ }
|
|
810
|
+
try { await supabase.removeChannel(channel) } catch { /* noop */ }
|
|
793
811
|
for (const t of terms.values()) { try { t.term.kill() } catch { /* noop */ } }
|
|
794
812
|
for (const s of sessions.values()) { try { s.session.end() } catch { /* noop */ } }
|
|
795
813
|
detachLocal()
|
|
796
|
-
process.exit(0)
|
|
814
|
+
setTimeout(() => process.exit(0), 250) // small grace for the leave/close frames to flush over the socket
|
|
797
815
|
}
|
|
798
816
|
process.on('SIGINT', shutdown)
|
|
799
817
|
process.on('SIGTERM', shutdown)
|