Expand terminal recovery to multi-page recent history

This commit is contained in:
sladro 2026-04-10 21:22:07 +08:00
parent 9aeb84d428
commit 7b401eb3f6
3 changed files with 97 additions and 25 deletions

View File

@ -56,6 +56,7 @@ class TerminalSessionCoordinator extends ChangeNotifier {
static const Duration reconnectDelay = Duration(seconds: 1);
static const Duration resizeDebounceDelay = Duration(milliseconds: 120);
static const int historyPageSize = 200;
static const int recentRecoveryOutputLineTarget = 1000;
static const int pendingInputCharacterLimit = 2048;
final TerminalInteractionController controller;
@ -361,7 +362,15 @@ class TerminalSessionCoordinator extends ChangeNotifier {
}
Future<HistoryWindow?> loadRecentHistoryWindow() async {
return _loadHistory();
var history = controller.historyWindow.outputSeedText.isNotEmpty
? controller.historyWindow
: await _loadHistory();
while (history != null &&
history.hasMoreAbove &&
_countOutputLines(history.outputSeedText) < recentRecoveryOutputLineTarget) {
history = await _loadHistory(loadOlder: true);
}
return history;
}
Future<HistoryWindow?> _loadHistory({bool loadOlder = false}) async {
@ -682,6 +691,19 @@ class TerminalSessionCoordinator extends ChangeNotifier {
return buffer.toString();
}
int _countOutputLines(String text) {
if (text.isEmpty) {
return 0;
}
final normalized = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
final parts = normalized.split('\n');
if (parts.isNotEmpty && parts.last.isEmpty) {
return parts.length - 1;
}
return parts.length;
}
List<_JournalItem> _readJournalItems(Map<String, dynamic> payload) {
final rawItems = (payload['items'] as List?) ?? const <dynamic>[];
return rawItems

View File

@ -713,6 +713,77 @@ void main() {
expect(receivedFrames, ['gap-5', 'gap-6']);
},
);
test(
'loadRecentHistoryWindow pages backward until the recent recovery window is filled',
() async {
final controller = TerminalInteractionController();
controller.applyFrame('skip-initial-history-load');
final apiClient = _FakeAgentApiClient(
journalResponses: [
<String, dynamic>{
'sessionId': 'abc',
'items': [
{
'sessionId': 'abc',
'sequence': 6,
'kind': 'output',
'payload': 'middle\r\n',
'timestampUtc': '2026-04-07T03:20:06Z',
},
{
'sessionId': 'abc',
'sequence': 7,
'kind': 'output',
'payload': 'tail',
'timestampUtc': '2026-04-07T03:20:07Z',
},
],
'hasMoreBefore': true,
'hasMoreAfter': false,
'currentSequence': 7,
},
<String, dynamic>{
'sessionId': 'abc',
'items': [
{
'sessionId': 'abc',
'sequence': 5,
'kind': 'output',
'payload': 'header\r\n',
'timestampUtc': '2026-04-07T03:20:05Z',
},
],
'hasMoreBefore': false,
'hasMoreAfter': true,
'currentSequence': 7,
},
],
);
final sessionFactory = _FakeTerminalSessionFactory();
final session = Session(
sessionId: 'abc',
name: 'codex-main',
status: 'idle',
);
final coordinator = TerminalSessionCoordinator(
controller: controller,
apiClient: apiClient,
session: session,
sessionFactory: sessionFactory.create,
onFrame: (_) {},
onRestore: (_) {},
viewportProvider: () => const TerminalViewport(columns: 80, rows: 24),
);
await coordinator.start();
final history = await coordinator.loadRecentHistoryWindow();
expect(apiClient.requestedJournalBeforeSequences, [null, 6]);
expect(history, isNotNull);
expect(history!.outputSeedText, 'header\r\nmiddle\r\ntail');
},
);
}
class _FakeAgentApiClient extends AgentApiClient {

View File

@ -1258,13 +1258,6 @@ void main() {
<String, dynamic>{
'sessionId': 'session-1',
'items': [
{
'sessionId': 'session-1',
'sequence': 5,
'kind': 'output',
'payload': 'header\r\n',
'timestampUtc': '2026-04-07T03:20:05Z',
},
{
'sessionId': 'session-1',
'sequence': 6,
@ -1280,7 +1273,7 @@ void main() {
'timestampUtc': '2026-04-07T03:20:07Z',
},
],
'hasMoreBefore': false,
'hasMoreBefore': true,
'hasMoreAfter': false,
'currentSequence': 8,
},
@ -1294,23 +1287,9 @@ void main() {
'payload': 'header\r\n',
'timestampUtc': '2026-04-07T03:20:05Z',
},
{
'sessionId': 'session-1',
'sequence': 6,
'kind': 'output',
'payload': 'middle-output\r\n',
'timestampUtc': '2026-04-07T03:20:06Z',
},
{
'sessionId': 'session-1',
'sequence': 7,
'kind': 'output',
'payload': 'tail-only',
'timestampUtc': '2026-04-07T03:20:07Z',
},
],
'hasMoreBefore': false,
'hasMoreAfter': false,
'hasMoreAfter': true,
'currentSequence': 8,
},
],
@ -1342,7 +1321,7 @@ void main() {
expect(terminal.buffer.getText(), contains('header'));
expect(terminal.buffer.getText(), contains('middle-output'));
expect(apiClient.requestedBeforeSequences, [null, null]);
expect(apiClient.requestedBeforeSequences, [null, 6]);
},
);