claude-code-session-manager 0.6.0 → 0.7.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 (24) hide show
  1. package/dist/assets/{cssMode-lflrC2i6.js → cssMode-n6P73NOL.js} +1 -1
  2. package/dist/assets/{editor.main-CSEn01y_.js → editor.main-C5N-IAZE.js} +3 -3
  3. package/dist/assets/{freemarker2-C6z0bRyX.js → freemarker2-pYaw8aW7.js} +1 -1
  4. package/dist/assets/{handlebars-B8qhzkfD.js → handlebars-6lzqG1Qt.js} +1 -1
  5. package/dist/assets/{html-ClohgtU2.js → html-BSwRVs7K.js} +1 -1
  6. package/dist/assets/{htmlMode-Dz83xKTf.js → htmlMode-SxYq5ncR.js} +1 -1
  7. package/dist/assets/{index-L3iJj9Hb.js → index-B9O0rahO.js} +256 -256
  8. package/dist/assets/{javascript-7TdbROJ_.js → javascript-DTG_L--r.js} +1 -1
  9. package/dist/assets/{jsonMode-JC4N0hXM.js → jsonMode-BnjJ_O8o.js} +1 -1
  10. package/dist/assets/{liquid-DZ-smecj.js → liquid-Bim-6sAj.js} +1 -1
  11. package/dist/assets/{lspLanguageFeatures-C1GF1Vk4.js → lspLanguageFeatures-Bb0xKMlG.js} +1 -1
  12. package/dist/assets/{mdx-COF8YTAc.js → mdx-D76-Dd3O.js} +1 -1
  13. package/dist/assets/{python-Bt-i4SS8.js → python-CZXdAfWd.js} +1 -1
  14. package/dist/assets/{razor-CzPEv6v-.js → razor-DJhAKh2n.js} +1 -1
  15. package/dist/assets/{tsMode-DSY_MbAd.js → tsMode-CbK0-ZcV.js} +1 -1
  16. package/dist/assets/{typescript-fdsVN98s.js → typescript-DSTpxnxU.js} +1 -1
  17. package/dist/assets/{xml-De0u_YGE.js → xml-qdPlplP1.js} +1 -1
  18. package/dist/assets/{yaml-DfkoGAru.js → yaml-DFiEYpry.js} +1 -1
  19. package/dist/index.html +1 -1
  20. package/package.json +1 -1
  21. package/src/main/index.cjs +33 -4
  22. package/src/main/scheduler.cjs +35 -34
  23. package/src/main/voiceSettings.cjs +1 -10
  24. package/src/preload/api.d.ts +0 -2
@@ -220,23 +220,22 @@ function reconcile(state) {
220
220
 
221
221
  // ---------- next-reset detection ----------
222
222
 
223
- let cachedNextReset = { at: null, fetchedAt: 0 };
223
+ let cachedNextReset = null;
224
224
  let cachedUtilization = null; // five_hour utilization %, 0–100, or null if unknown
225
225
 
226
226
  async function refreshNextReset() {
227
227
  try {
228
228
  const r = await billing.fetchUsage();
229
- const at = r?.usage?.five_hour?.resets_at ?? null;
230
- cachedNextReset = { at, fetchedAt: Date.now() };
229
+ cachedNextReset = r?.usage?.five_hour?.resets_at ?? null;
231
230
  cachedUtilization = r?.usage?.five_hour?.utilization ?? cachedUtilization;
232
- return at;
231
+ return cachedNextReset;
233
232
  } catch {
234
- return cachedNextReset.at;
233
+ return cachedNextReset;
235
234
  }
236
235
  }
237
236
 
238
237
  function getNextResetCached() {
239
- return cachedNextReset.at;
238
+ return cachedNextReset;
240
239
  }
241
240
 
242
241
  // ---------- timer ----------
@@ -245,6 +244,8 @@ let mainWindow = null;
245
244
  let fireTimer = null;
246
245
  let resumeTimer = null;
247
246
  let pollTimer = null;
247
+ let rescheduleInterval = null;
248
+ let initialPollTimeout = null;
248
249
  let isExecuting = false;
249
250
  let cancelToken = { cancelled: false };
250
251
  let claudeBinPathCached = null;
@@ -315,7 +316,6 @@ async function rescheduleTimer() {
315
316
  function setPaused(reason, resumeAtIso) {
316
317
  const s = readQueue();
317
318
  if (s.paused && s.paused.reason === reason) {
318
- // already paused for this reason; just refresh resumeAt if newer
319
319
  if (resumeAtIso) s.paused.resumeAt = resumeAtIso;
320
320
  } else {
321
321
  s.paused = { reason, since: new Date().toISOString(), resumeAt: resumeAtIso || null };
@@ -324,19 +324,18 @@ function setPaused(reason, resumeAtIso) {
324
324
  broadcast();
325
325
  cancelToken.cancelled = true;
326
326
  if (resumeTimer) { clearTimeout(resumeTimer); resumeTimer = null; }
327
- if (resumeAtIso) {
328
- // Resume 30s after the reset to give the auth/billing endpoint time to flip.
329
- const delay = Math.max(30_000, new Date(resumeAtIso).getTime() - Date.now() + 30_000);
330
- if (delay <= 0x7fffffff) {
331
- resumeTimer = setTimeout(() => {
332
- clearPause('resume-timer');
333
- runDueJobs().catch(() => {});
334
- }, delay);
335
- console.log(`[scheduler] paused (${reason}); auto-resume in ${Math.round(delay/1000)}s`);
336
- } else {
337
- console.warn(`[scheduler] paused (${reason}); resumeAt too far in future for setTimeout (${delay}ms)`);
338
- }
327
+ if (!resumeAtIso) return;
328
+ // Resume 30s after the reset to give the auth/billing endpoint time to flip.
329
+ const delay = Math.max(30_000, new Date(resumeAtIso).getTime() - Date.now() + 30_000);
330
+ if (delay > 0x7fffffff) {
331
+ console.warn(`[scheduler] paused (${reason}); resumeAt too far for setTimeout (${delay}ms)`);
332
+ return;
339
333
  }
334
+ resumeTimer = setTimeout(() => {
335
+ clearPause('resume-timer');
336
+ runDueJobs().catch(() => {});
337
+ }, delay);
338
+ console.log(`[scheduler] paused (${reason}); auto-resume in ${Math.round(delay/1000)}s`);
340
339
  }
341
340
 
342
341
  function clearPause(source) {
@@ -350,6 +349,16 @@ function clearPause(source) {
350
349
  }
351
350
  }
352
351
 
352
+ /** Mutate a job in place to "pending" with cleared run metadata. */
353
+ function resetJobFields(job, errorMsg) {
354
+ job.status = 'pending';
355
+ job.runId = null;
356
+ job.startedAt = null;
357
+ job.finishedAt = null;
358
+ job.exitCode = null;
359
+ job.error = errorMsg ?? null;
360
+ }
361
+
353
362
  /** Scan the tail of a job's log for the canonical rate-limit signal. We look
354
363
  * at the last 16 KB — final result event always lands at the end. */
355
364
  function detectRateLimitInLog(logPath) {
@@ -514,12 +523,7 @@ async function runDueJobs() {
514
523
  if (i2 >= 0) {
515
524
  const treatAsPending = res.rateLimited || (sn.paused && sn.paused.reason === 'rate_limit');
516
525
  if (treatAsPending) {
517
- sn.jobs[i2].status = 'pending';
518
- sn.jobs[i2].runId = null;
519
- sn.jobs[i2].startedAt = null;
520
- sn.jobs[i2].finishedAt = null;
521
- sn.jobs[i2].exitCode = null;
522
- sn.jobs[i2].error = res.rateLimited ? 'paused: rate limit' : 'paused: queue halted';
526
+ resetJobFields(sn.jobs[i2], res.rateLimited ? 'paused: rate limit' : 'paused: queue halted');
523
527
  } else {
524
528
  sn.jobs[i2].status = res.exitCode === 0 ? 'completed' : 'failed';
525
529
  sn.jobs[i2].finishedAt = new Date().toISOString();
@@ -632,12 +636,7 @@ function registerScheduleHandlers() {
632
636
  const state = readQueue();
633
637
  const idx = state.jobs.findIndex((j) => j.slug === slug);
634
638
  if (idx < 0) return { ok: false, error: 'not found' };
635
- state.jobs[idx].status = 'pending';
636
- state.jobs[idx].runId = null;
637
- state.jobs[idx].startedAt = null;
638
- state.jobs[idx].finishedAt = null;
639
- state.jobs[idx].exitCode = null;
640
- state.jobs[idx].error = null;
639
+ resetJobFields(state.jobs[idx]);
641
640
  writeQueue(state);
642
641
  broadcast();
643
642
  return { ok: true };
@@ -704,14 +703,16 @@ async function init() {
704
703
 
705
704
  await rescheduleTimer();
706
705
  // Refresh next-reset every 10 minutes — billing window can shift if usage
707
- // resets early or the auth token rotates.
708
- setInterval(() => { rescheduleTimer().catch(() => {}); }, 10 * 60_000);
706
+ // resets early or the auth token rotates. Tracked so re-init doesn't leak.
707
+ if (rescheduleInterval) clearInterval(rescheduleInterval);
708
+ rescheduleInterval = setInterval(() => { rescheduleTimer().catch(() => {}); }, 10 * 60_000);
709
709
  // when-available poll loop. Tick every 2 minutes; the function itself is
710
710
  // a no-op when policy != 'when-available' or queue is empty/paused.
711
711
  if (pollTimer) clearInterval(pollTimer);
712
712
  pollTimer = setInterval(() => { pollWhenAvailable().catch(() => {}); }, 2 * 60_000);
713
713
  // First tick fires after a short delay so billing is warmed up.
714
- setTimeout(() => { pollWhenAvailable().catch(() => {}); }, 15_000);
714
+ if (initialPollTimeout) clearTimeout(initialPollTimeout);
715
+ initialPollTimeout = setTimeout(() => { pollWhenAvailable().catch(() => {}); }, 15_000);
715
716
  }
716
717
 
717
718
  module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR };
@@ -260,7 +260,6 @@ async function saveWizard(state) {
260
260
  const DEFAULT_TURN_DETECTOR = Object.freeze({
261
261
  enabled: false,
262
262
  mode: 'off', // 'audio' | 'text' | 'off'
263
- dictationMode: false,
264
263
  schemaVersion: TURN_DETECTOR_SCHEMA,
265
264
  });
266
265
 
@@ -270,21 +269,13 @@ function isValidTurnDetectorState(t) {
270
269
  if (!t || typeof t !== 'object') return false;
271
270
  if (typeof t.enabled !== 'boolean') return false;
272
271
  if (typeof t.mode !== 'string' || !TURN_DETECTOR_MODES.has(t.mode)) return false;
273
- if (typeof t.dictationMode !== 'boolean') return false;
274
272
  return true;
275
273
  }
276
274
 
277
275
  function normalizeTurnDetector(t) {
278
276
  const mode = TURN_DETECTOR_MODES.has(t.mode) ? t.mode : 'off';
279
- // Cross-field invariant: `enabled` and `mode === 'off'` are inconsistent.
280
- // We normalize so callers can be sloppy: if mode is 'off' then enabled=false.
281
277
  const enabled = mode === 'off' ? false : !!t.enabled;
282
- return {
283
- enabled,
284
- mode,
285
- dictationMode: !!t.dictationMode,
286
- schemaVersion: TURN_DETECTOR_SCHEMA,
287
- };
278
+ return { enabled, mode, schemaVersion: TURN_DETECTOR_SCHEMA };
288
279
  }
289
280
 
290
281
  /**
@@ -169,8 +169,6 @@ export interface VoiceTurnDetectorState {
169
169
  enabled: boolean;
170
170
  /** 'audio' = smart-turn-v3 (planned); 'text' = rejected on license; 'off' = pure-VAD (default). */
171
171
  mode: 'audio' | 'text' | 'off';
172
- /** When true, bypass the model and use pure-VAD endpointing (out-of-distribution code). */
173
- dictationMode: boolean;
174
172
  schemaVersion: 1;
175
173
  }
176
174