run method
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();
}