claude-code-session-manager 0.105.0 → 0.107.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/{AgentLibrary-BDtr4oI-.js → AgentLibrary-Bjeq2-kp.js} +1 -1
- package/dist/assets/{Button-CuXBCQDp.js → Button-CRLhoI2K.js} +1 -1
- package/dist/assets/{DataModel-DmvRipT5.js → DataModel-iPJw5jcG.js} +1 -1
- package/dist/assets/{History-B-v9pwwr.js → History-DfPR3_6N.js} +1 -1
- package/dist/assets/{Hooks-S_0oHHlr.js → Hooks-Bw7H_DmC.js} +1 -1
- package/dist/assets/{Library-BcbGJxQ3.js → Library-rqaJJJ1b.js} +1 -1
- package/dist/assets/{ListDetail-DtqHqJHt.js → ListDetail-qI27OYfy.js} +1 -1
- package/dist/assets/{MarkdownEditor-B56sPp_l.js → MarkdownEditor-BEFuHVNz.js} +1 -1
- package/dist/assets/{McpServers-D5VnmxAn.js → McpServers-Bz9O79rz.js} +1 -1
- package/dist/assets/{Memory-0yGZLyRP.js → Memory-DobxIoZ-.js} +1 -1
- package/dist/assets/{Panel-NoAADJwx.js → Panel-Cyt1wpUf.js} +1 -1
- package/dist/assets/{Permissions-D2Dh-ee8.js → Permissions-ChSNRUJJ.js} +1 -1
- package/dist/assets/{Plugins-kGoUgS9b.js → Plugins-CII221R2.js} +2 -2
- package/dist/assets/{ProvenanceBadge-CHwDFTX7.js → ProvenanceBadge-C_sk5-v3.js} +1 -1
- package/dist/assets/{SaveBar-BtKNMIYK.js → SaveBar-BuYrg5E9.js} +1 -1
- package/dist/assets/Scheduler-eixbMGEk.js +16 -0
- package/dist/assets/{ScopeSwitcher-BusohWZ5.js → ScopeSwitcher-BM0gLPhd.js} +1 -1
- package/dist/assets/Settings-qD-0W-Ut.js +4 -0
- package/dist/assets/{SkillReferenceGraph-BHOdIpsy.js → SkillReferenceGraph-D0Hgdfxq.js} +1 -1
- package/dist/assets/{Skills-DSTLZimI.js → Skills-C3T_2_2o.js} +1 -1
- package/dist/assets/{SystemPrompt-BG7wFWdb.js → SystemPrompt-B-xTy9Qn.js} +1 -1
- package/dist/assets/{TagLibrary-Bx27aE3_.js → TagLibrary-BH67tR5z.js} +1 -1
- package/dist/assets/{TiptapBody-j0O8J7YI.js → TiptapBody-CP_F_mwb.js} +1 -1
- package/dist/assets/{Toggle-DzVZ6rC7.js → Toggle-haRPHS5N.js} +1 -1
- package/dist/assets/{ViewTabs-BizjuynT.js → ViewTabs-BHV3MA5u.js} +1 -1
- package/dist/assets/{index-BLVzrc4k.css → index-tr1w3hHB.css} +1 -1
- package/dist/assets/index-v_3LvhMx.js +3077 -0
- package/dist/assets/{settingsSchema-7MRAxaJk.js → settingsSchema-CE46TA3V.js} +1 -1
- package/dist/assets/useAgentsDirWatch-BZTi2K2e.js +1 -0
- package/dist/assets/{useConfigDirWatch-ot0VYwQC.js → useConfigDirWatch-CjcswsWR.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/scripts/scheduler-mcp-server.cjs +58 -2
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +4 -2
- package/src/main/crashDiagnostics.cjs +39 -3
- package/src/main/index.cjs +47 -3
- package/src/main/ipcSchemas.cjs +120 -0
- package/src/main/lib/claudeBin.cjs +22 -8
- package/src/main/lib/cleanEnv.cjs +6 -5
- package/src/main/lib/customerFeedbackAdminRoutes.cjs +94 -0
- package/src/main/lib/customerFeedbackClient.cjs +261 -0
- package/src/main/lib/customerFeedbackInbox.cjs +286 -0
- package/src/main/lib/customerFeedbackIpc.cjs +43 -0
- package/src/main/lib/e2eStateMachine.cjs +39 -0
- package/src/main/lib/insideHome.cjs +1 -1
- package/src/main/lib/mcpToolCatalog.cjs +31 -6
- package/src/main/lib/projectFolder.cjs +89 -0
- package/src/main/lib/usageCircuit.cjs +15 -0
- package/src/main/lib/winUserData.cjs +22 -0
- package/src/main/scheduler.cjs +68 -20
- package/src/main/usage.cjs +10 -2
- package/src/main/webRemote.cjs +1364 -0
- package/src/preload/api.d.ts +132 -1
- package/src/preload/index.cjs +51 -0
- package/dist/assets/Scheduler-B0_kMMDs.js +0 -16
- package/dist/assets/Settings-DuRJntaE.js +0 -3
- package/dist/assets/index-CLIaYljt.js +0 -3073
- package/dist/assets/useAgentsDirWatch-BUwfU2Kq.js +0 -1
package/src/main/scheduler.cjs
CHANGED
|
@@ -78,7 +78,7 @@ const { detectRateLimitInLog } = require('./lib/rateLimitDetect.cjs');
|
|
|
78
78
|
const { stripAppOwnedChurn } = require('./lib/jobDirtFilter.cjs');
|
|
79
79
|
const { GATE_AUTHORITY_VERDICTS, decideGateAuthority } = require('./lib/gateAuthority.cjs');
|
|
80
80
|
const { resolveBindingRateLimitReset } = require('./lib/rateLimitWindow.cjs');
|
|
81
|
-
const { isResetFresh, bindingWindow, degradedBudget } = require('./lib/usageCircuit.cjs');
|
|
81
|
+
const { isResetFresh, bindingWindow, degradedBudget, meterFailureBudget } = require('./lib/usageCircuit.cjs');
|
|
82
82
|
const { computeQueueHealth } = require('./lib/queueHealth.cjs');
|
|
83
83
|
const { createLoadGate, topCpuConsumers } = require('./lib/loadGate.cjs');
|
|
84
84
|
const { openLog, withChildAndLog } = require('./lib/childWithLog.cjs');
|
|
@@ -3499,8 +3499,11 @@ function recordObservedReset(resetIso) {
|
|
|
3499
3499
|
/** Pure: this poll/executor cycle's conservative budget while the meter is degraded. */
|
|
3500
3500
|
function computeDegradedBudget() {
|
|
3501
3501
|
return degradedBudget(lastGoodUsagePayload, {
|
|
3502
|
-
|
|
3503
|
-
|
|
3502
|
+
// Only an executor-observed rate limit (the paused.reason === 'rate_limit'
|
|
3503
|
+
// path) may pin utilization; a 429 from the METER says nothing about the
|
|
3504
|
+
// account. That pause already holds dispatch itself, so no evidence here.
|
|
3505
|
+
observed429: false,
|
|
3506
|
+
resetsAt: null,
|
|
3504
3507
|
now: Date.now(),
|
|
3505
3508
|
configuredCap: sessionSlots.snapshot().total,
|
|
3506
3509
|
});
|
|
@@ -4047,8 +4050,11 @@ function nextRapidRateLimitCount(prevCount, { rateLimited, durationMs }) {
|
|
|
4047
4050
|
* either, which used to leave an indefinite pause with no resume timer at
|
|
4048
4051
|
* all (a queue that never comes back on its own) — this covers both.
|
|
4049
4052
|
*/
|
|
4053
|
+
const AGENT_PAUSE_TTL_MS = 30 * 60_000;
|
|
4054
|
+
|
|
4050
4055
|
function computeEffectiveResumeAt(reason, resumeAtIso, nowMs = Date.now()) {
|
|
4051
4056
|
if (resumeAtIso && isResetFresh(resumeAtIso, nowMs)) return resumeAtIso;
|
|
4057
|
+
if (reason === 'agent') return new Date(nowMs + AGENT_PAUSE_TTL_MS).toISOString();
|
|
4052
4058
|
if (reason === 'network' || reason === 'rate_limit') {
|
|
4053
4059
|
return new Date(nowMs + 30 * 60_000).toISOString();
|
|
4054
4060
|
}
|
|
@@ -4067,15 +4073,16 @@ function computeResumeDelay(effectiveResumeAtIso, nowMs = Date.now()) {
|
|
|
4067
4073
|
}
|
|
4068
4074
|
|
|
4069
4075
|
async function setPaused(reason, resumeAtIso, opts = {}) {
|
|
4070
|
-
const { observedAt = null, force = false } = opts;
|
|
4076
|
+
const { observedAt = null, force = false, by = null } = opts;
|
|
4071
4077
|
const isManual = reason === 'manual';
|
|
4078
|
+
const isAgent = reason === 'agent';
|
|
4072
4079
|
// Honor manual-override cooldown: if the user cleared a pause within the
|
|
4073
4080
|
// last 5 minutes, suppress auto-pause re-engagement UNLESS this pause is
|
|
4074
4081
|
// backed by a fresh observation (a run that started after the clear) or is
|
|
4075
4082
|
// forced (the rapid-repeat hard pause, which the cooldown cannot suppress).
|
|
4076
4083
|
// A user-initiated 'manual' pause is never an auto-detection, so the cooldown
|
|
4077
4084
|
// (which exists to ignore STALE auto-detections) does not apply to it.
|
|
4078
|
-
if (!isManual && isCooldownSuppressed({ pauseClearedManuallyAt, now: Date.now(), observedAt, force })) {
|
|
4085
|
+
if (!isManual && !isAgent && isCooldownSuppressed({ pauseClearedManuallyAt, now: Date.now(), observedAt, force })) {
|
|
4079
4086
|
console.log(`[scheduler] setPaused(${reason}) suppressed by manual override cooldown`);
|
|
4080
4087
|
return;
|
|
4081
4088
|
}
|
|
@@ -4088,6 +4095,7 @@ async function setPaused(reason, resumeAtIso, opts = {}) {
|
|
|
4088
4095
|
// 'manual' never arms a resume timer: only schedule:resume / run-now clears it.
|
|
4089
4096
|
const effectiveResumeAt = isManual ? null : computeEffectiveResumeAt(reason, resumeAtIso);
|
|
4090
4097
|
|
|
4098
|
+
let committed = null;
|
|
4091
4099
|
const kept = await mutate((s) => {
|
|
4092
4100
|
// A user pause outranks every auto-pause (rate_limit/auth/network): the
|
|
4093
4101
|
// auto path must not overwrite it, or its resume timer would auto-clear it.
|
|
@@ -4097,12 +4105,15 @@ async function setPaused(reason, resumeAtIso, opts = {}) {
|
|
|
4097
4105
|
} else {
|
|
4098
4106
|
s.paused = { reason, since: new Date().toISOString(), resumeAt: effectiveResumeAt || null };
|
|
4099
4107
|
}
|
|
4108
|
+
if (by && isAgent) s.paused.by = by;
|
|
4109
|
+
committed = { reason: s.paused.reason, resumeAt: s.paused.resumeAt ?? null, by: s.paused.by ?? null };
|
|
4100
4110
|
return false;
|
|
4101
4111
|
});
|
|
4102
4112
|
if (kept) {
|
|
4103
4113
|
console.log(`[scheduler] setPaused(${reason}) ignored: a manual pause is in force`);
|
|
4104
4114
|
return;
|
|
4105
4115
|
}
|
|
4116
|
+
if (committed) appendAuditEvent('scheduler_pause', committed);
|
|
4106
4117
|
await broadcast({ flush: true });
|
|
4107
4118
|
cancelToken.cancelled = true;
|
|
4108
4119
|
if (resumeTimer) { clearTimeout(resumeTimer); resumeTimer = null; }
|
|
@@ -4123,6 +4134,7 @@ async function setPaused(reason, resumeAtIso, opts = {}) {
|
|
|
4123
4134
|
async function clearPause(source) {
|
|
4124
4135
|
if (resumeTimer) { clearTimeout(resumeTimer); resumeTimer = null; }
|
|
4125
4136
|
const humanOverride = source === 'manual' || source === 'run-now';
|
|
4137
|
+
let clearedReason = null;
|
|
4126
4138
|
const wasPaused = await mutate((s) => {
|
|
4127
4139
|
// A human Resume / Run now also re-closes every launch circuit breaker:
|
|
4128
4140
|
// the operator is asserting the environment is fixed (CLI updated,
|
|
@@ -4135,9 +4147,11 @@ async function clearPause(source) {
|
|
|
4135
4147
|
}
|
|
4136
4148
|
if (!s.paused) return false;
|
|
4137
4149
|
console.log(`[scheduler] clearPause (${source || 'manual'})`);
|
|
4150
|
+
clearedReason = s.paused.reason ?? null;
|
|
4138
4151
|
s.paused = null;
|
|
4139
4152
|
return true;
|
|
4140
4153
|
});
|
|
4154
|
+
if (wasPaused) appendAuditEvent('scheduler_resume', { source: source || 'manual', clearedReason });
|
|
4141
4155
|
// Un-cancel the tick guard on every recovery path, not just runDueJobs().
|
|
4142
4156
|
applyPauseCleared(wasPaused, cancelToken);
|
|
4143
4157
|
if (wasPaused) lastPauseClearedAt = Date.now();
|
|
@@ -6322,7 +6336,15 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
6322
6336
|
|
|
6323
6337
|
// Distinct comm (`sm-claude-job`) + slug-labelled argv0 for System Monitor.
|
|
6324
6338
|
// Both keep the word `claude`, which the /\bclaude\b/ reaper gates need.
|
|
6325
|
-
const
|
|
6339
|
+
const jobArgs = buildClaudeSpawnArgs({
|
|
6340
|
+
prompt,
|
|
6341
|
+
model: personaResolution.model,
|
|
6342
|
+
effort: personaEffort,
|
|
6343
|
+
sessionId,
|
|
6344
|
+
resume: !!resumeTarget,
|
|
6345
|
+
systemPrompt: personaResolution.systemPrompt,
|
|
6346
|
+
});
|
|
6347
|
+
const jobSpawn = claudeSpawnTarget('job', job.slug, claudeBin, jobArgs);
|
|
6326
6348
|
|
|
6327
6349
|
const { child } = withChildAndLog({
|
|
6328
6350
|
fd,
|
|
@@ -6334,18 +6356,12 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
6334
6356
|
// Resume mode passes `--resume <sessionId>` (reconnect to the SAME
|
|
6335
6357
|
// session) INSTEAD of `--session-id <sessionId>` (mint a new one) —
|
|
6336
6358
|
// never both, see buildClaudeSpawnArgs.
|
|
6337
|
-
args:
|
|
6338
|
-
prompt,
|
|
6339
|
-
model: personaResolution.model,
|
|
6340
|
-
effort: personaEffort,
|
|
6341
|
-
sessionId,
|
|
6342
|
-
resume: !!resumeTarget,
|
|
6343
|
-
systemPrompt: personaResolution.systemPrompt,
|
|
6344
|
-
}),
|
|
6359
|
+
args: jobSpawn.args || jobArgs,
|
|
6345
6360
|
options: {
|
|
6346
6361
|
cwd: spawnCwd,
|
|
6347
6362
|
env: childEnv,
|
|
6348
6363
|
...(jobSpawn.argv0 ? { argv0: jobSpawn.argv0 } : {}),
|
|
6364
|
+
...(jobSpawn.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
6349
6365
|
// detached:true puts the child in its own process group so we can kill
|
|
6350
6366
|
// the entire descendant tree (including any stray background bashes the
|
|
6351
6367
|
// agent spawned) with killTree(pid). Without this, child.kill()
|
|
@@ -10375,8 +10391,17 @@ async function pollLoop() {
|
|
|
10375
10391
|
// Don't update firstNon429FailureAt — 429s don't count toward the 30-min network-pause threshold.
|
|
10376
10392
|
backoffMs = nextBackoffMs(backoffMs);
|
|
10377
10393
|
backoffNextAt = Date.now() + backoffMs;
|
|
10378
|
-
|
|
10379
|
-
|
|
10394
|
+
const cachedBudget = meterFailureBudget({ cachedPayload: r.cached, staleSinceMs: r.staleSince, now: Date.now() });
|
|
10395
|
+
if (cachedBudget) {
|
|
10396
|
+
// Meter 429 ≠ account limit: trust the recent cached reading, full slot pool.
|
|
10397
|
+
lastGoodUsagePayload = r.cached;
|
|
10398
|
+
cachedUtilization = cachedBudget.utilization;
|
|
10399
|
+
cachedBindingWindowName = cachedBudget.name;
|
|
10400
|
+
degradedConcurrencyCapValue = null;
|
|
10401
|
+
} else {
|
|
10402
|
+
applyDegradedBudget();
|
|
10403
|
+
}
|
|
10404
|
+
console.log(`[scheduler] billing meter rate-limited (HTTP 429) — firing on ${cachedBudget ? 'cached' : 'degraded'} budget (util=${cachedUtilization}%, cap=${degradedConcurrencyCapValue}) (failure #${consecutiveFailures}); retry in ${backoffMs / 1000}s`);
|
|
10380
10405
|
warnFailureStreakIfNeeded();
|
|
10381
10406
|
persistSchedulerState();
|
|
10382
10407
|
const cur = await readQueue();
|
|
@@ -12946,7 +12971,10 @@ async function init() {
|
|
|
12946
12971
|
// happens when the app was closed across the reset window.
|
|
12947
12972
|
const boot = await readQueue();
|
|
12948
12973
|
await clearStaleDrainAtBoot(boot);
|
|
12949
|
-
if (boot.paused && boot.paused.
|
|
12974
|
+
if (boot.paused && (boot.paused.reason === 'manual' || boot.paused.reason === 'agent')) {
|
|
12975
|
+
console.log(`[scheduler] boot: clearing persisted '${boot.paused.reason}' pause so dispatch runs`);
|
|
12976
|
+
await clearPause('boot');
|
|
12977
|
+
} else if (boot.paused && boot.paused.resumeAt && new Date(boot.paused.resumeAt).getTime() <= Date.now()) {
|
|
12950
12978
|
await clearPause('boot-elapsed');
|
|
12951
12979
|
} else if (boot.paused && boot.paused.resumeAt) {
|
|
12952
12980
|
// Re-arm the resume timer (lost across restart).
|
|
@@ -13291,8 +13319,15 @@ const remote = {
|
|
|
13291
13319
|
// User-initiated pause/resume — the admin-route/MCP twins of the
|
|
13292
13320
|
// schedule:pause / schedule:resume IPC handlers, through the same setPaused /
|
|
13293
13321
|
// clearPause. Pause stops NEW dispatch only; running jobs are never touched.
|
|
13294
|
-
async pause() {
|
|
13295
|
-
|
|
13322
|
+
async pause(opts = {}) {
|
|
13323
|
+
const str = (v, max = 300) => (typeof v === 'string' && v ? v.slice(0, max) : null);
|
|
13324
|
+
await setPaused('agent', null, {
|
|
13325
|
+
by: {
|
|
13326
|
+
originClaudeSessionId: str(opts.originClaudeSessionId),
|
|
13327
|
+
cwd: str(opts.cwd, 1000),
|
|
13328
|
+
reason: str(opts.reason),
|
|
13329
|
+
},
|
|
13330
|
+
});
|
|
13296
13331
|
return { ok: true };
|
|
13297
13332
|
},
|
|
13298
13333
|
|
|
@@ -13648,7 +13683,20 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
13648
13683
|
});
|
|
13649
13684
|
|
|
13650
13685
|
adminHttp.registerRoute('POST', '/admin/scheduler/pause', async (req, res) => {
|
|
13651
|
-
|
|
13686
|
+
const raw = await readBody(req);
|
|
13687
|
+
let parsed;
|
|
13688
|
+
try {
|
|
13689
|
+
parsed = raw ? JSON.parse(raw) : {};
|
|
13690
|
+
} catch {
|
|
13691
|
+
sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
|
|
13692
|
+
return;
|
|
13693
|
+
}
|
|
13694
|
+
if (!parsed || typeof parsed !== 'object') parsed = {};
|
|
13695
|
+
sendJson(res, 200, await remoteObj.pause({
|
|
13696
|
+
originClaudeSessionId: parsed.originClaudeSessionId,
|
|
13697
|
+
cwd: parsed.cwd,
|
|
13698
|
+
reason: parsed.reason,
|
|
13699
|
+
}));
|
|
13652
13700
|
});
|
|
13653
13701
|
|
|
13654
13702
|
adminHttp.registerRoute('POST', '/admin/scheduler/resume', async (req, res) => {
|
package/src/main/usage.cjs
CHANGED
|
@@ -302,10 +302,18 @@ async function fetchUsage() {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
if (lastRateLimitedResult && retryNotBeforeMs && Date.now() < retryNotBeforeMs) {
|
|
305
|
-
return { ...lastRateLimitedResult, suppressed: true };
|
|
305
|
+
return withCachedPayload({ ...lastRateLimitedResult, suppressed: true });
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
return singleFlightNetworkFetch();
|
|
308
|
+
return withCachedPayload(await singleFlightNetworkFetch());
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Attaches the last good payload (+ its age anchor) to a 429 so callers can keep using it. */
|
|
312
|
+
function withCachedPayload(r) {
|
|
313
|
+
if (r && r.kind === 'meter_rate_limited' && cache && cache.data) {
|
|
314
|
+
return { ...r, cached: cache.data, staleSince: cache.fetchedAt };
|
|
315
|
+
}
|
|
316
|
+
return r;
|
|
309
317
|
}
|
|
310
318
|
|
|
311
319
|
function registerBillingHandlers() {
|