claude-code-session-manager 0.65.0 → 0.67.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-CiqimwWl.js +3 -0
- package/dist/assets/{History-DB-9-zwc.js → History-DbzjII2Z.js} +2 -2
- package/dist/assets/{Hooks-DnxqMRrR.js → Hooks-C0PTplI_.js} +3 -3
- package/dist/assets/{HostBilko-Cw6JocV7.js → HostBilko-vGc1FYLD.js} +1 -1
- package/dist/assets/{Library-BYSB0dmY.js → Library-4tU4rg2h.js} +1 -1
- package/dist/assets/{ListDetail-BfKqnL0r.js → ListDetail-BGIvkCwZ.js} +1 -1
- package/dist/assets/{MarkdownEditor-Cs-l5JOv.js → MarkdownEditor-ChFdpCam.js} +1 -1
- package/dist/assets/{McpServers-bBqj3hGg.js → McpServers-DsRWWpU3.js} +2 -2
- package/dist/assets/{Memory-DzPGXT5J.js → Memory-DmxoxxxY.js} +6 -6
- package/dist/assets/{Panel-BX6UZ-W0.js → Panel-KC-rv3jS.js} +1 -1
- package/dist/assets/{Permissions-Db7S4T3Z.js → Permissions-DrDjCRFl.js} +3 -3
- package/dist/assets/{Plugins-CIuey9R3.js → Plugins-KexSQwk0.js} +2 -2
- package/dist/assets/{ProvenanceBadge-FPWNWc_-.js → ProvenanceBadge-Rg-jL94y.js} +1 -1
- package/dist/assets/SaveBar-BbEo8U2e.js +1 -0
- package/dist/assets/Scheduler-C4Ti9TCc.js +14 -0
- package/dist/assets/ScopeSwitcher-Di9BmYze.js +1 -0
- package/dist/assets/Settings-CxyhPuYQ.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CprLcOCe.js → SkillReferenceGraph-0q26RHrn.js} +1 -1
- package/dist/assets/Skills-BXkVyCo5.js +3 -0
- package/dist/assets/SystemPrompt-Bwe3NTbi.js +1 -0
- package/dist/assets/{TagLibrary-q66s4N3i.js → TagLibrary-CnwEf6EI.js} +1 -1
- package/dist/assets/{TiptapBody-I22aArc2.js → TiptapBody-faCtLj9L.js} +1 -1
- package/dist/assets/{Toggle-D9sapSAv.js → Toggle-Bw7G_-RR.js} +1 -1
- package/dist/assets/{index-BRwaw_1W.js → index-B6dJ2CsU.js} +666 -665
- package/dist/assets/{index-LlWpj2VJ.css → index-CEnMgeQU.css} +1 -1
- package/dist/assets/{settingsSchema-CTc4qelV.js → settingsSchema-B5C9hZoS.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +73 -15
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +10 -0
- package/src/main/__tests__/agentLibrary.test.cjs +40 -0
- package/src/main/__tests__/develop-skill-failure-modes.test.cjs +70 -0
- package/src/main/__tests__/flatPrdTickSweep.test.cjs +110 -0
- package/src/main/__tests__/health-per-project-stall.test.cjs +82 -0
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +68 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +311 -0
- package/src/main/__tests__/prdCreate.test.cjs +7 -2
- package/src/main/__tests__/prdMigration.test.cjs +17 -0
- package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +91 -0
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +109 -0
- package/src/main/__tests__/scheduleJobSchema.test.cjs +127 -0
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +65 -0
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +152 -0
- package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +59 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +203 -0
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +196 -0
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +108 -0
- package/src/main/agentLibrary.cjs +40 -2
- package/src/main/health.cjs +97 -2
- package/src/main/index.cjs +2 -0
- package/src/main/ipcSchemas.cjs +60 -0
- package/src/main/lib/localAdminHttp.cjs +10 -3
- package/src/main/lib/prdAdminRoutes.cjs +175 -0
- package/src/main/lib/prdCreate.cjs +37 -2
- package/src/main/lib/prdFrontmatter.cjs +179 -1
- package/src/main/lib/prdMigration.cjs +82 -5
- package/src/main/lib/queueStore.cjs +41 -7
- package/src/main/lib/scheduleJobSchema.cjs +114 -0
- package/src/main/lib/scheduleJobTransitions.cjs +164 -0
- package/src/main/lib/schedulerConfig.cjs +7 -0
- package/src/main/scheduler/prdParser.cjs +7 -0
- package/src/main/scheduler.cjs +769 -138
- package/src/preload/api.d.ts +54 -2
- package/src/preload/index.cjs +12 -0
- package/dist/assets/AgentLibrary-DYriNDGf.js +0 -1
- package/dist/assets/Scheduler-X5y252Qw.js +0 -14
- package/dist/assets/ScopeSwitcher-DU7M_q5-.js +0 -1
- package/dist/assets/Settings-DEptXcEY.js +0 -3
- package/dist/assets/Skills-CGN56X1i.js +0 -3
- package/dist/assets/SystemPrompt-DIEK7FpJ.js +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduleJobTransitions.cjs — the ONE place a ScheduleJob's `status` field
|
|
3
|
+
* is assigned, mirroring epicMint.cjs's fail-closed ownership of the Epic's
|
|
4
|
+
* `proposed -> active` transition.
|
|
5
|
+
*
|
|
6
|
+
* Before this module, `j.status = '...'` was a bare mutation at 16 separate
|
|
7
|
+
* sites in scheduler.cjs — no legality table, no record of who changed a
|
|
8
|
+
* status or why. That is why the 1021/1022 stall (2026-08-07) was
|
|
9
|
+
* undiagnosable from the app: the only evidence was a heartbeat count, not a
|
|
10
|
+
* trace of what the job's status actually did over time.
|
|
11
|
+
*
|
|
12
|
+
* `transitionJob(job, toStatus, { reason, source })` is the chokepoint:
|
|
13
|
+
* - Looks up `job.status -> toStatus` in LEGAL_TRANSITIONS. An identity
|
|
14
|
+
* transition (status unchanged) is always accepted as a no-op — it never
|
|
15
|
+
* mutates statusHistory or writes an audit record, since nothing changed.
|
|
16
|
+
* - A legal transition mutates `job.status`, appends one bounded
|
|
17
|
+
* `statusHistory` entry, and appends one record to the existing
|
|
18
|
+
* ~/.claude/session-manager/audit-log.jsonl via auditLog.cjs (the same
|
|
19
|
+
* writer epicMint.cjs already uses for `epic_mint`/`epic_mint_refused` —
|
|
20
|
+
* deliberately not a second log file).
|
|
21
|
+
* - An illegal transition is refused: the job is left untouched, an
|
|
22
|
+
* error-level line is logged, a refusal counter increments, and a
|
|
23
|
+
* `job_transition_refused` audit record is written. It never throws —
|
|
24
|
+
* scheduler.cjs's tickQueue must keep running even if a call site asks
|
|
25
|
+
* for an illegal edge (same non-blocking posture as dodDrainHook.cjs).
|
|
26
|
+
*
|
|
27
|
+
* Plain Node module (no Electron deps), like scheduleJobSchema.cjs and
|
|
28
|
+
* epicMint.cjs, so it stays requirable from watchdog scripts outside
|
|
29
|
+
* Electron.
|
|
30
|
+
*/
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const { appendAuditEvent } = require('./auditLog.cjs');
|
|
34
|
+
|
|
35
|
+
// Bounded so queue.json (mutation cost, broadcast payload, pickNextBatch
|
|
36
|
+
// scan) stays small — same rationale as lib/queueHistory.cjs's retention
|
|
37
|
+
// window.
|
|
38
|
+
const STATUS_HISTORY_CAP = 20;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Explicit from->to edges. Every real assignment site in scheduler.cjs maps
|
|
42
|
+
* onto one of these (verified against the 16 sites this module replaces):
|
|
43
|
+
* - pending->running (dispatch), pending->completed (archived-PRD skip,
|
|
44
|
+
* manual archive of an already-shipped PRD), pending->failed (admin
|
|
45
|
+
* cancelJob on a not-yet-started job)
|
|
46
|
+
* - running->completed|failed|needs_review (normal run outcomes, reaper),
|
|
47
|
+
* running->pending (halt/rate-limit reset, transient-failure retry)
|
|
48
|
+
* - investigating->failed|needs_review (restore prior status once the
|
|
49
|
+
* investigation probe exits), investigating->completed (defensive: the
|
|
50
|
+
* restored prior status could in principle be 'completed' if a caller
|
|
51
|
+
* ever marks investigating on a completed job), investigating->pending
|
|
52
|
+
* (admin reset can target a job mid-investigation)
|
|
53
|
+
* - failed->investigating (spawn a probe), failed->pending (admin reset /
|
|
54
|
+
* transient retry), failed->completed (auto-promote a fix-plan's healed
|
|
55
|
+
* original)
|
|
56
|
+
* - needs_review->investigating, needs_review->pending, needs_review->completed
|
|
57
|
+
* (heal on reverify, or auto-promote) — same shape as `failed`
|
|
58
|
+
* - completed->pending (force-only reset, gated separately by
|
|
59
|
+
* resetJobFields' own guard — this table only says the edge is
|
|
60
|
+
* structurally legal, not that every caller may take it unconditionally)
|
|
61
|
+
* - quarantined->pending (reconcile()'s adopt path, PRD-authoring lockdown:
|
|
62
|
+
* a PRD discovered with no `createdVia` provenance stamp is queued
|
|
63
|
+
* 'quarantined' instead of 'pending'; the ONLY way out is the PRD being
|
|
64
|
+
* stamped via the update-prd API — reconcile() detects the stamp on its
|
|
65
|
+
* next pass and promotes the row)
|
|
66
|
+
*/
|
|
67
|
+
const LEGAL_TRANSITIONS = {
|
|
68
|
+
pending: ['running', 'completed', 'failed'],
|
|
69
|
+
running: ['completed', 'failed', 'needs_review', 'pending'],
|
|
70
|
+
investigating: ['failed', 'needs_review', 'completed', 'pending'],
|
|
71
|
+
failed: ['investigating', 'pending', 'completed'],
|
|
72
|
+
needs_review: ['investigating', 'pending', 'completed'],
|
|
73
|
+
completed: ['pending'],
|
|
74
|
+
quarantined: ['pending'],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let refusedTransitionCount = 0;
|
|
78
|
+
|
|
79
|
+
function isLegalTransition(from, to) {
|
|
80
|
+
if (from === to) return true;
|
|
81
|
+
const edges = LEGAL_TRANSITIONS[from];
|
|
82
|
+
return Array.isArray(edges) && edges.includes(to);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* transitionJob(job, toStatus, { reason, source }) → boolean
|
|
87
|
+
*
|
|
88
|
+
* Mutates `job` in place on success. `reason` and `source` are free-text
|
|
89
|
+
* (why this transition is happening, and which call site requested it) —
|
|
90
|
+
* both are required in spirit (every call site in scheduler.cjs passes real
|
|
91
|
+
* strings) but not enforced here, since a missing reason/source is a lesser
|
|
92
|
+
* sin than a call this function refuses outright.
|
|
93
|
+
*/
|
|
94
|
+
function transitionJob(job, toStatus, { reason, source, allowAnyFrom = false } = {}) {
|
|
95
|
+
if (!job || typeof job !== 'object') return false;
|
|
96
|
+
const from = job.status;
|
|
97
|
+
|
|
98
|
+
// `allowAnyFrom` is a narrow escape hatch for repairing a row whose
|
|
99
|
+
// persisted `status` was never a legal value to begin with (e.g. the
|
|
100
|
+
// 1021/1022 incident's `"status": "queued"`, quarantined by
|
|
101
|
+
// scheduleJobSchema.cjs and repaired by reconcile()'s invalid-row pass).
|
|
102
|
+
// That is a data repair, not a lifecycle transition — there is no legal
|
|
103
|
+
// predecessor to check `from` against — so it still goes through this
|
|
104
|
+
// chokepoint (mutation, statusHistory, audit) without consulting
|
|
105
|
+
// LEGAL_TRANSITIONS. Every other caller leaves this false.
|
|
106
|
+
if (!allowAnyFrom && !isLegalTransition(from, toStatus)) {
|
|
107
|
+
refusedTransitionCount += 1;
|
|
108
|
+
console.error(
|
|
109
|
+
`[scheduleJobTransitions] illegal transition refused: slug=${job.slug ?? '(unknown)'} `
|
|
110
|
+
+ `from=${from ?? '(none)'} to=${toStatus ?? '(none)'} reason=${reason ?? '(none)'} source=${source ?? '(none)'}`,
|
|
111
|
+
);
|
|
112
|
+
appendAuditEvent('job_transition_refused', {
|
|
113
|
+
slug: job.slug ?? null,
|
|
114
|
+
from: from ?? null,
|
|
115
|
+
to: toStatus ?? null,
|
|
116
|
+
reason: reason ?? null,
|
|
117
|
+
source: source ?? null,
|
|
118
|
+
cwd: job.cwd ?? null,
|
|
119
|
+
});
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (from === toStatus) return true; // identity — nothing changed, nothing to record
|
|
124
|
+
|
|
125
|
+
job.status = toStatus;
|
|
126
|
+
const entry = {
|
|
127
|
+
from: from ?? null,
|
|
128
|
+
to: toStatus,
|
|
129
|
+
reason: reason ?? null,
|
|
130
|
+
source: source ?? null,
|
|
131
|
+
at: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
const history = Array.isArray(job.statusHistory) ? job.statusHistory : [];
|
|
134
|
+
history.push(entry);
|
|
135
|
+
while (history.length > STATUS_HISTORY_CAP) history.shift();
|
|
136
|
+
job.statusHistory = history;
|
|
137
|
+
|
|
138
|
+
appendAuditEvent('job_transition', {
|
|
139
|
+
slug: job.slug ?? null,
|
|
140
|
+
from: from ?? null,
|
|
141
|
+
to: toStatus,
|
|
142
|
+
reason: reason ?? null,
|
|
143
|
+
source: source ?? null,
|
|
144
|
+
cwd: job.cwd ?? null,
|
|
145
|
+
});
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getRefusedTransitionCount() {
|
|
150
|
+
return refusedTransitionCount;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Test-only: reset the module-level refusal counter between test cases.
|
|
154
|
+
function _resetRefusedTransitionCountForTests() {
|
|
155
|
+
refusedTransitionCount = 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
transitionJob,
|
|
160
|
+
LEGAL_TRANSITIONS,
|
|
161
|
+
STATUS_HISTORY_CAP,
|
|
162
|
+
getRefusedTransitionCount,
|
|
163
|
+
_resetRefusedTransitionCountForTests,
|
|
164
|
+
};
|
|
@@ -36,4 +36,11 @@ module.exports = {
|
|
|
36
36
|
// rollup line current (see historyAggregator.cjs's refreshIntradayToday).
|
|
37
37
|
// Cheap: LRU-warm live parse, no full transcript re-read.
|
|
38
38
|
HISTORY_INTRADAY_REFRESH_MS: 5 * 60_000,
|
|
39
|
+
// A 'quarantined' PRD (no createdVia provenance) sitting un-adopted past
|
|
40
|
+
// this age is escalated: warn-logged naming project + slug + age, and
|
|
41
|
+
// surfaced distinctly on Home (see homeNeedsYou.ts's matching constant)
|
|
42
|
+
// so it cannot be stranded indefinitely with nothing looking at it — see
|
|
43
|
+
// findStaleQuarantinedJobs in scheduler.cjs. Overridable via
|
|
44
|
+
// SM_QUARANTINE_ESCALATE_HOURS for testing/tuning.
|
|
45
|
+
QUARANTINE_ESCALATE_MS: 24 * 60 * 60_000,
|
|
39
46
|
};
|
|
@@ -92,6 +92,13 @@ async function parsePrdRaw(filePath) {
|
|
|
92
92
|
// queue row is completed (a slug with no row is treated as already
|
|
93
93
|
// done/archived, matching retireCompletedSlugs semantics).
|
|
94
94
|
dependsOn: parseDependsOn(fm.dependsOn),
|
|
95
|
+
// Provenance stamp (PRD-authoring lockdown): set only by prdCreate.cjs's
|
|
96
|
+
// buildPrdBody (create) or the update-prd route's legacy-adopt patch
|
|
97
|
+
// (migration/manual adopt). A PRD discovered on disk with no value here
|
|
98
|
+
// was never written through the sanctioned API path — reconcile()
|
|
99
|
+
// quarantines it instead of queuing it to run.
|
|
100
|
+
createdVia: fm.createdVia || null,
|
|
101
|
+
issuedAt: fm.issuedAt || null,
|
|
95
102
|
body: body.trim(),
|
|
96
103
|
};
|
|
97
104
|
}
|