* Clean up expired dynamic directories based on the organization manifest. * Only runs cleanup for 'auto' policy and 'session'-scoped directories. * Campaign-scoped cleanup happens when campaigns complete, not on session end. * * Respects cleanupPolicy: * - 'auto': clean silently * - 'prom
()
| 509 | if (fs.existsSync(costFile)) { |
| 510 | const lines = fs.readFileSync(costFile, 'utf8').split('\n').filter(Boolean); |
| 511 | if (lines.length > 0) { |
| 512 | const latest = JSON.parse(lines[lines.length - 1]); |
| 513 | if (latest && typeof latest.estimated_cost === 'number') { |
| 514 | sessionCost = typeof latest.override_cost === 'number' ? latest.override_cost : latest.estimated_cost; |
| 515 | } |
| 516 | } |
| 517 | } |
| 518 | } catch { /* fall back to flat estimate */ } |
| 519 | |
| 520 | daemon.estimatedSpend = (daemon.estimatedSpend || 0) + sessionCost; |
| 521 | daemon.lastTickAt = now; |
| 522 | daemon.lastTickStatus = 'completed'; |
| 523 | |
| 524 | // Append to log |
| 525 | if (!Array.isArray(daemon.log)) daemon.log = []; |
| 526 | daemon.log.push({ |
| 527 | session: daemon.sessionCount, |
| 528 | timestamp: now, |
| 529 | status: 'completed', |
| 530 | phase: loopSummary.split(':')[0] || 'unknown', |
| 531 | summary: loopSummary, |
| 532 | estimatedCost: sessionCost, |
| 533 | }); |
| 534 | |
| 535 | // Check if campaign completed -- stop the daemon |
| 536 | if (campaignStatus === 'completed' || campaignStatus === 'parked') { |
| 537 | daemon.status = 'completed'; |
| 538 | daemon.stoppedAt = now; |
| 539 | daemon.stopReason = `campaign-${campaignStatus}`; |
| 540 | } |
| 541 | |
| 542 | // Check if campaign hit level-up -- pause the daemon |
| 543 | if (campaignStatus === 'level-up-pending') { |
| 544 | daemon.status = 'paused-level-up'; |
| 545 | } |
| 546 | |
| 547 | // Budget gate: stop if budget exhausted |
| 548 | if (typeof daemon.budget === 'number' && daemon.estimatedSpend >= daemon.budget) { |
| 549 | daemon.status = 'stopped'; |
| 550 | daemon.stoppedAt = now; |
| 551 | daemon.stopReason = 'budget-exhausted'; |
| 552 | } |
| 553 | |
| 554 | fs.writeFileSync(daemonPath, JSON.stringify(daemon, null, 2) + '\n', 'utf8'); |
| 555 | } catch { /* non-critical -- don't block session end */ } |
| 556 | } |
| 557 | |
| 558 | /** |
| 559 | * Clean up expired dynamic directories based on the organization manifest. |
| 560 | * Only runs cleanup for 'auto' policy and 'session'-scoped directories. |
| 561 | * Campaign-scoped cleanup happens when campaigns complete, not on session end. |
| 562 | * |
| 563 | * Respects cleanupPolicy: |
| 564 | * - 'auto': clean silently |
| 565 | * - 'prompt' or 'manual': skip (can't prompt at session end) |
| 566 | */ |
| 567 | function cleanupDynamicDirectories() { |
| 568 | try { |