fix: prevent agents from running indefinitely after app quit

Agents kept running after closing AgentCompany because worker processes
auto-recreated on exit, retry timers were never cleared, child CLI
processes weren't terminated, and orchestration had no quit guards.

Changes:
- Add isQuitting guards to all orchestration/automation entry points
- Track and clear all setTimeout/setInterval timers on quit
- Send cancel-run to all active CLI processes before killing worker
- Add destroyed flag to prevent worker recreation after intentional destroy
- Add quit confirmation dialog: Stop All & Quit / Minimize to Background / Cancel
- Bump version to 0.4.96

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-25 12:40:58 -04:00
1 parent 222c80e544
commit 05c57be98f
7 files changed
+190 -19

No files matched your search

+16 -2
View File
@@ -192,6 +192,7 @@ export async function bootstrapApplication({
// Re-register timers for scheduled social posts (survives app restart).
// Scan all companies for queued browser actions with a future scheduledAt time
// and set up setTimeout to dispatch them at the right moment.
const scheduledPostTimers = new Set<ReturnType<typeof setTimeout>>();
try {
const snapshot = db.listSnapshot();
for (const company of snapshot.companies) {
@@ -203,12 +204,15 @@ export async function bootstrapApplication({
if (payload.scheduledAt) {
const delay = new Date(payload.scheduledAt).getTime() - Date.now();
if (delay > 0) {
setTimeout(() => {
const browserActions = serviceGraph.getBrowserActionService();
const timer = setTimeout(() => {
scheduledPostTimers.delete(timer);
if (isQuittingRef.current) return;
const browserActions = serviceGraph?.getBrowserActionService();
if (browserActions) {
void browserActions.dispatchBrowserAction(action.id, company.id);
}
}, delay);
scheduledPostTimers.add(timer);
log.info(`[bootstrap] Re-registered scheduled post timer: action ${action.id} in ${Math.round(delay / 1000)}s`);
}
// If delay <= 0, the action is overdue -- normal dispatch cycle will pick it up
@@ -221,6 +225,16 @@ export async function bootstrapApplication({
log.warn(`[bootstrap] Failed to re-register scheduled post timers: ${error instanceof Error ? error.message : String(error)}`);
}
// Register cleanup for bootstrap-owned resources during quit.
// This runs BEFORE service-graph's onBeforeQuit (which is registered later).
app.on("before-quit", () => {
for (const timer of scheduledPostTimers) {
clearTimeout(timer);
}
scheduledPostTimers.clear();
notificationService.destroy();
});
// Show the window immediately so the user sees the app launch without delay.
await serviceGraph.getDesktopShellService()?.ensureWindow();
serviceGraph.getAppLifecycleService()?.register(hasSingleInstanceLock);