claude-code-session-manager 0.29.0 → 0.30.0
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-DyiZVN0v.js → TiptapBody-Py59G5JF.js} +1 -1
- package/dist/assets/{index-qS7GdCsL.js → index-BBlKMDCL.js} +4 -4
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/main/kg.cjs +41 -0
- package/src/main/lib/cleanEnv.cjs +32 -1
- package/src/main/lib/credentials.cjs +8 -2
- package/src/main/pluginInstall.cjs +2 -10
- package/src/main/pty.cjs +5 -12
- package/src/main/scheduler.cjs +5 -3
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-BBlKMDCL.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-DMIi9YZH.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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/kg.cjs
CHANGED
|
@@ -51,6 +51,11 @@ const BATCH = 20; // prompts per extraction call (also a per-pro
|
|
|
51
51
|
const KNOWN_VOCAB = 200; // top node names pre-seeded for dedup-at-extraction
|
|
52
52
|
const MAX_TAIL_BYTES = 8 * 1024 * 1024; // bound bytes scanned per ingest run
|
|
53
53
|
const MAX_EXTRACTIONS_PER_RUN = 30; // bound claude calls per run (cost/time)
|
|
54
|
+
// prompts.jsonl is append-only and otherwise grows forever (observed 325 MB).
|
|
55
|
+
// Once we're caught up past this size, rotate it to prompts.jsonl.1 (one backup
|
|
56
|
+
// kept) and reset the cursor — extraction only ever needs the new tail, and the
|
|
57
|
+
// already-extracted prompts live in the per-project graphs.
|
|
58
|
+
const MAX_LOG_BYTES = 256 * 1024 * 1024;
|
|
54
59
|
// Coalescing window before an auto-ingest after new prompts land. Units never
|
|
55
60
|
// mix projects, and a project switch in the log closes the current batch — so
|
|
56
61
|
// with concurrent sessions a short window yields 1-2-prompt batches and one
|
|
@@ -159,6 +164,30 @@ async function saveIngestState(s) {
|
|
|
159
164
|
await writeJson(INGEST_STATE_PATH, s);
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Rotate prompts.jsonl once it grows past MAX_LOG_BYTES. Only call when caught
|
|
169
|
+
* up (lastOffset >= size) so no unconsumed prompts are lost. Renames to a single
|
|
170
|
+
* `.1` backup (overwriting any prior one) and resets the cursor to 0; the
|
|
171
|
+
* UserPromptSubmit hook recreates prompts.jsonl on its next append. Best-effort:
|
|
172
|
+
* a failure leaves the log in place and is retried on the next caught-up cycle.
|
|
173
|
+
*/
|
|
174
|
+
async function rotateLogIfTooLarge(st, stat) {
|
|
175
|
+
if (!stat || stat.size < MAX_LOG_BYTES) return false;
|
|
176
|
+
const rotated = LOG_PATH + '.1';
|
|
177
|
+
try {
|
|
178
|
+
try { await fsp.unlink(rotated); } catch { /* no prior backup */ }
|
|
179
|
+
await fsp.rename(LOG_PATH, rotated);
|
|
180
|
+
st.lastOffset = 0;
|
|
181
|
+
st.updatedAt = new Date().toISOString();
|
|
182
|
+
await saveIngestState(st);
|
|
183
|
+
logger.writeLine({ scope: 'kg', level: 'info', message: 'rotated prompts.jsonl', meta: { rotatedBytes: stat.size, backup: rotated } });
|
|
184
|
+
return true;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
logger.writeLine({ scope: 'kg', level: 'warn', message: 'log rotation failed', meta: { error: e?.message } });
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
162
191
|
/**
|
|
163
192
|
* Per-project prompt-count sidecar: { [encodedCwd]: { count: number, cwd: string } }
|
|
164
193
|
* Returns null when the file does not yet exist (triggers a one-time migration scan).
|
|
@@ -356,7 +385,19 @@ async function ingest() {
|
|
|
356
385
|
try { stat = await fsp.stat(LOG_PATH); }
|
|
357
386
|
catch { broadcast('kg:ingest-progress', { phase: 'done', ingesting: false, added: 0 }); return { ok: true, added: 0, note: 'no log yet' }; }
|
|
358
387
|
|
|
388
|
+
// Truncation/rotation recovery: if the log shrank below our watermark it was
|
|
389
|
+
// rotated or truncated out from under us — restart from the top of the new
|
|
390
|
+
// file rather than wedging forever at a stale offset.
|
|
391
|
+
if (stat.size < st.lastOffset) {
|
|
392
|
+
logger.writeLine({ scope: 'kg', level: 'info', message: 'log shrank; resetting ingest cursor', meta: { size: stat.size, lastOffset: st.lastOffset } });
|
|
393
|
+
st.lastOffset = 0;
|
|
394
|
+
st.updatedAt = new Date().toISOString();
|
|
395
|
+
await saveIngestState(st);
|
|
396
|
+
}
|
|
397
|
+
|
|
359
398
|
if (stat.size <= st.lastOffset) {
|
|
399
|
+
// Caught up — a safe moment to rotate the log if it has grown too large.
|
|
400
|
+
await rotateLogIfTooLarge(st, stat);
|
|
360
401
|
broadcast('kg:ingest-progress', { phase: 'done', ingesting: false, added: 0 });
|
|
361
402
|
return { ok: true, added: 0, note: 'up to date' };
|
|
362
403
|
}
|
|
@@ -1,5 +1,36 @@
|
|
|
1
|
+
const os = require('node:os');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
1
4
|
const SECRET_KEY_RE = /^(?:.*_)?(TOKEN|API_?KEY|SECRET|PASSWORD|AUTHORIZATION|COOKIE|REFRESH[_-]?TOKEN|ACCESS[_-]?TOKEN)$/i;
|
|
2
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Common user + Homebrew bin dirs to PREPEND to a spawned child's PATH.
|
|
8
|
+
* Electron can launch from Finder/Dock with a stripped PATH, and Apple Silicon
|
|
9
|
+
* Homebrew lives under /opt/homebrew — without these, `claude`, git, node, etc.
|
|
10
|
+
* are invisible to ptys and `claude -p` jobs on macOS. Single source of truth
|
|
11
|
+
* so pty.cjs and pluginInstall.cjs can't drift (one of them was missing
|
|
12
|
+
* /opt/homebrew and broke plugin install on Apple Silicon).
|
|
13
|
+
*/
|
|
14
|
+
function userBinDirs() {
|
|
15
|
+
const home = os.homedir();
|
|
16
|
+
return [
|
|
17
|
+
path.join(home, '.claude', 'local'),
|
|
18
|
+
path.join(home, '.local', 'bin'),
|
|
19
|
+
path.join(home, '.npm-global', 'bin'),
|
|
20
|
+
'/opt/homebrew/bin',
|
|
21
|
+
'/opt/homebrew/sbin',
|
|
22
|
+
'/usr/local/bin',
|
|
23
|
+
'/usr/bin',
|
|
24
|
+
'/bin',
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Electron's PATH prefixed with the user/Homebrew bin dirs — the value to put
|
|
29
|
+
* in a spawned child's PATH so `claude`, node, git, etc. resolve on macOS. */
|
|
30
|
+
function pathWithUserBins() {
|
|
31
|
+
return `${userBinDirs().join(':')}:${process.env.PATH || ''}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
3
34
|
function cleanChildEnv(extra = {}) {
|
|
4
35
|
const env = { ...process.env, ...extra };
|
|
5
36
|
for (const k of Object.keys(env)) {
|
|
@@ -17,4 +48,4 @@ function cleanChildEnv(extra = {}) {
|
|
|
17
48
|
return env;
|
|
18
49
|
}
|
|
19
50
|
|
|
20
|
-
module.exports = { cleanChildEnv };
|
|
51
|
+
module.exports = { cleanChildEnv, userBinDirs, pathWithUserBins };
|
|
@@ -5,7 +5,8 @@ const fs = require('node:fs');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const os = require('node:os');
|
|
7
7
|
const { spawn, execFileSync } = require('node:child_process');
|
|
8
|
-
const { cleanChildEnv } = require('./cleanEnv.cjs');
|
|
8
|
+
const { cleanChildEnv, pathWithUserBins } = require('./cleanEnv.cjs');
|
|
9
|
+
const { resolveClaudeBin } = require('./claudeBin.cjs');
|
|
9
10
|
|
|
10
11
|
const CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
11
12
|
|
|
@@ -185,7 +186,12 @@ function tryCliFallback() {
|
|
|
185
186
|
const timer = setTimeout(() => settle({ ok: false, reason: 'timeout' }), 15_000);
|
|
186
187
|
let child;
|
|
187
188
|
try {
|
|
188
|
-
|
|
189
|
+
// Resolve the absolute binary + inject Homebrew/user bins so this works
|
|
190
|
+
// even when Electron launched from Finder/Dock with a stripped PATH (mac).
|
|
191
|
+
child = spawn(resolveClaudeBin(), ['--version'], {
|
|
192
|
+
stdio: 'ignore',
|
|
193
|
+
env: cleanChildEnv({ PATH: pathWithUserBins() }),
|
|
194
|
+
});
|
|
189
195
|
} catch (e) {
|
|
190
196
|
clearTimeout(timer);
|
|
191
197
|
return settle({ ok: false, reason: e?.message });
|
|
@@ -21,7 +21,7 @@ const pty = require('node-pty');
|
|
|
21
21
|
const path = require('node:path');
|
|
22
22
|
const os = require('node:os');
|
|
23
23
|
const fs = require('node:fs');
|
|
24
|
-
const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
|
|
24
|
+
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
25
25
|
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
26
26
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
27
27
|
const { schemas } = require('./ipcSchemas.cjs');
|
|
@@ -58,16 +58,8 @@ function send(channel, payload) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function childEnv() {
|
|
61
|
-
const home = os.homedir();
|
|
62
|
-
const extraPath = [
|
|
63
|
-
path.join(home, '.local', 'bin'),
|
|
64
|
-
path.join(home, '.npm-global', 'bin'),
|
|
65
|
-
'/usr/local/bin',
|
|
66
|
-
'/usr/bin',
|
|
67
|
-
'/bin',
|
|
68
|
-
].join(':');
|
|
69
61
|
return cleanChildEnv({
|
|
70
|
-
PATH:
|
|
62
|
+
PATH: pathWithUserBins(),
|
|
71
63
|
TERM: 'xterm-256color',
|
|
72
64
|
FORCE_COLOR: '0', // strip ANSI so the renderer doesn't have to.
|
|
73
65
|
});
|
package/src/main/pty.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('node:path');
|
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const { addAllowedRoot } = require('./config.cjs');
|
|
7
|
-
const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
|
|
7
|
+
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
8
8
|
const { checkInsideHome } = require('./lib/insideHome.cjs');
|
|
9
9
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
10
10
|
|
|
@@ -62,18 +62,11 @@ class PtyManager {
|
|
|
62
62
|
return { pid: existing.proc.pid, cwd: existing.cwd, reattached: true };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Prefer `claude` on PATH; extend PATH with common user bin dirs
|
|
66
|
-
// Electron can launch with a stripped environment
|
|
67
|
-
|
|
68
|
-
path.join(os.homedir(), '.local', 'bin'),
|
|
69
|
-
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
70
|
-
'/usr/local/bin',
|
|
71
|
-
'/usr/bin',
|
|
72
|
-
'/bin',
|
|
73
|
-
].join(':');
|
|
74
|
-
|
|
65
|
+
// Prefer `claude` on PATH; extend PATH with common user + Homebrew bin dirs
|
|
66
|
+
// since Electron can launch with a stripped environment (Apple Silicon
|
|
67
|
+
// Homebrew is /opt/homebrew, absent from the default Electron PATH).
|
|
75
68
|
const env = cleanChildEnv({
|
|
76
|
-
PATH:
|
|
69
|
+
PATH: pathWithUserBins(),
|
|
77
70
|
TERM: 'xterm-256color',
|
|
78
71
|
COLORTERM: 'truecolor',
|
|
79
72
|
FORCE_COLOR: '1',
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -48,7 +48,7 @@ const { randomUUID } = require('node:crypto');
|
|
|
48
48
|
const { execFile } = require('node:child_process');
|
|
49
49
|
const { ipcMain } = require('electron');
|
|
50
50
|
const billing = require('./usage.cjs');
|
|
51
|
-
const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
|
|
51
|
+
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
52
52
|
const supervisor = require('./supervisor.cjs');
|
|
53
53
|
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
54
54
|
const { readTail } = require('./lib/fileTail.cjs');
|
|
@@ -921,7 +921,9 @@ async function executeJob(job, runDir, defaultCwd, onPid) {
|
|
|
921
921
|
// Strip Claude Code env and secrets that leak in when session-manager is
|
|
922
922
|
// launched from a `claude` shell. CLAUDE_EFFORT=xhigh forces Opus and
|
|
923
923
|
// overrides `--model sonnet`, so scheduled jobs burn Opus credits silently.
|
|
924
|
-
|
|
924
|
+
// PATH must include Homebrew/user bins or the job's node/git children ENOENT
|
|
925
|
+
// when Electron was launched from Finder/Dock on macOS (stripped PATH).
|
|
926
|
+
const childEnv = cleanChildEnv({ PATH: pathWithUserBins() });
|
|
925
927
|
|
|
926
928
|
// Track whether the agent has emitted a `result` event in its JSONL stream.
|
|
927
929
|
// null until seen; then one of "success" | "error_max_turns" | … per the
|
|
@@ -1230,7 +1232,7 @@ DO NOT attempt the fix. ONLY write the file. When the file exists, exit immediat
|
|
|
1230
1232
|
}
|
|
1231
1233
|
|
|
1232
1234
|
const claudeBin = resolveClaudeBin();
|
|
1233
|
-
const childEnv = cleanChildEnv();
|
|
1235
|
+
const childEnv = cleanChildEnv({ PATH: pathWithUserBins() }); // Homebrew/user bins for macOS
|
|
1234
1236
|
|
|
1235
1237
|
// Investigation needs only a deadman watchdog — no idle-tail or result-tail
|
|
1236
1238
|
// since investigations are short-running Opus probes with a hard ceiling.
|