Expand terminal recovery to multi-page recent history
This commit is contained in:
parent
9aeb84d428
commit
7b401eb3f6
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user