praxis-agent 0.21.4 → 0.21.6

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.
@@ -16,7 +16,7 @@ import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js'
16
16
  import { loadClaudeReleaseNotes } from './tui/release-notes.js';
17
17
  import { StreamingFrameBuffer } from './tui/streaming-frame-buffer.js';
18
18
  import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
19
- import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, projectTranscriptTail, } from './tui/transcript-viewport.js';
19
+ import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, projectTranscriptTail, projectTranscriptWindow, transcriptLineCount, } from './tui/transcript-viewport.js';
20
20
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
21
21
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
22
22
  import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
@@ -431,6 +431,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
431
431
  const [costUsd, setCostUsd] = useState();
432
432
  const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens);
433
433
  const [history, setHistory] = useState([...initialHistory]);
434
+ const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
434
435
  // Startup diagnostics are useful before the first prompt, but they are not
435
436
  // conversation history and must not suppress the new-session welcome panel.
436
437
  // Only real user/assistant transcript entries start a conversation; every
@@ -453,7 +454,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
453
454
  // stream visible. Classic and screen-reader modes always render the full
454
455
  // history exactly as before.
455
456
  const projectedHistory = fixedViewport && !axScreenReader
456
- ? projectTranscriptTail(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width)
457
+ ? transcriptScrollOffset > 0
458
+ ? projectTranscriptWindow(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width, transcriptScrollOffset)
459
+ : projectTranscriptTail(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width)
457
460
  : history;
458
461
  const sessionLoadRef = useRef(0);
459
462
  const [turnDiffs, setTurnDiffs] = useState([]);
@@ -721,6 +724,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
721
724
  // transcript state so the active stream never renders below a tool,
722
725
  // thinking, or completion entry that it textually precedes.
723
726
  streamingFrameRef.current?.flush();
727
+ setTranscriptScrollOffset(0);
724
728
  setHistory((current) => [...current, line]);
725
729
  };
726
730
  useEffect(() => {
@@ -2590,6 +2594,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2590
2594
  void restoring.finally(() => onTurnChange?.(null));
2591
2595
  };
2592
2596
  const submit = async (prompt, shellCommand, images = []) => {
2597
+ setTranscriptScrollOffset(0);
2593
2598
  const turnNumber = turnNumberRef.current + 1;
2594
2599
  const turnStartedAt = Date.now();
2595
2600
  turnNumberRef.current = turnNumber;
@@ -2898,6 +2903,33 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2898
2903
  return;
2899
2904
  }
2900
2905
  const isKeybinding = (action) => keybindingAction === action;
2906
+ if (fixedViewport &&
2907
+ menuRef.current === null &&
2908
+ !permission &&
2909
+ !planApproval &&
2910
+ !question &&
2911
+ !elicitation &&
2912
+ !selectingSession) {
2913
+ const page = Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS);
2914
+ const scrollDelta = key.pageUp
2915
+ ? page
2916
+ : key.pageDown
2917
+ ? -page
2918
+ : controlKey('u') || controlKey('b')
2919
+ ? page / 2
2920
+ : controlKey('f') || controlKey('n')
2921
+ ? -page / 2
2922
+ : inputRef.current.length === 0 && key.upArrow
2923
+ ? 1
2924
+ : inputRef.current.length === 0 && key.downArrow
2925
+ ? -1
2926
+ : 0;
2927
+ if (scrollDelta !== 0) {
2928
+ setTranscriptScrollOffset((current) => Math.min(Math.max(0, transcriptLineCount(history, width) -
2929
+ Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS)), Math.max(0, current + Math.trunc(scrollDelta))));
2930
+ return;
2931
+ }
2932
+ }
2901
2933
  if (runtimeSettingsRef.current.editor === 'vim' &&
2902
2934
  !permission &&
2903
2935
  !planApproval &&
@@ -5691,7 +5723,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5691
5723
  minHeight: 0,
5692
5724
  overflowY: 'hidden',
5693
5725
  }
5694
- : {}), children: _jsx(Transcript, { items: projectedHistory, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }) }), externalEditorRequest !== null ||
5726
+ : {}), children: _jsx(Transcript, { items: projectedHistory, activeText: activeText, activeThinking: activeThinking, activeStreamVisible: transcriptScrollOffset === 0, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }) }), externalEditorRequest !== null ||
5695
5727
  keybindingsEditing ||
5696
5728
  memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (permission.kind === 'tool' && toolPermissionModel ? (_jsx(ToolPermissionDialog, { model: toolPermissionModel, selection: permissionSelection, feedbackMode: permissionFeedbackMode, feedback: input, ruleEditor: permissionRuleEditor, screenReader: axScreenReader })) : (_jsxs(DialogFrame, { title: `Retry interrupted ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }) }), _jsx(Text, { children: "Do you want to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 0, children: [selectionPrefix(permissionSelection === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 1, children: [selectionPrefix(permissionSelection === 1, axScreenReader), "2. No"] }), permissionFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
5697
5729
  (permissionSelection === 0
@@ -90,10 +90,11 @@ export declare function activeStreamWindow(text: string): {
90
90
  pendingText: string;
91
91
  truncated: boolean;
92
92
  };
93
- export declare function Transcript({ items, activeText, activeThinking, thinkingExpanded, detailedTranscript, screenReader, }: {
93
+ export declare function Transcript({ items, activeText, activeThinking, activeStreamVisible, thinkingExpanded, detailedTranscript, screenReader, }: {
94
94
  items: readonly TranscriptItem[];
95
95
  activeText: string;
96
96
  activeThinking?: string;
97
+ activeStreamVisible?: boolean;
97
98
  thinkingExpanded?: boolean;
98
99
  detailedTranscript?: boolean;
99
100
  screenReader: boolean;
@@ -511,7 +511,7 @@ function ActiveStreamText({ text }) {
511
511
  const { stableText, pendingText, truncated } = activeStreamWindow(text);
512
512
  return (_jsxs(Box, { flexDirection: "column", children: [truncated ? _jsx(Text, { dimColor: true, children: "\u2026 earlier streaming content \u2026" }) : null, stableText ? _jsx(MarkdownText, { text: stableText }) : null, pendingText ? _jsx(Text, { children: pendingText }) : null] }));
513
513
  }
514
- export function Transcript({ items, activeText, activeThinking = '', thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
514
+ export function Transcript({ items, activeText, activeThinking = '', activeStreamVisible = true, thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
515
515
  const palette = useTuiPalette();
516
516
  const detailed = thinkingExpanded || detailedTranscript;
517
517
  const results = new Map(items
@@ -623,7 +623,7 @@ export function Transcript({ items, activeText, activeThinking = '', thinkingExp
623
623
  return (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u23BF ", item.text] }) }, index));
624
624
  }
625
625
  return (_jsxs(Text, { ...(item.kind === 'warning' ? { color: palette.error } : {}), dimColor: item.kind === 'notice', children: [item.kind === 'warning' ? '⚠ ' : '· ', item.text] }, index));
626
- }), activeThinking ? (_jsx(ThinkingBlock, { text: activeThinking, active: true, expanded: detailed, screenReader: screenReader })) : activeText ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Praxis: " })) : (_jsx(Text, { color: palette.accent, children: "\u2733 " })), screenReader ? (_jsx(MarkdownText, { text: activeText })) : (_jsx(ActiveStreamText, { text: activeText }))] })) : null] }));
626
+ }), activeStreamVisible && activeThinking ? (_jsx(ThinkingBlock, { text: activeThinking, active: true, expanded: detailed, screenReader: screenReader })) : activeStreamVisible && activeText ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Praxis: " })) : (_jsx(Text, { color: palette.accent, children: "\u2733 " })), screenReader ? (_jsx(MarkdownText, { text: activeText })) : (_jsx(ActiveStreamText, { text: activeText }))] })) : null] }));
627
627
  }
628
628
  export function DiffDashboard({ snapshots, sourceIndex, selectedIndex, viewingFile, scrollOffset, width, screenReader, }) {
629
629
  const palette = useTuiPalette();
@@ -19,6 +19,7 @@ export declare const TRANSCRIPT_TRUNCATION_MARKER = "\u2026";
19
19
  * rather than dropping items that would actually render.
20
20
  */
21
21
  export declare function estimateTranscriptLines(item: TranscriptItem, width: number): number;
22
+ export declare function transcriptLineCount(items: readonly TranscriptItem[], width: number): number;
22
23
  /**
23
24
  * Pure, deterministic suffix projector for the fullscreen transcript region.
24
25
  *
@@ -31,4 +32,6 @@ export declare function estimateTranscriptLines(item: TranscriptItem, width: num
31
32
  * that fit and the input is never mutated.
32
33
  */
33
34
  export declare function projectTranscriptTail(items: readonly TranscriptItem[], budget: number, width: number): readonly TranscriptItem[];
35
+ /** Projects a fixed-size transcript window, measured upward from the newest row. */
36
+ export declare function projectTranscriptWindow(items: readonly TranscriptItem[], budget: number, width: number, scrollOffset: number): readonly TranscriptItem[];
34
37
  //# sourceMappingURL=transcript-viewport.d.ts.map
@@ -117,6 +117,9 @@ export function estimateTranscriptLines(item, width) {
117
117
  return wrappedLineCount(bounded(`${item.stdout}\n${item.stderr}`, 500), width);
118
118
  }
119
119
  }
120
+ export function transcriptLineCount(items, width) {
121
+ return items.reduce((total, item) => total + estimateTranscriptLines(item, width), 0);
122
+ }
120
123
  /**
121
124
  * Pure, deterministic suffix projector for the fullscreen transcript region.
122
125
  *
@@ -153,4 +156,29 @@ export function projectTranscriptTail(items, budget, width) {
153
156
  }
154
157
  return items.slice(start);
155
158
  }
159
+ /** Projects a fixed-size transcript window, measured upward from the newest row. */
160
+ export function projectTranscriptWindow(items, budget, width, scrollOffset) {
161
+ if (scrollOffset <= 0)
162
+ return projectTranscriptTail(items, budget, width);
163
+ const endRows = Math.max(0, transcriptLineCount(items, width) - scrollOffset);
164
+ const startRows = Math.max(0, endRows - budget);
165
+ let rows = 0;
166
+ let start = 0;
167
+ let end = 0;
168
+ for (let index = 0; index < items.length; index += 1) {
169
+ const item = items[index];
170
+ if (!item)
171
+ break;
172
+ const nextRows = rows + estimateTranscriptLines(item, width);
173
+ if (nextRows <= startRows) {
174
+ start = index + 1;
175
+ }
176
+ if (rows < endRows)
177
+ end = index + 1;
178
+ rows = nextRows;
179
+ if (rows >= endRows)
180
+ break;
181
+ }
182
+ return items.slice(start, end);
183
+ }
156
184
  //# sourceMappingURL=transcript-viewport.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.21.4",
3
+ "version": "0.21.6",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",