watchForInput method
void
watchForInput()
Implementation
void watchForInput() {
try {
// Create broadcast stream controller once from stdin. Headless / AI
// launches often get an immediately-closed stdin — do not poison the
// controller permanently when that happens.
if (_stdinController == null || _stdinController!.isClosed) {
_stdinController = StreamController<List<int>>.broadcast();
_stdinSourceSubscription?.cancel();
_stdinSourceSubscription = io.stdin.listen(
(event) => _stdinController?.add(event),
onDone: () {
logger.detail('stdin closed; hotkeys unavailable until restart');
_stdinSourceSubscription = null;
},
onError: (Object e) {
logger.detail('stdin error: $e');
},
cancelOnError: false,
);
}
// Cancel existing subscription if any
_inputSubscription?.cancel();
// Re-lock input to ensure single-key mode is maintained
lockInput();
_inputSubscription = _stdinController?.stream.listen((event) {
_handleDevCommand(utf8.decode(event));
});
} catch (e) {
logger
..detail('stdin not available (headless/AI mode): $e')
..detail('Use .revali_cmd file for r/c/q commands instead');
}
logger.detail('Watching for kill signal');
var attemptsToKill = 0;
final stream = Platform.isWindows
? ProcessSignal.sigint.watch()
: StreamGroup.merge([
ProcessSignal.sigterm.watch(),
ProcessSignal.sigint.watch(),
]);
_killSubscription ??= stream.listen((event) {
// Killing the child during hot-reload restart must not tear down the
// parent CLI (signals can surface here when the child shares a group).
if (_intentionalServerRestart || _isReloading) {
logger.detail('Ignoring $event during reload/restart');
return;
}
logger.detail('Received process signal: $event');
if (attemptsToKill > 0) {
logger.detail('Second signal received, forcing exit');
exit(1);
} else if (attemptsToKill == 0) {
logger.detail('Gracefully shutting down (user requested)');
stop().ignore();
}
attemptsToKill++;
});
}