pi-ui-extend 0.1.32 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/app/app.d.ts +2 -0
  3. package/dist/app/app.js +28 -0
  4. package/dist/app/commands/command-session-actions.js +29 -1
  5. package/dist/app/constants.d.ts +1 -1
  6. package/dist/app/constants.js +2 -2
  7. package/dist/app/icons.d.ts +4 -9
  8. package/dist/app/icons.js +12 -35
  9. package/dist/app/model/model-usage-status.d.ts +2 -1
  10. package/dist/app/model/model-usage-status.js +33 -25
  11. package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
  12. package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
  13. package/dist/app/rendering/conversation-tool-renderer.js +12 -18
  14. package/dist/app/rendering/conversation-viewport.d.ts +4 -0
  15. package/dist/app/rendering/conversation-viewport.js +144 -13
  16. package/dist/app/rendering/dcp-stats.js +42 -16
  17. package/dist/app/rendering/render-controller.js +4 -0
  18. package/dist/app/rendering/status-line-renderer.d.ts +8 -1
  19. package/dist/app/rendering/status-line-renderer.js +36 -1
  20. package/dist/app/rendering/tab-line-renderer.js +2 -2
  21. package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
  22. package/dist/app/rendering/tool-block-renderer.js +37 -11
  23. package/dist/app/runtime.js +1 -1
  24. package/dist/app/screen/mouse-controller.d.ts +5 -1
  25. package/dist/app/screen/mouse-controller.js +16 -0
  26. package/dist/app/screen/scroll-controller.d.ts +20 -0
  27. package/dist/app/screen/scroll-controller.js +127 -10
  28. package/dist/app/session/lazy-session-manager.js +35 -5
  29. package/dist/app/session/pix-system-message.d.ts +1 -0
  30. package/dist/app/session/pix-system-message.js +14 -3
  31. package/dist/app/session/queued-message-controller.d.ts +11 -4
  32. package/dist/app/session/queued-message-controller.js +74 -59
  33. package/dist/app/session/queued-message-entries.d.ts +2 -1
  34. package/dist/app/session/queued-message-entries.js +12 -1
  35. package/dist/app/session/session-event-controller.d.ts +42 -1
  36. package/dist/app/session/session-event-controller.js +500 -31
  37. package/dist/app/session/session-history.js +23 -4
  38. package/dist/app/session/tabs-controller.d.ts +11 -1
  39. package/dist/app/session/tabs-controller.js +102 -21
  40. package/dist/app/types.d.ts +14 -1
  41. package/dist/bundled-extensions/question/contract.d.ts +25 -0
  42. package/dist/bundled-extensions/question/contract.js +94 -0
  43. package/dist/bundled-extensions/question/index.d.ts +7 -0
  44. package/dist/bundled-extensions/question/index.js +28 -0
  45. package/dist/bundled-extensions/question/render.d.ts +4 -0
  46. package/dist/bundled-extensions/question/render.js +27 -0
  47. package/dist/bundled-extensions/question/result.d.ts +6 -0
  48. package/dist/bundled-extensions/question/result.js +84 -0
  49. package/dist/bundled-extensions/question/tool-description.d.ts +7 -0
  50. package/dist/bundled-extensions/question/tool-description.js +11 -0
  51. package/dist/bundled-extensions/question/tui.d.ts +2 -0
  52. package/dist/bundled-extensions/question/tui.js +577 -0
  53. package/dist/bundled-extensions/question/types.d.ts +103 -0
  54. package/dist/bundled-extensions/question/types.js +1 -0
  55. package/dist/bundled-extensions/session-title/config.d.ts +17 -0
  56. package/dist/bundled-extensions/session-title/config.js +150 -0
  57. package/dist/bundled-extensions/session-title/index.d.ts +5 -0
  58. package/dist/bundled-extensions/session-title/index.js +384 -0
  59. package/dist/bundled-extensions/session-title/title-generation.d.ts +26 -0
  60. package/dist/bundled-extensions/session-title/title-generation.js +141 -0
  61. package/dist/bundled-extensions/terminal-bell/index.d.ts +14 -0
  62. package/dist/bundled-extensions/terminal-bell/index.js +491 -0
  63. package/dist/config.d.ts +1 -1
  64. package/dist/config.js +2 -1
  65. package/dist/default-pix-config.js +2 -1
  66. package/dist/icon-theme.d.ts +7 -0
  67. package/dist/icon-theme.js +36 -0
  68. package/dist/schemas/pi-tools-suite-schema.d.ts +4 -0
  69. package/dist/schemas/pi-tools-suite-schema.js +5 -0
  70. package/dist/schemas/pix-schema.d.ts +1 -0
  71. package/dist/schemas/pix-schema.js +1 -0
  72. package/external/pi-tools-suite/README.md +7 -7
  73. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +16 -16
  74. package/external/pi-tools-suite/src/async-subagents/core/state.ts +18 -4
  75. package/external/pi-tools-suite/src/async-subagents/core/types.ts +4 -0
  76. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +14 -26
  77. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +0 -1
  78. package/external/pi-tools-suite/src/dcp/config.ts +14 -14
  79. package/external/pi-tools-suite/src/dcp/index.ts +31 -43
  80. package/external/pi-tools-suite/src/dcp/state-persistence.ts +151 -0
  81. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +25 -18
  82. package/external/pi-tools-suite/src/tool-descriptions.ts +34 -54
  83. package/package.json +3 -2
  84. package/schemas/pi-tools-suite.json +14 -0
  85. package/schemas/pix.json +7 -0
  86. package/extensions/question/contract.ts +0 -100
  87. package/extensions/question/index.ts +0 -34
  88. package/extensions/question/render.ts +0 -28
  89. package/extensions/question/result.ts +0 -86
  90. package/extensions/question/tool-description.ts +0 -11
  91. package/extensions/question/tui.ts +0 -629
  92. package/extensions/question/types.ts +0 -123
  93. package/extensions/session-title/config.ts +0 -164
  94. package/extensions/session-title/index.ts +0 -502
  95. package/extensions/terminal-bell/index.ts +0 -345
@@ -28,6 +28,8 @@ export class AppMouseController {
28
28
  statusDraftQueueTarget;
29
29
  statusThinkingExpandTarget;
30
30
  statusCompactToolsTarget;
31
+ statusQuickScrollUpTarget;
32
+ statusQuickScrollDownTarget;
31
33
  statusTerminalBellSoundTarget;
32
34
  statusSessionTarget;
33
35
  statusPromptEnhancerTarget;
@@ -78,6 +80,8 @@ export class AppMouseController {
78
80
  return;
79
81
  if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusModelUsageClick(event)))
80
82
  return;
83
+ if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusQuickScrollClick(event)))
84
+ return;
81
85
  if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusSessionClick(event)))
82
86
  return;
83
87
  if (event.button === 0 && this.withClickFlash(event, () => this.handleExtensionInputClick(event)))
@@ -316,6 +320,8 @@ export class AppMouseController {
316
320
  this.statusUserJumpTarget,
317
321
  this.statusThinkingExpandTarget,
318
322
  this.statusCompactToolsTarget,
323
+ this.statusQuickScrollUpTarget,
324
+ this.statusQuickScrollDownTarget,
319
325
  this.statusTerminalBellSoundTarget,
320
326
  this.statusSessionTarget,
321
327
  this.statusPromptEnhancerTarget,
@@ -600,6 +606,16 @@ export class AppMouseController {
600
606
  this.host.toggleVoiceLanguage();
601
607
  return true;
602
608
  }
609
+ handleStatusQuickScrollClick(event) {
610
+ const target = [this.statusQuickScrollUpTarget, this.statusQuickScrollDownTarget].find((candidate) => !!candidate
611
+ && event.y === candidate.row
612
+ && event.x >= candidate.startColumn
613
+ && event.x < candidate.endColumn);
614
+ if (!target)
615
+ return false;
616
+ this.host.scrollConversationQuick(target.direction);
617
+ return true;
618
+ }
603
619
  handleInputClick(event) {
604
620
  const columns = this.host.terminalColumns();
605
621
  const terminalRows = this.host.terminalRows();
@@ -12,6 +12,10 @@ export type ConversationTextScrollTarget = {
12
12
  entryId?: string;
13
13
  needles: readonly string[];
14
14
  };
15
+ export type AppScrollState = {
16
+ scrollFromBottom: number;
17
+ detachedScrollStart?: number;
18
+ };
15
19
  export type AppScrollControllerHost = {
16
20
  conversationViewport(): ConversationViewport;
17
21
  editorLayoutRenderer(): EditorLayoutRenderer;
@@ -24,6 +28,11 @@ export type AppScrollControllerHost = {
24
28
  render?: boolean;
25
29
  onPrependedEntries?: (entries: readonly Entry[]) => void;
26
30
  }): Promise<boolean>;
31
+ hasNewerSessionHistory?(): boolean;
32
+ isLoadingNewerSessionHistory?(): boolean;
33
+ loadNewerSessionHistory?(options?: {
34
+ render?: boolean;
35
+ }): Promise<boolean>;
27
36
  render(): void;
28
37
  };
29
38
  export declare class AppScrollController {
@@ -34,6 +43,15 @@ export declare class AppScrollController {
34
43
  constructor(host: AppScrollControllerHost);
35
44
  reset(): void;
36
45
  scrollToBottom(): boolean;
46
+ scrollToTop(): boolean;
47
+ scrollToAbsoluteTop(): Promise<boolean>;
48
+ scrollToAbsoluteBottom(): Promise<boolean>;
49
+ quickScrollDirections(columns: number, bodyHeight: number): {
50
+ up: boolean;
51
+ down: boolean;
52
+ };
53
+ captureState(): AppScrollState;
54
+ restoreState(state: AppScrollState): void;
37
55
  conversationView(columns: number, bodyHeight: number): {
38
56
  lines: RenderedLine[];
39
57
  metrics: AppScrollMetrics;
@@ -45,7 +63,9 @@ export declare class AppScrollController {
45
63
  render?: boolean;
46
64
  }): boolean;
47
65
  private shouldLoadOlderHistory;
66
+ private shouldLoadNewerHistory;
48
67
  private loadOlderHistoryAnchored;
68
+ private loadNewerHistoryAnchored;
49
69
  adjustForHistoryWindowPrune(edge: "top" | "bottom", lineCount: number): void;
50
70
  scrollToConversationEntry(entryId: string): boolean;
51
71
  scrollToConversationText(target: ConversationTextScrollTarget): boolean;
@@ -17,6 +17,59 @@ export class AppScrollController {
17
17
  this.detachedScrollStart = undefined;
18
18
  return changed;
19
19
  }
20
+ scrollToTop() {
21
+ const columns = this.host.terminalColumns();
22
+ const terminalRows = this.host.terminalRows();
23
+ const rows = editorLayoutRows(terminalRows, this.host.tabPanelRows(terminalRows));
24
+ const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
25
+ const metrics = this.scrollMetrics(columns, bodyHeight);
26
+ return this.setScrollStart(0, metrics);
27
+ }
28
+ async scrollToAbsoluteTop() {
29
+ let loadedOlder = false;
30
+ while (this.host.hasOlderSessionHistory?.() === true && this.host.isLoadingOlderSessionHistory?.() !== true) {
31
+ const loaded = await this.host.loadOlderSessionHistory?.({ render: false });
32
+ if (!loaded)
33
+ break;
34
+ loadedOlder = true;
35
+ this.host.render();
36
+ await yieldToEventLoop();
37
+ }
38
+ return this.scrollToTop() || loadedOlder;
39
+ }
40
+ async scrollToAbsoluteBottom() {
41
+ let loadedNewer = false;
42
+ while (this.host.hasNewerSessionHistory?.() === true && this.host.isLoadingNewerSessionHistory?.() !== true) {
43
+ const loaded = await this.host.loadNewerSessionHistory?.({ render: false });
44
+ if (!loaded)
45
+ break;
46
+ loadedNewer = true;
47
+ this.host.render();
48
+ await yieldToEventLoop();
49
+ }
50
+ return this.scrollToBottom() || loadedNewer;
51
+ }
52
+ quickScrollDirections(columns, bodyHeight) {
53
+ if (bodyHeight <= 0)
54
+ return { up: false, down: false };
55
+ const metrics = this.scrollMetrics(columns, bodyHeight);
56
+ return {
57
+ up: metrics.start > 0 || this.host.hasOlderSessionHistory?.() === true,
58
+ down: metrics.start < metrics.maxScroll || this.host.hasNewerSessionHistory?.() === true,
59
+ };
60
+ }
61
+ captureState() {
62
+ return {
63
+ scrollFromBottom: this.scrollFromBottom,
64
+ ...(this.detachedScrollStart === undefined ? {} : { detachedScrollStart: this.detachedScrollStart }),
65
+ };
66
+ }
67
+ restoreState(state) {
68
+ this.scrollFromBottom = Math.max(0, state.scrollFromBottom);
69
+ this.detachedScrollStart = state.detachedScrollStart === undefined
70
+ ? undefined
71
+ : Math.max(0, state.detachedScrollStart);
72
+ }
20
73
  conversationView(columns, bodyHeight) {
21
74
  const metrics = this.scrollMetrics(columns, bodyHeight);
22
75
  return {
@@ -55,16 +108,23 @@ export class AppScrollController {
55
108
  const rows = editorLayoutRows(terminalRows, this.host.tabPanelRows(terminalRows));
56
109
  const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
57
110
  const metrics = this.scrollMetrics(columns, bodyHeight);
58
- const shouldLoadOlderHistory = this.shouldLoadOlderHistory(delta, metrics);
59
111
  const { conversationLineCount, maxScroll } = metrics;
60
112
  const nextScrollFromBottom = Math.max(0, Math.min(maxScroll, this.scrollFromBottom + -delta));
113
+ const nextStart = Math.max(0, maxScroll - nextScrollFromBottom);
114
+ const shouldLoadOlderHistory = this.shouldLoadOlderHistory(delta, metrics, nextStart);
115
+ const shouldLoadNewerHistory = this.shouldLoadNewerHistory(delta, metrics, nextStart);
116
+ const pinToTopAfterOlderHistoryLoad = shouldLoadOlderHistory && nextStart === 0;
61
117
  let changed = false;
62
118
  if (nextScrollFromBottom === this.scrollFromBottom) {
63
119
  if (nextScrollFromBottom === 0 && this.detachedScrollStart !== undefined && delta > 0) {
64
120
  this.detachedScrollStart = undefined;
65
121
  changed = true;
66
122
  }
67
- else if (!shouldLoadOlderHistory) {
123
+ else if (pinToTopAfterOlderHistoryLoad && this.detachedScrollStart !== 0) {
124
+ this.detachedScrollStart = 0;
125
+ changed = true;
126
+ }
127
+ else if (!shouldLoadOlderHistory && !shouldLoadNewerHistory) {
68
128
  return false;
69
129
  }
70
130
  }
@@ -76,15 +136,17 @@ export class AppScrollController {
76
136
  changed = true;
77
137
  }
78
138
  if (shouldLoadOlderHistory)
79
- this.loadOlderHistoryAnchored(metrics, { render: shouldRender });
139
+ this.loadOlderHistoryAnchored(metrics, { render: shouldRender, pinToTop: pinToTopAfterOlderHistoryLoad });
140
+ if (shouldLoadNewerHistory)
141
+ this.loadNewerHistoryAnchored({ render: shouldRender, pinToBottom: nextScrollFromBottom === 0 });
80
142
  if (shouldRender)
81
143
  this.host.render();
82
- return changed || shouldLoadOlderHistory;
144
+ return changed || shouldLoadOlderHistory || shouldLoadNewerHistory;
83
145
  }
84
- shouldLoadOlderHistory(delta, metrics) {
146
+ shouldLoadOlderHistory(delta, metrics, nextStart = metrics.start) {
85
147
  if (delta >= 0)
86
148
  return false;
87
- if (metrics.start > this.olderHistoryThresholdLines)
149
+ if (metrics.start > this.olderHistoryThresholdLines && nextStart > this.olderHistoryThresholdLines)
88
150
  return false;
89
151
  if (this.host.hasOlderSessionHistory?.() !== true)
90
152
  return false;
@@ -92,12 +154,29 @@ export class AppScrollController {
92
154
  return false;
93
155
  return true;
94
156
  }
157
+ shouldLoadNewerHistory(delta, metrics, nextStart = metrics.start) {
158
+ if (delta <= 0)
159
+ return false;
160
+ if (metrics.maxScroll - metrics.start > this.olderHistoryThresholdLines && metrics.maxScroll - nextStart > this.olderHistoryThresholdLines)
161
+ return false;
162
+ if (this.host.hasNewerSessionHistory?.() !== true)
163
+ return false;
164
+ if (this.host.isLoadingNewerSessionHistory?.() === true)
165
+ return false;
166
+ return true;
167
+ }
95
168
  loadOlderHistoryAnchored(metrics, options) {
96
169
  void this.host.loadOlderSessionHistory?.({
97
170
  render: false,
98
171
  onPrependedEntries: (entries) => {
172
+ if (this.detachedScrollStart === undefined)
173
+ return;
174
+ if (options.pinToTop) {
175
+ this.detachedScrollStart = 0;
176
+ return;
177
+ }
99
178
  const prependedLineCount = this.host.conversationViewport().measuredLineCountForEntries(metrics.viewportColumns, entries.map((entry) => entry.id));
100
- if (prependedLineCount > 0 && this.detachedScrollStart !== undefined)
179
+ if (prependedLineCount > 0)
101
180
  this.detachedScrollStart += prependedLineCount;
102
181
  },
103
182
  }).then((loaded) => {
@@ -105,6 +184,14 @@ export class AppScrollController {
105
184
  this.host.render();
106
185
  });
107
186
  }
187
+ loadNewerHistoryAnchored(options) {
188
+ void this.host.loadNewerSessionHistory?.({ render: false }).then((loaded) => {
189
+ if (loaded && options.pinToBottom)
190
+ this.scrollToBottom();
191
+ if (loaded && options.render)
192
+ this.host.render();
193
+ });
194
+ }
108
195
  adjustForHistoryWindowPrune(edge, lineCount) {
109
196
  if (lineCount <= 0)
110
197
  return;
@@ -121,7 +208,7 @@ export class AppScrollController {
121
208
  const conversationViewport = this.host.conversationViewport();
122
209
  const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
123
210
  const metrics = this.scrollMetrics(columns, bodyHeight);
124
- const position = conversationViewport.entryBlockPositions(metrics.viewportColumns).find((candidate) => candidate.entry.id === entryId);
211
+ const position = conversationViewport.entryBlockPositionById(metrics.viewportColumns, entryId);
125
212
  if (!position)
126
213
  return false;
127
214
  this.setScrollStart(position.offset, metrics);
@@ -138,9 +225,8 @@ export class AppScrollController {
138
225
  const conversationViewport = this.host.conversationViewport();
139
226
  const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
140
227
  const metrics = this.scrollMetrics(columns, bodyHeight);
141
- const positions = conversationViewport.entryBlockPositions(metrics.viewportColumns);
142
228
  const targetPosition = target.entryId
143
- ? positions.find((position) => position.entry.id === target.entryId)
229
+ ? conversationViewport.entryBlockPositionById(metrics.viewportColumns, target.entryId)
144
230
  : undefined;
145
231
  const targetMatch = targetPosition ? lineMatchInPosition(targetPosition, needles) : undefined;
146
232
  if (targetMatch) {
@@ -153,6 +239,18 @@ export class AppScrollController {
153
239
  this.host.render();
154
240
  return true;
155
241
  }
242
+ for (const entry of conversationViewport.entries()) {
243
+ if (!entryMatchesNeedles(entry, needles))
244
+ continue;
245
+ const position = conversationViewport.entryBlockPositionById(metrics.viewportColumns, entry.id);
246
+ const match = position ? lineMatchInPosition(position, needles) : undefined;
247
+ if (!match)
248
+ continue;
249
+ this.setScrollStart(match.start, metrics);
250
+ this.host.render();
251
+ return true;
252
+ }
253
+ const positions = conversationViewport.entryBlockPositions(metrics.viewportColumns);
156
254
  const anyMatch = positions
157
255
  .map((position) => lineMatchInPosition(position, needles))
158
256
  .find((match) => match !== undefined);
@@ -177,6 +275,9 @@ export class AppScrollController {
177
275
  return safeColumns;
178
276
  }
179
277
  }
278
+ async function yieldToEventLoop() {
279
+ await new Promise((resolve) => { setTimeout(resolve, 0); });
280
+ }
180
281
  function editorLayoutRows(terminalRows, tabPanelRows) {
181
282
  return Math.max(1, terminalRows - tabPanelRows);
182
283
  }
@@ -195,6 +296,22 @@ function normalizeLineSearchNeedles(needles) {
195
296
  function normalizeLineSearchText(text) {
196
297
  return sanitizeText(text).replace(/…/gu, " ").replace(/\s+/gu, " ").trim().toLocaleLowerCase();
197
298
  }
299
+ function entryMatchesNeedles(entry, needles) {
300
+ const text = normalizeLineSearchText(entrySearchText(entry));
301
+ return needles.some((needle) => text.includes(needle));
302
+ }
303
+ function entrySearchText(entry) {
304
+ switch (entry.kind) {
305
+ case "shell":
306
+ return [entry.command, entry.output, entry.status, entry.error ?? ""].join("\n");
307
+ case "tool":
308
+ return [entry.toolName, entry.argsText, entry.output, entry.status].join("\n");
309
+ case "custom":
310
+ return [entry.customType, entry.text].join("\n");
311
+ default:
312
+ return "text" in entry ? entry.text : "";
313
+ }
314
+ }
198
315
  function lineMatchInPosition(position, needles) {
199
316
  const lineIndex = lineIndexForNeedles(position.block.lines, needles);
200
317
  return lineIndex === undefined ? undefined : { start: position.offset + lineIndex };
@@ -426,7 +426,7 @@ async function readTailSessionEntriesWithByteCount(filePath, byteCount, limit, s
426
426
  const firstNewline = buffer.indexOf(10);
427
427
  parseStart = firstNewline >= 0 ? firstNewline + 1 : buffer.length;
428
428
  }
429
- return selectLastSessionEntries(parseSessionEntryBufferLines(buffer.subarray(parseStart), start + parseStart), limit, size);
429
+ return selectLastSessionEntries(parseSessionEntryBufferLines(buffer.subarray(parseStart), start + parseStart), limit, start, size);
430
430
  }
431
431
  catch {
432
432
  return { entries: [], startOffset: 0 };
@@ -445,12 +445,41 @@ async function readSessionEntriesBeforeOffset(filePath, endOffset, limit) {
445
445
  const maxBytes = Math.min(endOffset, MAX_TAIL_BYTES);
446
446
  while (byteCount <= maxBytes) {
447
447
  const result = await readSessionEntriesBeforeOffsetWithByteCount(filePath, endOffset, byteCount, limit);
448
- if (result.entries.length >= limit || byteCount >= maxBytes || byteCount >= endOffset)
448
+ if (result.entries.length >= limit || byteCount >= endOffset)
449
449
  return result;
450
+ if (byteCount >= maxBytes)
451
+ return await readSessionEntriesBeforeOffsetStreaming(filePath, endOffset, limit);
450
452
  byteCount = Math.min(endOffset, Math.max(byteCount + 1, byteCount * 2));
451
453
  }
452
454
  return { entries: [], startOffset: 0 };
453
455
  }
456
+ async function readSessionEntriesBeforeOffsetStreaming(filePath, endOffset, limit) {
457
+ const selected = [];
458
+ let offset = 0;
459
+ const stream = createReadStream(filePath, { encoding: "utf8", start: 0, end: Math.max(0, endOffset - 1) });
460
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
461
+ try {
462
+ for await (const line of lines) {
463
+ const lineOffset = offset;
464
+ offset += Buffer.byteLength(line, "utf8") + 1;
465
+ if (lineOffset >= endOffset)
466
+ break;
467
+ const entry = parseSessionEntryLine(line);
468
+ if (!entry)
469
+ continue;
470
+ selected.push({ entry, offset: lineOffset });
471
+ if (selected.length > limit)
472
+ selected.shift();
473
+ }
474
+ }
475
+ finally {
476
+ stream.destroy();
477
+ }
478
+ return {
479
+ entries: selected.map((item) => item.entry),
480
+ startOffset: selected[0]?.offset ?? 0,
481
+ };
482
+ }
454
483
  async function readSessionEntriesBeforeOffsetWithByteCount(filePath, endOffset, byteCount, limit) {
455
484
  let file;
456
485
  try {
@@ -463,7 +492,7 @@ async function readSessionEntriesBeforeOffsetWithByteCount(filePath, endOffset,
463
492
  const firstNewline = buffer.indexOf(10);
464
493
  parseStart = firstNewline >= 0 ? firstNewline + 1 : buffer.length;
465
494
  }
466
- return selectLastSessionEntries(parseSessionEntryBufferLines(buffer.subarray(parseStart), start + parseStart), limit, start);
495
+ return selectLastSessionEntries(parseSessionEntryBufferLines(buffer.subarray(parseStart), start + parseStart), limit, start, start);
467
496
  }
468
497
  catch {
469
498
  return { entries: [], startOffset: 0 };
@@ -472,11 +501,12 @@ async function readSessionEntriesBeforeOffsetWithByteCount(filePath, endOffset,
472
501
  await file?.close();
473
502
  }
474
503
  }
475
- function selectLastSessionEntries(parsedEntries, limit, emptyStartOffset) {
504
+ function selectLastSessionEntries(parsedEntries, limit, windowStartOffset, emptyStartOffset) {
476
505
  const selected = parsedEntries.slice(-limit);
506
+ const hasOlderEntriesBeforeSelected = windowStartOffset > 0 || parsedEntries.length > selected.length;
477
507
  return {
478
508
  entries: selected.map((item) => item.entry),
479
- startOffset: selected[0]?.offset ?? emptyStartOffset,
509
+ startOffset: hasOlderEntriesBeforeSelected ? selected[0]?.offset ?? emptyStartOffset : 0,
480
510
  };
481
511
  }
482
512
  function parseSessionEntryBufferLines(buffer, baseOffset) {
@@ -2,6 +2,7 @@ import type { AgentSession, SessionEntry } from "@earendil-works/pi-coding-agent
2
2
  export declare const PIX_SYSTEM_MESSAGE_CUSTOM_TYPE = "pix-system";
3
3
  export declare const PIX_SYSTEM_DISPLAY_ENTRY_CUSTOM_TYPE = "pix:system_message";
4
4
  export declare const PIX_SESSION_ENTRY_ID_FIELD = "__pixSessionEntryId";
5
+ export declare const PIX_THINKING_LEVEL_FIELD = "__pixThinkingLevel";
5
6
  export declare function appendPixSystemDisplayEntry(session: AgentSession, text: string): void;
6
7
  export declare function sessionHistoryDisplayMessages(session: AgentSession): readonly unknown[];
7
8
  export type SessionHistoryOlderMessagesReader = {
@@ -2,6 +2,7 @@ import { isRecord } from "../guards.js";
2
2
  export const PIX_SYSTEM_MESSAGE_CUSTOM_TYPE = "pix-system";
3
3
  export const PIX_SYSTEM_DISPLAY_ENTRY_CUSTOM_TYPE = "pix:system_message";
4
4
  export const PIX_SESSION_ENTRY_ID_FIELD = "__pixSessionEntryId";
5
+ export const PIX_THINKING_LEVEL_FIELD = "__pixThinkingLevel";
5
6
  export function appendPixSystemDisplayEntry(session, text) {
6
7
  const trimmed = text.trim();
7
8
  if (!trimmed)
@@ -25,9 +26,13 @@ export function sessionHistoryOlderMessagesReader(session) {
25
26
  }
26
27
  export function sessionHistoryDisplayMessagesFromEntries(branch) {
27
28
  const messages = [];
29
+ let currentThinkingLevel;
28
30
  for (const entry of branch) {
29
31
  if (entry.type === "message") {
30
- messages.push(withSessionEntryId(entry.message, entry.id));
32
+ messages.push(withSessionHistoryMetadata(entry.message, entry.id, currentThinkingLevel));
33
+ }
34
+ else if (entry.type === "thinking_level_change") {
35
+ currentThinkingLevel = typeof entry.thinkingLevel === "string" ? entry.thinkingLevel : currentThinkingLevel;
31
36
  }
32
37
  else if (entry.type === "custom_message") {
33
38
  messages.push({
@@ -52,8 +57,14 @@ export async function sessionHistoryFullBranchEntries(session) {
52
57
  const reader = session.sessionManager;
53
58
  return await reader.readFullBranchEntries?.() ?? session.sessionManager.getBranch();
54
59
  }
55
- function withSessionEntryId(message, entryId) {
56
- return isRecord(message) ? { ...message, [PIX_SESSION_ENTRY_ID_FIELD]: entryId } : message;
60
+ function withSessionHistoryMetadata(message, entryId, thinkingLevel) {
61
+ if (!isRecord(message))
62
+ return message;
63
+ return {
64
+ ...message,
65
+ [PIX_SESSION_ENTRY_ID_FIELD]: entryId,
66
+ ...(thinkingLevel === undefined ? {} : { [PIX_THINKING_LEVEL_FIELD]: thinkingLevel }),
67
+ };
57
68
  }
58
69
  function isPixSystemDisplayEntry(entry) {
59
70
  return entry.type === "custom"
@@ -23,12 +23,14 @@ export type AppQueuedMessageControllerHost = {
23
23
  };
24
24
  export declare class AppQueuedMessageController {
25
25
  private readonly host;
26
+ readonly autoUserMessages: SubmittedUserMessage[];
26
27
  readonly deferredUserMessages: SubmittedUserMessage[];
27
- private promptSubmissionInFlight;
28
- private flushingDeferredUserMessages;
28
+ private promptSubmissionInFlightSession;
29
29
  private immediateSendInProgress;
30
30
  constructor(host: AppQueuedMessageControllerHost);
31
31
  reset(): void;
32
+ captureAutoUserMessages(): SubmittedUserMessage[];
33
+ restoreAutoUserMessages(messages: readonly SubmittedUserMessage[]): void;
32
34
  captureDeferredUserMessages(): SubmittedUserMessage[];
33
35
  restoreDeferredUserMessages(messages: readonly SubmittedUserMessage[]): void;
34
36
  createSubmittedUserMessage(promptText: string, displayText: string, images: ImageContent[]): SubmittedUserMessage;
@@ -36,7 +38,7 @@ export declare class AppQueuedMessageController {
36
38
  sendUserMessageToSession(message: SubmittedUserMessage, options?: {
37
39
  streamingBehavior?: "steer" | "followUp";
38
40
  }): Promise<void>;
39
- flushDeferredUserMessages(): Promise<void>;
41
+ flushAutoUserMessages(): Promise<void>;
40
42
  queuedMessageCounts(): {
41
43
  steering: number;
42
44
  followUp: number;
@@ -54,10 +56,14 @@ export declare class AppQueuedMessageController {
54
56
  queuedEntries(): Extract<Entry, {
55
57
  kind: "queued";
56
58
  }>[];
59
+ autoQueuedEntries(): Extract<Entry, {
60
+ kind: "queued";
61
+ }>[];
57
62
  deferredQueuedEntries(): Extract<Entry, {
58
63
  kind: "queued";
59
64
  }>[];
60
- private shouldDeferUserMessage;
65
+ private shouldQueueUserMessageAsSteering;
66
+ private queueUserMessageAsSteering;
61
67
  deferUserMessage(message: SubmittedUserMessage): void;
62
68
  private rewriteSdkQueuedMessages;
63
69
  private takeQueuedEntryForInterruptedSend;
@@ -71,4 +77,5 @@ export declare class AppQueuedMessageController {
71
77
  private restorableSubmittedMessageText;
72
78
  private cloneSubmittedUserMessage;
73
79
  private notifyDeferredUserMessagesChanged;
80
+ private notifyAutoUserMessagesChanged;
74
81
  }