run method

Future<void> run()

Runs the REPL until /exit or the input stream closes.

Implementation

Future<void> run() async {
  await _cubeBootRestore();
  await _claimSessionLease();
  // Restart honesty (issue #450): jobs in the manifest were left
  // running by the previous run — count them, then take the file over.
  await _waiting.captureLostJobs();
  await _loadAgentContext();
  // Persisted model cache (stale-while-revalidate): the /model picker
  // serves the last fetched lists instantly at boot; the live refresh
  // revalidates on the first menu open (stale entries) — no boot HTTP.
  await _loadPersistedModelCache();
  _session = await _initializeSession();
  // Ownership lease (#428): claim before anything can drive — a live
  // lease flips this boot into viewer mode (no takeover exists).
  await _claimSessionLease();
  // gh-1241: NO segment marker here — an idle owner boot must add zero
  // session bytes (issue #428 invariant); the marker lands lazily at the
  // first drive ([_runPrompt]) instead.
  // Sleep prevention (#325/#326): only the EXPLICIT session hold
  // acquires here — the default per-run hold acquires at every run
  // start instead, so an idle agent never pins the machine awake.
  await acquirePowerAssertions();
  // Session scope (tools.yaml next to the session file) is live now.
  unawaited(AgentCliTools(this).rebuildToolAvailability());
  _syncMailboxPrefix();
  // Boot marker: every wedge post-mortem starts with "which BUILD held
  // the busy row?" — parallel fa processes share this log, so name the
  // version next to the session id before any lifecycle line.
  _logDiagnostic('fa boot sid=$_logSid version=$_version');
  _wireTransientRetryNotice();
  _wireDeliverySloNotice();
  _wireImageDropNotice();
  _wireOperativePinNotice();
  onUnknownFinishReason = (reason) =>
      _logDiagnostic('unknown finish_reason sid=$_logSid reason=$reason');
  _livePresence = await _registerLivePresence();
  // Phase 3a: rehydrate the subagent registry from the resumed session's
  // `subagent_registry` records — agents of this session are visible again
  // (across restarts AND across instances sharing the session repo).
  // AWAITED (issue #332): zombie queued/running rows are settled to
  // terminal BEFORE the first prompt can spawn children — a fire-and-
  // forget load let a same-id first spawn race it (the snapshot copy
  // would clobber the live row). [SubagentManager.rehydrate] also skips
  // ids already registered by this process, so the race stays closed.
  await _subagentManager.rehydrate();
  // Phase 2: session-start maintenance trigger — fire-and-forget when the
  // last run is >24h old; never blocks the first turn.
  unawaited(
    _memory.maintenanceDue().then((due) async {
      if (due) await _memory.maintain();
    }),
  );
  // Due scheduled messages (schedule_message): re-arm any pending records
  // from previous runs — restart-survivable reminders.
  unawaited(_scheduledMessages.start());
  // herdr pane state (issue #1481): the boot report — the pane never sits
  // `unknown` before the first turn. No-op outside herdr.
  _herdrBootIdle();
  final interruptSub = io.interrupts.listen((_) {
    if (isBusy) {
      // Line-mode abort marker: the settle path uses it to DROP the
      // leftover steering loudly instead of re-running it (the TUI sets
      // the same flag in its onInterrupt and resets it in its submit
      // finally).
      _abortRequested = true;
      _abortRunOrCompaction();
    } else if (_activeCompactionAbort != null) {
      // Compaction-ONLY interrupt (issue #1085 round-2 review): a bare
      // /compact or post-run compaction runs outside the run bracket —
      // Ctrl+C stops the compaction while the SESSION stays alive. No
      // run-abort markers here: the sticky abort belt would otherwise
      // insta-abort the next prompt and kill the REPL.
      _activeCompactionAbort?.cancel('interrupted by user');
    }
  });
  final taskSub = _taskConfig.jobManager.completions.listen(
    _onTaskJobCompleted,
  );
  // Subagent status board (gh-1415): every registry event refreshes the
  // TUI's live rows (spawn → row appears; settle → flash + collapse).
  _subagentBoardSub = _subagentManager.events.listen(
    (_) => _subagentBoard.refresh(),
  );
  _hubEnsureEventSubs();
  final inboxTimer = _startInboxWatcher();
  try {
    if (_useTui) {
      // The TUI prints the banner itself into its output history (buffered
      // by the controller until the program's event loop is listening).
      await _runTuiRepl();
    } else {
      await _runLineRepl();
    }
  } finally {
    await _teardownAfterRepl(interruptSub, taskSub, inboxTimer);
  }
  await printSessionResumeHint();
}