thinkpool-pair 0.7.118 → 0.7.119
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/package.json +1 -1
- package/service.mjs +39 -0
package/package.json
CHANGED
package/service.mjs
CHANGED
|
@@ -188,6 +188,41 @@ WantedBy=default.target
|
|
|
188
188
|
throw new Error(`unsupported platform: ${platform}`)
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
// Blocking sleep (installService runs synchronously from the interactive menu; the
|
|
192
|
+
// user is watching, so we WANT to wait + report the real outcome, not fire-and-forget).
|
|
193
|
+
const sleepMs = (ms) => { try { execSync(`sleep ${(ms / 1000).toFixed(2)}`) } catch { /* noop */ } }
|
|
194
|
+
// Which thinkpool-pair versions are running right now (the account supervisor shows as
|
|
195
|
+
// `npm exec thinkpool-pair@<ver>`). Returns a Set of version strings.
|
|
196
|
+
const runningPairVersions = () => {
|
|
197
|
+
try {
|
|
198
|
+
const out = execSync(`ps ax -o command= 2>/dev/null | grep -oE 'thinkpool-pair@[0-9]+\\.[0-9]+\\.[0-9]+' | sort -u`, { encoding: 'utf8', shell: '/bin/bash' })
|
|
199
|
+
return new Set(out.split('\n').map((l) => l.trim().split('@')[1]).filter(Boolean))
|
|
200
|
+
} catch { return new Set() }
|
|
201
|
+
}
|
|
202
|
+
const darwinSvcLoaded = (lbl) => { try { execSync(`launchctl print gui/$(id -u)/${lbl}`, { stdio: 'ignore' }); return true } catch { return false } }
|
|
203
|
+
// After the reload, CONFIRM the new pinned version is actually running — the shell
|
|
204
|
+
// post can report "loaded" while an ORPHANED old child bridge (that bootout didn't
|
|
205
|
+
// reap) keeps serving the OLD version, which is the "Restart & update never works"
|
|
206
|
+
// bug (Max 2026-07-02). If the new version isn't up, SIGKILL lingering ROOM child
|
|
207
|
+
// bridges (never the no-arg menu process) + re-bootstrap, then report the truth.
|
|
208
|
+
function verifyDarwinReload(lbl, file, version) {
|
|
209
|
+
const want = String(version)
|
|
210
|
+
const up = () => darwinSvcLoaded(lbl) && runningPairVersions().has(want)
|
|
211
|
+
for (let i = 0; i < 25 && !up(); i++) sleepMs(600) // fresh (cache-wiped) fetch takes a bit
|
|
212
|
+
if (up()) { process.stderr.write(` ◆ verified: bridge is now running v${want}.\n`); return true }
|
|
213
|
+
process.stderr.write(` ⚠ v${want} not up yet — clearing stale bridges and re-bootstrapping…\n`)
|
|
214
|
+
try { execSync(`launchctl bootout gui/$(id -u)/${lbl} 2>/dev/null || true`, { stdio: 'ignore' }) } catch { /* noop */ }
|
|
215
|
+
// Kill orphaned ROOM child bridges (`bridge.mjs <ROOM>`), which bootout may not have
|
|
216
|
+
// reaped. The uppercase room-code guard never matches the no-arg / `setup` menu.
|
|
217
|
+
try { execSync(`pkill -9 -f 'thinkpool-pair/bridge.mjs [A-Z0-9]' 2>/dev/null || true`, { stdio: 'ignore', shell: '/bin/bash' }) } catch { /* noop */ }
|
|
218
|
+
sleepMs(500)
|
|
219
|
+
try { execSync(`launchctl enable gui/$(id -u)/${lbl} 2>/dev/null || true`, { stdio: 'ignore' }) } catch { /* noop */ }
|
|
220
|
+
for (let i = 0; i < 25 && !up(); i++) { try { execSync(`launchctl bootstrap gui/$(id -u) "${file}" 2>/dev/null || true`, { stdio: 'ignore' }) } catch { /* noop */ } sleepMs(600) }
|
|
221
|
+
if (up()) { process.stderr.write(` ◆ recovered: bridge is now running v${want}.\n`); return true }
|
|
222
|
+
process.stderr.write(` ⚠ could NOT confirm v${want} is running. Recover by hand:\n launchctl bootout gui/$(id -u)/${lbl} ; pkill -9 -f thinkpool-pair ; launchctl bootstrap gui/$(id -u) "${file}"\n`)
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
|
|
191
226
|
// staleProof defaults ON for every real install: even a FIRST install can be launched from
|
|
192
227
|
// a stale npx cache (the launcher itself is npx-resolved), so without the cache-wiping
|
|
193
228
|
// wrapper a fresh `install-service` could pin a stale build just like the update path did.
|
|
@@ -203,6 +238,10 @@ export function installService(room, cmdArgs = [], { autoUpdate = false, version
|
|
|
203
238
|
for (const cmd of a.post) {
|
|
204
239
|
try { execSync(cmd, { stdio: 'inherit' }) } catch (e) { process.stderr.write(` ⚠ "${cmd}" failed: ${e.message}\n`) }
|
|
205
240
|
}
|
|
241
|
+
// Darwin: confirm the reload actually put the NEW version live, self-heal if not.
|
|
242
|
+
// Only for a PINNED (re)install where we know the exact version to look for — an
|
|
243
|
+
// autoUpdate (@latest) install has no fixed version number to verify against.
|
|
244
|
+
if (process.platform === 'darwin' && !autoUpdate) verifyDarwinReload(label(room), a.file, version)
|
|
206
245
|
// Be honest about the cadence — it is NOT instant. The supervisor polls npm
|
|
207
246
|
// roughly every 30 min (THINKPOOL_PAIR_UPDATE_INTERVAL) and only restarts once
|
|
208
247
|
// every served session is idle, so a just-published version can take a while.
|