mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
fix(autopilot): give full-cycle dispatch a 30-minute timeout floor (#2852)
Dispatch timeout was derived as interval*2 with a 5-minute floor, tuned for light per-interval work. A full autopilot cycle routinely needs more than 10 minutes at common intervals, so healthy full cycles were killed mid-run. Full-cycle dispatch now gets a 30-minute floor; lighter dispatches keep the interval-derived budget. Adds a regression test for the full-cycle floor.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
export function resolveAutopilotDispatchTimeoutMs(
|
||||
baseIntervalSeconds: number,
|
||||
fullCycle: boolean,
|
||||
): number {
|
||||
const intervalDerivedTimeoutMs = Math.max(baseIntervalSeconds * 2 * 1000, 300_000);
|
||||
return fullCycle
|
||||
? Math.max(intervalDerivedTimeoutMs, 1_800_000)
|
||||
: intervalDerivedTimeoutMs;
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import { logSelfUpgrade } from '../core/audit/self-upgrade-audit.ts';
|
||||
import { detectInstallMethod } from './upgrade.ts';
|
||||
import { evaluateQuietHours } from '../core/minions/quiet-hours.ts';
|
||||
import { inspectLock } from '../core/db-lock.ts';
|
||||
import { resolveAutopilotDispatchTimeoutMs } from './autopilot-timeout.ts';
|
||||
|
||||
/**
|
||||
* v0.37.7.0 #1162 — classify autopilot reconnect-loop errors.
|
||||
@@ -689,7 +690,7 @@ export async function runAutopilot(engine: BrainEngine, args: string[]) {
|
||||
const queue = new MinionQueue(engine);
|
||||
const slotMs = Math.floor(Date.now() / (baseInterval * 1000)) * baseInterval * 1000;
|
||||
const slot = new Date(slotMs).toISOString();
|
||||
const timeoutMs = Math.max(baseInterval * 2 * 1000, 300_000);
|
||||
const timeoutMs = resolveAutopilotDispatchTimeoutMs(baseInterval, false);
|
||||
|
||||
// ── v0.40 D17: per-source freshness check ────────────────────
|
||||
// Runs first; independent of score gate. Submits a 'sync' job per
|
||||
@@ -931,7 +932,9 @@ export async function runAutopilot(engine: BrainEngine, args: string[]) {
|
||||
const result = await dispatchPerSource(engine, queue, {
|
||||
repoPath,
|
||||
slot,
|
||||
timeoutMs,
|
||||
// Full cycles can outlive short daemon intervals. Keep lighter dispatches
|
||||
// interval-derived while giving per-source consolidation enough time.
|
||||
timeoutMs: resolveAutopilotDispatchTimeoutMs(baseInterval, true),
|
||||
fanoutMax,
|
||||
jsonMode,
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { resolveAutopilotDispatchTimeoutMs } from '../src/commands/autopilot-timeout.ts';
|
||||
|
||||
const AUTOPILOT_SRC = readFileSync(
|
||||
join(import.meta.dir, '..', 'src', 'commands', 'autopilot.ts'),
|
||||
@@ -48,6 +49,21 @@ describe('autopilot.ts ↔ dispatchPerSource wiring', () => {
|
||||
expect(Math.abs(dispatchIdx - fullCycleIdx)).toBeLessThan(3000);
|
||||
});
|
||||
|
||||
test('applies the 30-minute timeout floor only to full-cycle dispatch', () => {
|
||||
const baseIntervalSeconds = 60;
|
||||
const intervalDerivedTimeoutMs = Math.max(baseIntervalSeconds * 2 * 1000, 300_000);
|
||||
|
||||
expect(resolveAutopilotDispatchTimeoutMs(baseIntervalSeconds, true)).toBeGreaterThanOrEqual(30 * 60_000);
|
||||
expect(resolveAutopilotDispatchTimeoutMs(baseIntervalSeconds, false)).toBe(intervalDerivedTimeoutMs);
|
||||
|
||||
expect(AUTOPILOT_SRC).toContain(
|
||||
'const timeoutMs = resolveAutopilotDispatchTimeoutMs(baseInterval, false);',
|
||||
);
|
||||
expect(AUTOPILOT_SRC).toMatch(
|
||||
/dispatchPerSource\(engine, queue, \{[\s\S]{0,300}timeoutMs: resolveAutopilotDispatchTimeoutMs\(baseInterval, true\)/,
|
||||
);
|
||||
});
|
||||
|
||||
test('updates lastFullCycleAt on dispatch (so the 60-min floor is honored)', () => {
|
||||
// After the dispatchPerSource call, the lastFullCycleAt module var
|
||||
// must update so the next tick doesn't immediately re-fan-out.
|
||||
|
||||
Reference in New Issue
Block a user