claude-code-session-manager 0.17.1 → 0.17.2
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/src/main/index.cjs +49 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
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
|
|
@@ -878,6 +924,8 @@ app.whenReady().then(async () => {
|
|
|
878
924
|
} catch (e) {
|
|
879
925
|
logs.writeLine({ scope: 'main', level: 'warn', message: 'powerSaveBlocker failed', meta: { error: e?.message } });
|
|
880
926
|
}
|
|
927
|
+
// Linux backstop — Electron's blocker no-ops under COSMIC (see above).
|
|
928
|
+
startSystemdInhibit();
|
|
881
929
|
|
|
882
930
|
// OTEL: load persisted config and start the exporter only if `enabled`.
|
|
883
931
|
// Failures are non-fatal — the app must keep working without telemetry.
|
|
@@ -904,6 +952,7 @@ app.on('will-quit', () => {
|
|
|
904
952
|
try { powerSaveBlocker.stop(powerBlockerId); } catch { /* */ }
|
|
905
953
|
powerBlockerId = -1;
|
|
906
954
|
}
|
|
955
|
+
stopSystemdInhibit();
|
|
907
956
|
});
|
|
908
957
|
|
|
909
958
|
app.on('window-all-closed', () => {
|