claude-code-session-manager 0.17.1 → 0.17.3
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/dist/assets/{TiptapBody-B16U7NOC.js → TiptapBody-8BWmdZaW.js} +1 -1
- package/dist/assets/{cssMode-BlRHn_L6.js → cssMode-BDpLlH5R.js} +1 -1
- package/dist/assets/{freemarker2-Bk2fXLx4.js → freemarker2-CkC8ydXL.js} +1 -1
- package/dist/assets/{handlebars-eLqjBxBa.js → handlebars-BDtW8qF8.js} +1 -1
- package/dist/assets/{html-BzvHaJnM.js → html-BtBFsoaA.js} +1 -1
- package/dist/assets/{htmlMode-C9rcOKw8.js → htmlMode-CIkNmEpe.js} +1 -1
- package/dist/assets/{index-DqO1hmJF.js → index-eVpHUwxK.js} +392 -391
- package/dist/assets/{javascript-BAOAmGHx.js → javascript-iwGMs0GW.js} +1 -1
- package/dist/assets/{jsonMode-CM0KALCa.js → jsonMode-C_uHUcGc.js} +1 -1
- package/dist/assets/{liquid-Bi-cszJ-.js → liquid-DcCA4gOa.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BxkipFwP.js → lspLanguageFeatures-B0pbksf_.js} +1 -1
- package/dist/assets/{mdx-DwbRtWl-.js → mdx-DNgSs4ym.js} +1 -1
- package/dist/assets/{python-D-IprHOk.js → python-U-Ln7aQn.js} +1 -1
- package/dist/assets/{razor-CC6ogdu3.js → razor-CP9dyqZw.js} +1 -1
- package/dist/assets/{tsMode-BJR_Ezbp.js → tsMode-DjCDF-t0.js} +1 -1
- package/dist/assets/{typescript-FkJpv2Nj.js → typescript-DNPiGbRp.js} +1 -1
- package/dist/assets/{xml-BVuV92it.js → xml-CQ2LZN0I.js} +1 -1
- package/dist/assets/{yaml-CKj9MriX.js → yaml-CXIPQJpq.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/main/crashDiagnostics.cjs +12 -1
- package/src/main/index.cjs +70 -3
package/src/main/index.cjs
CHANGED
|
@@ -48,6 +48,52 @@ let rebooting = false;
|
|
|
48
48
|
// powerSaveBlocker handle — keeps the system from suspending while the app runs
|
|
49
49
|
// so the scheduler's polling and jobs aren't frozen. -1 = not held.
|
|
50
50
|
let powerBlockerId = -1;
|
|
51
|
+
// Linux-only backstop. Electron's `powerSaveBlocker('prevent-app-suspension')`
|
|
52
|
+
// leans on org.gnome.SessionManager, which COSMIC (Pop!_OS 24.04+) doesn't
|
|
53
|
+
// implement — under COSMIC it silently registers only a *delay*-mode logind
|
|
54
|
+
// lock, which postpones suspend by seconds instead of blocking it, so the
|
|
55
|
+
// machine idle-suspends with the app open. We spawn our own block-mode
|
|
56
|
+
// `systemd-inhibit` child, which talks straight to logind and is
|
|
57
|
+
// desktop-agnostic. Handle to the child so we can release it on quit.
|
|
58
|
+
let systemdInhibitChild = null;
|
|
59
|
+
|
|
60
|
+
function startSystemdInhibit() {
|
|
61
|
+
if (process.platform !== 'linux') return;
|
|
62
|
+
// Idempotent: if a live child already holds the lock, don't spawn a second.
|
|
63
|
+
if (systemdInhibitChild && systemdInhibitChild.exitCode === null && !systemdInhibitChild.killed) return;
|
|
64
|
+
try {
|
|
65
|
+
// `sleep:idle` blocks both explicit suspend and the idle-action timer.
|
|
66
|
+
// The holder is a poll on OUR pid rather than `sleep infinity`: when the
|
|
67
|
+
// main process dies — graceful quit, SIGKILL, OR an OOM crash — `kill -0`
|
|
68
|
+
// fails within one tick and the holder exits, releasing the lock. Without
|
|
69
|
+
// this the child reparents to init on a hard kill and the inhibitor leaks
|
|
70
|
+
// forever (one stranded lock per crash). 5s tick = worst-case 5s of stale
|
|
71
|
+
// lock after death, which errs toward "stay awake" — the safe direction.
|
|
72
|
+
const child = spawn('systemd-inhibit', [
|
|
73
|
+
'--what=sleep:idle',
|
|
74
|
+
'--who=Claude Session Manager',
|
|
75
|
+
'--why=Scheduler polling and claude -p jobs must survive idle',
|
|
76
|
+
'--mode=block',
|
|
77
|
+
'sh', '-c', `while kill -0 ${process.pid} 2>/dev/null; do sleep 5; done`,
|
|
78
|
+
], { stdio: 'ignore', detached: false });
|
|
79
|
+
child.on('error', (e) => {
|
|
80
|
+
logs.writeLine({ scope: 'main', level: 'warn', message: 'systemd-inhibit spawn failed', meta: { error: e?.message } });
|
|
81
|
+
});
|
|
82
|
+
if (child.pid) {
|
|
83
|
+
systemdInhibitChild = child;
|
|
84
|
+
logs.writeLine({ scope: 'main', level: 'info', message: 'systemd-inhibit block lock held', meta: { pid: child.pid } });
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
logs.writeLine({ scope: 'main', level: 'warn', message: 'systemd-inhibit unavailable', meta: { error: e?.message } });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stopSystemdInhibit() {
|
|
92
|
+
if (systemdInhibitChild) {
|
|
93
|
+
try { systemdInhibitChild.kill('SIGTERM'); } catch { /* */ }
|
|
94
|
+
systemdInhibitChild = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
51
97
|
|
|
52
98
|
// Boot diagnostics — populated at app.whenReady so the renderer can poll their
|
|
53
99
|
// state via IPC and surface toasts on the failure paths. The first-paint
|
|
@@ -230,6 +276,7 @@ async function rebootApp() {
|
|
|
230
276
|
logReboot('falling back to app.relaunch()');
|
|
231
277
|
app.relaunch();
|
|
232
278
|
}
|
|
279
|
+
runShutdownCleanup(); // app.exit bypasses will-quit
|
|
233
280
|
app.exit(0);
|
|
234
281
|
}
|
|
235
282
|
|
|
@@ -292,6 +339,7 @@ function createWindow() {
|
|
|
292
339
|
// instead of blindly trying http://localhost:5173, which would (a) load
|
|
293
340
|
// remote content and (b) almost always fail in a packaged install.
|
|
294
341
|
console.error('[main] dist/index.html missing and SM_DEV is not set — refusing to load remote content. Reinstall or set SM_DEV=1 for dev.');
|
|
342
|
+
runShutdownCleanup(); // app.exit bypasses will-quit
|
|
295
343
|
app.exit(1);
|
|
296
344
|
return;
|
|
297
345
|
}
|
|
@@ -686,7 +734,13 @@ app.whenReady().then(async () => {
|
|
|
686
734
|
// uncleanly (the silent OOM-kill we've been chasing), then starts the memory
|
|
687
735
|
// heartbeat + render/child-process-gone hooks for this run. See
|
|
688
736
|
// crashDiagnostics.cjs for the full rationale.
|
|
689
|
-
crashDiagnostics.init({
|
|
737
|
+
crashDiagnostics.init({
|
|
738
|
+
logs,
|
|
739
|
+
getPowerBlockerId: () => powerBlockerId,
|
|
740
|
+
// If a suspend ever fires while we're alive, the OS lock lapsed — re-assert
|
|
741
|
+
// our block-mode systemd-inhibit so the next idle window stays covered.
|
|
742
|
+
onSuspendWhileAlive: () => { stopSystemdInhibit(); startSystemdInhibit(); },
|
|
743
|
+
});
|
|
690
744
|
|
|
691
745
|
// Boot-time detection 1: surface `claude` binary resolution so a missing
|
|
692
746
|
// install becomes visible to the renderer instead of failing silently on
|
|
@@ -878,6 +932,8 @@ app.whenReady().then(async () => {
|
|
|
878
932
|
} catch (e) {
|
|
879
933
|
logs.writeLine({ scope: 'main', level: 'warn', message: 'powerSaveBlocker failed', meta: { error: e?.message } });
|
|
880
934
|
}
|
|
935
|
+
// Linux backstop — Electron's blocker no-ops under COSMIC (see above).
|
|
936
|
+
startSystemdInhibit();
|
|
881
937
|
|
|
882
938
|
// OTEL: load persisted config and start the exporter only if `enabled`.
|
|
883
939
|
// Failures are non-fatal — the app must keep working without telemetry.
|
|
@@ -893,7 +949,15 @@ app.whenReady().then(async () => {
|
|
|
893
949
|
});
|
|
894
950
|
});
|
|
895
951
|
|
|
896
|
-
|
|
952
|
+
// Centralized teardown. `will-quit` fires on a normal quit, but `app.exit()`
|
|
953
|
+
// (in-app reboot, dist-missing abort) bypasses will-quit entirely — so those
|
|
954
|
+
// sites must call this directly, otherwise markCleanShutdown never runs and the
|
|
955
|
+
// next boot misreports a clean reboot as an UNCLEAN OOM crash, and the
|
|
956
|
+
// powerSaveBlocker/inhibitor leak until process death.
|
|
957
|
+
let teardownDone = false;
|
|
958
|
+
function runShutdownCleanup() {
|
|
959
|
+
if (teardownDone) return; // idempotent — will-quit may still fire after an app.exit path
|
|
960
|
+
teardownDone = true;
|
|
897
961
|
// Mark a clean exit so the next boot can distinguish a graceful quit from an
|
|
898
962
|
// OOM-kill / native crash (which leaves the sentinel `open`).
|
|
899
963
|
crashDiagnostics.markCleanShutdown();
|
|
@@ -904,7 +968,10 @@ app.on('will-quit', () => {
|
|
|
904
968
|
try { powerSaveBlocker.stop(powerBlockerId); } catch { /* */ }
|
|
905
969
|
powerBlockerId = -1;
|
|
906
970
|
}
|
|
907
|
-
|
|
971
|
+
stopSystemdInhibit();
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
app.on('will-quit', runShutdownCleanup);
|
|
908
975
|
|
|
909
976
|
app.on('window-all-closed', () => {
|
|
910
977
|
if (rebooting) return; // new window is about to be created
|