thinkpool-pair 0.7.147 → 0.7.149
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/account.mjs +39 -18
- package/bridge.mjs +15 -2
- 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
|
-
|
|
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
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
},
|
|
332
|
-
|
|
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
|
@@ -1840,8 +1840,21 @@ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rol
|
|
|
1840
1840
|
// mid-turn) flash a brief "Compacting…" beat so it isn't silent — we only learn of it
|
|
1841
1841
|
// post-hoc, so there's no real duration to animate (Max, 2026-07-02).
|
|
1842
1842
|
if (evt.kind === 'compaction') {
|
|
1843
|
-
if (entry.compacting)
|
|
1844
|
-
|
|
1843
|
+
if (entry.compacting) {
|
|
1844
|
+
// MANUAL /compact: attribute the recap card, then CLEAR the "Compacting…"
|
|
1845
|
+
// indicator right here — the compact_boundary milestone IS the "done" signal.
|
|
1846
|
+
// We used to wait for the turn's `result` (below), but a real /compact re-inits
|
|
1847
|
+
// the SDK session, so the result can land after entry.compacting was lost →
|
|
1848
|
+
// the indicator hung forever and kept counting (Max, 2026-07-03). The milestone
|
|
1849
|
+
// is emitted with compacting still true (that's how we attribute `by`), so it's
|
|
1850
|
+
// the reliable place to stop it. The result handler below stays as the backstop
|
|
1851
|
+
// for a NO-OP /compact ("nothing to compact"), which emits a result but no
|
|
1852
|
+
// compaction milestone.
|
|
1853
|
+
evt.by = entry.compactBy || null
|
|
1854
|
+
entry.compacting = false
|
|
1855
|
+
entry.compactBy = null
|
|
1856
|
+
bcast('code-event', { term: id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
1857
|
+
} else if (evt.trigger === 'auto') {
|
|
1845
1858
|
bcast('code-event', { term: id, evt: { kind: 'compact', status: 'start', ts: Date.now() } })
|
|
1846
1859
|
setTimeout(() => { bcast('code-event', { term: id, evt: { kind: 'compact', status: 'done', ts: Date.now() } }) }, 2500)
|
|
1847
1860
|
}
|