@oss-autopilot/core 0.51.0 → 0.51.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.
- package/dist/cli.bundle.cjs +20 -20
- package/dist/cli.bundle.cjs.map +3 -3
- package/dist/commands/dashboard-server.js +15 -1
- package/dist/core/state.d.ts +7 -0
- package/dist/core/state.js +44 -0
- package/package.json +1 -1
|
@@ -165,7 +165,7 @@ export async function startDashboardServer(options) {
|
|
|
165
165
|
// ── Rate limiters ───────────────────────────────────────────────────────
|
|
166
166
|
const dataLimiter = new RateLimiter({ maxRequests: 30, windowMs: 60_000 }); // 30/min
|
|
167
167
|
const actionLimiter = new RateLimiter({ maxRequests: 10, windowMs: 60_000 }); // 10/min
|
|
168
|
-
const refreshLimiter = new RateLimiter({ maxRequests:
|
|
168
|
+
const refreshLimiter = new RateLimiter({ maxRequests: 6, windowMs: 60_000 }); // 6/min
|
|
169
169
|
// ── Request handler ──────────────────────────────────────────────────────
|
|
170
170
|
const server = http.createServer(async (req, res) => {
|
|
171
171
|
const method = req.method || 'GET';
|
|
@@ -179,6 +179,16 @@ export async function startDashboardServer(options) {
|
|
|
179
179
|
sendError(res, 429, 'Too many requests');
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
+
// Re-read state.json if CLI commands modified it externally
|
|
183
|
+
if (stateManager.reloadIfChanged()) {
|
|
184
|
+
try {
|
|
185
|
+
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
warn(MODULE, `Failed to rebuild dashboard data after state reload: ${errorMessage(error)}`);
|
|
189
|
+
// Intentional: serve previous cachedJsonData rather than returning 500
|
|
190
|
+
}
|
|
191
|
+
}
|
|
182
192
|
sendJson(res, 200, cachedJsonData);
|
|
183
193
|
return;
|
|
184
194
|
}
|
|
@@ -227,6 +237,8 @@ export async function startDashboardServer(options) {
|
|
|
227
237
|
server.requestTimeout = REQUEST_TIMEOUT_MS;
|
|
228
238
|
// ── POST /api/action handler ─────────────────────────────────────────────
|
|
229
239
|
async function handleAction(req, res) {
|
|
240
|
+
// Reload state before mutating to avoid overwriting external CLI changes
|
|
241
|
+
stateManager.reloadIfChanged();
|
|
230
242
|
let body;
|
|
231
243
|
try {
|
|
232
244
|
const raw = await readBody(req);
|
|
@@ -293,6 +305,7 @@ export async function startDashboardServer(options) {
|
|
|
293
305
|
return;
|
|
294
306
|
}
|
|
295
307
|
try {
|
|
308
|
+
stateManager.reloadIfChanged();
|
|
296
309
|
warn(MODULE, 'Refreshing dashboard data from GitHub...');
|
|
297
310
|
const result = await fetchDashboardData(currentToken);
|
|
298
311
|
cachedDigest = result.digest;
|
|
@@ -408,6 +421,7 @@ export async function startDashboardServer(options) {
|
|
|
408
421
|
if (token) {
|
|
409
422
|
fetchDashboardData(token)
|
|
410
423
|
.then((result) => {
|
|
424
|
+
stateManager.reloadIfChanged();
|
|
411
425
|
cachedDigest = result.digest;
|
|
412
426
|
cachedCommentedIssues = result.commentedIssues;
|
|
413
427
|
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues, result.allMergedPRs, result.allClosedPRs);
|
package/dist/core/state.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare function atomicWriteFileSync(filePath: string, data: string, mode
|
|
|
31
31
|
export declare class StateManager {
|
|
32
32
|
private state;
|
|
33
33
|
private readonly inMemoryOnly;
|
|
34
|
+
private lastLoadedMtimeMs;
|
|
34
35
|
/**
|
|
35
36
|
* Create a new StateManager instance.
|
|
36
37
|
* @param inMemoryOnly - When true, state is held only in memory and never read from or
|
|
@@ -92,6 +93,12 @@ export declare class StateManager {
|
|
|
92
93
|
* use the StateManager methods to make changes.
|
|
93
94
|
*/
|
|
94
95
|
getState(): Readonly<AgentState>;
|
|
96
|
+
/**
|
|
97
|
+
* Re-read state from disk if the file has been modified since the last load/save.
|
|
98
|
+
* Uses mtime comparison (single statSync call) to avoid unnecessary JSON parsing.
|
|
99
|
+
* Returns true if state was reloaded, false if unchanged or in-memory mode.
|
|
100
|
+
*/
|
|
101
|
+
reloadIfChanged(): boolean;
|
|
95
102
|
/**
|
|
96
103
|
* Store the latest daily digest for dashboard rendering.
|
|
97
104
|
* @param digest - The freshly generated digest from the current daily run.
|
package/dist/core/state.js
CHANGED
|
@@ -169,6 +169,7 @@ function migrateV1ToV2(rawState) {
|
|
|
169
169
|
export class StateManager {
|
|
170
170
|
state;
|
|
171
171
|
inMemoryOnly;
|
|
172
|
+
lastLoadedMtimeMs = 0;
|
|
172
173
|
/**
|
|
173
174
|
* Create a new StateManager instance.
|
|
174
175
|
* @param inMemoryOnly - When true, state is held only in memory and never read from or
|
|
@@ -369,6 +370,13 @@ export class StateManager {
|
|
|
369
370
|
warn(MODULE, `Failed to clean up removed features from state: ${errorMessage(cleanupError)}`);
|
|
370
371
|
// Continue with loaded state — cleanup will be retried on next load
|
|
371
372
|
}
|
|
373
|
+
// Record file mtime so reloadIfChanged() can detect external writes
|
|
374
|
+
try {
|
|
375
|
+
this.lastLoadedMtimeMs = fs.statSync(getStatePath()).mtimeMs;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
debug(MODULE, `Could not read state file mtime (reload detection will always trigger): ${errorMessage(error)}`);
|
|
379
|
+
}
|
|
372
380
|
// Log appropriate message based on version
|
|
373
381
|
const repoCount = Object.keys(state.repoScores).length;
|
|
374
382
|
debug(MODULE, `Loaded state v${state.version}: ${repoCount} repo scores tracked`);
|
|
@@ -499,6 +507,8 @@ export class StateManager {
|
|
|
499
507
|
}
|
|
500
508
|
// Atomic write: write to temp file then rename to prevent corruption on crash
|
|
501
509
|
atomicWriteFileSync(statePath, JSON.stringify(this.state, null, 2), 0o600);
|
|
510
|
+
// Update mtime so own writes don't trigger reloadIfChanged()
|
|
511
|
+
this.lastLoadedMtimeMs = fs.statSync(statePath).mtimeMs;
|
|
502
512
|
debug(MODULE, 'State saved successfully');
|
|
503
513
|
}
|
|
504
514
|
finally {
|
|
@@ -535,6 +545,40 @@ export class StateManager {
|
|
|
535
545
|
getState() {
|
|
536
546
|
return this.state;
|
|
537
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Re-read state from disk if the file has been modified since the last load/save.
|
|
550
|
+
* Uses mtime comparison (single statSync call) to avoid unnecessary JSON parsing.
|
|
551
|
+
* Returns true if state was reloaded, false if unchanged or in-memory mode.
|
|
552
|
+
*/
|
|
553
|
+
reloadIfChanged() {
|
|
554
|
+
if (this.inMemoryOnly)
|
|
555
|
+
return false;
|
|
556
|
+
try {
|
|
557
|
+
const statePath = getStatePath();
|
|
558
|
+
const currentMtimeMs = fs.statSync(statePath).mtimeMs;
|
|
559
|
+
if (currentMtimeMs === this.lastLoadedMtimeMs)
|
|
560
|
+
return false;
|
|
561
|
+
this.state = this.load();
|
|
562
|
+
// load() only records lastLoadedMtimeMs on the happy path. Ensure it is
|
|
563
|
+
// always current after reload (covers backup-restore and fresh-state paths)
|
|
564
|
+
// to prevent repeated unnecessary reloads on every request.
|
|
565
|
+
try {
|
|
566
|
+
this.lastLoadedMtimeMs = fs.statSync(statePath).mtimeMs;
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// If file was just loaded, stat should not fail. If it does,
|
|
570
|
+
// next reloadIfChanged() will simply trigger another reload.
|
|
571
|
+
}
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
// statSync failure (file deleted) is benign — keep current in-memory state.
|
|
576
|
+
// load() failure should not happen (load() handles its own recovery),
|
|
577
|
+
// but if it does, keeping current state is the safest option.
|
|
578
|
+
warn(MODULE, `Failed to reload state from disk: ${errorMessage(error)}`);
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
538
582
|
/**
|
|
539
583
|
* Store the latest daily digest for dashboard rendering.
|
|
540
584
|
* @param digest - The freshly generated digest from the current daily run.
|