executeAutoDream method

Future<void> executeAutoDream()

Entry point from post-sampling hooks.

Implementation

Future<void> executeAutoDream() async {
  if (!_initialized) return;
  if (!_isGateOpen()) return;

  final cfg = getConfig();

  // --- Time gate ---
  int lastAt;
  try {
    lastAt = await lockService.readLastConsolidatedAt();
  } catch (e) {
    logDebug('[autoDream] readLastConsolidatedAt failed: $e');
    return;
  }
  final hoursSince =
      (DateTime.now().millisecondsSinceEpoch - lastAt) / 3600000.0;
  if (hoursSince < cfg.minHours) return;

  // --- Scan throttle ---
  final sinceScanMs =
      DateTime.now().millisecondsSinceEpoch - _lastSessionScanAt;
  if (sinceScanMs < _sessionScanIntervalMs) {
    logDebug(
      '[autoDream] scan throttle — time-gate passed but last scan was '
      '${(sinceScanMs / 1000).round()}s ago',
    );
    return;
  }
  _lastSessionScanAt = DateTime.now().millisecondsSinceEpoch;

  // --- Session gate ---
  List<String> sessionIds;
  try {
    sessionIds = await lockService.listSessionsTouchedSince(lastAt);
  } catch (e) {
    logDebug('[autoDream] listSessionsTouchedSince failed: $e');
    return;
  }
  // Exclude the current session.
  final currentSession = getSessionId();
  sessionIds = sessionIds.where((id) => id != currentSession).toList();
  if (sessionIds.length < cfg.minSessions) {
    logDebug(
      '[autoDream] skip — ${sessionIds.length} sessions since last '
      'consolidation, need ${cfg.minSessions}',
    );
    return;
  }

  // --- Lock ---
  int? priorMtime;
  try {
    priorMtime = await lockService.tryAcquireLock();
  } catch (e) {
    logDebug('[autoDream] lock acquire failed: $e');
    return;
  }
  if (priorMtime == null) return;

  logDebug(
    '[autoDream] firing — ${hoursSince.toStringAsFixed(1)}h since last, '
    '${sessionIds.length} sessions to review',
  );
  logEvent('tengu_auto_dream_fired', {
    'hours_since': hoursSince.round(),
    'sessions_since': sessionIds.length,
  });

  final taskId = 'dream_${_nextTaskId++}';
  dreamTasks[taskId] = DreamTaskState(
    taskId: taskId,
    sessionsReviewing: sessionIds.length,
    priorMtime: priorMtime,
  );

  try {
    final memoryRoot = getAutoMemPath();
    final transcriptDir = getProjectDir(getOriginalCwd());
    final extra =
        '''

**Tool constraints for this run:** Bash is restricted to read-only commands (`ls`, `find`, `grep`, `cat`, `stat`, `wc`, `head`, `tail`, and similar). Anything that writes, redirects to a file, or modifies state will be denied.

Sessions since last consolidation (${sessionIds.length}):
${sessionIds.map((id) => '- $id').join('\n')}''';

    final prompt = buildConsolidationPrompt(memoryRoot, transcriptDir, extra);

    final result = await runDreamAgent(
      prompt: prompt,
      onMessage: (msg) => _watchProgress(taskId, msg),
    );

    // Complete.
    final currentTask = dreamTasks[taskId];
    if (currentTask != null) {
      dreamTasks[taskId] = currentTask.copyWith(
        status: DreamTaskStatus.completed,
      );
    }

    logDebug(
      '[autoDream] completed — cache: read=${result.cacheReadTokens} '
      'created=${result.cacheCreatedTokens}',
    );
    logEvent('tengu_auto_dream_completed', {
      'cache_read': result.cacheReadTokens,
      'cache_created': result.cacheCreatedTokens,
      'output': result.outputTokens,
      'sessions_reviewed': sessionIds.length,
    });
  } catch (e) {
    logDebug('[autoDream] fork failed: $e');
    logEvent('tengu_auto_dream_failed', {});

    final currentTask = dreamTasks[taskId];
    if (currentTask != null) {
      dreamTasks[taskId] = currentTask.copyWith(
        status: DreamTaskStatus.failed,
      );
    }

    // Rewind mtime so time-gate passes again.
    await lockService.rollbackLock(priorMtime);
  }
}