(projectRoot)
| 107 | } |
| 108 | |
| 109 | function daemonLoop(projectRoot) { |
| 110 | const daemon = readJsonIfExists(planningDir(projectRoot, 'daemon.json')); |
| 111 | if (!daemon) return null; |
| 112 | return { |
| 113 | version: 1, |
| 114 | id: `daemon-${daemon.campaignSlug || 'active'}`, |
| 115 | type: 'daemon', |
| 116 | title: `Daemon: ${daemon.campaignSlug || 'active campaign'}`, |
| 117 | status: daemon.status === 'running' ? 'running' : normalizeStatus(daemon.status, 'stopped'), |
| 118 | trigger: { kind: 'daemon', cadence: daemon.interval || null, command: '/daemon tick' }, |
| 119 | budget: { maxAttempts: null, maxSpend: daemon.budget ?? null, costPerRun: daemon.costPerSession ?? null }, |
| 120 | verifier: { command: null, profile: 'campaign-end-conditions', required: true }, |
| 121 | stopConditions: ['done', 'budget-exhausted', 'no-active-work', 'needs-human-review'], |
| 122 | statePath: '.planning/daemon.json', |
| 123 | runs: daemon.log || [], |
| 124 | updatedAt: daemon.lastTickAt || daemon.startedAt || null, |
| 125 | source: 'daemon', |
| 126 | }; |
| 127 | } |
| 128 | |
| 129 | function codexAutomationLoops(projectRoot) { |
| 130 | const dir = planningDir(projectRoot, 'codex-automations'); |
no test coverage detected