()
| 121 | let lastAutoDumpAt = 0 |
| 122 | |
| 123 | const tick = async () => { |
| 124 | const { heapUsed, rss } = process.memoryUsage() |
| 125 | |
| 126 | // Sub-threshold abnormal-growth warning. Skip on the first (un-seeded) |
| 127 | // sample — we need a prior reading to measure a delta against. |
| 128 | if (heapUsed < high && lastHeap >= 0) { |
| 129 | if (!warned && heapUsed >= warnBytes && heapUsed - lastHeap >= WARN_GROWTH_STEP) { |
| 130 | warned = true |
| 131 | onWarn?.({ heapUsed, level: 'normal', rss }) |
| 132 | } else if (heapUsed < warnBytes) { |
| 133 | warned = false |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | lastHeap = heapUsed |
| 138 | |
| 139 | const level: MemoryLevel = heapUsed >= critical ? 'critical' : heapUsed >= high ? 'high' : 'normal' |
| 140 | |
| 141 | if (level === 'normal') { |
| 142 | dumped.clear() |
| 143 | |
| 144 | return |
| 145 | } |
| 146 | |
| 147 | if (dumped.has(level) || inFlight.has(level)) { |
| 148 | return |
| 149 | } |
| 150 | |
| 151 | if (Date.now() - lastAutoDumpAt < cooldownMs) { |
| 152 | return |
| 153 | } |
| 154 | |
| 155 | inFlight.add(level) |
| 156 | lastAutoDumpAt = Date.now() |
| 157 | |
| 158 | // Prune Ink content caches before dump/exit — half on 'high' (recoverable), |
| 159 | // full on 'critical' (post-dump RSS reduction, keeps user running). |
| 160 | // Deferred import keeps `@clawcodex/ink` off the cold-start critical path; |
| 161 | // by the time a tick fires 10s after launch the app has already loaded |
| 162 | // the same module, so this resolves instantly from the ESM cache. |
| 163 | try { |
| 164 | try { |
| 165 | const evictInkCaches = await _ensureEvictInkCaches() |
| 166 | evictInkCaches(level === 'critical' ? 'all' : 'half') |
| 167 | } catch { |
| 168 | // Best-effort: if the dynamic import fails for any reason we still |
| 169 | // continue to the heap dump below so the user gets diagnostics. |
| 170 | } |
| 171 | |
| 172 | dumped.add(level) |
| 173 | const dump = await performHeapDump(level === 'critical' ? 'auto-critical' : 'auto-high').catch(() => null) |
| 174 | |
| 175 | const snap: MemorySnapshot = { heapUsed, level, rss } |
| 176 | |
| 177 | ;(level === 'critical' ? onCritical : onHigh)?.(snap, dump) |
| 178 | } finally { |
| 179 | inFlight.delete(level) |
| 180 | } |
no test coverage detected