claude-code-session-manager 0.19.0 → 0.20.1

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.
Files changed (35) hide show
  1. package/dist/assets/{TiptapBody-CO4q65kH.js → TiptapBody-Db7_uXrI.js} +1 -1
  2. package/dist/assets/{cssMode-0tbceX4i.js → cssMode-DFKJhhi6.js} +1 -1
  3. package/dist/assets/{freemarker2-Dv8wl_HH.js → freemarker2-DUat8x8o.js} +1 -1
  4. package/dist/assets/{handlebars-MzrjkW3b.js → handlebars-B2C1qhAI.js} +1 -1
  5. package/dist/assets/{html-C0YEYUHk.js → html-khtg0DVs.js} +1 -1
  6. package/dist/assets/{htmlMode-Bf9ccIo3.js → htmlMode-Jmhs-vfl.js} +1 -1
  7. package/dist/assets/{index-BsSklu93.css → index-BkkBX1z7.css} +1 -1
  8. package/dist/assets/{index-BXeFi7dA.js → index-pqnuXM14.js} +634 -624
  9. package/dist/assets/{javascript-BZhQgLYg.js → javascript-i1CXbgg4.js} +1 -1
  10. package/dist/assets/{jsonMode-XkKuSIs5.js → jsonMode-DXZaj-kR.js} +1 -1
  11. package/dist/assets/{liquid-B6fnroVU.js → liquid-Ds7jUF53.js} +1 -1
  12. package/dist/assets/{lspLanguageFeatures-BAIq7N4N.js → lspLanguageFeatures-B_15vO6X.js} +1 -1
  13. package/dist/assets/{mdx-DzH38OXA.js → mdx-DgrrLgTE.js} +1 -1
  14. package/dist/assets/{python-ak0De5ar.js → python-Cff3tPw3.js} +1 -1
  15. package/dist/assets/{razor-DC-IpQpX.js → razor-DlyG7FmM.js} +1 -1
  16. package/dist/assets/{tsMode-DaZCqNuS.js → tsMode-DRmmmttS.js} +1 -1
  17. package/dist/assets/{typescript-D5YkmMgh.js → typescript-DQFL2T1p.js} +1 -1
  18. package/dist/assets/{whisperWorker-CcsPqZUS.js → whisperWorker-Dbia1OpC.js} +15 -15
  19. package/dist/assets/{xml-8idHpw2C.js → xml-CwsJEzdU.js} +1 -1
  20. package/dist/assets/{yaml-Dm8NKlcv.js → yaml-BDsDjf-y.js} +1 -1
  21. package/dist/index.html +2 -2
  22. package/package.json +5 -2
  23. package/src/main/health.cjs +216 -0
  24. package/src/main/historyAggregator.cjs +15 -9
  25. package/src/main/index.cjs +7 -2
  26. package/src/main/ipcSchemas.cjs +43 -0
  27. package/src/main/kg.cjs +0 -0
  28. package/src/main/lib/reaperHelpers.cjs +67 -0
  29. package/src/main/lib/schedulerBatch.cjs +212 -0
  30. package/src/main/lib/schedulerConfig.cjs +9 -1
  31. package/src/main/scheduler.cjs +274 -125
  32. package/src/main/webRemote.cjs +916 -0
  33. package/src/preload/api.d.ts +78 -15
  34. package/src/preload/index.cjs +41 -8
  35. package/src/main/projectSkills.cjs +0 -124
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * schedulerBatch.cjs — pure batch-picking logic for the scheduler.
5
+ *
6
+ * Extracted from scheduler.cjs so the functions can be unit-tested without
7
+ * loading the full scheduler (which requires electron + heavy I/O).
8
+ *
9
+ * Group-ordering gates (failure-gate, running-gate) are evaluated
10
+ * PER PROJECT (keyed by cwd). Jobs in different projects do not serialize
11
+ * each other. Within a single project, the sequential-group semantics are
12
+ * fully preserved.
13
+ */
14
+
15
+ const path = require('node:path');
16
+ const os = require('node:os');
17
+
18
+ const DEFAULT_PROJECT_CWD = path.join(os.homedir(), 'Projects', 'session-manager');
19
+
20
+ /**
21
+ * Per-project batch picker. Applies group-ordering rules scoped to a single
22
+ * project (all jobs sharing one cwd).
23
+ *
24
+ * Rules (same as original global pickNextBatch, but scoped):
25
+ * 1. Find the lowest parallelGroup with pending jobs not already running.
26
+ * 2. Failure gate: if an earlier group has failed jobs, hold this project.
27
+ * 3. If that group has jobs in flight (backfill), fire more from SAME group.
28
+ * 4. If a lower-numbered group arrives late (late-arrival), fire it now.
29
+ * 5. If no group is in flight, start the lowest pending group fresh.
30
+ *
31
+ * @param {object[]} projectJobs - All jobs for this project (all statuses).
32
+ * @param {Set<string>} runningSlugsInProject - Slugs from the global
33
+ * runningSet that belong to this project.
34
+ * @param {number} slots - Maximum jobs to return (global remaining slots;
35
+ * caller enforces the global cap across projects).
36
+ * @returns {object[]} Jobs to spawn for this project this tick.
37
+ */
38
+ function pickForProject(projectJobs, runningSlugsInProject, slots) {
39
+ const pending = projectJobs.filter(
40
+ (j) => j.status === 'pending' && !runningSlugsInProject.has(j.slug),
41
+ );
42
+ if (pending.length === 0) return [];
43
+
44
+ const projectCwd = (projectJobs.find((j) => j.cwd) || {}).cwd || DEFAULT_PROJECT_CWD;
45
+
46
+ // Lowest pending group (computed up-front for the failure-gate check).
47
+ const lowestPendingGroup = pending.reduce(
48
+ (min, j) => Math.min(min, j.parallelGroup ?? 99),
49
+ Infinity,
50
+ );
51
+
52
+ // Cross-group failure gate: refuse to advance past a group with failed jobs.
53
+ // A failed foundation PRD should not allow later groups to run and
54
+ // silently corrupt project state. needs_review is NOT a blocker.
55
+ const blockingFailures = projectJobs.filter(
56
+ (j) => j.status === 'failed' && (j.parallelGroup ?? 99) < lowestPendingGroup,
57
+ );
58
+ if (blockingFailures.length > 0) {
59
+ const slugs = blockingFailures.map((j) => j.slug).join(', ');
60
+ console.log(
61
+ `[scheduler] failure-gate [${projectCwd}]: holding g${lowestPendingGroup} — ` +
62
+ `${blockingFailures.length} failed job(s) in earlier groups [${slugs}]. ` +
63
+ `Reset to pending or archive to unblock.`,
64
+ );
65
+ return [];
66
+ }
67
+
68
+ // Groups with at least one job in flight: either tracked in runningSlugsInProject
69
+ // (this process spawned it) or still marked 'running' in queue.json
70
+ // (persisted from a previous session that hasn't been orphan-reset yet).
71
+ const jobBySlug = new Map(projectJobs.map((j) => [j.slug, j]));
72
+ const activeGroups = new Set();
73
+ for (const slug of runningSlugsInProject) {
74
+ const job = jobBySlug.get(slug);
75
+ if (job) activeGroups.add(job.parallelGroup ?? 99);
76
+ }
77
+ for (const j of projectJobs) {
78
+ if (j.status === 'running' && !runningSlugsInProject.has(j.slug)) {
79
+ activeGroups.add(j.parallelGroup ?? 99);
80
+ }
81
+ }
82
+
83
+ if (activeGroups.size > 0) {
84
+ const lowestActive = Math.min(...activeGroups);
85
+ if (lowestPendingGroup > lowestActive) {
86
+ // Earlier group still running — wait for it to drain before advancing.
87
+ console.log(
88
+ `[scheduler] concurrency [${projectCwd}]: g${lowestActive} in flight, holding g${lowestPendingGroup}`,
89
+ );
90
+ return [];
91
+ }
92
+ if (lowestPendingGroup < lowestActive) {
93
+ // Late-arrival: a lower-numbered (higher-priority) PRD reconciled AFTER
94
+ // a higher-numbered group was already picked. Fire it now in parallel
95
+ // with the active group rather than starving it until drain.
96
+ if (slots <= 0) {
97
+ console.log(
98
+ `[scheduler] concurrency [${projectCwd}]: no slots for late-arrival g${lowestPendingGroup}`,
99
+ );
100
+ return [];
101
+ }
102
+ const batch = pending
103
+ .filter((j) => (j.parallelGroup ?? 99) === lowestPendingGroup)
104
+ .slice(0, slots);
105
+ console.log(
106
+ `[scheduler] concurrency [${projectCwd}]: firing late-arrival g${lowestPendingGroup} ` +
107
+ `(${batch.length} job(s)) alongside active g${lowestActive}`,
108
+ );
109
+ return batch;
110
+ }
111
+ // Backfill slots remaining in the current group.
112
+ if (slots <= 0) {
113
+ console.log(`[scheduler] concurrency [${projectCwd}]: cap reached, no slots`);
114
+ return [];
115
+ }
116
+ const batch = pending
117
+ .filter((j) => (j.parallelGroup ?? 99) === lowestActive)
118
+ .slice(0, slots);
119
+ if (batch.length > 0) {
120
+ console.log(
121
+ `[scheduler] concurrency [${projectCwd}]: backfilling ${batch.length} into g${lowestActive}`,
122
+ );
123
+ }
124
+ return batch;
125
+ }
126
+
127
+ // No active group — start the next group fresh.
128
+ if (slots <= 0) {
129
+ console.log(`[scheduler] concurrency [${projectCwd}]: cap reached, no slots`);
130
+ return [];
131
+ }
132
+ const batch = pending
133
+ .filter((j) => (j.parallelGroup ?? 99) === lowestPendingGroup)
134
+ .slice(0, slots);
135
+ console.log(
136
+ `[scheduler] concurrency [${projectCwd}]: starting g${lowestPendingGroup} with ${batch.length} job(s)`,
137
+ );
138
+ return batch;
139
+ }
140
+
141
+ /**
142
+ * Pick the next batch of jobs to spawn this tick.
143
+ *
144
+ * Group-ordering gates are evaluated PER PROJECT (keyed by cwd), so jobs in
145
+ * different projects are not serialized by each other's groups. Within a
146
+ * single project, the existing sequential-group semantics are fully preserved.
147
+ *
148
+ * O(N) where N = allJobs.length.
149
+ *
150
+ * @param {object[]} allJobs - Full queue.json job list.
151
+ * @param {Set<string>} running - In-process running slugs (runningSet).
152
+ * @param {number} cap - concurrencyCap.
153
+ * @returns {object[]} Jobs to spawn this tick.
154
+ */
155
+ function pickNextBatch(allJobs, running, cap) {
156
+ if (!allJobs.some((j) => j.status === 'pending' && !running.has(j.slug))) return [];
157
+
158
+ // Global slot accounting: take the higher of in-process running count and
159
+ // queue.json running count (handles orphaned running entries from a previous
160
+ // session not yet reaped).
161
+ const queueRunningCount = allJobs.filter((j) => j.status === 'running').length;
162
+ const effectiveRunning = Math.max(running.size, queueRunningCount);
163
+ let slots = cap - effectiveRunning;
164
+ if (slots <= 0) {
165
+ console.log(
166
+ `[scheduler] concurrency: cap ${cap} reached (${effectiveRunning} running), no slots`,
167
+ );
168
+ return [];
169
+ }
170
+
171
+ // Group all jobs by project cwd.
172
+ const projectMap = new Map();
173
+ for (const job of allJobs) {
174
+ const key = job.cwd || DEFAULT_PROJECT_CWD;
175
+ if (!projectMap.has(key)) projectMap.set(key, []);
176
+ projectMap.get(key).push(job);
177
+ }
178
+
179
+ // Build per-project candidate list (only projects that have pending jobs).
180
+ const projectCandidates = [];
181
+ for (const [, projectJobs] of projectMap) {
182
+ const hasPending = projectJobs.some(
183
+ (j) => j.status === 'pending' && !running.has(j.slug),
184
+ );
185
+ if (!hasPending) continue;
186
+
187
+ const runningSlugsInProject = new Set(
188
+ projectJobs.filter((j) => running.has(j.slug)).map((j) => j.slug),
189
+ );
190
+ const lowestPendingForProject = projectJobs
191
+ .filter((j) => j.status === 'pending' && !running.has(j.slug))
192
+ .reduce((min, j) => Math.min(min, j.parallelGroup ?? 99), Infinity);
193
+
194
+ projectCandidates.push({ projectJobs, runningSlugsInProject, lowestPendingForProject });
195
+ }
196
+
197
+ // Sort by lowest pending group so earlier (higher-priority) groups win
198
+ // slot allocation ties across projects.
199
+ projectCandidates.sort((a, b) => a.lowestPendingForProject - b.lowestPendingForProject);
200
+
201
+ // Aggregate batch across projects, consuming global slots as we go.
202
+ const batch = [];
203
+ for (const { projectJobs, runningSlugsInProject } of projectCandidates) {
204
+ if (slots <= 0) break;
205
+ const projectBatch = pickForProject(projectJobs, runningSlugsInProject, slots);
206
+ batch.push(...projectBatch);
207
+ slots -= projectBatch.length;
208
+ }
209
+ return batch;
210
+ }
211
+
212
+ module.exports = { pickForProject, pickNextBatch, DEFAULT_PROJECT_CWD };
@@ -1,7 +1,15 @@
1
1
  module.exports = {
2
- POLL_INTERVAL_MS: 2 * 60_000,
2
+ // Steady-state billing-usage poll cadence (success path). Drives when the
3
+ // `when-available` policy notices the 5h window crossing the utilization
4
+ // threshold — i.e. when to stop (util ≥ threshold) and start (util < threshold)
5
+ // jobs around the 5-hour limit. Reset-time resume is scheduled exactly (not
6
+ // poll-bound), so 10 min only bounds how late we react to utilization drift.
7
+ POLL_INTERVAL_MS: 10 * 60_000,
8
+ // Exponential backoff floor for polling retries after transient failures.
3
9
  POLL_MIN_INTERVAL_MS: 90_000,
10
+ // Cadence for refreshing the AppStatusBar's 5h-usage chip (billing meter).
4
11
  USAGE_REFRESH_INTERVAL_MS: 15_000,
12
+ // Timeout for HTTP requests to Anthropic API (billing endpoint, etc).
5
13
  HTTP_TIMEOUT_MS: 30_000,
6
14
  HTTP_RETRY_DELAY_MS: 1_000,
7
15
  OFFSET_MINUTES_MAX: 180,